diff --git a/qiling/arch/evm/abi.py b/qiling/arch/evm/abi.py index 34ff28964..33620217e 100644 --- a/qiling/arch/evm/abi.py +++ b/qiling/arch/evm/abi.py @@ -1,12 +1,18 @@ #!/usr/bin/env python3 -from eth_abi import encode_abi +from eth_abi import encode_abi, decode_abi, encode_single, decode_single +from eth_utils.abi import collapse_if_tuple +from eth_utils import function_signature_to_4byte_selector, function_abi_to_4byte_selector, decode_hex, encode_hex from .vm.utils import bytecode_to_bytes class QlArchEVMABI: @staticmethod def convert(datatypes:list, values:list) -> str: + return QlArchEVMABI.encode_params(datatypes, values) + + @staticmethod + def encode_params(datatypes:list, values:list) -> str: for idx, item in enumerate(datatypes): if item == 'address': if isinstance(values[idx], int): @@ -14,4 +20,27 @@ def convert(datatypes:list, values:list) -> str: elif isinstance(values[idx], str): values[idx] = bytecode_to_bytes(values[idx]) - return encode_abi(datatypes, values).hex() \ No newline at end of file + return encode_abi(datatypes, values).hex() + + @staticmethod + def decode_params(datatypes:list, value:str) -> list: + return decode_abi(datatypes, value) + + @staticmethod + def encode_function_call(abi:str, params:list) -> str: + abi = abi.replace(' ', '') + if '(' not in abi or ')' not in abi: + raise ValueError(f'Function signature must contain "(" and ")": {abi}') + signature = function_signature_to_4byte_selector(abi) + inputs = abi[abi.index('('):] + params = encode_single(inputs, params) + return encode_hex(signature + params) + + @staticmethod + def encode_function_call_abi(abi:dict, params:list) -> str: + signature = function_abi_to_4byte_selector(abi) + inputs = ",".join( + [collapse_if_tuple(abi_input) for abi_input in abi.get("inputs", [])] + ) + params = encode_single(f"({inputs})", params) + return encode_hex(signature + params) \ No newline at end of file diff --git a/qiling/arch/evm/vm/dbgcui.py b/qiling/arch/evm/vm/dbgcui.py index c9db85cff..b1c2daf8e 100644 --- a/qiling/arch/evm/vm/dbgcui.py +++ b/qiling/arch/evm/vm/dbgcui.py @@ -59,10 +59,12 @@ def hexdump(src, length=16, sep='.', minrows=8, start=0, prevsrc="", to_list=Fal return result return '\n'.join(result) -def stackdump(src, length=16, minrows=8, start=0, to_list=False): +def stackdump(src, minrows=8, to_list=False): result = [] - for i in range(start, min(minrows, len(src))): + # only the last N elements should be printed + start = len(src) - min(minrows, len(src)) + for i in range(min(minrows, len(src)) + start - 1, start - 1, -1): v_type = src[i][0] value = src[i][1] if v_type is bytes: diff --git a/tests/test_evm.py b/tests/test_evm.py index 36d82acd4..79cf062bc 100644 --- a/tests/test_evm.py +++ b/tests/test_evm.py @@ -111,5 +111,39 @@ def check_balance(sender, destination): result = check_balance(user2, c1) self.assertEqual(int(result.output.hex()[2:], 16), 452312848583266388373324160190187140051835877600158453279131187530910662654) + def test_abi_encoding(self): + ql = Qiling(code="0x608060405234801561001057600080fd5b506101a4806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ead710c414610030575b600080fd5b6100e96004803603602081101561004657600080fd5b810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561012957808201518184015260208101905061010e565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b606081905091905056fea2646970667358221220cf43353b75256fc42aaffd9632e06963c5c2aad72a91004bfd2f98cd56ae1a0c64736f6c63430006000033",archtype="evm", verbose=4) + + user1 = ql.arch.evm.create_account(balance=100*10**18) + c1 = ql.arch.evm.create_account() + + # Deploy runtime code + msg0 = ql.arch.evm.create_message(user1, b'', contract_address=c1) + ql.run(code=msg0) + + # # SMART CONTRACT DEPENDENT: transform from user1 to user2 + call_param = ['Hello World'] + call_data = ql.arch.evm.abi.encode_function_call('greet(string)', call_param) + + function_abi = { + 'name': 'greet', + 'type': 'function', + 'inputs': [{ + 'type': 'string', + 'name': '' + }] + } + call_data2 = ql.arch.evm.abi.encode_function_call_abi(function_abi, call_param) + call_data3 = '0xead710c4'+ ql.arch.evm.abi.convert(['string'], call_param) + + self.assertEqual(call_data, call_data2) + self.assertEqual(call_data, call_data3) + + msg1 = ql.arch.evm.create_message(user1, c1, data=call_data) + result = ql.run(code=msg1) + + result_data = ql.arch.evm.abi.decode_params(['string'], result.output) + self.assertEqual(call_param[0], result_data[0]) + if __name__ == "__main__": unittest.main() \ No newline at end of file