Skip to content

Commit 5561d49

Browse files
committed
EIP-210 blockhash contract tests
1 parent 30add13 commit 5561d49

File tree

5 files changed

+269
-0
lines changed

5 files changed

+269
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
_site
22
.sass-cache
33
.jekyll-metadata
4+
/.idea

EIPS/eip-210/tests/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/*.egg-info/
2+
/.cache/
3+
/__pycache__/

EIPS/eip-210/tests/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Usage
2+
3+
1. Setup virtualenv
4+
2. `pip install -r requirements.txt`
5+
3. `py.test`
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
six
2+
pytest
3+
ethereum==1.6.1
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import pytest
2+
import os
3+
import random
4+
from ethereum import tester, utils
5+
from ethereum.config import default_config
6+
from rlp.utils import decode_hex
7+
8+
from pprint import pprint
9+
10+
EIP_BLOCKHASH_CODE = decode_hex(
11+
b'73fffffffffffffffffffffffffffffffffffffffe33141561006a5760014303600035610100820755610100810715156100455760003561010061010083050761010001555b6201000081071515610064576000356101006201000083050761020001555b5061013e565b4360003512151561008457600060405260206040f361013d565b61010060003543031315156100a857610100600035075460605260206060f361013c565b6101006000350715156100c55762010000600035430313156100c8565b60005b156100ea576101006101006000350507610100015460805260206080f361013b565b620100006000350715156101095763010000006000354303131561010c565b60005b1561012f57610100620100006000350507610200015460a052602060a0f361013a565b600060c052602060c0f35b5b5b5b5b' # noqa
12+
)
13+
14+
BLOCKHASH_ADDR = decode_hex(b'00000000000000000000000000000000000000f0')
15+
EIP_SYSTEM_ADDR = decode_hex(b'fffffffffffffffffffffffffffffffffffffffe')
16+
17+
SYSTEM_PRIV = os.urandom(32)
18+
SYSTEM_ADDR = utils.privtoaddr(SYSTEM_PRIV)
19+
SYSTEM_GAS_LIMIT = 1000000
20+
SYSTEM_GAS_PRICE = 0
21+
BLOCKHASH_CODE = EIP_BLOCKHASH_CODE.replace(EIP_SYSTEM_ADDR, SYSTEM_ADDR, 1)
22+
NULL_HASH = b'\0' * 32
23+
24+
BLOCKHASH_LEVEL1_COST = 330
25+
BLOCKHASH_LEVEL2_COST = 429
26+
BLOCKHASH_LEVEL3_COST = 514
27+
28+
# Configure execution in pre-Metropolis mode.
29+
default_config['HOMESTEAD_FORK_BLKNUM'] = 0
30+
default_config['DAO_FORK_BLKNUM'] = 0
31+
default_config['ANTI_DOS_FORK_BLKNUM'] = 0
32+
default_config['CLEARING_FORK_BLKNUM'] = 0
33+
34+
35+
class State(tester.state):
36+
def exec_system(self):
37+
"""Execute BLOCKHASH contract from SYSTEM account"""
38+
39+
prev_block_hash = self.block.get_parent().hash
40+
assert len(prev_block_hash) == 32
41+
42+
gas_limit = tester.gas_limit
43+
tester.gas_limit = SYSTEM_GAS_LIMIT
44+
45+
gas_price = tester.gas_price
46+
tester.gas_price = SYSTEM_GAS_PRICE
47+
48+
output = self.send(sender=SYSTEM_PRIV, to=BLOCKHASH_ADDR, value=0,
49+
evmdata=prev_block_hash)
50+
assert len(output) == 0
51+
52+
tester.gas_limit = gas_limit
53+
tester.gas_price = gas_price
54+
55+
def get_slot(self, index):
56+
"""Get storage entry of BLOCKHASH_ADDR of given index"""
57+
int_value = self.block.get_storage_data(BLOCKHASH_ADDR, index)
58+
return utils.zpad(utils.coerce_to_bytes(int_value), 32)
59+
60+
61+
def fake_slot(index):
62+
value = b'BLOCKHASH slot {:17}'.format(index)
63+
assert len(value) == 32
64+
return value
65+
66+
67+
@pytest.fixture(scope='module')
68+
def state():
69+
state = State()
70+
state.block._set_acct_item(BLOCKHASH_ADDR, 'code', BLOCKHASH_CODE)
71+
72+
for i in range(257):
73+
state.mine()
74+
state.exec_system()
75+
76+
return state
77+
78+
79+
@pytest.fixture
80+
def fake_state():
81+
"""Create BLOCKHASH contract state. "Mining" more than 256 blocks is not
82+
feasible with ethereum.tester."""
83+
state = State()
84+
state.block.number = 256 * 65536 + 1
85+
state.block._set_acct_item(BLOCKHASH_ADDR, 'code', BLOCKHASH_CODE)
86+
87+
for i in range(3 * 256):
88+
int_value = utils.big_endian_to_int(fake_slot(i))
89+
state.block.set_storage_data(BLOCKHASH_ADDR, i, int_value)
90+
91+
return state
92+
93+
94+
def test_setup(state):
95+
assert state.block.get_code(BLOCKHASH_ADDR) == BLOCKHASH_CODE
96+
assert state.block.get_balance(SYSTEM_ADDR) == 0
97+
assert state.block.get_nonce(SYSTEM_ADDR) > 0
98+
99+
assert state.get_slot(0) == state.blocks[256].hash
100+
for i in range(1, 256):
101+
assert state.get_slot(i) == state.blocks[i].hash
102+
103+
assert state.get_slot(256) == state.blocks[0].hash
104+
assert state.get_slot(257) == state.blocks[256].hash
105+
for i in range(258, 256 + 256):
106+
assert state.get_slot(i) == NULL_HASH
107+
108+
assert state.get_slot(512) == state.blocks[0].hash
109+
for i in range(513, 512 + 256):
110+
assert state.get_slot(i) == NULL_HASH
111+
112+
113+
def test_get_prev_block_hash(state):
114+
prev = state.block.number - 1
115+
pprint(prev)
116+
expected_hash = state.blocks[prev].hash
117+
arg = utils.zpad(utils.coerce_to_bytes(prev), 32)
118+
out = state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
119+
evmdata=arg)
120+
assert out['output'] == expected_hash
121+
assert out['gas'] == BLOCKHASH_LEVEL1_COST
122+
123+
124+
def test_get_current_block_hash(state):
125+
arg = utils.zpad(utils.coerce_to_bytes(state.block.number), 32)
126+
out = state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
127+
evmdata=arg)
128+
assert out['output'] == b'\0' * 32
129+
assert out['gas'] == 79
130+
131+
132+
def test_get_future_block_hash(state):
133+
arg = utils.zpad(utils.coerce_to_bytes(3**11 + 13), 32)
134+
out = state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
135+
evmdata=arg)
136+
assert out['output'] == b'\0' * 32
137+
assert out['gas'] == 79
138+
139+
140+
def test_first256th_slot(state):
141+
n = state.block.number
142+
state.block.number = 60000 # Allow accessing 256th block hashes
143+
144+
arg = utils.zpad(utils.coerce_to_bytes(0), 32)
145+
out = state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
146+
evmdata=arg)
147+
assert out['output'] == state.blocks[0].hash
148+
assert out['gas'] == BLOCKHASH_LEVEL2_COST
149+
150+
state.block.number = n
151+
152+
153+
def test_overflow(state):
154+
n = state.block.number
155+
state.block.number = 1
156+
157+
arg = utils.zpad(utils.coerce_to_bytes(2**256 - 256), 32)
158+
out = state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
159+
evmdata=arg)
160+
assert out['output'] == NULL_HASH
161+
assert out['gas'] == 79
162+
163+
state.block.number = n
164+
165+
166+
def test_fake_state_setup(fake_state):
167+
for i in range(3 * 256):
168+
assert fake_state.get_slot(i) == fake_slot(i)
169+
assert fake_state.get_slot(3 * 256) == NULL_HASH
170+
171+
172+
def test_overflow2(fake_state):
173+
fake_state.block.number = 255
174+
175+
arg = '\xff' * 31
176+
out = fake_state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
177+
evmdata=arg)
178+
assert out['output'] == NULL_HASH
179+
assert out['gas'] == 79
180+
181+
182+
def test_blockhash_last256(fake_state):
183+
start_block = fake_state.block.number - 256
184+
for n in range(start_block, fake_state.block.number):
185+
arg = utils.zpad(utils.coerce_to_bytes(n), 32)
186+
out = fake_state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
187+
evmdata=arg)
188+
assert out['gas'] == BLOCKHASH_LEVEL1_COST
189+
assert out['output'] == fake_slot(n % 256)
190+
191+
192+
def test_blockhash_level2(fake_state):
193+
last256th = fake_state.block.number - (fake_state.block.number % 256)
194+
195+
# TODO: We can only access 255 block hashes on level 2.
196+
start_block = last256th - (255 - 1) * 256
197+
198+
arg = utils.zpad(utils.coerce_to_bytes(last256th), 32)
199+
output = fake_state.send(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
200+
evmdata=arg)
201+
assert output == fake_slot(0)
202+
203+
for n in range(start_block, last256th, 256):
204+
arg = utils.zpad(utils.coerce_to_bytes(n), 32)
205+
out = fake_state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
206+
evmdata=arg)
207+
assert out['gas'] == BLOCKHASH_LEVEL2_COST
208+
level2_offset = (n / 256) % 256
209+
assert out['output'] == fake_slot(256 + level2_offset)
210+
211+
212+
def test_blockhash_level3(fake_state):
213+
last65kth = fake_state.block.number - (fake_state.block.number % 65536)
214+
215+
# TODO: We can only access 255 block hashes on level 3.
216+
start_block = last65kth - (255 - 1) * 65536
217+
218+
arg = utils.zpad(utils.coerce_to_bytes(last65kth), 32)
219+
output = fake_state.send(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
220+
evmdata=arg)
221+
assert output == fake_slot(0)
222+
223+
for n in range(start_block, last65kth, 65536):
224+
arg = utils.zpad(utils.coerce_to_bytes(n), 32)
225+
out = fake_state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
226+
evmdata=arg)
227+
assert out['gas'] == BLOCKHASH_LEVEL3_COST
228+
level3_offset = (n / 65536) % 256
229+
assert out['output'] == fake_slot(512 + level3_offset)
230+
231+
232+
def test_blockhash_future_blocks(fake_state):
233+
for n in range(fake_state.block.number, fake_state.block.number + 10):
234+
arg = utils.zpad(utils.coerce_to_bytes(n), 32)
235+
out = fake_state.profile(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
236+
evmdata=arg)
237+
assert out['gas'] == 79
238+
assert out['output'] == NULL_HASH
239+
240+
241+
def test_blockhash_not_covered_blocks(fake_state):
242+
current_n = fake_state.block.number
243+
for _ in range(1000):
244+
n = random.randint(0, current_n - 256)
245+
arg = utils.zpad(utils.coerce_to_bytes(n), 32)
246+
output = fake_state.send(sender=tester.k1, to=BLOCKHASH_ADDR, value=0,
247+
evmdata=arg)
248+
if current_n - n < 65536:
249+
if n % 256 == 0:
250+
assert output != NULL_HASH
251+
else:
252+
assert output == NULL_HASH
253+
else:
254+
if n % 65536 == 0:
255+
assert output != NULL_HASH
256+
else:
257+
assert output == NULL_HASH

0 commit comments

Comments
 (0)