def parse_call_response(fn_abi, result): output_types = get_abi_output_types(fn_abi) output_data = Web3().codec.decode_abi(get_abi_output_types(fn_abi), binascii.unhexlify(result)) _normalizers = itertools.chain( BASE_RETURN_NORMALIZERS, [], ) normalized_data = map_abi_data(_normalizers, output_types, output_data) if len(normalized_data) == 1: return normalized_data[0] else: return normalized_data
def _call_contract_etherscan( self, contract_address: ChecksumEthAddress, abi: List, method_name: str, arguments: Optional[List[Any]] = None, ) -> Any: """Performs an eth_call to an ethereum contract via etherscan May raise: - RemoteError if there is a problem with reaching etherscan or with the returned result """ web3 = Web3() contract = web3.eth.contract(address=contract_address, abi=abi) input_data = contract.encodeABI(method_name, args=arguments if arguments else []) result = self.etherscan.eth_call( to_address=contract_address, input_data=input_data, ) fn_abi = contract._find_matching_fn_abi( fn_identifier=method_name, args=arguments, ) output_types = get_abi_output_types(fn_abi) output_data = web3.codec.decode_abi(output_types, bytes.fromhex(result[2:])) if len(output_data) == 1: return output_data[0] return output_data
async def _async_call(self, func, *args, **kwargs): tx = prepare_transaction(self.__contract.address, None, func.function_identifier, func.contract_abi, func.abi, {}, args, kwargs) if self.__block is None: return_data = await self.__contract.web3.eth.call(tx) else: return_data = await self.__contract.web3.eth.call(tx, self.__block) output_types = get_abi_output_types(func.abi) result = decode_abi(output_types, hexstr_if_str(to_bytes, return_data)) return result if len(func.abi['outputs']) > 1 else result[0]
def decode( self, result: Decodable, method_name: str, arguments: Optional[List[Any]] = None, ) -> Tuple[Any, ...]: contract = WEB3.eth.contract(address=self.address, abi=self.abi) fn_abi = contract._find_matching_fn_abi( fn_identifier=method_name, args=arguments if arguments else [], ) output_types = get_abi_output_types(fn_abi) return WEB3.codec.decode_abi(output_types, result)
def _call_contract_etherscan( self, contract_address: ChecksumEthAddress, abi: List, method_name: str, arguments: Optional[List[Any]] = None, ) -> Any: """Performs an eth_call to an ethereum contract via etherscan May raise: - RemoteError if there is a problem with reaching etherscan or with the returned result """ web3 = Web3() contract = web3.eth.contract(address=contract_address, abi=abi) input_data = contract.encodeABI(method_name, args=arguments if arguments else []) result = self.etherscan.eth_call( to_address=contract_address, input_data=input_data, ) if result == '0x': raise BlockchainQueryError( f'Error doing call on contract {contract_address} for {method_name} ' f'with arguments: {str(arguments)} via etherscan. Returned 0x result', ) fn_abi = contract._find_matching_fn_abi( fn_identifier=method_name, args=arguments, ) output_types = get_abi_output_types(fn_abi) output_data = web3.codec.decode_abi(output_types, bytes.fromhex(result[2:])) if len(output_data) == 1: # due to https://github.com/PyCQA/pylint/issues/4114 return output_data[0] # pylint: disable=unsubscriptable-object return output_data
def mock_requests_get(url, *args, **kwargs): if 'etherscan.io/api?module=account&action=balance&address' in url: addr = url[67:109] value = eth_map[addr].get('ETH', '0') response = f'{{"status":"1","message":"OK","result":{value}}}' elif 'etherscan.io/api?module=account&action=balancemulti' in url: queried_accounts = [] length = 72 # process url and get the accounts while True: if len(url) < length: break potential_address = url[length:length + 42] if 'apikey=' in potential_address: break queried_accounts.append(potential_address) length += 43 accounts = [] for addr in queried_accounts: value = eth_map[addr].get('ETH', '0') accounts.append({'account': addr, 'balance': eth_map[addr]['ETH']}) response = f'{{"status":"1","message":"OK","result":{json.dumps(accounts)}}}' elif 'api.etherscan.io/api?module=account&action=tokenbalance' in url: token_address = url[80:122] msg = 'token address missing from test mapping' assert token_address in CONTRACT_ADDRESS_TO_TOKEN, msg response = '{"status":"1","message":"OK","result":"0"}' token = CONTRACT_ADDRESS_TO_TOKEN[token_address] account = url[131:173] value = eth_map[account].get(token.identifier, 0) response = f'{{"status":"1","message":"OK","result":"{value}"}}' elif 'api.etherscan.io/api?module=account&action=txlistinternal&' in url: if 'transactions' in original_queries: return original_requests_get(url, *args, **kwargs) # By default when mocking, don't query for transactions response = '{"status":"1","message":"OK","result":[]}' elif 'api.etherscan.io/api?module=account&action=txlist&' in url: if 'transactions' in original_queries: return original_requests_get(url, *args, **kwargs) # By default when mocking, don't query for transactions response = '{"status":"1","message":"OK","result":[]}' elif 'api.etherscan.io/api?module=logs&action=getLogs&' in url: if 'logs' in original_queries: return original_requests_get(url, *args, **kwargs) # By default when mocking, don't query logs response = '{"status":"1","message":"OK","result":[]}' elif 'api.etherscan.io/api?module=block&action=getblocknobytime&' in url: if 'blocknobytime' in original_queries: return original_requests_get(url, *args, **kwargs) # By default when mocking don't query blocknobytime response = '{"status":"1","message":"OK","result":"1"}' elif f'api.etherscan.io/api?module=proxy&action=eth_call&to={ZERION_ADAPTER_ADDRESS}' in url: # noqa: E501 if 'zerion' in original_queries: return original_requests_get(url, *args, **kwargs) web3 = Web3() contract = web3.eth.contract(address=ZERION_ADAPTER_ADDRESS, abi=ZERION_ABI) if 'data=0xc84aae17' in url: # getBalances data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='getBalances', args=['address'], ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) # TODO: This here always returns empty response. If/when we want to # mock it for etherscan, this is where we do it args = [] result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0x85c6a7930' in url: # getProtocolBalances data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='getProtocolBalances', args=['address', ['some', 'protocol', 'names']], ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) # TODO: This here always returns empty response. If/when we want to # mock it for etherscan, this is where we do it args = [] result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0x3b692f52' in url: # getProtocolNames data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='getProtocolNames', ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) # TODO: This here always returns empty response. If/when we want to # mock it for etherscan, this is where we do it args = [] result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' else: raise AssertionError(f'Unexpected etherscan call during tests: {url}') elif 'api.etherscan.io/api?module=proxy&action=eth_call&to=0xB6456b57f03352bE48Bf101B46c1752a0813491a' in url: # noqa: E501 # ADEX Staking contract if 'adex_staking' in original_queries: return original_requests_get(url, *args, **kwargs) if 'data=0x447b15f4' in url: # a mocked share value response = '{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000fc4a48782d85b51"}' # noqa: E501 else: raise AssertionError(f'Unknown call to Adex Staking pool during tests: {url}') elif f'api.etherscan.io/api?module=proxy&action=eth_call&to={ETH_MULTICALL.address}' in url: # noqa: E501 web3 = Web3() contract = web3.eth.contract(address=ETH_MULTICALL.address, abi=ETH_MULTICALL.abi) if 'b6456b57f03352be48bf101b46c1752a0813491a' in url: multicall_purpose = 'adex_staking' elif '5f3b5dfeb7b28cdbd7faba78963ee202a494e2a2' in url: multicall_purpose = 'vecrv' else: raise AssertionError('Unknown multicall in mocked tests') if 'data=0x252dba42' in url: # aggregate if multicall_purpose == 'adex_staking': if 'adex_staking' in original_queries: return original_requests_get(url, *args, **kwargs) if 'mocked_adex_staking_balance' in extra_flags: # mock adex staking balance for a single account response = '{"jsonrpc": "2.0", "id": 1, "result": "0x0000000000000000000000000000000000000000000000000000000000bb45aa000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000152982285d2e4d5aeaa9"}' # noqa: E501 return MockResponse(200, response) data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract.functions.abi[1] assert fn_abi['name'] == 'aggregate', 'Abi position of multicall aggregate changed' input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) # For now the only mocked multicall is 32 bytes for multicall balance # of both veCRV and adex staking pool. # When there is more we have to figure out a way to differentiate # between them in mocking. Just return empty response here # all pylint ignores below due to https://github.com/PyCQA/pylint/issues/4114 args = [1, [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' for x in decoded_input[0]]] # pylint: disable=unsubscriptable-object # noqa: E501 result = '0x' + web3.codec.encode_abi(output_types, args).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' else: raise AssertionError('Unexpected etherscan multicall during tests: {url}') elif f'api.etherscan.io/api?module=proxy&action=eth_call&to={ETH_SCAN.address}' in url: if 'ethscan' in original_queries: return original_requests_get(url, *args, **kwargs) web3 = Web3() contract = web3.eth.contract(address=ETH_SCAN.address, abi=ETH_SCAN.abi) if 'data=0xdbdbb51b' in url: # Eth balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='etherBalances', args=[list(eth_map.keys())], ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] for account_address in decoded_input[0]: # pylint: disable=unsubscriptable-object # noqa: E501 account_address = deserialize_ethereum_address(account_address) args.append(int(eth_map[account_address]['ETH'])) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0x06187b4f' in url: # Multi token multiaddress balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] # not really the given args, but we just want the fn abi args = [list(eth_map.keys()), list(eth_map.keys())] fn_abi = contract._find_matching_fn_abi( fn_identifier='tokensBalances', args=args, ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] for account_address in decoded_input[0]: # pylint: disable=unsubscriptable-object # noqa: E501 account_address = deserialize_ethereum_address(account_address) x = [] for token_address in decoded_input[1]: # pylint: disable=unsubscriptable-object # noqa: E501 token_address = deserialize_ethereum_address(token_address) value_to_add = 0 for given_asset, value in eth_map[account_address].items(): given_asset = _get_token(given_asset) if given_asset is None: # not a token continue if token_address != given_asset.ethereum_address: continue value_to_add = int(value) break x.append(value_to_add) args.append(x) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0xe5da1b68' in url: # Multi token balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] # not really the given args, but we just want the fn abi args = ['str', list(eth_map.keys())] fn_abi = contract._find_matching_fn_abi( fn_identifier='tokensBalance', args=args, ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] account_address = deserialize_ethereum_address(decoded_input[0]) # pylint: disable=unsubscriptable-object # noqa: E501 x = [] for token_address in decoded_input[1]: # pylint: disable=unsubscriptable-object # noqa: E501 token_address = deserialize_ethereum_address(token_address) value_to_add = 0 for given_asset, value in eth_map[account_address].items(): given_asset = _get_token(given_asset) if given_asset is None: # not a token continue if token_address != given_asset.ethereum_address: continue value_to_add = int(value) break args.append(value_to_add) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' else: raise AssertionError(f'Unexpected etherscan call during tests: {url}') else: return original_requests_get(url, *args, **kwargs) return MockResponse(200, response)
def call_contract_function(web3, address, normalizers, function_identifier, transaction, block_id=None, contract_abi=None, fn_abi=None, *args, **kwargs): """ Helper function for interacting with a contract function using the `eth_call` API. """ call_transaction = prepare_transaction( address, web3, fn_identifier=function_identifier, contract_abi=contract_abi, fn_abi=fn_abi, transaction=transaction, fn_args=args, fn_kwargs=kwargs, ) if block_id is None: return_data = web3.eth.call(call_transaction) else: return_data = web3.eth.call(call_transaction, block_identifier=block_id) if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs) output_types = get_abi_output_types(fn_abi) try: output_data = decode_abi(output_types, return_data) except DecodingError as e: # Provide a more helpful error message than the one provided by # eth-abi-utils is_missing_code_error = (return_data in ACCEPTABLE_EMPTY_STRINGS and web3.eth.getCode(address) in ACCEPTABLE_EMPTY_STRINGS) if is_missing_code_error: msg = ( "Could not transact with/call contract function, is contract " "deployed correctly and chain synced?") else: msg = ( "Could not decode contract function call {} return data {} for " "output_types {}".format(function_identifier, return_data, output_types)) raise BadFunctionCallOutput(msg) from e _normalizers = itertools.chain( BASE_RETURN_NORMALIZERS, normalizers, ) normalized_data = map_abi_data(_normalizers, output_types, output_data) if len(normalized_data) == 1: return normalized_data[0] else: return normalized_data
def mock_requests_get(url, *args, **kwargs): if 'etherscan.io/api?module=account&action=balance&address' in url: addr = url[67:109] value = eth_map[addr].get('ETH', '0') response = f'{{"status":"1","message":"OK","result":{value}}}' elif 'etherscan.io/api?module=account&action=balancemulti' in url: queried_accounts = [] length = 72 # process url and get the accounts while True: if len(url) < length: break potential_address = url[length:length + 42] if 'apikey=' in potential_address: break queried_accounts.append(potential_address) length += 43 accounts = [] for addr in queried_accounts: value = eth_map[addr].get('ETH', '0') accounts.append({ 'account': addr, 'balance': eth_map[addr]['ETH'] }) response = f'{{"status":"1","message":"OK","result":{json.dumps(accounts)}}}' elif 'api.etherscan.io/api?module=account&action=tokenbalance' in url: token_address = url[80:122] msg = 'token address missing from test mapping' assert token_address in CONTRACT_ADDRESS_TO_TOKEN, msg response = '{"status":"1","message":"OK","result":"0"}' token = CONTRACT_ADDRESS_TO_TOKEN[token_address] account = url[131:173] value = eth_map[account].get(token.identifier, 0) response = f'{{"status":"1","message":"OK","result":"{value}"}}' elif f'api.etherscan.io/api?module=proxy&action=eth_call&to={ETH_SCAN_ADDRESS}' in url: web3 = Web3() contract = web3.eth.contract(address=ETH_SCAN_ADDRESS, abi=ETH_SCAN_ABI) if 'data=0xdbdbb51b' in url: # Eth balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='etherBalances', args=[list(eth_map.keys())], ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] for account_address in decoded_input[0]: account_address = to_checksum_address(account_address) args.append(int(eth_map[account_address]['ETH'])) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0x06187b4f' in url: # Multi token balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] # not really the given args, but we just want the fn abi args = [list(eth_map.keys()), list(eth_map.keys())] fn_abi = contract._find_matching_fn_abi( fn_identifier='tokensBalances', args=args, ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] for account_address in decoded_input[0]: account_address = to_checksum_address(account_address) x = [] for token_address in decoded_input[1]: token_address = to_checksum_address(token_address) value_to_add = 0 for given_asset, value in eth_map[ account_address].items(): if not isinstance(given_asset, EthereumToken): continue if token_address != given_asset.ethereum_address: continue value_to_add = int(value) break x.append(value_to_add) args.append(x) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' else: raise AssertionError( f'Unexpected etherscan call during tests: {url}') else: return original_requests_get(url, *args, **kwargs) return MockResponse(200, response)
def mock_requests_get(url, *args, **kwargs): if 'etherscan.io/api?module=proxy&action=eth_blockNumber' in url: response = f'{{"status":"1","message":"OK","result":"{TEST_LATEST_BLOCKNUMBER_HEX}"}}' elif 'etherscan.io/api?module=proxy&action=eth_call' in url: to_address = url.split( 'https://api.etherscan.io/api?module=proxy&action=eth_call&to=', )[1][:42] input_data = url.split('data=')[1].split('&apikey')[0] if to_address == DS_PROXY_REGISTRY.address: if not input_data.startswith('0xc4552791'): raise AssertionError( 'Call to unexpected method of DSR ProxyRegistry during tests', ) # It's a call to proxy registry. Return the mapping if account1[2:].lower() in input_data: proxy_account = address_to_32byteshexstr(proxy1) elif account2[2:].lower() in input_data: proxy_account = address_to_32byteshexstr(proxy2) else: proxy_account = '0x' + '0' * 64 response = f'{{"status":"1","message":"OK","result":"{proxy_account}"}}' elif to_address == ETH_MULTICALL.address: web3 = Web3() contract = web3.eth.contract(address=ETH_MULTICALL.address, abi=ETH_MULTICALL.abi) data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract.functions.abi[1] output_types = get_abi_output_types(fn_abi) args = [1, proxies] result = '0x' + web3.codec.encode_abi(output_types, args).hex() response = f'{{"status":"1","message":"OK","result":"{result}"}}' elif to_address == MAKERDAO_POT.address: if input_data.startswith('0x0bebac86'): # pie if proxy1_contents in input_data: result = int_to_32byteshexstr( params.account1_current_normalized_balance) elif proxy2_contents in input_data: result = int_to_32byteshexstr( params.account2_current_normalized_balance) else: # result = int_to_32byteshexstr(0) raise AssertionError( 'Pie call for unexpected account during tests') elif input_data.startswith('0xc92aecc4'): # chi result = int_to_32byteshexstr(params.current_chi) elif input_data.startswith('0x487bf082'): # dsr result = int_to_32byteshexstr(params.current_dsr) else: raise AssertionError( 'Call to unexpected method of MakerDao pot during tests', ) response = f'{{"status":"1","message":"OK","result":"{result}"}}' else: raise AssertionError( f'Etherscan call to unknown contract {to_address} during tests', ) elif 'etherscan.io/api?module=logs&action=getLogs' in url: contract_address = url.split('&address=')[1].split('&topic0')[0] topic0 = url.split('&topic0=')[1].split('&topic0_1')[0] topic1 = url.split('&topic1=')[1].split('&topic1_2')[0] topic2 = None if '&topic2=' in url: topic2 = url.split('&topic2=')[1].split('&')[0] from_block = int(url.split('&fromBlock=')[1].split('&')[0]) to_block = int(url.split('&toBlock=')[1].split('&')[0]) if contract_address == MAKERDAO_POT.address: if topic0.startswith('0x049878f3'): # join events = [] if proxy1_contents in topic1: if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_event) elif proxy2_contents in topic1: if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_event) else: raise AssertionError( f'Etherscan log query to makerdao POT contract for ' f'join for unknown account {topic1}', ) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' elif topic0.startswith('0x7f8661a1'): # exit events = [] if proxy1_contents in topic1: if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_event) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( 'Etherscan unknown log query to makerdao POT contract') elif contract_address == MAKERDAO_VAT.address: if topic0.startswith('0xbb35783b'): # move events = [] if proxy1_contents in topic1: # deposit from acc1 if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_move_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_move_event) elif proxy2_contents in topic1: # deposit from acc2 if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_move_event) elif proxy1_contents in topic2: # withdrawal from acc1 if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_move_event) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( 'Etherscan unknown log query to makerdao VAT contract') elif contract_address == MAKERDAO_DAI_JOIN.address: events = [] if topic0.startswith('0x3b4da69f'): # join if proxy1_contents in topic1: # deposit from acc1 if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_move_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_move_event) elif proxy2_contents in topic1: # deposit from acc2 if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_move_event) elif topic0.startswith('0xef693bed'): # exit if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_move_event) else: raise AssertionError( 'Etherscan unknown call to makerdao DAIJOIN contract') response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( f'Etherscan getLogs call to unknown contract {contract_address} during tests', ) else: return original_requests_get(url, *args, **kwargs) return MockResponse(200, response)
def call_contract_function( web3, address, normalizers, function_identifier, transaction, block_id=None, contract_abi=None, fn_abi=None, *args, **kwargs): """ Helper function for interacting with a contract function using the `eth_call` API. """ call_transaction = prepare_transaction( address, web3, fn_identifier=function_identifier, contract_abi=contract_abi, fn_abi=fn_abi, transaction=transaction, fn_args=args, fn_kwargs=kwargs, ) if block_id is None: return_data = web3.eth.call(call_transaction) else: return_data = web3.eth.call(call_transaction, block_identifier=block_id) if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs) output_types = get_abi_output_types(fn_abi) try: output_data = decode_abi(output_types, return_data) except DecodingError as e: # Provide a more helpful error message than the one provided by # eth-abi-utils is_missing_code_error = ( return_data in ACCEPTABLE_EMPTY_STRINGS and web3.eth.getCode(address) in ACCEPTABLE_EMPTY_STRINGS ) if is_missing_code_error: msg = ( "Could not transact with/call contract function, is contract " "deployed correctly and chain synced?" ) else: msg = ( "Could not decode contract function call {} return data {} for " "output_types {}".format( function_identifier, return_data, output_types ) ) raise BadFunctionCallOutput(msg) from e _normalizers = itertools.chain( BASE_RETURN_NORMALIZERS, normalizers, ) normalized_data = map_abi_data(_normalizers, output_types, output_data) if len(normalized_data) == 1: return normalized_data[0] else: return normalized_data
def mock_requests_get(url, *args, **kwargs): if 'etherscan.io/api?module=account&action=balance&address' in url: addr = url[67:109] value = eth_map[addr].get('ETH', '0') response = f'{{"status":"1","message":"OK","result":{value}}}' elif 'etherscan.io/api?module=account&action=balancemulti' in url: queried_accounts = [] length = 72 # process url and get the accounts while True: if len(url) < length: break potential_address = url[length:length + 42] if 'apikey=' in potential_address: break queried_accounts.append(potential_address) length += 43 accounts = [] for addr in queried_accounts: value = eth_map[addr].get('ETH', '0') accounts.append({ 'account': addr, 'balance': eth_map[addr]['ETH'] }) response = f'{{"status":"1","message":"OK","result":{json.dumps(accounts)}}}' elif 'api.etherscan.io/api?module=account&action=tokenbalance' in url: token_address = url[80:122] msg = 'token address missing from test mapping' assert token_address in CONTRACT_ADDRESS_TO_TOKEN, msg response = '{"status":"1","message":"OK","result":"0"}' token = CONTRACT_ADDRESS_TO_TOKEN[token_address] account = url[131:173] value = eth_map[account].get(token.identifier, 0) response = f'{{"status":"1","message":"OK","result":"{value}"}}' elif 'api.etherscan.io/api?module=account&action=txlistinternal&' in url: # By default when mocking, don't query for transactions response = '{"status":"1","message":"OK","result":[]}' elif 'api.etherscan.io/api?module=account&action=txlist&' in url: # By default when mocking, don't query for transactions response = '{"status":"1","message":"OK","result":[]}' elif 'api.etherscan.io/api?module=logs&action=getLogs&' in url: if 'logs' in original_queries: return original_requests_get(url, *args, **kwargs) # By default when mocking, don't query logs response = '{"status":"1","message":"OK","result":[]}' elif 'api.etherscan.io/api?module=block&action=getblocknobytime&' in url: if 'blocknobytime' in original_queries: return original_requests_get(url, *args, **kwargs) # By default when mocking don't query blocknobytime response = '{"status":"1","message":"OK","result":"1"}' elif f'api.etherscan.io/api?module=proxy&action=eth_call&to={ZERION_ADAPTER_ADDRESS}' in url: # noqa: E501 if 'zerion' in original_queries: return original_requests_get(url, *args, **kwargs) web3 = Web3() contract = web3.eth.contract(address=ZERION_ADAPTER_ADDRESS, abi=ZERION_ABI) if 'data=0xc84aae17' in url: # getBalances data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='getBalances', args=['address'], ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) # TODO: This here always returns empty response. If/when we want to # mock it for etherscan, this is where we do it args = [] result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' else: raise AssertionError( f'Unexpected etherscan call during tests: {url}') elif f'api.etherscan.io/api?module=proxy&action=eth_call&to={ETH_SCAN.address}' in url: if 'ethscan' in original_queries: return original_requests_get(url, *args, **kwargs) web3 = Web3() contract = web3.eth.contract(address=ETH_SCAN.address, abi=ETH_SCAN.abi) if 'data=0xdbdbb51b' in url: # Eth balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract._find_matching_fn_abi( fn_identifier='etherBalances', args=[list(eth_map.keys())], ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] for account_address in decoded_input[0]: account_address = to_checksum_address(account_address) args.append(int(eth_map[account_address]['ETH'])) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0x06187b4f' in url: # Multi token multiaddress balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] # not really the given args, but we just want the fn abi args = [list(eth_map.keys()), list(eth_map.keys())] fn_abi = contract._find_matching_fn_abi( fn_identifier='tokensBalances', args=args, ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] for account_address in decoded_input[0]: account_address = to_checksum_address(account_address) x = [] for token_address in decoded_input[1]: token_address = to_checksum_address(token_address) value_to_add = 0 for given_asset, value in eth_map[ account_address].items(): given_asset = _get_token(given_asset) if given_asset is None: # not a token continue if token_address != given_asset.ethereum_address: continue value_to_add = int(value) break x.append(value_to_add) args.append(x) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' elif 'data=0xe5da1b68' in url: # Multi token balance query data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] # not really the given args, but we just want the fn abi args = ['str', list(eth_map.keys())] fn_abi = contract._find_matching_fn_abi( fn_identifier='tokensBalance', args=args, ) input_types = get_abi_input_types(fn_abi) output_types = get_abi_output_types(fn_abi) decoded_input = web3.codec.decode_abi(input_types, bytes.fromhex(data[10:])) args = [] account_address = to_checksum_address(decoded_input[0]) x = [] for token_address in decoded_input[1]: token_address = to_checksum_address(token_address) value_to_add = 0 for given_asset, value in eth_map[account_address].items(): given_asset = _get_token(given_asset) if given_asset is None: # not a token continue if token_address != given_asset.ethereum_address: continue value_to_add = int(value) break args.append(value_to_add) result = '0x' + web3.codec.encode_abi(output_types, [args]).hex() response = f'{{"jsonrpc":"2.0","id":1,"result":"{result}"}}' else: raise AssertionError( f'Unexpected etherscan call during tests: {url}') else: return original_requests_get(url, *args, **kwargs) return MockResponse(200, response)