def perform_tx(): op = dbsession.query(CryptoOperation).get(opid) # Check everyting looks sane assert op.crypto_account.id assert op.crypto_account.account.id asset = op.holding_account.asset assert asset.id # Set information on asset that we have now created and have its smart contract id assert not asset.external_id, "Asset has been already assigned its smart contract id. Recreate error?" address = bin_to_eth_address(op.crypto_account.address.address) # Create Tonex proxy object token = Token.create_token(web3, name=asset.name, symbol=asset.symbol, supply=asset.supply, owner=address) # Call geth RPC API over Populus contract proxy op.txid = txid_to_bin(token.initial_txid) op.block = None op.external_address = eth_address_to_bin(token.address) asset.external_id = op.external_address op.mark_performed() op.mark_broadcasted()
def setup_listeners(self): """Setup subsystems that scan for incoming events from geth.""" wallet_contract = HostedWallet.contract_class(self.web3) token_contract = Token.contract_class(self.web3) self.eth_wallet_listener = EthWalletListener(self.web3, wallet_contract, self.dbsession, self.asset_network_id, registry=self.registry) self.eth_token_listener = EthTokenListener(self.web3, token_contract, self.dbsession, self.asset_network_id, registry=self.registry) self.confirmation_updater = DatabaseConfirmationUpdater(self.web3, self.dbsession, self.asset_network_id, self.registry) self.op_queue_manager = OperationQueueManager(self.web3, self.dbsession, self.asset_network_id, self.registry)
def test_starter_token(dbsession, registry, eth_network_id, web3: Web3, eth_service: EthereumService, house_address, toybox): """See that a fresh user account is supplied with some play assets.""" with transaction.manager: user = create_user(dbsession, registry) setup_user_account(user) # Let all the events completed success, fail = eth_service.run_event_cycle() assert success == 1 assert fail == 0 # We need another event cycle to process the initial asset transfers with transaction.manager: user = dbsession.query(User).first() opid = user.user_data["starter_asset_txs"][0]["toybox"] assert opid wait_for_op_confirmations(eth_service, opid) # Let the transfer come through eth_service.run_event_cycle() # Make sure we confirm the deposit with transaction.manager: user = dbsession.query(User).first() user_depo = user.owned_crypto_operations.join(CryptoOperation).filter_by(operation_type=CryptoOperationType.deposit).first() opid = user_depo.crypto_operation.id wait_for_op_confirmations(eth_service, opid) with transaction.manager: # Sanity check our token contract posts us logs user = dbsession.query(User).first() client = get_rpc_client(web3) asset = dbsession.query(Asset).get(toybox) address = bin_to_eth_address(asset.external_id) network = dbsession.query(AssetNetwork).get(eth_network_id) user_address = UserCryptoAddress.get_default(user, network).address house_address = dbsession.query(CryptoAddress).get(house_address) house = bin_to_eth_address(house_address.address) token = Token.get(web3, address) # Check we correctly resolved low level logs token_logs = client.get_logs(from_block=0, address=[address]) wallet_logs = client.get_logs(from_block=0, address=[house]) assert len(token_logs) > 0 assert len(wallet_logs) > 0 # Check contract state matches assert token.contract.call().balanceOf(house) == 9990 assert token.contract.call().balanceOf(bin_to_eth_address(user_address.address)) == 10 # Check our internal book keeping matches assert house_address.get_account(asset).account.get_balance() == 9990 assert user_address.get_account(asset).account.get_balance() == 10
def test_withdraw_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, deposit_address: str, token_asset: str, target_account: str): """"See that we can withdraw token outside to an account.""" with transaction.manager: address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = dbsession.query(Asset).get(token_asset) caccount = address.get_account(asset) op = caccount.withdraw(Decimal(2000), eth_address_to_bin(target_account), "Sending to friend") opid = op.id assert caccount.account.get_balance() == 8000 # Run import token operation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # TX was broadcasted, marked as complete with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert isinstance(op, CryptoAddressWithdraw) asset = dbsession.query(Asset).get(token_asset) assert op.broadcasted_at assert not op.completed_at wait_for_op_confirmations(eth_service, opid) # After tx has been mined the new balances should match with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.broadcasted_at assert op.completed_at asset = dbsession.query(Asset).get(token_asset) # Tokens have been removed on from account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(deposit_address) == 8000 # Tokens have been credited on to account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(target_account) == 2000
def perform_tx(): op = dbsession.query(CryptoOperation).get(opid) address = bin_to_eth_address(op.external_address) token = Token.get(web3, address) network = op.network def gen_error(e: Exception): # Set operation as impossible to complete # Set user readable and technical error explanation op.mark_failed() op.other_data[ "error"] = "Address did not provide EIP-20 token API:" + address op.other_data["exception"] = str(e) logger.exception(e) try: name = token.contract.call().name() symbol = token.contract.call().symbol() supply = Decimal(token.contract.call().totalSupply()) except BadFunctionCallOutput as e: # When we try to access a contract attrib which is not supported by underlying code gen_error(e) return asset = network.create_asset(name=name, symbol=symbol, supply=supply, asset_class=AssetClass.token) asset.external_id = op.external_address # Fill in balances for the addresses we host # TODO: Too much for one transaction for caddress in dbsession.query(CryptoAddress).all(): # Returns 0 for unknown addresses try: amount = token.contract.call().balanceOf( bin_to_eth_address(caddress.address)) except BadFunctionCallOutput as e: # Bad contract doesn't define balanceOf() # This leaves badly imported asset gen_error(e) return if amount > 0: account = caddress.get_or_create_account(asset) account.account.do_withdraw_or_deposit( Decimal(amount), "Token contract import") # This operation immediately closes op.mark_performed() op.mark_broadcasted() op.mark_complete()
def test_withdraw_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, deposit_address: str, token_asset: str, target_account: str): """"See that we can withdraw token outside to an account.""" with transaction.manager: address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = dbsession.query(Asset).get(token_asset) caccount = address.get_account(asset) op = caccount.withdraw(Decimal(2000), eth_address_to_bin(target_account), "Sending to friend") opid = op.id assert caccount.account.get_balance() == 8000 # Run import token operation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # TX was broadcasted, marked as complete with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert isinstance(op, CryptoAddressWithdraw) asset = dbsession.query(Asset).get(token_asset) assert op.broadcasted_at assert not op.completed_at wait_for_op_confirmations(eth_service, opid) with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.broadcasted_at assert op.completed_at asset = dbsession.query(Asset).get(token_asset) # Tokens have been removed on from account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(deposit_address) == 8000 # Tokens have been credited on to account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(target_account) == 2000
def token(dbsession, web3, eth_service, eth_network_id) -> Callable: # signer, multisig, cap extra_arguments = [ web3.eth.coinbase, "0x8f480474b014ea63d4fe5e52478e833fb9e8f938", # Mikko's testnet address to_wei(6000, "ether"), 2, # 2 share per each ETH 10**18, # 1 ETH 10**18 wei ] token = Token.create_token(web3, name="Toycrowd", supply=0, symbol="TOYCROWD", owner=web3.eth.coinbase, extra_arguments=extra_arguments, contract_name="CrowdfundToken") print("Token deployed") return token
def perform_tx(): op = dbsession.query(CryptoOperation).get(opid) address = bin_to_eth_address(op.external_address) token = Token.get(web3, address) network = op.network def gen_error(e: Exception): # Set operation as impossible to complete # Set user readable and technical error explanation op.mark_failed() op.other_data["error"] = "Address did not provide EIP-20 token API:" + address op.other_data["exception"] = str(e) logger.exception(e) try: name = token.contract.call().name() symbol = token.contract.call().symbol() supply = Decimal(token.contract.call().totalSupply()) except eth_abi.exceptions.DecodingError as e: # When we try to access a contract attrib which is not supported by underlying code gen_error(e) return asset = network.create_asset(name=name, symbol=symbol, supply=supply, asset_class=AssetClass.token) asset.external_id = op.external_address # Fill in balances for the addresses we host # TODO: Too much for one transaction for caddress in dbsession.query(CryptoAddress).all(): # Returns 0 for unknown addresses try: amount = token.contract.call().balanceOf(bin_to_eth_address(caddress.address)) except eth_abi.exceptions.DecodingError as e: # Bad contract doesn't define balanceOf() # This leaves badly imported asset gen_error(e) return if amount > 0: account = caddress.get_or_create_account(asset) account.account.do_withdraw_or_deposit(Decimal(amount), "Token contract import") # This operation immediately closes op.mark_performed() op.mark_broadcasted() op.mark_complete()
def test_deposit_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, coinbase: str, deposit_address: str, token: Token): """"See that we can deposit tokens to accounts.""" # Import a contract where coinbase has all balance with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) import_token(network, eth_address_to_bin(token.address)) # Run import token operation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # Coinbase transfers token balance to deposit address txid = token.transfer(deposit_address, Decimal(4000)) confirm_transaction(web3, txid) # We should pick up incoming deposit success_count, failure_count = eth_service.run_listener_operations() assert success_count == 1 assert failure_count == 0 # Check that data is setup correctly on incoming transaction with transaction.manager: op = dbsession.query(CryptoOperation).all()[-1] opid = op.id assert not op.completed_at address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = op.holding_account.asset assert op.holding_account.get_balance() == 4000 assert op.completed_at is None assert address.get_account(asset).account.get_balance() == 0 # Not credited until confirmations reached assert address.crypto_address_accounts.count() == 1 # Wait the token transaction to get enough confirmations wait_for_op_confirmations(eth_service, opid) # Check that the transaction is not final with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at assert op.completed_at address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = op.holding_account.asset assert op.holding_account.get_balance() == 0 assert address.get_account(asset).account.get_balance() == 4000 assert op.state == CryptoOperationState.success
def token(web3, coinbase) -> Contract: """Deploy a token contract in the blockchain.""" # signer, multisig, cap extra_arguments = [ coinbase, MULTISIG, CAP, 2, # 2 share per each ETH 10**18, # 1 ETH 10**18 wei ] return Token.create_token(web3, name="Mootoken", supply=0, symbol="MOO", owner=coinbase, extra_arguments=extra_arguments, contract_name="CrowdfundToken")
def withdraw_token(web3: Web3, dbsession: Session, opid: UUID): """Perform token withdraw operation from the wallet.""" @retryable(tm=dbsession.transaction_manager) def prepare_withdraw(): # Check everyting looks sane op = dbsession.query(CryptoOperation).get(opid) assert op.crypto_account.id assert op.crypto_account.account.id assert op.holding_account.id assert op.holding_account.get_balance() > 0 assert op.external_address assert op.required_confirmation_count # Should be set by the creator asset = op.holding_account.asset assert asset.asset_class == AssetClass.token from_address = bin_to_eth_address(op.crypto_account.address.address) to_address = bin_to_eth_address(op.external_address) asset_address = bin_to_eth_address(asset.external_id) # How much we are withdrawing amount = op.holding_account.transactions.one().amount op.mark_performed() # Don't try to pick this op automatically again return from_address, to_address, asset_address, amount @retryable(tm=dbsession.transaction_manager) def close_withdraw(): # Fill in details. # Block number will be filled in later, when confirmation updater picks a transaction receipt for this operation. op = dbsession.query(CryptoOperation).get(opid) op.txid = txid_to_bin(txid) op.block = None op.mark_broadcasted() from_address, to_address, asset_address, amount = prepare_withdraw() wallet = HostedWallet.get(web3, from_address) token = Token.get(web3, asset_address) amount = token.validate_transfer_amount(amount) # Perform actual transfer outside retryable transaction # boundaries to avoid double withdrwa txid = wallet.execute(token.contract, "transfer", [to_address, amount]) close_withdraw()
def test_import_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, coinbase: str, deposit_address: str, token: Token): """Import an existing smart contract token to system.""" # Make sure we have an address that holds some of the tokens so it is cleared up during import txid = token.transfer(deposit_address, Decimal(4000)) confirm_transaction(web3, txid) with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) op = import_token(network, eth_address_to_bin(token.address)) opid = op.id # Let's create another address that doesn't hold tokens # and see that import doesn't fail for it addr = CryptoAddress(network=network, address=eth_address_to_bin("0x2f70d3d26829e412a602e83fe8eebf80255aeea5")) dbsession.add(addr) success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # Check that we created a new asset and its imports where fulfilled with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at # We got account with tokens on it network = dbsession.query(AssetNetwork).get(eth_network_id) caddress = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) asset = network.assets.filter_by(external_id=eth_address_to_bin(token.address)).one() assert asset.name == "Mootoken" assert asset.symbol == "MOO" assert asset.supply == 10000 caccount = caddress.get_account_by_address(eth_address_to_bin(token.address)) assert caccount.account.get_balance() == 4000
def token(web3, coinbase) -> Contract: """Deploy a token contract in the blockchain.""" return Token.create_token(web3, name="Mootoken", supply=10000, symbol="MOO", owner=coinbase)
def test_starter_token(dbsession, registry, eth_network_id, web3: Web3, eth_service: EthereumService, house_address, toybox): """See that a fresh user account is supplied with some play assets.""" with transaction.manager: user = create_user(dbsession, registry) setup_user_account(user, do_mainnet=True) # Let all the events completed success, fail = eth_service.run_event_cycle() assert success == 1 assert fail == 0 # We need another event cycle to process the initial asset transfers with transaction.manager: user = dbsession.query(User).first() opid = user.user_data["starter_asset_txs"][0]["toybox"] assert opid wait_for_op_confirmations(eth_service, opid) # Let the transfer come through eth_service.run_event_cycle() # Make sure we confirm the deposit with transaction.manager: user = dbsession.query(User).first() user_depo = user.owned_crypto_operations.join( CryptoOperation).filter_by( operation_type=CryptoOperationType.deposit).first() opid = user_depo.crypto_operation.id wait_for_op_confirmations(eth_service, opid) with transaction.manager: # Sanity check our token contract posts us logs user = dbsession.query(User).first() client = get_rpc_client(web3) asset = dbsession.query(Asset).get(toybox) address = bin_to_eth_address(asset.external_id) network = dbsession.query(AssetNetwork).get(eth_network_id) user_address = UserCryptoAddress.get_default(user, network).address house_address = dbsession.query(CryptoAddress).get(house_address) house = bin_to_eth_address(house_address.address) token = Token.get(web3, address) # Check we correctly resolved low level logs token_logs = client.get_logs(from_block=0, address=[address]) wallet_logs = client.get_logs(from_block=0, address=[house]) assert len(token_logs) > 0 assert len(wallet_logs) > 0 # Check contract state matches assert token.contract.call().balanceOf(house) == 9990 assert token.contract.call().balanceOf( bin_to_eth_address(user_address.address)) == 10 # Check our internal book keeping matches assert house_address.get_account(asset).account.get_balance() == 9990 assert user_address.get_account(asset).account.get_balance() == 10