Skip to content

Commit

Permalink
Fix EVM account existence checks for selfdestruct and call (#1801)
Browse files Browse the repository at this point in the history
* Add account_exists method to EVMWorld

* Update acct existence check in EVM.CALL_gas

* Update acct existence check in EVM.SELFDESTRUCT_gas
  • Loading branch information
wolflo committed Sep 8, 2020
1 parent a41c19a commit 76ccb04
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 7 deletions.
18 changes: 13 additions & 5 deletions manticore/platforms/evm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2278,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:
Expand Down Expand Up @@ -2897,6 +2896,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)
Expand Down
69 changes: 67 additions & 2 deletions tests/ethereum/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -1785,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,
Expand All @@ -1798,7 +1814,7 @@ def test_call_gas(self):
PUSH1 0x0
PUSH1 0X0
PUSH1 0x0
PUSH20 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
PUSH20 0xfffffffffffffffffffffffffffffffffffffff
PUSH1 0x0
CALL
STOP
Expand All @@ -1820,7 +1836,7 @@ def test_call_gas(self):
PUSH1 0x0
PUSH1 0X0
PUSH1 0x1
PUSH20 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
PUSH20 0xfffffffffffffffffffffffffffffffffffffff
PUSH1 0x0
CALL
STOP
Expand Down Expand Up @@ -1897,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):
Expand Down

0 comments on commit 76ccb04

Please sign in to comment.