def unlockable_to_eth( self, from_chain: str, to_chain: str, asset_name: str, receiver: str, ) -> Tuple[int, int]: """Check unlockable balance on Ethereum.""" token_origin = self.get_asset_address(asset_name, to_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) if not is_ethereum_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Ethereum address".format( token_origin)) if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver)) hera = self.connect_aergo(from_chain) w3 = self.get_web3(to_chain) account_ref = receiver[2:] + token_origin[2:] position = b'\x06' # Unlocks eth_trie_key = keccak( bytes.fromhex(account_ref) + position.rjust(32, b'\0')) aergo_storage_key = '_sv__burns-'.encode('utf-8') \ + bytes.fromhex(account_ref) return aergo_to_eth.withdrawable(bridge_from, bridge_to, hera, w3, aergo_storage_key, eth_trie_key)
def build_lock_proof( aergo_from: herapy.Aergo, w3: Web3, receiver: str, bridge_from: str, bridge_to: str, bridge_to_abi: str, lock_height: int, token_origin: str, ): """ Check the last anchored root includes the lock and build a lock proof for that root """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an ethereum address".format(receiver) ) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format(token_origin) ) trie_key = "_sv__locks-".encode('utf-8') + bytes.fromhex(receiver[2:]) \ + token_origin.encode('utf-8') return _build_deposit_proof( aergo_from, w3, bridge_from, bridge_to, bridge_to_abi, lock_height, trie_key )
def unlockable_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, receiver: str, ) -> Tuple[int, int]: """Check unlockable balance on Aergo.""" token_origin = self.get_asset_address(asset_name, to_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format( token_origin)) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) hera = self.connect_aergo(to_chain) w3 = self.get_web3(from_chain) account_ref = receiver + token_origin position = b'\x07' # Burns eth_trie_key = keccak( account_ref.encode('utf-8') + position.rjust(32, b'\0')) aergo_storage_key = ('_sv__unlocks-' + account_ref).encode('utf-8') return eth_to_aergo.withdrawable(bridge_from, bridge_to, w3, hera, eth_trie_key, aergo_storage_key)
def unfreezable( self, from_chain: str, to_chain: str, receiver: str, ) -> Tuple[int, int]: """Check unfreezable balance on Aergo.""" token_origin = self.get_asset_address('aergo_erc20', from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) if not is_ethereum_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Ethereum address".format( token_origin)) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) hera = self.connect_aergo(to_chain) w3 = self.get_web3(from_chain) account_ref_eth = \ receiver.encode('utf-8') + bytes.fromhex(token_origin[2:]) position = b'\x05' # Locks eth_trie_key = keccak(account_ref_eth + position.rjust(32, b'\0')) aergo_storage_key = ('_sv__unfreezes-' + receiver).encode('utf-8') \ + bytes.fromhex(token_origin[2:]) return eth_to_aergo.withdrawable(bridge_from, bridge_to, w3, hera, eth_trie_key, aergo_storage_key)
def burn( aergo_from: herapy.Aergo, bridge_from: str, receiver: str, value: int, token_pegged: str, gas_limit: int, gas_price: int, ) -> Tuple[int, str, Transaction]: """ Burn a minted token on a sidechain. """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver)) if not is_aergo_address(token_pegged): raise InvalidArgumentsError( "token_pegged {} must be an Aergo address".format(token_pegged)) args = (receiver[2:].lower(), {"_bignum": str(value)}, token_pegged) tx, result = aergo_from.call_sc(bridge_from, "burn", args=args, gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Burn asset Tx commit failed : {}".format(result)) # Check burn success result = aergo_from.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Burn asset Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) # get precise burn height tx_detail = aergo_from.get_tx(tx.tx_hash) burn_height = tx_detail.block.height return burn_height, str(tx.tx_hash), tx_detail
def burn(w3: Web3, signer_acct, receiver: str, amount: int, bridge_from: str, bridge_from_abi: str, token_pegged: str, gas_limit: int, gas_price: int) -> Tuple[int, str, AttributeDict]: """ Burn a token that was minted on ethereum. """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) if not is_ethereum_address(token_pegged): raise InvalidArgumentsError( "token_pegged {} must be an Ethereum address".format(token_pegged)) bridge_from = Web3.toChecksumAddress(bridge_from) eth_bridge = w3.eth.contract(address=bridge_from, abi=bridge_from_abi) construct_txn = eth_bridge.functions.burn( receiver, amount, token_pegged).buildTransaction({ 'chainId': w3.eth.chainId, 'from': signer_acct.address, 'nonce': w3.eth.getTransactionCount(signer_acct.address), 'gas': gas_limit, 'gasPrice': w3.toWei(gas_price, 'gwei') }) signed = signer_acct.sign_transaction(construct_txn) tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction) receipt = w3.eth.waitForTransactionReceipt(tx_hash) if receipt.status != 1: raise TxError("Burn asset Tx execution failed: {}".format(receipt)) logger.info("\u26fd Eth Gas used: %s", receipt.gasUsed) return receipt.blockNumber, tx_hash.hex(), receipt
def get_balance( account_addr: str, asset_addr: str, aergo: herapy.Aergo, ) -> int: """ Get an account or the default wallet balance of Aer or any token on a given network. """ if not is_aergo_address(account_addr): raise InvalidArgumentsError( "Account {} must be an Aergo address".format(account_addr)) balance = 0 if asset_addr == "aergo": # query aergo bits on network_name ret_account = aergo.get_account(address=account_addr) balance = ret_account.balance else: balance_q = aergo.query_sc_state(asset_addr, ["_sv__balances-" + account_addr]) if not balance_q.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(balance_q)) if balance_q.var_proofs[0].inclusion: balance = json.loads(balance_q.var_proofs[0].value)['_bignum'] return int(balance)
def mint(aergo_to: herapy.Aergo, receiver: str, lock_proof: AttributeDict, token_origin: str, bridge_to: str, gas_limit: int, gas_price: int) -> Tuple[str, str, Transaction]: """ Unlock the receiver's deposit balance on aergo_to. """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) if not is_ethereum_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Ethereum address".format(token_origin)) ap = format_proof_for_lua(lock_proof.storageProof[0].proof) balance = int.from_bytes(lock_proof.storageProof[0].value, "big") ubig_balance = {'_bignum': str(balance)} # call unlock on aergo_to with the burn proof from aergo_from tx, result = aergo_to.call_sc( bridge_to, "mint", args=[receiver, ubig_balance, token_origin[2:].lower(), ap], gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Mint asset Tx commit failed : {}".format(result)) result = aergo_to.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Mint asset Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) token_pegged = json.loads(result.detail)[0] return token_pegged, str(tx.tx_hash), result
def withdrawable( bridge_from: str, bridge_to: str, hera: herapy.Aergo, w3: Web3, aergo_storage_key: bytes, eth_trie_key: bytes, ) -> Tuple[int, int]: # total_deposit : total latest deposit including pending _, block_height = hera.get_blockchain_status() block_from = hera.get_block_headers( block_height=block_height, list_size=1) root_from = block_from[0].blocks_root_hash deposit_proof = hera.query_sc_state( bridge_from, [aergo_storage_key], root=root_from, compressed=False ) if not deposit_proof.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(deposit_proof)) total_deposit = 0 if deposit_proof.var_proofs[0].inclusion: total_deposit = int(deposit_proof.var_proofs[0].value .decode('utf-8')[1:-1]) # get total withdrawn and last anchor height bridge_to = Web3.toChecksumAddress(bridge_to) storage_value = w3.eth.getStorageAt(bridge_to, eth_trie_key, 'latest') total_withdrawn = int.from_bytes(storage_value, "big") # Height is at position 1 in solidity contract storage_value = w3.eth.getStorageAt(bridge_to, 1, 'latest') last_anchor_height = int.from_bytes(storage_value, "big") # get anchored deposit : total deposit before the last anchor block_from = hera.get_block_headers( block_height=last_anchor_height, list_size=1) root_from = block_from[0].blocks_root_hash deposit_proof = hera.query_sc_state( bridge_from, [aergo_storage_key], root=root_from, compressed=False ) if not deposit_proof.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(deposit_proof)) anchored_deposit = 0 if deposit_proof.var_proofs[0].inclusion: anchored_deposit = int(deposit_proof.var_proofs[0].value .decode('utf-8')[1:-1]) withdrawable_balance = anchored_deposit - total_withdrawn pending = total_deposit - anchored_deposit return withdrawable_balance, pending
def get_bridge_tempo(self, from_chain: str, to_chain: str, aergo: herapy.Aergo = None, bridge_address: str = None, sync: bool = False) -> Tuple[int, int]: """ Return the anchoring periode of from_chain onto to_chain and minimum finality time of from_chain. This information is queried from bridge_to. """ if not sync: t_anchor = self.config_data('networks', to_chain, 'bridges', from_chain, "t_anchor") t_final = self.config_data('networks', to_chain, 'bridges', from_chain, "t_final") return t_anchor, t_final logger.info( "getting latest t_anchor and t_final from bridge contract...") if aergo is None: aergo = self._connect_aergo(to_chain) if bridge_address is None: bridge_address = self.config_data('networks', to_chain, 'bridges', from_chain, 'addr') # Get bridge information bridge_info = aergo.query_sc_state(bridge_address, [ "_sv__tAnchor", "_sv__tFinal", ]) if not bridge_info.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(bridge_info)) if not bridge_info.var_proofs[0].inclusion: raise InvalidArgumentsError("Cannot query T_anchor", bridge_info) if not bridge_info.var_proofs[1].inclusion: raise InvalidArgumentsError("Cannot query T_final", bridge_info) t_anchor, t_final = [ int(item.value) for item in bridge_info.var_proofs ] aergo.disconnect() self.config_data('networks', to_chain, 'bridges', from_chain, "t_anchor", value=t_anchor) self.config_data('networks', to_chain, 'bridges', from_chain, "t_final", value=t_final) self.save_config() return t_anchor, t_final
def build_deposit_proof(aergo_from: herapy.Aergo, aergo_to: herapy.Aergo, receiver: str, bridge_from: str, bridge_to: str, deposit_height: int, token_origin: str, key_word: str) -> herapy.obj.sc_state.SCState: """ Check the last anchored root includes the lock and build a lock proof for that root """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) # check last merged height anchor_info = aergo_to.query_sc_state(bridge_to, ["_sv__anchorHeight"]) if not anchor_info.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(anchor_info)) if not anchor_info.var_proofs[0].inclusion: raise InvalidArgumentsError("Cannot query last anchored height", anchor_info) last_merged_height_to = int(anchor_info.var_proofs[0].value) _, current_height = aergo_to.get_blockchain_status() # waite for anchor containing our transfer stream = aergo_to.receive_event_stream(bridge_to, "newAnchor", start_block_no=current_height) while last_merged_height_to < deposit_height: logger.info( "deposit not recorded in current anchor, waiting new anchor " "event... / deposit height : %s / last anchor height : %s ", deposit_height, last_merged_height_to) new_anchor_event = next(stream) last_merged_height_to = new_anchor_event.arguments[1] stream.stop() # get inclusion proof of lock in last merged block merge_block_from = aergo_from.get_block_headers( block_height=last_merged_height_to, list_size=1) root_from = merge_block_from[0].blocks_root_hash account_ref = receiver + token_origin proof = aergo_from.query_sc_state(bridge_from, [key_word + account_ref], root=root_from, compressed=False) if not proof.verify_proof(root_from): raise InvalidMerkleProofError( "Unable to verify {} proof".format(key_word)) if not proof.account.state_proof.inclusion: raise InvalidMerkleProofError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(proof)) if not proof.var_proofs[0].inclusion: raise InvalidMerkleProofError( "No tokens deposited for this account reference: {}".format(proof)) return proof
def mint( w3: Web3, signer_acct, receiver: str, lock_proof: herapy.obj.sc_state.SCState, token_origin: str, bridge_to: str, bridge_to_abi: str, gas_limit: int, gas_price: int ) -> Tuple[str, str, AttributeDict]: """ Mint the receiver's deposit balance on aergo_to. """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver) ) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format(token_origin) ) receiver = Web3.toChecksumAddress(receiver) balance = int(lock_proof.var_proofs[0].value.decode('utf-8')[1:-1]) ap = lock_proof.var_proofs[0].auditPath bitmap = lock_proof.var_proofs[0].bitmap leaf_height = lock_proof.var_proofs[0].height # call mint on ethereum with the lock proof from aergo_from eth_bridge = w3.eth.contract( address=bridge_to, abi=bridge_to_abi ) construct_txn = eth_bridge.functions.mint( receiver, balance, token_origin, ap, bitmap, leaf_height ).buildTransaction({ 'chainId': w3.eth.chainId, 'from': signer_acct.address, 'nonce': w3.eth.getTransactionCount( signer_acct.address ), 'gas': gas_limit, 'gasPrice': w3.toWei(gas_price, 'gwei') }) signed = signer_acct.sign_transaction(construct_txn) tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction) receipt = w3.eth.waitForTransactionReceipt(tx_hash) if receipt.status != 1: raise TxError("Mint asset Tx execution failed: {}".format(receipt)) logger.info("\u26fd Eth Gas used: %s", receipt.gasUsed) events = eth_bridge.events.mintEvent().processReceipt(receipt) token_pegged = events[0]['args']['tokenAddress'] return token_pegged, tx_hash.hex(), receipt
def prompt_transfer_networks(self): """Prompt user to choose 2 networks between registered bridged networks. """ networks = self.get_registered_networks() questions = [{ 'type': 'list', 'name': 'from_chain', 'message': 'Departure network', 'choices': networks }] answers = inquirer.prompt(questions, style=aergo_style) from_chain = answers['from_chain'] networks = [ net for net in self.wallet.config_data('networks', from_chain, 'bridges') ] if len(networks) == 0: raise InvalidArgumentsError('No bridge registered to this network') questions = [{ 'type': 'list', 'name': 'to_chain', 'message': 'Destination network', 'choices': networks }] answers = inquirer.prompt(questions, style=aergo_style) to_chain = answers['to_chain'] return from_chain, to_chain
def get_aergo(self, network_name: str, privkey_name: str = 'default', privkey_pwd: str = None, skip_state: bool = False) -> herapy.Aergo: """ Return aergo provider with account loaded from keystore """ if network_name is None: raise InvalidArgumentsError("Provide network_name") keystore_path = self.config_data('wallet', privkey_name, 'keystore') with open(keystore_path, "r") as f: keystore = f.read() aergo = self._connect_aergo(network_name) if privkey_pwd is None: logger.info("Decrypt exported private key '%s'", privkey_name) while True: try: privkey_pwd = getpass("Password: "******"Wrong password, try again") continue break else: aergo.import_account_from_keystore(keystore, privkey_pwd, skip_state=skip_state) return aergo
def get_balance_eth( self, asset_name: str, network_name: str, asset_origin_chain: str = None, account_name: str = 'default', account_addr: str = None, ) -> Tuple[int, str]: """ Get account name balance of asset_name on network_name, and specify asset_origin_chain for a pegged asset query, """ if account_addr is None: account_addr = self.get_eth_wallet_address(account_name) if not is_ethereum_address(account_addr): raise InvalidArgumentsError( "Account {} must be an Ethereum address".format(account_addr)) w3 = self.get_web3(network_name) asset_addr = self.get_asset_address(asset_name, network_name, asset_origin_chain) if asset_origin_chain is None: if asset_addr == 'ether': balance = eth_u.get_balance(account_addr, asset_addr, w3) return balance, asset_addr abi = self.load_erc20_abi(network_name, asset_name) else: abi = self.load_minted_erc20_abi(network_name, asset_origin_chain) balance = eth_u.get_balance(account_addr, asset_addr, w3, abi) return balance, asset_addr
def mint( aergo_to: herapy.Aergo, receiver: str, lock_proof: herapy.obj.sc_state.SCState, token_origin: str, bridge_to: str, gas_limit: int, gas_price: int ) -> Tuple[str, str]: """ Mint the receiver's deposit balance on aergo_to. """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver) ) auditPath = lock_proof.var_proofs[0].auditPath ap = [node.hex() for node in auditPath] balance = lock_proof.var_proofs[0].value.decode('utf-8')[1:-1] ubig_balance = {'_bignum': str(balance)} # call mint on aergo_to with the lock proof from aergo_from tx, result = aergo_to.call_sc( bridge_to, "mint", args=[receiver, ubig_balance, token_origin, ap], gas_limit=gas_limit, gas_price=gas_price ) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Mint asset Tx commit failed : {}".format(result)) result = aergo_to.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Mint asset Tx execution failed : {}".format(result)) logger.info("\u26fd gas used: %s", result.gas_used) token_pegged = json.loads(result.detail)[0] return token_pegged, str(tx.tx_hash)
def withdrawable(bridge_from: str, bridge_to: str, w3: Web3, hera: herapy.Aergo, eth_trie_key: bytes, aergo_storage_key: bytes) -> Tuple[int, int]: # total_deposit : total latest deposit including pending bridge_from = Web3.toChecksumAddress(bridge_from) storage_value = w3.eth.getStorageAt(bridge_from, eth_trie_key, 'latest') total_deposit = int.from_bytes(storage_value, "big") # get total withdrawn and last anchor height withdraw_proof = hera.query_sc_state( bridge_to, ["_sv__anchorHeight", aergo_storage_key], compressed=False) if not withdraw_proof.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(withdraw_proof)) if not withdraw_proof.var_proofs[0].inclusion: raise InvalidMerkleProofError("Cannot query last anchored height", withdraw_proof) total_withdrawn = 0 if withdraw_proof.var_proofs[1].inclusion: total_withdrawn = int( withdraw_proof.var_proofs[1].value.decode('utf-8')[1:-1]) last_anchor_height = int(withdraw_proof.var_proofs[0].value) # get anchored deposit : total deposit before the last anchor storage_value = w3.eth.getStorageAt(bridge_from, eth_trie_key, last_anchor_height) anchored_deposit = int.from_bytes(storage_value, "big") withdrawable_balance = anchored_deposit - total_withdrawn pending = total_deposit - anchored_deposit return withdrawable_balance, pending
def unfreeze(aergo_to: herapy.Aergo, receiver: str, lock_proof: AttributeDict, bridge_to: str, gas_limit: int, gas_price: int) -> Tuple[str, Transaction]: """ Unlock the receiver's deposit balance on aergo_to. """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) ap = format_proof_for_lua(lock_proof.storageProof[0].proof) balance = int.from_bytes(lock_proof.storageProof[0].value, "big") ubig_balance = {'_bignum': str(balance)} # call unlock on aergo_to with the burn proof from aergo_from tx, result = aergo_to.call_sc(bridge_to, "unfreeze", args=[receiver, ubig_balance, ap], gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Unfreeze asset Tx commit failed : {}".format(result)) result = aergo_to.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Unfreeze asset Tx execution failed : {}".format(result)) logger.info("\u26fd Unfreeze tx fee paid: %s", result.fee_used) logger.info("\u26fd Aergo gas used: %s", result.gas_used) return str(tx.tx_hash), result
def register_account(self, account_name: str, keystore_path: str, password: str = None, addr: str = None) -> str: """Register and exported account to config.json""" try: self.config_data('wallet', account_name) except KeyError: # if KeyError then account doesn't already exists if addr is None: aergo = herapy.Aergo() with open(keystore_path, "r") as f: keystore = f.read() account = aergo.import_account_from_keystore(keystore, password, skip_state=True, skip_self=True) addr = str(account.address) self.config_data('wallet', account_name, value={}) self.config_data('wallet', account_name, 'addr', value=addr) self.config_data('wallet', account_name, 'keystore', value=keystore_path) self.save_config() return addr error = "Error: account name '{}' already exists".format(account_name) raise InvalidArgumentsError(error)
def freeze( aergo_from: herapy.Aergo, bridge_from: str, receiver: str, value: int, gas_limit: int, gas_price: int, ) -> Tuple[int, str, Transaction]: """ Freeze aergo native """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver)) args = (receiver[2:].lower(), {"_bignum": str(value)}) tx, result = aergo_from.call_sc(bridge_from, "freeze", amount=value, args=args, gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Freeze asset Tx commit failed : {}".format(result)) # Check freeze success result = aergo_from.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Freeze Aer Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) # get precise burn height tx_detail = aergo_from.get_tx(tx.tx_hash) freeze_height = tx_detail.block.height return freeze_height, str(tx.tx_hash), tx_detail
def lock_to_eth(self, from_chain: str, to_chain: str, asset_name: str, amount: int, receiver: str, privkey_name: str = 'default', privkey_pwd: str = None) -> Tuple[int, str]: """ Initiate Aergo Standard Token transfer to Ethereum sidechain""" logger.info(from_chain + ' -> ' + to_chain) if not is_ethereum_address(receiver): raise InvalidArgumentsError( "receiver {} must be an Ethereum address".format(receiver)) if asset_name == 'aergo': raise InvalidArgumentsError( 'aer cannot be locked on Aergo, must be frozen') aergo_from = self.get_aergo(from_chain, privkey_name, privkey_pwd) sender = str(aergo_from.account.address) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) asset_address = self.get_asset_address(asset_name, from_chain) gas_limit = 300000 balance = aergo_u.get_balance(sender, asset_address, aergo_from) if balance < amount: raise InsufficientBalanceError("not enough token balance") logger.info("\U0001f4b0 %s balance on origin before transfer: %s", asset_name, balance / 10**18) aer_balance = aergo_u.get_balance(sender, 'aergo', aergo_from) if aer_balance < gas_limit * self.aergo_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) lock_height, tx_hash, _ = aergo_to_eth.lock(aergo_from, bridge_from, receiver, amount, asset_address, gas_limit, self.aergo_gas_price) logger.info('\U0001f512 Lock success: %s', tx_hash) # remaining balance on origin : aer or asset balance = aergo_u.get_balance(sender, asset_address, aergo_from) logger.info( "\U0001f4b0 remaining %s balance on origin after transfer: %s", asset_name, balance / 10**18) aergo_from.disconnect() return lock_height, tx_hash
def unlock_to_eth( self, from_chain: str, to_chain: str, asset_name: str, receiver: str = None, burn_height: int = 0, privkey_name: str = 'default', privkey_pwd: str = None, ) -> str: """ Finalize ERC20 or Eth transfer back to Ethereum origin """ logger.info(from_chain + ' -> ' + to_chain) bridge_to_abi = self.load_bridge_abi(to_chain, from_chain) erc20_abi = self.load_erc20_abi(to_chain, asset_name) aergo_from = self.connect_aergo(from_chain) # get ethereum tx signer w3 = self.get_web3(to_chain) signer_acct = self.get_signer(w3, privkey_name, privkey_pwd) tx_sender = signer_acct.address if receiver is None: receiver = tx_sender if not is_ethereum_address(receiver): raise InvalidArgumentsError( "receiver {} must be an Ethereum address".format(receiver)) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) asset_address = self.get_asset_address(asset_name, to_chain) balance = eth_u.get_balance(receiver, asset_address, w3, erc20_abi) logger.info( "\U0001f4b0 %s balance on destination before transfer : %s", asset_name, balance / 10**18) gas_limit = 200000 eth_balance = eth_u.get_balance(tx_sender, 'ether', w3) if eth_balance * 10**9 < gas_limit * self.eth_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) burn_proof = aergo_to_eth.build_burn_proof(aergo_from, w3, receiver, bridge_from, bridge_to, bridge_to_abi, burn_height, asset_address) logger.info("\u2699 Built burn proof") tx_hash, _ = aergo_to_eth.unlock(w3, signer_acct, receiver, burn_proof, asset_address, bridge_to, bridge_to_abi, gas_limit, self.eth_gas_price) logger.info('\U0001f513 Unlock success: %s', tx_hash) # new balance on origin balance = eth_u.get_balance(receiver, asset_address, w3, erc20_abi) logger.info("\U0001f4b0 %s balance on destination after transfer : %s", asset_name, balance / 10**18) aergo_from.disconnect() return tx_hash
def _build_deposit_proof(w3: Web3, aergo_to: herapy.Aergo, bridge_from: str, bridge_to: str, deposit_height: int, trie_key: bytes): """ Check the last anchored root includes the deposit and build a deposit (lock or burn) proof for that root """ bridge_from = Web3.toChecksumAddress(bridge_from) # check last merged height _, aergo_current_height = aergo_to.get_blockchain_status() anchor_info = aergo_to.query_sc_state(bridge_to, ["_sv__anchorHeight"]) if not anchor_info.account.state_proof.inclusion: raise InvalidArgumentsError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(anchor_info)) if not anchor_info.var_proofs[0].inclusion: raise InvalidArgumentsError("Cannot query last anchored height", anchor_info) last_merged_height_to = int(anchor_info.var_proofs[0].value) # waite for anchor containing our transfer stream = aergo_to.receive_event_stream(bridge_to, "newAnchor", start_block_no=aergo_current_height) while last_merged_height_to < deposit_height: logger.info( "\u23F0 deposit not recorded in current anchor, waiting new " "anchor event... / deposit height : %s / last anchor height : %s ", deposit_height, last_merged_height_to) new_anchor_event = next(stream) last_merged_height_to = new_anchor_event.arguments[1] stream.stop() # get inclusion proof of lock in last merged block block = w3.eth.getBlock(last_merged_height_to) eth_proof = w3.eth.getProof(bridge_from, [trie_key], last_merged_height_to) try: verify_eth_getProof_inclusion(eth_proof, block.stateRoot) except AssertionError as e: raise InvalidMerkleProofError("Unable to verify deposit proof", eth_proof, e) if trie_key != eth_proof.storageProof[0].key: raise InvalidMerkleProofError("Proof doesnt match requested key", eth_proof, trie_key) if len(eth_proof.storageProof[0].value) == 0: raise InvalidMerkleProofError("Trie key {} doesn't exist".format( trie_key.hex())) return eth_proof
def unfreeze( self, from_chain: str, to_chain: str, receiver: str = None, lock_height: int = 0, privkey_name: str = 'default', privkey_pwd: str = None, ) -> str: """ Finalize ERC20Aergo transfer to Aergo Mainnet by unfreezing (aers are already minted and freezed in the bridge contract) """ logger.info(from_chain + ' -> ' + to_chain) asset_name = 'aergo_erc20' w3 = self.get_web3(from_chain) aergo_to = self.get_aergo(to_chain, privkey_name, privkey_pwd) tx_sender = str(aergo_to.account.address) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) asset_address = self.get_asset_address(asset_name, from_chain) if receiver is None: receiver = tx_sender if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) balance = aergo_u.get_balance(receiver, 'aergo', aergo_to) logger.info("\U0001f4b0 %s balance on destination before transfer: %s", asset_name, balance / 10**18) gas_limit = 300000 if receiver != tx_sender: aer_balance = aergo_u.get_balance(tx_sender, 'aergo', aergo_to) if aer_balance < gas_limit * self.aergo_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) lock_proof = eth_to_aergo.build_lock_proof(w3, aergo_to, receiver, bridge_from, bridge_to, lock_height, asset_address) logger.info("\u2699 Built lock proof") tx_hash, _ = eth_to_aergo.unfreeze(aergo_to, receiver, lock_proof, bridge_to, gas_limit, self.aergo_gas_price) logger.info('\U0001f4a7 Unfreeze success: %s', tx_hash) # new balance on destination balance = aergo_u.get_balance(receiver, 'aergo', aergo_to) logger.info("\U0001f4b0 %s balance on destination after transfer: %s", asset_name, balance / 10**18) aergo_to.disconnect() # record mint address in file return tx_hash
def get_balance(account_addr: str, asset_addr: str, w3: Web3, erc20_abi: str = None) -> int: if not is_ethereum_address(account_addr): raise InvalidArgumentsError( "Account {} must be an Ethereum address".format(account_addr)) account_addr = Web3.toChecksumAddress(account_addr) balance = 0 if asset_addr == "ether": balance = w3.eth.getBalance(account_addr) else: asset_addr = Web3.toChecksumAddress(asset_addr) if erc20_abi is None: raise InvalidArgumentsError("Provide token abi to query balance") token_contract = w3.eth.contract(address=asset_addr, abi=erc20_abi) try: balance = token_contract.functions.balanceOf(account_addr).call() except BadFunctionCallOutput as e: raise InvalidArgumentsError(e, asset_addr) return balance
def lock_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, amount: int, receiver: str, privkey_name: str = 'default', privkey_pwd: str = None, ) -> Tuple[int, str]: """ Initiate ERC20 token or Ether transfer to Aergo sidechain """ logger.info(from_chain + ' -> ' + to_chain) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) bridge_from_abi = self.load_bridge_abi(from_chain, to_chain) erc20_abi = self.load_erc20_abi(from_chain, asset_name) w3 = self.get_web3(from_chain) signer_acct = self.get_signer(w3, privkey_name, privkey_pwd) token_owner = signer_acct.address bridge_from = self.get_bridge_contract_address(from_chain, to_chain) erc20_address = self.get_asset_address(asset_name, from_chain) balance = eth_u.get_balance(token_owner, erc20_address, w3, erc20_abi) logger.info("\U0001f4b0 %s balance on origin before transfer : %s", asset_name, balance / 10**18) if balance < amount: raise InsufficientBalanceError("not enough token balance") gas_limit = 500000 # estimation eth_balance = eth_u.get_balance(token_owner, 'ether', w3) if eth_balance * 10**9 < gas_limit * self.eth_gas_price: err = "not enough eth balance to pay tx fee" raise InsufficientBalanceError(err) next_nonce, tx_hash = eth_u.increase_approval(bridge_from, erc20_address, amount, w3, erc20_abi, signer_acct, gas_limit, self.eth_gas_price) logger.info("\u2b06 Increase approval success: %s", tx_hash) lock_height, tx_hash, _ = eth_to_aergo.lock( w3, signer_acct, receiver, amount, bridge_from, bridge_from_abi, erc20_address, gas_limit, self.eth_gas_price, next_nonce) logger.info('\U0001f512 Lock success: %s', tx_hash) balance = eth_u.get_balance(token_owner, erc20_address, w3, erc20_abi) logger.info( "\U0001f4b0 remaining %s balance on origin after transfer: %s", asset_name, balance / 10**18) return lock_height, tx_hash
def lock( aergo_from: herapy.Aergo, bridge_from: str, receiver: str, value: int, asset: str, gas_limit: int, gas_price: int, ) -> Tuple[int, str, Transaction]: """ Lock can be called to lock aer or tokens. it supports delegated transfers when tx broadcaster is not the same as the token owner """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "receiver {} must be an Ethereum address".format(receiver)) if not is_aergo_address(asset): raise InvalidArgumentsError( "asset {} must be an Aergo address".format(asset)) args = (bridge_from, {"_bignum": str(value)}, receiver[2:].lower()) tx, result = aergo_from.call_sc(asset, "transfer", args=args, amount=0, gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Lock asset Tx commit failed : {}".format(result)) # Check lock success result = aergo_from.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Lock asset Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) # get precise lock height tx_detail = aergo_from.get_tx(tx.tx_hash) lock_height = tx_detail.block.height return lock_height, str(tx.tx_hash), tx_detail
def update_unfreeze_fee(self): from_chain, to_chain = self.prompt_transfer_networks() if self.wallet.config_data('networks', to_chain, 'type') == 'ethereum': raise InvalidArgumentsError( "Unfreeze only available on Aergo network") fee = prompt_number("New Aergo unfreeze fee on {}".format(to_chain)) self.wallet.config_data('networks', to_chain, 'bridges', from_chain, 'unfreeze_fee', value=fee) self.wallet.save_config()
def burn_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, amount: int, receiver: str, privkey_name: str = 'default', privkey_pwd: str = None, ) -> Tuple[int, str]: """ Initiate minted Standard token transfer back to aergo origin""" logger.info(from_chain + ' -> ' + to_chain) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) bridge_from_abi = self.load_bridge_abi(from_chain, to_chain) minted_erc20_abi = self.load_minted_erc20_abi(from_chain, to_chain) w3 = self.get_web3(from_chain) signer_acct = self.get_signer(w3, privkey_name, privkey_pwd) token_owner = signer_acct.address bridge_from = self.get_bridge_contract_address(from_chain, to_chain) token_pegged = self.get_asset_address(asset_name, from_chain, asset_origin_chain=to_chain) balance = eth_u.get_balance(token_owner, token_pegged, w3, minted_erc20_abi) logger.info("\U0001f4b0 %s balance on origin before transfer : %s", asset_name, balance / 10**18) if balance < amount: raise InsufficientBalanceError("not enough token balance") gas_limit = 200000 eth_balance = eth_u.get_balance(token_owner, 'ether', w3) if eth_balance * 10**9 < gas_limit * self.eth_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) burn_height, tx_hash, _ = eth_to_aergo.burn(w3, signer_acct, receiver, amount, bridge_from, bridge_from_abi, token_pegged, gas_limit, self.eth_gas_price) logger.info('\U0001f525 Burn success: %s', tx_hash) balance = eth_u.get_balance(token_owner, token_pegged, w3, minted_erc20_abi) logger.info( "\U0001f4b0 remaining %s balance on origin after transfer: %s", asset_name, balance / 10**18) return burn_height, tx_hash
def _build_deposit_proof( aergo_from: herapy.Aergo, w3: Web3, bridge_from: str, bridge_to: str, bridge_to_abi: str, deposit_height: int, trie_key: bytes, ): """ Check the last anchored root includes the deposit and build a deposit proof for that root """ # check last merged height eth_bridge = w3.eth.contract( address=bridge_to, abi=bridge_to_abi ) try: last_merged_height_to = eth_bridge.functions._anchorHeight().call() except BadFunctionCallOutput as e: raise InvalidArgumentsError(e, bridge_to) # waite for anchor containing our transfer if last_merged_height_to < deposit_height: logger.info( "\u23F0 deposit not recorded in current anchor, waiting new " "anchor event... / deposit height : %s / last anchor height : %s ", deposit_height, last_merged_height_to ) while last_merged_height_to < deposit_height: time.sleep(1) last_merged_height_to = eth_bridge.functions._anchorHeight().call() # get inclusion proof of lock in last merged block merge_block_from = aergo_from.get_block_headers( block_height=last_merged_height_to, list_size=1) root_from = merge_block_from[0].blocks_root_hash proof = aergo_from.query_sc_state( bridge_from, [trie_key], root=root_from, compressed=True ) if not proof.verify_proof(root_from): raise InvalidMerkleProofError("Unable to verify deposit proof", proof) if not proof.account.state_proof.inclusion: raise InvalidMerkleProofError( "Contract doesnt exist in state, check contract deployed and " "chain synced {}".format(proof)) if not proof.var_proofs[0].inclusion: raise InvalidMerkleProofError( "No tokens deposited for this account reference: {}" .format(proof)) return proof