def scanned_distribution(logger, dbsession, web3, private_key_hex, sample_csv_file, sample_token): """Create some sample transactions so we can scan the token holder balances.""" token_address = sample_token entries = read_csv(logger, sample_csv_file) new_distributes, old_distributes = distribute_tokens(logger, dbsession, "testing", web3, ethereum_abi_file=None, ethereum_private_key=private_key_hex, ethereum_gas_limit=None, ethereum_gas_price=None, token_address=token_address, dists=entries) assert new_distributes == 2 assert old_distributes == 0 # Check they got mined # Send transactions to emphmereal test chain txs = broadcast(logger, dbsession, "testing", web3, ethereum_private_key=private_key_hex, ethereum_gas_limit=None, ethereum_gas_price=None, ) # Check they got mined txs = update_status(logger, dbsession, "testing", web3, ethereum_private_key=private_key_hex, ethereum_gas_limit=None, ethereum_gas_price=None, ) # Check that rerun does not recreate txs new_distributes, old_distributes = distribute_tokens(logger, dbsession, "testing", web3, ethereum_abi_file=None, ethereum_private_key=private_key_hex, ethereum_gas_limit=None, ethereum_gas_price=None, token_address=token_address, dists=entries) assert new_distributes == 0 assert old_distributes == 2 token_scan(logger, dbsession, "testing", web3, None, token_address) return token_address
def token_scan(config: BoardCommmadConfiguration, token_address, start_block, end_block): """Update token holder balances from a blockchain to a local database. Reads the Ethereum blockchain for a certain token and builds a local database of token holders and transfers. If start block and end block information are omitted, continue the scan where we were left last time. Scan operations may take a while. """ assert is_ethereum_network(config.network) logger = config.logger from sto.ethereum.tokenscan import token_scan dbsession = config.dbsession updated_addresses = token_scan(logger, dbsession, config.network, ethereum_node_url=config.ethereum_node_url, ethereum_abi_file=config.ethereum_abi_file, token_address=token_address, start_block=start_block, end_block=end_block) logger.info("Updated %d token holder balances", len(updated_addresses))
def test_simple_token_balance_scan(logger, dbsession, network, sample_distribution, web3): """See that we probably scan token balances.""" token_address = sample_distribution start_block = 1 end_block = web3.eth.blockNumber all_balances = token_scan(logger, dbsession, network, web3, None, token_address, start_block, end_block) correct_result = { '0x0bdcc26C4B8077374ba9DB82164B77d6885b92a6': 300 * 10**18, '0xDE5bC059aA433D72F25846bdFfe96434b406FA85': 9199 * 10**18, '0xE738f7A6Eb317b8B286c27296cD982445c9D8cd2': 500 * 10**18 } # print("All balances:", all_balances) assert all_balances == correct_result # Read balances from the datbase token_status = dbsession.query(TokenScanStatus).filter_by( address=token_address).one() assert token_status.network == "testing" assert token_status.get_total_token_holder_count() == 3 last_balance_a6 = token_status.get_accounts().filter_by( address="0x0bdcc26C4B8077374ba9DB82164B77d6885b92a6").one() assert last_balance_a6.get_balance_uint() == 300 * 10**18 assert last_balance_a6.last_block_num == 6 assert last_balance_a6.last_block_updated_at is not None last_balance_d2 = token_status.get_accounts().filter_by( address="0xE738f7A6Eb317b8B286c27296cD982445c9D8cd2").one() assert last_balance_d2.get_balance_uint() == 500 * 10**18 assert last_balance_d2.last_block_num == 7 assert last_balance_d2.last_block_updated_at is not None # Rescan should be ok, yield to same results # This will drop data and scan again rescanned_all_balances = token_scan(logger, dbsession, network, web3, None, token_address, start_block, end_block) assert all_balances == rescanned_all_balances assert token_status.start_block == 1 assert token_status.end_block == 7
def test_token_scan_incremental(logger, dbsession, network, private_key_hex, sample_token, web3, test_account_1, test_account_2, test_account_3, token_contract): """Call token scan repeatly and see we get new events in.""" token_address = sample_token send_issuer_tokens(logger, dbsession, web3, private_key_hex, token_address, test_account_1, 101) # Run incremental scan balances = token_scan(logger, dbsession, network, web3, None, sample_token) correct_result = { '0xDE5bC059aA433D72F25846bdFfe96434b406FA85': 9898 * 10**18, test_account_1: 101 * 10**18 } assert balances == correct_result # Issuer distributes some more tokens start_block = web3.eth.blockNumber + 1 send_issuer_tokens(logger, dbsession, web3, private_key_hex, token_address, test_account_2, 333) end_block = web3.eth.blockNumber balances = token_scan(logger, dbsession, network, web3, None, sample_token, start_block, end_block) correct_result = { '0xDE5bC059aA433D72F25846bdFfe96434b406FA85': 9565 * 10**18, test_account_2: 333 * 10**18 } assert balances == correct_result # Account 1 send some tokens to account 3 start_block = web3.eth.blockNumber + 1 token_contract.functions.transfer(test_account_3, 51 * 10**18).transact( {"from": test_account_1}) end_block = web3.eth.blockNumber balances = token_scan(logger, dbsession, network, web3, None, sample_token, start_block, end_block) correct_result = { test_account_1: 50 * 10**18, test_account_3: 51 * 10**18, } assert balances == correct_result # Account 1 empties itself, send all remaining tokens to account 3 start_block = web3.eth.blockNumber + 1 token_contract.functions.transfer(test_account_3, 50 * 10**18).transact( {"from": test_account_1}) end_block = web3.eth.blockNumber balances = token_scan(logger, dbsession, network, web3, None, sample_token, start_block, end_block) correct_result = { test_account_1: 0 * 10**18, test_account_3: 101 * 10**18, } assert balances == correct_result # We have final token holder counts token_status = dbsession.query(TokenScanStatus).filter_by( address=token_address).one() assert token_status.get_total_token_holder_count( ) == 3 # issuer, test_account_2, test_account_3 assert token_status.get_total_token_holder_count( include_empty=True ) == 4 # issuer, test_accont_1, test_account_2, test_account_3 # Do a full scan from the scratch to see the results look ok #<Token:0x890042E3d93aC10A426c7ac9e96ED6416B0cC616, holder:0xDE5bC059aA433D72F25846bdFfe96434b406FA85, updated at:8, balance:9565000000000000000000> #<Token:0x890042E3d93aC10A426c7ac9e96ED6416B0cC616, holder:0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF, updated at:10, balance:0> #<Token:0x890042E3d93aC10A426c7ac9e96ED6416B0cC616, holder:0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, updated at:8, balance:333000000000000000000> #<Token:0x890042E3d93aC10A426c7ac9e96ED6416B0cC616, holder:0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, updated at:10, balance:101000000000000000000> # Check that result match when start from the beginning balances = token_scan(logger, dbsession, network, web3, None, sample_token, start_block=1, end_block=web3.eth.blockNumber) correct_result = { '0xDE5bC059aA433D72F25846bdFfe96434b406FA85': 9565 * 10**18, test_account_1: 0, test_account_2: 333 * 10**18, test_account_3: 101 * 10**18, } assert balances == correct_result # Check that result match when start from the middle balances = token_scan(logger, dbsession, network, web3, None, sample_token, start_block=7, end_block=web3.eth.blockNumber) correct_result = { '0xDE5bC059aA433D72F25846bdFfe96434b406FA85': 9565 * 10**18, test_account_1: 0, test_account_2: 333 * 10**18, test_account_3: 101 * 10**18, } assert balances == correct_result
def scanned_distribution(logger, dbsession, web3, private_key_hex, sample_csv_file, sample_token, click_runner, db_path, monkeypatch_create_web3): """Create some sample transactions so we can scan the token holder balances.""" token_address = sample_token entries = read_csv(logger, sample_csv_file) for entry in entries: # whitelist customers result = click_runner.invoke(cli, [ '--database-file', db_path, '--ethereum-private-key', private_key_hex, '--ethereum-gas-limit', 999999999, 'kyc-manage', '--whitelist-address', entry.address ]) assert result.exit_code == 0 result = click_runner.invoke(cli, [ '--database-file', db_path, '--ethereum-private-key', private_key_hex, '--ethereum-gas-limit', 999999999, "distribute-multiple", '--csv-input', sample_csv_file, '--address', token_address ]) assert result.exit_code == 0 result = click_runner.invoke(cli, [ '--database-file', db_path, '--ethereum-private-key', private_key_hex, '--ethereum-gas-limit', 999999999, 'tx-broadcast', ]) assert result.exit_code == 0 # Check they got mined # Send transactions to emphmereal test chain result = click_runner.invoke(cli, [ '--database-file', db_path, '--ethereum-private-key', private_key_hex, '--ethereum-gas-limit', 999999999, 'tx-broadcast', ]) assert result.exit_code == 0 # Check they got mined txs = update_status( logger, dbsession, "testing", web3, ethereum_private_key=private_key_hex, ethereum_gas_limit=None, ethereum_gas_price=None, ) # Check that rerun does not recreate txs result = click_runner.invoke(cli, [ '--database-file', db_path, '--ethereum-private-key', private_key_hex, '--ethereum-gas-limit', 999999999, "distribute-multiple", '--csv-input', sample_csv_file, '--address', token_address ]) assert result.exit_code == 0 result = click_runner.invoke(cli, [ '--database-file', db_path, '--ethereum-private-key', private_key_hex, '--ethereum-gas-limit', 999999999, 'tx-broadcast', ]) assert result.exit_code == 0 token_scan(logger, dbsession, "testing", web3, None, token_address) return token_address