From 67001705b15b2e9d6a79ea54f7d46d5fc20eb892 Mon Sep 17 00:00:00 2001 From: wolflo Date: Tue, 1 Sep 2020 14:26:57 +0400 Subject: [PATCH 1/3] Add account_exists method to EVMWorld --- manticore/platforms/evm.py | 9 +++++++++ tests/ethereum/test_general.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index cac72fbc4..6feb00546 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -2897,6 +2897,15 @@ def get_balance(self, address): return 0 return Operators.EXTRACT(self._world_state[address]["balance"], 0, 256) + def account_exists(self, address): + if address not in self._world_state: + return False # accounts default to nonexistent + return ( + self.has_code(address) + or Operators.UGT(self.get_nonce(address), 0) + or Operators.UGT(self.get_balance(address), 0) + ) + def add_to_balance(self, address, value): if isinstance(value, BitVec): value = Operators.ZEXTEND(value, 512) diff --git a/tests/ethereum/test_general.py b/tests/ethereum/test_general.py index dc408c9da..c0a717b6e 100644 --- a/tests/ethereum/test_general.py +++ b/tests/ethereum/test_general.py @@ -1435,6 +1435,20 @@ def inner_func(self, a, b): # wasn't requested. inner_func(None, self.bv, 123) + def test_account_exists(self): + constraints = ConstraintSet() + world = evm.EVMWorld(constraints) + default = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + empty = world.create_account(nonce=0, balance=0, code=b"") + has_code = world.create_account(nonce=0, balance=0, code=b"ff") + has_nonce = world.create_account(nonce=1, balance=0, code=b"") + has_balance = world.create_account(nonce=0, balance=1, code=b"") + self.assertTrue(world.account_exists(has_code)) + self.assertTrue(world.account_exists(has_nonce)) + self.assertTrue(world.account_exists(has_balance)) + self.assertFalse(world.account_exists(empty)) + self.assertFalse(world.account_exists(default)) + class EthSolidityMetadataTests(unittest.TestCase): def test_tuple_signature_for_components(self): From fa4d888b69a295aba0d0531117cf5607fb14595c Mon Sep 17 00:00:00 2001 From: wolflo Date: Tue, 1 Sep 2020 14:47:42 +0400 Subject: [PATCH 2/3] Update acct existence check in EVM.CALL_gas --- manticore/platforms/evm.py | 7 +++---- tests/ethereum/test_general.py | 6 ++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index 6feb00546..921338c6c 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -2141,10 +2141,9 @@ def CALL_gas(self, wanted_gas, address, value, in_offset, in_size, out_offset, o GCALLNEW = 25000 wanted_gas = Operators.ZEXTEND(wanted_gas, 512) fee = Operators.ITEBV(512, value == 0, 0, GCALLVALUE) - known_address = False - for address_i in self.world.accounts: - known_address = Operators.OR(known_address, address == address_i) - fee += Operators.ITEBV(512, Operators.OR(known_address, value == 0), 0, GCALLNEW) + fee += Operators.ITEBV( + 512, Operators.OR(self.world.account_exists(address), value == 0), 0, GCALLNEW + ) fee += self._get_memfee(in_offset, in_size) exception = False diff --git a/tests/ethereum/test_general.py b/tests/ethereum/test_general.py index c0a717b6e..22e34f459 100644 --- a/tests/ethereum/test_general.py +++ b/tests/ethereum/test_general.py @@ -1799,6 +1799,8 @@ def test_call_gas(self): GCALLSTIPEND = 2300 # additional gas sent with a call if value > 0 with disposable_mevm() as m: + # empty call target + m.create_account(address=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) # nonempty call target m.create_account( address=0x111111111111111111111111111111111111111, @@ -1812,7 +1814,7 @@ def test_call_gas(self): PUSH1 0x0 PUSH1 0X0 PUSH1 0x0 - PUSH20 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + PUSH20 0xfffffffffffffffffffffffffffffffffffffff PUSH1 0x0 CALL STOP @@ -1834,7 +1836,7 @@ def test_call_gas(self): PUSH1 0x0 PUSH1 0X0 PUSH1 0x1 - PUSH20 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + PUSH20 0xfffffffffffffffffffffffffffffffffffffff PUSH1 0x0 CALL STOP From 23f782ddafdb51dec9d387acec37e5ec133acbc0 Mon Sep 17 00:00:00 2001 From: wolflo Date: Tue, 1 Sep 2020 14:55:20 +0400 Subject: [PATCH 3/3] Update acct existence check in EVM.SELFDESTRUCT_gas --- manticore/platforms/evm.py | 2 +- tests/ethereum/test_general.py | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index 921338c6c..eb6087ddf 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -2277,7 +2277,7 @@ def SELFDESTRUCT_gas(self, recipient): CreateBySelfdestructGas = 25000 SelfdestructRefundGas = 24000 fee = 0 - if recipient not in self.world and self.world.get_balance(self.address) != 0: + if not self.world.account_exists(recipient) and self.world.get_balance(self.address) != 0: fee += CreateBySelfdestructGas if self.address not in self.world._deleted_accounts: diff --git a/tests/ethereum/test_general.py b/tests/ethereum/test_general.py index 22e34f459..f7f154aeb 100644 --- a/tests/ethereum/test_general.py +++ b/tests/ethereum/test_general.py @@ -1913,6 +1913,55 @@ def test_call_gas(self): GCALLSTATIC + GCALLVALUE + GCALLNEW - GCALLSTIPEND ) + def test_selfdestruct_gas(self): + GSDSTATIC = 26003 # 21000 + 3 (push op) + 5000 static cost for selfdestruct + GNEWACCOUNT = 25000 + RSELFDESTRUCT = 24000 + + with disposable_mevm() as m: + # empty call target + empty = m.create_account(address=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + # nonempty call target + nonempty = m.create_account(address=0x1111111111111111111111111111111111111111, nonce=1) + + asm_sd_empty = """ PUSH20 0xffffffffffffffffffffffffffffffffffffffff + SELFDESTRUCT + """ + asm_sd_nonempty = """ PUSH20 0x1111111111111111111111111111111111111111 + SELFDESTRUCT + """ + + caller = m.create_account( + address=0x222222222222222222222222222222222222222, balance=1000000000000000000 + ) + + # selfdestruct to empty acct with no value + sd_empty = m.create_account(code=EVMAsm.assemble(asm_sd_empty)) + m.transaction(caller=caller, address=sd_empty, data=b"", value=0, gas=50000000) + self.assertEqual(m.count_ready_states(), 1) + state = next(m.ready_states) + txs = state.platform.transactions + # no value, so only static cost charged and refund is gas_used / 2 + self.assertEqual(txs[-1].used_gas, round(GSDSTATIC - (GSDSTATIC / 2))) + + # selfdestruct to existing acct with value > 0 + sd_nonempty = m.create_account(code=EVMAsm.assemble(asm_sd_nonempty)) + m.transaction(caller=caller, address=sd_nonempty, data=b"", value=1, gas=50000000) + self.assertEqual(m.count_ready_states(), 1) + state = next(m.ready_states) + txs = state.platform.transactions + # recipient exists, so only static cost charged and refund is gas_used / 2 + self.assertEqual(txs[-1].used_gas, round(GSDSTATIC - (GSDSTATIC / 2))) + + # selfdestruct to empty acct with value > 0, forcing addition to state trie + sd_empty = m.create_account(code=EVMAsm.assemble(asm_sd_empty)) + m.transaction(caller=caller, address=sd_empty, data=b"", value=1, gas=50000000) + self.assertEqual(m.count_ready_states(), 1) + state = next(m.ready_states) + txs = state.platform.transactions + # new account gas charged and full refund returned + self.assertEqual(txs[-1].used_gas, GSDSTATIC + GNEWACCOUNT - RSELFDESTRUCT) + class EthPluginTests(unittest.TestCase): def test_FilterFunctions_fallback_function_matching(self):