def setup(make_project, db, configuration: Config, erc20_token): configuration['token_contract_addr'] = erc20_token.address eth_token, eth_token_hash = init_token_contract(configuration) swap_contract, swap_contract_hash = init_swap_contract( configuration, eth_token, eth_token_hash) add_minter(eth_token, swap_contract) erc_token, erc_token_hash = init_token_contract(configuration, swap_contract) add_to_whitelist(swap_contract, erc_token, erc_token_hash) change_owner(swap_contract, configuration["multisig_acc_addr"]) # add token pairings to db TokenPairing(src_network="Ethereum", src_coin="ETH", src_address="native", dst_network="Secret", dst_coin="secret-ETH", dst_address=eth_token).save() TokenPairing(src_network="Ethereum", src_coin="ERC", src_address=erc20_token.address, dst_network="Secret", dst_coin="secret-ERC", dst_address=erc_token).save() configuration["swap_code_hash"] = swap_contract_hash configuration["scrt_swap_address"] = swap_contract
def configure_db(): with database("test_db", "cluster0.dka2m.mongodb.net", "leader", "6FXQ3gHXQQAkbpmI"): # TokenPairing(src_network="Ethereum", src_coin="ETH", src_address="native", # dst_network="Secret", dst_coin="secret-ETH", dst_address="secret1nk5c3agzt3ytpkl8csfhf4e3qwleauex9ay69t").save() TokenPairing.objects().get( src_network="Ethereum", dst_coin="secret-TUSD").update( src_address="0x1cB0906955623920c86A3963593a02a405Bb97fC")
def configure_db(): with database(): # TokenPairing(src_network="Ethereum", src_coin="ETH", src_address="native", # dst_network="Secret", dst_coin="secret-ETH", dst_address="secret1nk5c3agzt3ytpkl8csfhf4e3qwleauex9ay69t").save() TokenPairing.objects().get( src_network="Ethereum", dst_address="secret13lj8gqvdfn45d03lfrrl087dje5d6unzus2usv", dst_coin="secret-YEENUS").delete()
def test_3_confirm_tx(web3_provider, ethr_signers, configuration: Config, erc20_contract, ethr_leader): secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ERC").dst_address assert increase_block_number(web3_provider, configuration.eth_confirmations) # To allow the new EthrSigner to "catch up", we start it after the event submission event in Ethereum # ethr_signers[-1].start() sleep(configuration.sleep_interval + 3) # Validate the tx is confirmed in the smart contract last_nonce = SwapTrackerObject.last_processed(src=secret_token_addr) assert last_nonce > -1 # account for fee fee = erc20_contract.contract.functions.balanceOf(PAYABLE_ADDRESS).call() assert fee > 0 assert TRANSFER_AMOUNT_ERC == erc20_contract.contract.functions.balanceOf( ethr_leader.signer.address).call() + fee assert increase_block_number(web3_provider, configuration.eth_confirmations) sleep(configuration.sleep_interval) last_nonce = SwapTrackerObject.last_processed(secret_token_addr) swap = Swap.objects().get(src_tx_hash=f'{last_nonce}|{secret_token_addr}') assert swap.status == Status.SWAP_CONFIRMED
def __init__(self, multisig_wallet: MultisigWallet, signer: CryptoManagerBase, dst_network: str, config: Config, **kwargs): self.config = config self.multisig_wallet = multisig_wallet self.erc20 = erc20_contract() token_map = {} pairs = TokenPairing.objects(dst_network=dst_network, src_network=self.network) for pair in pairs: token_map.update( {pair.dst_address: Token(pair.src_address, pair.src_coin)}) self.signer = signer # self.private_key = private_key # self.default_account = account self.token_map = token_map self.logger = get_logger(db_name=self.config['db_name'], logger_name=config.get( 'logger_name', self.__class__.__name__)) self.stop_event = Event() super().__init__(group=None, name="EtherLeader", target=self.run, **kwargs)
def test_2_swap_s20_to_eth(setup, web3_provider, ethr_leader, configuration: Config, ethr_signers, scrt_signers): swap_contract_addr = configuration['scrt_swap_address'] secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ETH").dst_address # start the eth signers for signer in ethr_signers[:-1]: signer.start() # Generate swap tx on secret network swap = {"send": {"amount": str(TRANSFER_AMOUNT_ETH), "msg": base64.standard_b64encode(zero_address.encode()).decode(), "recipient": swap_contract_addr}} sleep(configuration['sleep_interval']) last_nonce = SwapTrackerObject.last_processed(secret_token_addr) print(f"last processed before: {last_nonce}") tx_hash = run(f"secretcli tx compute execute {secret_token_addr} " f"'{json.dumps(swap)}' --from t1 --gas 300000 -y", shell=True, stdout=PIPE, stderr=PIPE) tx_hash = json.loads(tx_hash.stdout)['txhash'] sleep(configuration['sleep_interval'] + 6) assert query_data_success(tx_hash) != {} last_nonce_after = SwapTrackerObject.last_processed(secret_token_addr) print(f"last processed before: {last_nonce_after}") assert last_nonce + 1 == last_nonce_after # Give ethr signers time to handle the secret20 swap tx increase_block_number(web3_provider, configuration['eth_confirmations']) sleep(configuration['sleep_interval'] + 5)
def __init__(self, secret_multisig: SecretAccount, contract: MultisigWallet, src_network: str, config: Config, *args, **kwargs): super().__init__(*args, **kwargs) token_map = {} pairs = TokenPairing.objects(dst_network=self.network, src_network=src_network) for pair in pairs: token_map.update( {pair.src_address: Token(pair.dst_address, pair.dst_coin)}) self.multisig_name = secret_multisig.name self.config = config self.manager = SecretManager(contract, token_map, secret_multisig, config) self.logger = get_logger( db_name=self.config['db_name'], logger_name=config.get( 'logger_name', f"{self.__class__.__name__}-{self.multisig_name}")) self.stop_event = Event() super().__init__(group=None, name="SecretLeader", target=self.run, **kwargs)
def __init__(self, multisig_wallet: MultisigWallet, signer: CryptoManagerBase, dst_network: str, config: Config, **kwargs): self.config = config self.multisig_wallet = multisig_wallet self.erc20 = erc20_contract() self.pending_txs: List[str] = [] token_map = {} confirmer_token_map = {} pairs = TokenPairing.objects(dst_network=dst_network, src_network=self.network) for pair in pairs: token_map.update( {pair.dst_address: Token(pair.src_address, pair.src_coin)}) confirmer_token_map.update( {pair.src_address: Token(pair.dst_address, pair.dst_coin)}) self.signer = signer self.token_map = token_map self.logger = get_logger(db_name=config.db_name, loglevel=config.log_level, logger_name=config.logger_name or self.__class__.__name__) self.stop_event = Event() self.confirmer = EthConfirmer(self.multisig_wallet, confirmer_token_map, self.logger) self.event_listener = EthEventListener(self.multisig_wallet, config) self.stop_event = Event() super().__init__(group=None, name="EtherLeader", target=self.run, **kwargs)
def test_3_confirm_and_finalize_eth_tx(web3_provider, ethr_signers, configuration: Config): # To allow the new EthrSigner to "catch up", we start it after the event submission event in Ethereum secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ETH").dst_address prev_bal = web3_provider.eth.getBalance(zero_address, "latest") prev_bal_fee = web3_provider.eth.getBalance(PAYABLE_ADDRESS, "latest") ethr_signers[-1].start() sleep(1) assert increase_block_number(web3_provider, configuration.eth_confirmations) sleep(configuration.sleep_interval * 5) # Validate the tx is confirmed in the smart contract last_nonce = SwapTrackerObject.last_processed(secret_token_addr) # ethr_signers[-1].signer.multisig_contract.contract.functions.confirmations(last_nonce, # ethr_signers[-1].account).call() assert last_nonce >= 0 bal_fee = web3_provider.eth.getBalance(PAYABLE_ADDRESS, "latest") assert bal_fee > prev_bal_fee bal = web3_provider.eth.getBalance(zero_address, "latest") assert bal > prev_bal last_nonce = SwapTrackerObject.last_processed(secret_token_addr) swap = Swap.objects().get(src_tx_hash=f'{last_nonce}|{secret_token_addr}') assert swap.status == Status.SWAP_CONFIRMED configuration.eth_start_block = web3_provider.eth.blockNumber
def _refresh_token_map(self): token_map = {} pairs = TokenPairing.objects(src_network=self.network) for pair in pairs: token_map.update({pair.dst_address: Token(pair.src_address, pair.src_coin)}) self.token_map = token_map
def __getitem__(self, key: str): _key = key.lower() if _key not in self: for coin in TokenPairing.objects(): if self._key(coin) not in self: self.update({self._key(coin): Coin.from_db(coin)}) if _key in self: return self.data[_key] raise KeyError(f"Coin not found for key: {key}") return self.data[_key]
def _tx_erc20_params(self, amount, dest_address, dst_token: str, retry: bool) -> Tuple[bytes, str, int, str, int]: # if fee isn't 0 this will fail because tx_token isn't the ERC20 address from which to collect the fee if retry: fee = 0 elif self.config.network == "mainnet": decimals = self._coins.decimals(dst_token) try: x_rate = BridgeOracle.x_rate('ETH', self._coins.coin(dst_token)) except Exception: # pylint: disable=broad-except self.logger.warning(f"Failed to get price for token {dst_token} - falling back to db price") eth = TokenPairing.objects().get(name="Ethereum").price token = TokenPairing.objects().get(src_address=dst_token).price x_rate = float(eth) / float(token) self.logger.info(f'Calculated exchange rate: {x_rate=}') gas_price = BridgeOracle.gas_price() fee = BridgeOracle.calculate_fee(self.multisig_wallet.SUBMIT_GAS, gas_price, decimals, x_rate, amount) self.logger.info(f'Fee taken: {fee}') # for testing mostly else: fee = 1 if fee >= amount: raise ValueError checksum_addr = w3.toChecksumAddress(dest_address) data = self.erc20.encodeABI(fn_name='transfer', args=[checksum_addr, amount - fee]) tx_dest = dst_token tx_token = dst_token tx_amount = 0 return data, tx_dest, tx_amount, tx_token, fee
def _scan_swap(self): """ Scans secret network contract for swap events """ self.logger.info(f'Starting for account {self.signer.address} with tokens: {self.token_map=}') while not self.stop_event.is_set(): num_of_tokens = TokenPairing.objects(src_network=self.network).count() if num_of_tokens != len(self.token_map.keys()): self._refresh_token_map() self.logger.info(f'Refreshed tracked tokens. Now tracking {len(self.token_map.keys())} tokens') for transaction in Swap.objects(status=Status.SWAP_RETRY, src_network="Secret"): # self._handle_swap(swap_data, token, self.token_map[token].address) try: token, nonce = _parse_db_tx(transaction) swap_data = query_scrt_swap(nonce, self.config.scrt_swap_address, token) # self._retry(transaction) self._handle_swap(swap_data, token, self.token_map[token].address, True) except Exception as e: # pylint: disable=broad-except self.logger.error(f'Failed to retry swap: {e}') transaction.update(status=Status.SWAP_FAILED) for token in self.token_map: try: swap_tracker = SwapTrackerObject.get_or_create(src=token) next_nonce = swap_tracker.nonce + 1 self.logger.debug(f'Scanning token {token} for query #{next_nonce}') swap_data = query_scrt_swap(next_nonce, self.config.scrt_swap_address, token) self._handle_swap(swap_data, token, self.token_map[token].address) swap_tracker.nonce = next_nonce swap_tracker.save() next_nonce += 1 except CalledProcessError as e: if b'ERROR: query result: encrypted: Failed to get swap for token' not in e.stderr: self.logger.error(f"Failed to query swap: stdout: {e.stdout} stderr: {e.stderr}") # if b'ERROR: query result: encrypted: Failed to get swap for key' not in e.stderr: self.stop_event.wait(self.config.sleep_interval)
def test_2_swap_s20_to_erc(web3_provider, ethr_leader, configuration: Config, ethr_signers, erc20_contract): swap_contract_addr = configuration['scrt_swap_address'] secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ERC").dst_address # for signer in ethr_signers[:-1]: # signer.start() # Generate swap tx on secret network swap = {"send": {"amount": str(TRANSFER_AMOUNT_ERC), "msg": base64.b64encode(ethr_leader.signer.address.encode()).decode(), "recipient": swap_contract_addr}} last_nonce = SwapTrackerObject.last_processed(src=secret_token_addr) tx_hash = run(f"secretcli tx compute execute {secret_token_addr} " f"'{json.dumps(swap)}' --from t1 -b block -y --gas 300000", shell=True, stdout=PIPE, stderr=PIPE) tx_hash = json.loads(tx_hash.stdout)['txhash'] print(f'{tx_hash=}') # Verify that leader recognized the burn tx assert increase_block_number(web3_provider, configuration['eth_confirmations']) sleep(configuration['sleep_interval']) assert last_nonce + 1 == SwapTrackerObject.last_processed(src=secret_token_addr)
def __init__(self, multisig_contract: MultisigWallet, signer: CryptoManagerBase, dst_network: str, config: Config): # todo: simplify this, pylint is right self.multisig_contract = multisig_contract self.account = signer.address self.signer = signer self.config = config self.logger = get_logger( db_name=config['db_name'], logger_name=config.get( 'logger_name', f"{self.__class__.__name__}-{self.account[0:5]}")) self.erc20 = erc20_contract() self.catch_up_complete = False self.token_map = {} pairs = TokenPairing.objects(dst_network=dst_network, src_network=self.network) for pair in pairs: self.token_map.update( {pair.src_address: Token(pair.dst_address, pair.dst_coin)}) self.tracked_tokens = self.token_map.keys()
def __init__(self, **kwargs): super().__init__(**kwargs) for coin in TokenPairing.objects(): self.update({self._key(coin): Coin.from_db(coin)})
def test_11_swap_erc_to_s20(scrt_leader, scrt_signers, web3_provider, configuration: Config, erc20_contract, multisig_wallet, ethr_leader): secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ERC").dst_address # scrt_leader.start() # for signer in scrt_signers: # signer.start() t1_address = get_key_signer( "t1", Path.joinpath(project_base_path(), configuration.path_to_keys))['address'] # swap ethr for secret20 token, deliver tokens to address of 'a'. # (we will use 'a' later to check it received the money) # add usdt to the whitelisted token list account = web3_provider.eth.account.from_key(configuration.leader_key) nonce = web3_provider.eth.getTransactionCount(account.address, "pending") tx = multisig_wallet.contract.functions.addToken(erc20_contract.address, 1) raw_tx = tx.buildTransaction(transaction={ 'from': account.address, 'gas': 3000000, 'nonce': nonce }) signed_tx = account.sign_transaction(raw_tx) tx_hash = web3_provider.eth.sendRawTransaction(signed_tx.rawTransaction) # Get transaction hash from deployed contract tx_receipt = web3_provider.eth.waitForTransactionReceipt(tx_hash) # this will likely fail since the test before also allocates the allowance - just ignore if it fails try: _ = erc20_contract.contract.functions.approve(multisig_wallet.address, TRANSFER_AMOUNT_ERC). \ transact({'from': web3_provider.eth.coinbase}) except ValueError: pass tx_hash = multisig_wallet\ .contract\ .functions\ .swapToken(t1_address.encode(), TRANSFER_AMOUNT_ERC, erc20_contract.address)\ .transact({'from': web3_provider.eth.coinbase}).hex().lower() assert TRANSFER_AMOUNT_ERC == erc20_contract.contract.functions.balanceOf( multisig_wallet.address).call() # increase number of blocks to reach the confirmation threshold assert increase_block_number(web3_provider, configuration.eth_confirmations - 1) sleep(configuration.sleep_interval + 2) assert Swap.objects(src_tx_hash=tx_hash).count( ) == 0 # verify blocks confirmation threshold wasn't meet assert increase_block_number(web3_provider, 1) # add the 'missing' confirmation block # give event listener and manager time to process tx sleep(configuration.sleep_interval + 2) assert Swap.objects( src_tx_hash=tx_hash).count() == 1 # verify swap event recorded swap = Swap.objects(src_tx_hash=tx_hash).get() sleep(1) # check signers were notified of the tx and signed it assert Signatures.objects(tx_id=swap.id).count() == len(scrt_signers) # give time for manager to process the signatures sleep(configuration.sleep_interval + 2) assert Swap.objects().get( src_tx_hash=tx_hash).status in (Status.SWAP_SUBMITTED, Status.SWAP_CONFIRMED) _, log = event_log(tx_hash, ['SwapToken'], web3_provider, multisig_wallet.contract) transfer_amount = multisig_wallet.extract_amount(log) dest = multisig_wallet.extract_addr(log) # validate swap tx on ethr delivers to the destination viewing_key_set = '{"set_viewing_key": {"key": "lol"}}' _ = run( f"secretcli tx compute execute {secret_token_addr} " f"'{viewing_key_set}' --from {dest} -b block -y | jq '.txhash'", shell=True, stdout=PIPE) sleep(6) balance = f'{{"balance": {{"key": "lol", "address": "{dest}"}} }}' res = run(f"secretcli q compute query {secret_token_addr} " f"'{balance}'", shell=True, stdout=PIPE) print(f"{res.stdout=}") amount = json.loads(res.stdout)["balance"]["amount"] print(f"swap amount: {transfer_amount}, dest balance amount: {amount}") # give scrt_leader time to multi-sign already existing signatures sleep(configuration.sleep_interval + 5) assert Swap.objects().get( src_tx_hash=tx_hash).status == Status.SWAP_CONFIRMED
def deploy_scrt(): # docker exec -it secretdev secretcli tx compute store "/token.wasm.gz" --from a --gas 2000000 -b block -y # # docker exec -it secretdev secretcli tx compute store "/swap.wasm.gz" --from a --gas 2000000 -b block -y # 0xd475b764D1B2DCa1FE598247e5D49205E6Ac5E8e multisig_account = config.multisig_acc_addr deployer = "secret18839huzvv5v6v0z3tm6um2ascnt6vrqfsp8d4g" tokens = [{ "address": "0x1cB0906955623920c86A3963593a02a405Bb97fC", "name": "True USD", "decimals": 18, "symbol": "TUSD" }, { "address": "0xF6fF95D53E08c9660dC7820fD5A775484f77183A", "name": "YEENUS", "decimals": 8, "symbol": "YNUS" }, { "address": "native", "name": "Ethereum", "decimals": 15, "symbol": "ETH" }] swap_contract, swap_contract_hash = init_swap_contract(deployer) # swap_contract = "secret1u8mgmspdeakpf7u8leq68d5xtkykskwrytevyn" # swap_contract_hash = "5C36ABD74F5959DD9E8BCECB2EA308BEFEAFF2A50B9BCBD2338C079266F9F0BF" print(f"Swap contract deployed at: {swap_contract}") for token in tokens: config.token_contract_addr = token scrt_token, scrt_token_code = init_token_contract( deployer, token["decimals"], f'S{token["symbol"]}', f'Secret {token["name"]}', swap_contract) add_minter(scrt_token, deployer) print(f"Secret {token['name']}, Deployed at: {scrt_token}") add_to_whitelist(swap_contract, scrt_token, scrt_token_code, pow(10, token["decimals"])) import os uri = os.environ.get("db_uri") with database(uri): try: TokenPairing.objects().get( src_network="Ethereum", src_address=token["address"]).update( dst_address=scrt_token) print("Updated DB record") except: print("Added new pair to db") TokenPairing(src_network="Ethereum", src_coin=token["name"], src_address=token["address"], dst_network="Secret", dst_coin=f"secret-{token['name']}", dst_address=scrt_token, decimals=18, name="Ethereum").save() change_owner(swap_contract, config.multisig_acc_addr)
def test_1_swap_eth_to_s20(setup, scrt_signers, scrt_leader, web3_provider, configuration: Config, multisig_wallet): secret_token_addr = TokenPairing.objects().get(src_network="Ethereum", src_coin="ETH").dst_address scrt_leader.start() for signer in scrt_signers: signer.start() fee_collector = multisig_wallet.contract.functions.getFeeCollector().call() print(f"{fee_collector=}") t1_address = get_key_signer( "t1", Path.joinpath(project_base_path(), configuration.path_to_keys))['address'] # swap ethr for secret20 token, deliver tokens to address of 'a' # (we will use 'a' later to check it received the money) print( f"Creating new swap transaction at {web3_provider.eth.getBlock('latest').number + 1}" ) tx_hash = multisig_wallet.contract.functions.swap(t1_address.encode()). \ transact({'from': web3_provider.eth.coinbase, 'value': TRANSFER_AMOUNT_ETH}).hex().lower() # TODO: validate ethr increase of the smart contract # increase number of blocks to reach the confirmation threshold assert increase_block_number(web3_provider, configuration.eth_confirmations - 1) sleep(configuration.sleep_interval + 2) assert Swap.objects(src_tx_hash=tx_hash).count( ) == 0 # verify blocks confirmation threshold wasn't meet assert increase_block_number(web3_provider, 1) # add the 'missing' confirmation block # give event listener and manager time to process tx sleep(configuration.sleep_interval + 5) assert Swap.objects( src_tx_hash=tx_hash).count() == 1 # verify swap event recorded sleep(1) # check signers were notified of the tx and signed it assert Signatures.objects().count() == len(scrt_signers) sleep(5) if Swap.objects().get().status == Status.SWAP_SUBMITTED: sleep(configuration.sleep_interval + 5) assert Swap.objects().get().status == Status.SWAP_CONFIRMED # get tx details tx_hash = Swap.objects().get().src_tx_hash _, log = event_log(tx_hash, ['Swap'], web3_provider, multisig_wallet.contract) transfer_amount = log.args.amount dest = log.args.recipient.decode() # validate swap tx on ethr delivers to the destination viewing_key_set = '{"set_viewing_key": {"key": "lol"}}' tx_hash = run( f"secretcli tx compute execute {secret_token_addr} " f"'{viewing_key_set}' --from {dest} -b block -y | jq '.txhash'", shell=True, stdout=PIPE) sleep(6) balance = f'{{"balance": {{"key": "lol", "address": "{dest}"}} }}' res = run(f"secretcli q compute query {secret_token_addr} " f"'{balance}'", shell=True, stdout=PIPE) print(f"{res.stdout=}") amount = json.loads(res.stdout)["balance"]["amount"] print(f"swap amount: {transfer_amount}, dest balance amount: {amount}") assert int(amount) == log.args.amount