Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix EVM account existence checks for selfdestruct and call #1801

Merged
merged 3 commits into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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