def on_deposit(self, address: CryptoAddress, opid, log_data, log_entry) -> CryptoAddressDeposit: """Handle Hosted Wallet Deposit event. Create incoming holding account holding the ETH assets until we receive enough confirmations. """ op = CryptoAddressDeposit(address.network) # Get or create final account where we deposit the transaction asset = get_ether_asset(self.dbsession, network=address.network) crypto_account = address.get_or_create_account(asset) op.crypto_account = crypto_account op.external_address = eth_address_to_bin(log_data["from"]) # Create holding account that keeps the value until we receive N amount of confirmations acc = Account(asset=asset) self.dbsession.add(acc) self.dbsession.flush() value = wei_to_eth(log_data["value"]) acc.do_withdraw_or_deposit(value, "ETH deposit from {} in tx {}".format(log_data["from"], log_entry["transactionHash"])) op.holding_account = acc return op
def on_deposit(self, address: CryptoAddress, opid, log_data, log_entry) -> CryptoAddressDeposit: """Handle Hosted Wallet Deposit event. Create incoming holding account holding the ETH assets until we receive enough confirmations. """ op = CryptoAddressDeposit(address.network) # Get or create final account where we deposit the transaction asset = get_ether_asset(self.dbsession, network=address.network) crypto_account = address.get_or_create_account(asset) op.crypto_account = crypto_account op.external_address = eth_address_to_bin(log_data["from"]) # Create holding account that keeps the value until we receive N amount of confirmations acc = Account(asset=asset) self.dbsession.add(acc) self.dbsession.flush() value = wei_to_eth(log_data["value"]) acc.do_withdraw_or_deposit( value, "ETH deposit from {} in tx {}".format( log_data["from"], log_entry["transactionHash"])) op.holding_account = acc return op
def claim_fees(self, original_txid: str) -> Tuple[str, Decimal]: """Claim fees from previous execute() call. When a hosted wallet calls another contract through execute() gas is spent. This gas appears as cumulative gas in the transaction receipt. This gas cost should be targeted to the hosted wallet balance, not the original caller balance (geth coinbase). We use this method to settle the fee transaction between the hosted wallet and coinbase. This creates another event that is easy to pick up accounting and properly credit. :return: The new transaction id that settles the fees. """ assert original_txid.startswith("0x") original_txid_b = bytes(bytearray.fromhex(original_txid[2:])) receipt = self.web3.eth.getTransactionReceipt(original_txid) gas_price = self.web3.eth.gasPrice gas_used = receipt["cumulativeGasUsed"] wei_value = gas_used * gas_price price = wei_to_eth(wei_value) # TODO: Estimate the gas usage of this transaction for claiming the fees # and add it on the top of the original transaction gas # Transfer value back to owner, post a tx fee event txid = self.contract.transact().claimFees(original_txid_b, wei_value) return txid, price
def test_cap(web3, hosted_wallet, token, coinbase): """Transactions that would exceed cap is rejected.""" test_fund = wei_to_eth(CAP + 1) txid = send_balance_to_contract(token.contract, test_fund, gas=1000000) with pytest.raises(TransactionConfirmationError): confirm_transaction(web3, txid)
def test_withdraw_wallet(web3, topped_up_hosted_wallet, coinbase): """Withdraw eths from wallet contract to RPC coinbase address.""" hosted_wallet = topped_up_hosted_wallet current_balance = hosted_wallet.get_balance() current_coinbase_balance = wei_to_eth(web3.eth.getBalance(coinbase)) # We have enough coints to perform the test assert current_balance > TEST_VALUE # Withdraw and wait it go through txid = hosted_wallet.withdraw(coinbase, TEST_VALUE) confirm_transaction(web3, txid) new_balance = hosted_wallet.get_balance() new_coinbase_balance = wei_to_eth(web3.eth.getBalance(coinbase)) assert new_coinbase_balance != current_coinbase_balance, "Coinbase address balance did not change: {}".format(new_coinbase_balance) assert new_coinbase_balance > current_coinbase_balance assert new_balance < current_balance
def test_fund_wallet(web3, coinbase, hosted_wallet): """Send some funds int the wallet and see the balance updates.""" current_balance = wei_to_eth(web3.eth.getBalance(hosted_wallet.address)) # value = get_wallet_balance(wallet_contract_address) txid = send_balance_to_contract(hosted_wallet, TEST_VALUE) wait_tx(web3, txid) new_balance = hosted_wallet.get_balance() assert new_balance == current_balance + TEST_VALUE
def test_withdraw_eth_data(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, withdraw_address: str, target_account: str, decode_data_contract): """Perform a withdraw operation with data and gas set. """ # First check what's our balance before sending coins back current_balance = wei_to_eth(web3.eth.getBalance(target_account)) assert current_balance == 0 with transaction.manager: # Create withdraw operation caccount = dbsession.query(CryptoAddressAccount).one() #: We are going to withdraw the full amount on the account assert caccount.account.get_balance() == TEST_VALUE # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish op = caccount.withdraw(TEST_VALUE, eth_address_to_bin(decode_data_contract.address), "Getting all the moneys", required_confirmation_count=4) op.other_data["gas"] = 1000000 op.other_data["data"] = "0x001234" success_op_count, failed_op_count = eth_service.run_waiting_operations() assert success_op_count == 1 assert failed_op_count == 0 with transaction.manager: # create + deposit + withdraw op = dbsession.query(CryptoOperation).all()[2] txid = bin_to_txid(op.txid) # This should make the tx to included in a block confirm_transaction(web3, txid) value = decode_data_contract.call().value() data = decode_data_contract.call().data() assert value == 10000000000000000 assert data == '0x001234'
def get_balance(self) -> Decimal: """Gets the balance on this contract address over RPC and converts to ETH.""" return wei_to_eth(self.web3.eth.getBalance(self.address))
def test_withdraw_eth(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, withdraw_address: str, target_account: str): """Perform a withdraw operation. Create a database address with balance. """ # First check what's our balance before sending coins back current_balance = wei_to_eth(web3.eth.getBalance(target_account)) assert current_balance == 0 with transaction.manager: # Create withdraw operation caccount = dbsession.query(CryptoAddressAccount).one() #: We are going to withdraw the full amount on the account assert caccount.account.get_balance() == TEST_VALUE # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish op = caccount.withdraw(TEST_VALUE, eth_address_to_bin(target_account), "Getting all the moneys", required_confirmation_count=4) success_op_count, failed_op_count = eth_service.run_waiting_operations() assert success_op_count == 1 assert failed_op_count == 0 with transaction.manager: # We should have now three ops # One for creating the address # One for depositing value for the test # One for withdraw # We have one complete operation ops = list(dbsession.query(CryptoOperation).all()) assert len(ops) == 3 # Create + deposit + withdraw op = ops[-1] assert isinstance(op, CryptoAddressWithdraw) assert op.broadcasted_at is not None # This completes instantly, cannot be cancelled assert op.completed_at is None # We need at least one confirmation assert op.block is None assert op.txid is not None txid = bin_to_txid(op.txid) # This should make the tx to included in a block confirm_transaction(web3, txid) # Now we should get block number for the withdraw eth_service.run_confirmation_updates() # Geth reflects the deposit instantly internally, doesn't wait for blocks fee = get_withdrawal_fee(web3) new_balance = wei_to_eth(web3.eth.getBalance(target_account)) assert new_balance == current_balance + TEST_VALUE - fee current_block = web3.eth.blockNumber with transaction.manager: # Check we get block and txid # We have one complete operation ops = list(dbsession.query(CryptoOperation).all()) assert len(ops) == 3 # Create + deposit + withdraw op = ops[-1] assert op.broadcasted_at is not None # This completes instantly, cannot be cancelled assert op.completed_at is None, "Got confirmation for block {}, current {}, requires {}".format(op.block, current_block, op.required_confirmation_count) assert op.block is not None assert op.txid is not None block_num = op.block required_conf = op.required_confirmation_count # Wait block to make the confirmation happen wait_for_block_number(web3, block_num + required_conf + 1, timeout=60) # Now we should have enough blocks to mark the transaction as confirmed eth_service.run_confirmation_updates() with transaction.manager: # Check we get block and txid # We have one complete operation ops = list(dbsession.query(CryptoOperation).all()) op = ops[-1] assert op.state == CryptoOperationState.success assert op.completed_at is not None