def test_event_withdraw_wallet(web3, topped_up_hosted_wallet, coinbase): """Withdraw funds from the wallet and see that we get the event of the deposit.""" hosted_wallet = topped_up_hosted_wallet coinbase_address = coinbase listener, events = create_contract_listener(hosted_wallet.contract) # Do a withdraw from wallet txid = hosted_wallet.withdraw(coinbase_address, TEST_VALUE) confirm_transaction(web3, txid) # Wallet contract should generate events if the withdraw succeeded or not update_count = listener.poll() assert update_count == 1 assert len(events) == 1 event_name, input_data = events[0] assert event_name == "Withdraw" assert input_data["value"] == to_wei(TEST_VALUE) assert input_data["to"] == coinbase_address # Deposit some more, should generate one new event txid = hosted_wallet.withdraw(coinbase_address, TEST_VALUE) confirm_transaction(web3, txid) update_count = listener.poll() assert update_count == 1 assert event_name == "Withdraw" assert input_data["value"] == to_wei(TEST_VALUE) assert input_data["to"] == coinbase_address
def test_event_fund_wallet(web3, hosted_wallet): """Send some funds int the wallet and see that we get the event of the deposit.""" listener, events = create_contract_listener(hosted_wallet.contract) # value = get_wallet_balance(testnet_wallet_contract_address) txid = send_balance_to_contract(hosted_wallet, TEST_VALUE) confirm_transaction(web3, txid) update_count = listener.poll() assert update_count == 1 assert len(events) == 1 event_name, input_data = events[0] assert event_name == "Deposit" assert input_data["value"] == to_wei(TEST_VALUE) # Deposit some more, should generate one new event txid = send_balance_to_contract(hosted_wallet, TEST_VALUE) confirm_transaction(web3, txid) update_count = listener.poll() assert update_count == 1 assert len(events) == 2 event_name, input_data = events[1] assert event_name == "Deposit" assert input_data["value"] == to_wei(TEST_VALUE)
def test_event_claim_fees(web3, topped_up_hosted_wallet, coinbase): """We correctly can claim transaction fees from the hosted wallet contract.""" hosted_wallet = topped_up_hosted_wallet coinbase_address = coinbase # Do a withdraw to cause some fees listener, events = create_contract_listener(hosted_wallet.contract) assert hosted_wallet.get_balance() > TEST_VALUE txid = hosted_wallet.withdraw(coinbase_address, TEST_VALUE) confirm_transaction(web3, txid) # Claim fees for the withdraw operation claim_txid, price = hosted_wallet.claim_fees(txid) confirm_transaction(web3, claim_txid) # We should have event for withdraw + claims update_count = listener.poll() assert update_count == 2 assert len(events) == 2 event_name, input_data = events[-1] # Fee claim event assert event_name == "ClaimFee" assert input_data["txid"] == txid_to_bin( txid) # This was correctly targeted to original withdraw assert input_data["value"] == to_wei(price) # We claimed correct amount
def withdraw(self, to_address: str, amount_in_eth: Decimal, from_account=None, max_gas=100000) -> str: """Withdraw funds from a wallet contract. :param amount_in_eth: How much as ETH :param to_address: Destination address we are withdrawing to :param from_account: Which Geth account pays the gas :return: Transaction hash as 0x string """ assert isinstance(amount_in_eth, Decimal) # Don't let floats slip through wei = to_wei(amount_in_eth) if not from_account: # Default to coinbase for transaction fees from_account = self.contract.web3.eth.coinbase tx_info = { # The Ethereum account that pays the gas for this operation "from": from_account, "gas": max_gas, } # Sanity check that we own this wallet owner = self.contract.call().owner() # TODO: parent ABI not stable owner = ensure_0x_prefixed_hex(owner) assert owner == from_account # Interact with underlying wrapped contract txid = self.contract.transact(tx_info).withdraw(to_address, wei) return txid
def xxx_test_registrar_based_wallet(web3: Web3, coinbase): """Create registrar contract and register a wallet against it.""" wei_amount = to_wei(TEST_VALUE) # Check we get somewhat valid ids contract_def = get_compiled_contract_cached("OwnedRegistrar") registrar_contract, txid = deploy_contract(web3, contract_def) # Deploy wallet contract body wallet_contract_def = get_compiled_contract_cached("Wallet") wallet_contract, txid = deploy_contract(web3, wallet_contract_def) assert wallet_contract.call().version().decode("utf-8") == "1.0" # Register wallet contract body assert wallet_contract.address txid = registrar_contract.transact().setAddr(b"wallet", wallet_contract.address) confirm_transaction(web3, txid) # Check registration succeeded assert decode_addr( registrar_contract.call().addr(b"wallet")) == wallet_contract.address # Deploy relay against the registered wallet contract_def = get_compiled_contract_cached("Relay") assert registrar_contract.address relay, txid = deploy_contract( web3, contract_def, constructor_arguments=[registrar_contract.address, "wallet"]) # Test relayed wallet. We use Wallet ABI # against Relay contract. contract_def = get_compiled_contract_cached("Wallet") relayed_wallet = get_contract(web3, contract_def, relay.address) # Check relay internal data structures assert decode_addr( relay.call().registrarAddr()) == registrar_contract.address assert relay.call().name() == b"wallet" # We point to the wallet implementation impl_addr = decode_addr(relay.call().getImplAddr()) assert impl_addr == wallet_contract.address # Read a public variable through relay contract assert relayed_wallet.call().version().decode("utf-8") == "1.0" # Deposit some ETH txid = send_balance_to_contract(relayed_wallet.address, wei_amount) confirm_transaction(web3, txid) assert relayed_wallet.web3.eth.getBalance(relayed_wallet.address, wei_amount) # Withdraw ETH back relayed_wallet.transact().withdraw(coinbase, wei_amount) confirm_transaction(web3, txid) assert relayed_wallet.web3.eth.getBalance(relayed_wallet.address, 0)
def send_balance_to_address(web3: Web3, address: str, value: Decimal) -> str: assert address.startswith("0x") tx = { "from": web3.eth.coinbase, "to": address, "value": to_wei(value) } return web3.eth.sendTransaction(tx)
def send_balance_to_address(web3: Web3, address: str, value: Decimal) -> str: assert address.startswith("0x") tx = { "from": web3.eth.coinbase, "to": address, "value": to_wei(value), "gas": 600000 } return web3.eth.sendTransaction(tx)
def execute(self, to_contract: Contract, func: str, args=None, amount_in_eth: Optional[Decimal] = None, max_gas=300000): """Calls a smart contract from the hosted wallet. Creates a transaction that is proxyed through hosted wallet execute method. We need to have ABI as Populus Contract instance. :param wallet_address: Wallet address :param contract: Contract to called as address bound Populus Contract class :param func: Method name to be called :param args: Arguments passed to the method :param value: Additional value carried in the call in ETH :param gas: The max amount of gas the coinbase account is allowed to pay for this transaction. :return: txid of the execution as hex string """ assert isinstance(to_contract, Contract) if amount_in_eth: assert isinstance(amount_in_eth, Decimal) # Don't let floats slip through value = to_wei(amount_in_eth) else: value = 0 # Encode function arguments # function_abi = to_contract._find_matching_fn_abi(func, args) # 4 byte function hash # function_selector = function_abi_to_4byte_selector(function_abi) # data payload passed to the function arg_data = to_contract.encodeABI(func, args=args) call_data = arg_data # Latest web3 behavior, no manual function selector needed # test_event_execute() call data should look like # function selector + random int as 256-bit # 0x5093dc7d000000000000000000000000000000000000000000000000000000002a3f58fe # web3 takes bytes argument as actual bytes, not hex call_data = binascii.unhexlify(call_data[2:]) tx_info = { # The Ethereum account that pays the gas for this operation "from": self.contract.web3.eth.coinbase, "gas": max_gas, } txid = self.contract.transact(tx_info).execute(to_contract.address, value, max_gas, call_data) return txid
def xxx_test_registrar_based_wallet(web3: Web3, coinbase): """Create registrar contract and register a wallet against it.""" wei_amount = to_wei(TEST_VALUE) # Check we get somewhat valid ids contract_def = get_compiled_contract_cached("OwnedRegistrar") registrar_contract, txid = deploy_contract(web3, contract_def) # Deploy wallet contract body wallet_contract_def = get_compiled_contract_cached("Wallet") wallet_contract, txid = deploy_contract(web3, wallet_contract_def) assert wallet_contract.call().version().decode("utf-8") == "1.0" # Register wallet contract body assert wallet_contract.address txid = registrar_contract.transact().setAddr(b"wallet", wallet_contract.address) confirm_transaction(web3, txid) # Check registration succeeded assert decode_addr(registrar_contract.call().addr(b"wallet")) == wallet_contract.address # Deploy relay against the registered wallet contract_def = get_compiled_contract_cached("Relay") assert registrar_contract.address relay, txid = deploy_contract(web3, contract_def, constructor_arguments=[registrar_contract.address, "wallet"]) # Test relayed wallet. We use Wallet ABI # against Relay contract. contract_def = get_compiled_contract_cached("Wallet") relayed_wallet = get_contract(web3, contract_def, relay.address) # Check relay internal data structures assert decode_addr(relay.call().registrarAddr()) == registrar_contract.address assert relay.call().name() == b"wallet" # We point to the wallet implementation impl_addr = decode_addr(relay.call().getImplAddr()) assert impl_addr == wallet_contract.address # Read a public variable through relay contract assert relayed_wallet.call().version().decode("utf-8") == "1.0" # Deposit some ETH txid = send_balance_to_contract(relayed_wallet.address, wei_amount) confirm_transaction(web3, txid) assert relayed_wallet.web3.eth.getBalance(relayed_wallet.address, wei_amount) # Withdraw ETH back relayed_wallet.transact().withdraw(coinbase, wei_amount) confirm_transaction(web3, txid) assert relayed_wallet.web3.eth.getBalance(relayed_wallet.address, 0)
def withdraw(self, to_address: str, amount_in_eth: Decimal, from_account=None, max_gas=0, data=None) -> str: """Withdraw funds from a wallet contract. :param amount_in_eth: How much as ETH :param to_address: Destination address we are withdrawing to :param from_account: Which Geth account pays the gas :return: Transaction hash as 0x string """ assert isinstance(amount_in_eth, Decimal) # Don't let floats slip through wei = to_wei(amount_in_eth) if not from_account: # Default to coinbase for transaction fees from_account = self.contract.web3.eth.coinbase tx_info = { # The Ethereum account that pays the gas for this operation "from": from_account, } if max_gas: tx_info["gas"] = max_gas else: max_gas = 0 # Sanity check that we own this wallet owner = self.contract.call().owner() # TODO: parent ABI not stable owner = ensure_0x_prefixed_hex(owner) assert owner == from_account if data: txid = self.contract.transact(tx_info).execute( to_address, wei, max_gas, data) else: # Interact with underlying wrapped contract txid = self.contract.transact(tx_info).withdraw( to_address, wei, max_gas) return txid
def execute( self, to_contract: Contract, func: str, args=None, amount_in_eth: Optional[Decimal] = None, max_gas=100000 ): """Calls a smart contract from the hosted wallet. Creates a transaction that is proxyed through hosted wallet execute method. We need to have ABI as Populus Contract instance. :param wallet_address: Wallet address :param contract: Contract to called as address bound Populus Contract class :param func: Method name to be called :param args: Arguments passed to the method :param value: Additional value carried in the call in ETH :param gas: The max amount of gas the coinbase account is allowed to pay for this transaction. :return: txid of the execution as hex string """ assert isinstance(to_contract, Contract) if amount_in_eth: assert isinstance(amount_in_eth, Decimal) # Don't let floats slip through value = to_wei(amount_in_eth) else: value = 0 # Encode function arguments function_abi = to_contract._find_matching_fn_abi(func, args) # 4 byte function hash function_selector = function_abi_to_4byte_selector(function_abi) # data payload passed to the function arg_data = to_contract.encodeABI(func, args=args) call_data = function_selector + arg_data[2:] # test_event_execute() call data should look like # function selector + random int as 256-bit # 0x5093dc7d000000000000000000000000000000000000000000000000000000002a3f58fe # web3 takes bytes argument as actual bytes, not hex call_data = binascii.unhexlify(call_data[2:]) tx_info = { # The Ethereum account that pays the gas for this operation "from": self.contract.web3.eth.coinbase, "gas": max_gas, } txid = self.contract.transact(tx_info).execute(to_contract.address, value, max_gas, call_data) return txid
def send_balance_to_contract(contract: Contract, value: Decimal) -> str: """Send balance from geth coinbase to the contract. :param contract: Contract instance with an address :param value: How much to send :return: Transaction hash of the send operation """ web3 = contract.web3 tx = { "from": web3.eth.coinbase, "to": contract.address, "value": to_wei(value) } return web3.eth.sendTransaction(tx)
def send_balance_to_contract(contract: Contract, value: Decimal, gas=None) -> str: """Send balance from geth coinbase to the contract. :param contract: Contract instance with an address :param value: How much to send :return: Transaction hash of the send operation """ web3 = contract.web3 tx = { "from": web3.eth.coinbase, "to": contract.address, "value": to_wei(value) } if gas: tx["gas"] = gas return web3.eth.sendTransaction(tx)
def test_event_withdraw_wallet_too_much(web3: Web3, topped_up_hosted_wallet, coinbase): """Try to withdraw more than the wallet has.""" hosted_wallet = topped_up_hosted_wallet coinbase_address = coinbase listener, events = create_contract_listener(hosted_wallet.contract) too_much = Decimal(99999999) txid = hosted_wallet.withdraw(coinbase_address, too_much) confirm_transaction(web3, txid) update_count = listener.poll() # XXX: assert update_count == 1 assert len(events) == 1 event_name, input_data = events[0] assert event_name == "ExceededWithdraw" assert input_data["value"] == to_wei(too_much)