From 6d5b7d77eb0c85451c760671545583886efc1814 Mon Sep 17 00:00:00 2001 From: wolflo Date: Sun, 12 Jul 2020 23:10:35 +0400 Subject: [PATCH 1/2] Update gas metering for calls to empty accounts --- manticore/platforms/evm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index f65c1cac5..cac72fbc4 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -2144,7 +2144,7 @@ def CALL_gas(self, wanted_gas, address, value, in_offset, in_size, out_offset, o known_address = False for address_i in self.world.accounts: known_address = Operators.OR(known_address, address == address_i) - fee += Operators.ITEBV(512, Operators.AND(known_address, value == 0), 0, GCALLNEW) + fee += Operators.ITEBV(512, Operators.OR(known_address, value == 0), 0, GCALLNEW) fee += self._get_memfee(in_offset, in_size) exception = False From 9cf59734fd31b05fd6a9106869c5fcac3225a440 Mon Sep 17 00:00:00 2001 From: wolflo Date: Mon, 17 Aug 2020 23:02:12 +0400 Subject: [PATCH 2/2] Add test for call gas costs --- tests/ethereum/test_general.py | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/tests/ethereum/test_general.py b/tests/ethereum/test_general.py index 0aa4bee35..dc408c9da 100644 --- a/tests/ethereum/test_general.py +++ b/tests/ethereum/test_general.py @@ -1778,6 +1778,125 @@ def test_selfdestruct(self): ) self.assertEqual(m.count_ready_states(), 1) + def test_call_gas(self): + GCALLSTATIC = 21721 # 21000 + (3 * 7 push ops) + 700 static cost for call + GCALLVALUE = 9000 # cost added for nonzero callvalue + GCALLNEW = 25000 # cost added for forcing new acct creation + GCALLSTIPEND = 2300 # additional gas sent with a call if value > 0 + + with disposable_mevm() as m: + # nonempty call target + m.create_account( + address=0x111111111111111111111111111111111111111, + nonce=1 # nonempty account + ) + + # call(gas, target, value, in_offset, in_size, out_offset, out_size) + # call to empty acct with value = 0 + asm_call_empty_no_val = """ PUSH1 0x0 + PUSH1 0X0 + PUSH1 0x0 + PUSH1 0X0 + PUSH1 0x0 + PUSH20 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + PUSH1 0x0 + CALL + STOP + """ + # call to existing acct with value > 0 + asm_call_nonempty_w_val = """ PUSH1 0x0 + PUSH1 0X0 + PUSH1 0x0 + PUSH1 0X0 + PUSH1 0x1 + PUSH20 0x111111111111111111111111111111111111111 + PUSH1 0x0 + CALL + STOP + """ + # call to empty acct with value > 0, forcing addition to state trie + asm_call_empty_w_val = """ PUSH1 0x0 + PUSH1 0X0 + PUSH1 0x0 + PUSH1 0X0 + PUSH1 0x1 + PUSH20 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + PUSH1 0x0 + CALL + STOP + """ + + call_empty_no_val = m.create_account( + code=EVMAsm.assemble(asm_call_empty_no_val) + ) + call_nonempty_w_val = m.create_account( + balance=100, + code=EVMAsm.assemble(asm_call_nonempty_w_val) + ) + call_empty_w_val = m.create_account( + balance=100, + code=EVMAsm.assemble(asm_call_empty_w_val) + ) + + caller = m.create_account( + address=0x222222222222222222222222222222222222222, + balance=1000000000000000000 + ) + + # call to empty acct with value = 0 + m.transaction( + caller=caller, + address=call_empty_no_val, + 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 no call stipend should be sent + self.assertEqual(txs[-2].gas, 0) + # no value, so only static call cost should be charged + self.assertEqual(txs[-1].used_gas, GCALLSTATIC) + + # call to existing acct with value > 0 + m.transaction( + caller=caller, + address=call_nonempty_w_val, + data=b'', + value=0, + gas=50000000 + ) + self.assertEqual(m.count_ready_states(), 1) + state = next(m.ready_states) + txs = state.platform.transactions + # call stipend should be sent with call + self.assertEqual(txs[-2].gas, GCALLSTIPEND) + # cost of call should include value cost, but not new acct cost + self.assertEqual( + txs[-1].used_gas, + GCALLSTATIC + GCALLVALUE - GCALLSTIPEND + ) + + # call to empty acct with value > 0, forcing addition to state trie + m.transaction( + caller=caller, + address=call_empty_w_val, + data=b'', + value=0, + gas=50000000 + ) + self.assertEqual(m.count_ready_states(), 1) + state = next(m.ready_states) + txs = state.platform.transactions + # call stipend should be sent with call + self.assertEqual(txs[-2].gas, GCALLSTIPEND) + # cost of call should include value cost and new acct cost + self.assertEqual( + txs[-1].used_gas, + GCALLSTATIC + GCALLVALUE + GCALLNEW - GCALLSTIPEND + ) + class EthPluginTests(unittest.TestCase): def test_FilterFunctions_fallback_function_matching(self):