-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathbor-chain-specific-evm-behavior.bats
More file actions
308 lines (263 loc) · 12.1 KB
/
bor-chain-specific-evm-behavior.bats
File metadata and controls
308 lines (263 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#!/usr/bin/env bats
# bats file_tags=pos,execution-specs,bor-specific
setup() {
load "../../../../core/helpers/pos-setup.bash"
pos_setup
local wallet_json
wallet_json=$(cast wallet new --json | jq '.[0]')
ephemeral_private_key=$(echo "$wallet_json" | jq -r '.private_key')
ephemeral_address=$(echo "$wallet_json" | jq -r '.address')
echo "ephemeral_address: $ephemeral_address" >&3
cast send --rpc-url "$L2_RPC_URL" --private-key "$PRIVATE_KEY" --legacy --gas-limit 21000 --value 1ether "$ephemeral_address" >/dev/null
}
# bats test_tags=execution-specs,bor-specific,evm-opcode
@test "BLOCKHASH(0) returns zero on Bor (genesis hash not available)" {
# Deploy contract that runs BLOCKHASH(0) in the constructor and stores to slot 0.
# Constructor bytecode (18-byte prefix + 1-byte runtime):
# PUSH1 0x00 60 00 (byte 0-1)
# BLOCKHASH 40 (byte 2)
# PUSH1 0x00 60 00 (byte 3-4)
# SSTORE 55 (byte 5) ← stores BLOCKHASH(0) at slot 0
# PUSH1 0x01 60 01 (byte 6-7) runtime size = 1
# PUSH1 0x12 60 12 (byte 8-9) code offset = 18 (where runtime starts)
# PUSH1 0x00 60 00 (byte 10-11)
# CODECOPY 39 (byte 12)
# PUSH1 0x01 60 01 (byte 13-14)
# PUSH1 0x00 60 00 (byte 15-16)
# RETURN f3 (byte 17)
# STOP 00 (byte 18) runtime = just STOP
receipt=$(cast send \
--legacy \
--gas-limit 200000 \
--private-key "$ephemeral_private_key" \
--rpc-url "$L2_RPC_URL" \
--json \
--create "0x6000406000556001601260003960016000f300")
tx_status=$(echo "$receipt" | jq -r '.status')
if [[ "$tx_status" != "0x1" ]]; then
echo "Contract deployment failed: $tx_status" >&2
return 1
fi
contract_addr=$(echo "$receipt" | jq -r '.contractAddress')
stored_hash=$(cast storage "$contract_addr" 0 --rpc-url "$L2_RPC_URL")
if [[ "$stored_hash" != "0x0000000000000000000000000000000000000000000000000000000000000000" ]]; then
echo "Expected BLOCKHASH(0) = 0 on Bor, got: $stored_hash" >&2
return 1
fi
}
# bats test_tags=execution-specs,bor-specific,evm-gas
@test "CALL with value to non-existent account skips G_NEW_ACCOUNT on Bor" {
# Bor does not charge the 25,000 gas G_NEW_ACCOUNT surcharge for CALL with value
# to a non-existent account. This is a known behavioral difference.
# Deploy a contract that CALLs a non-existent address with 1 wei value and
# stores the remaining gas before/after.
#
# Constructor stores some ETH, then we call it.
# Runtime: receives a call, does CALL(gas, <random_addr>, 1, 0, 0, 0, 0) and returns success.
#
# Simpler approach: send a value transfer to a fresh (non-existent) address
# with an explicit gas limit and verify the exact gas used.
fresh_addr=$(cast wallet new --json | jq -r '.[0].address')
receipt=$(cast send \
--legacy \
--gas-limit 21000 \
--value 1 \
--private-key "$ephemeral_private_key" \
--rpc-url "$L2_RPC_URL" \
--json \
"$fresh_addr")
tx_status=$(echo "$receipt" | jq -r '.status')
if [[ "$tx_status" != "0x1" ]]; then
echo "Value transfer to non-existent account failed" >&2
return 1
fi
gas_used=$(echo "$receipt" | jq -r '.gasUsed' | xargs printf "%d\n")
# On standard Ethereum, a value transfer to a non-existent account costs
# 21000 + 25000 = 46000 gas. On Bor, it should be just 21000.
if [[ "$gas_used" -eq 21000 ]]; then
echo "Bor skips G_NEW_ACCOUNT: gasUsed=$gas_used (no 25K surcharge)" >&3
elif [[ "$gas_used" -eq 46000 ]]; then
echo "WARNING: Bor charged G_NEW_ACCOUNT (standard behavior): gasUsed=$gas_used" >&3
echo "This contradicts known Bor behavior — investigate" >&2
return 1
else
echo "Unexpected gasUsed=$gas_used for value transfer to non-existent account" >&2
echo "Expected 21000 (Bor) or 46000 (standard)" >&2
return 1
fi
}
# bats test_tags=execution-specs,bor-specific
@test "block coinbase (miner field) is zero address on Bor" {
# Bor distributes fees via system contracts rather than to the block's coinbase.
# The miner field in block headers should be the zero address.
block_json=$(cast block latest --json --rpc-url "$L2_RPC_URL")
miner=$(echo "$block_json" | jq -r '.miner')
miner_lower=$(echo "$miner" | tr '[:upper:]' '[:lower:]')
if [[ "$miner_lower" != "0x0000000000000000000000000000000000000000" ]]; then
echo "Expected zero-address coinbase on Bor, got: $miner" >&2
return 1
fi
}
# bats test_tags=execution-specs,bor-specific,evm-gas
@test "transaction with trivially low gas price (1 wei) is rejected" {
# Bor nodes enforce a txpool pricelimit (configurable, typically 25 Gwei).
# A 1-wei gas price should be rejected by any reasonable configuration.
set +e
receipt=$(cast send \
--legacy \
--gas-limit 21000 \
--gas-price 1 \
--private-key "$ephemeral_private_key" \
--rpc-url "$L2_RPC_URL" \
--json \
0x0000000000000000000000000000000000000000 2>&1)
send_exit=$?
set -e
# The node should reject this — either cast fails or the tx is not mined.
if [[ $send_exit -eq 0 ]]; then
tx_status=$(echo "$receipt" | jq -r '.status // empty' 2>/dev/null)
if [[ "$tx_status" == "0x1" ]]; then
echo "Expected rejection for 1 wei gas price, but tx succeeded" >&2
return 1
fi
fi
# Non-zero exit or failed status — node correctly rejected the low gas price tx
}
# bats test_tags=execution-specs,bor-specific,evm-gas
@test "transaction at node-reported gas price succeeds" {
# Use eth_gasPrice to get a price the node considers acceptable, then verify
# a transaction at that price is mined. This avoids hardcoding a pricelimit.
gas_price=$(cast gas-price --rpc-url "$L2_RPC_URL")
receipt=$(cast send \
--legacy \
--gas-limit 21000 \
--gas-price "$gas_price" \
--private-key "$ephemeral_private_key" \
--rpc-url "$L2_RPC_URL" \
--json \
0x0000000000000000000000000000000000000000)
tx_status=$(echo "$receipt" | jq -r '.status')
if [[ "$tx_status" != "0x1" ]]; then
echo "Transaction at eth_gasPrice ($gas_price) failed: $tx_status" >&2
return 1
fi
}
# bats test_tags=execution-specs,bor-specific,evm-opcode
@test "EXTCODEHASH for empty account returns zero on Bor" {
# Deploy contract that computes EXTCODEHASH of a fresh (empty) address and stores it.
# On standard Ethereum post-EIP-161, empty accounts return 0. Bor may differ.
fresh_addr=$(cast wallet new --json | jq -r '.[0].address')
# Strip 0x prefix and pad to 20 bytes for PUSH20
addr_hex="${fresh_addr#0x}"
# Runtime: PUSH20 <addr> EXTCODEHASH PUSH1 0x00 SSTORE STOP
# 73<20 bytes addr>3f60005500
runtime="73${addr_hex}3f60005500"
runtime_len=$(( ${#runtime} / 2 ))
runtime_len_hex=$(printf "%02x" "$runtime_len")
offset_hex=$(printf "%02x" $(( 12 )))
# Initcode: PUSH1 <len> PUSH1 <offset=12> PUSH1 0x00 CODECOPY PUSH1 <len> PUSH1 0x00 RETURN <runtime>
initcode="60${runtime_len_hex}60${offset_hex}60003960${runtime_len_hex}6000f3${runtime}"
receipt=$(cast send \
--legacy \
--gas-limit 200000 \
--private-key "$ephemeral_private_key" \
--rpc-url "$L2_RPC_URL" \
--json \
--create "0x${initcode}")
tx_status=$(echo "$receipt" | jq -r '.status')
if [[ "$tx_status" != "0x1" ]]; then
echo "Contract deployment failed: $tx_status" >&2
return 1
fi
contract_addr=$(echo "$receipt" | jq -r '.contractAddress')
stored_hash=$(cast storage "$contract_addr" 0 --rpc-url "$L2_RPC_URL")
# Document what Bor returns — expected to be zero for empty accounts
echo "EXTCODEHASH of empty account on Bor: $stored_hash" >&3
if [[ "$stored_hash" != "0x0000000000000000000000000000000000000000000000000000000000000000" ]]; then
echo "Bor returned non-zero EXTCODEHASH for empty account: $stored_hash" >&2
echo "This may indicate Bor returns keccak256('') instead of 0" >&2
return 1
fi
}
# bats test_tags=execution-specs,bor-specific
@test "StateReceiver system contract (0x0000000000000000000000000000000000001001) is callable" {
# The StateReceiver is a Bor system contract at address 0x...1001.
# Verify it has code deployed and responds to a call.
state_receiver="0x0000000000000000000000000000000000001001"
code=$(cast code "$state_receiver" --rpc-url "$L2_RPC_URL")
if [[ "$code" == "0x" ]]; then
echo "StateReceiver at $state_receiver has no code" >&2
return 1
fi
code_len=$(( (${#code} - 2) / 2 ))
echo "StateReceiver code length: $code_len bytes" >&3
# Call with empty data — should not revert (returns empty or some default).
set +e
call_result=$(cast call "$state_receiver" "0x" --rpc-url "$L2_RPC_URL" 2>&1)
call_exit=$?
set -e
# A revert with data is still a valid response — we just want the contract to exist
# and the RPC to not error with "no code at address" or similar.
echo "StateReceiver call result (exit=$call_exit): ${call_result:0:100}" >&3
}
# bats test_tags=execution-specs,bor-specific
@test "Bor produces blocks on approximately 2-second sprint cadence" {
# Bor uses a 2-second block production interval during sprints.
# Sample the latest 5 blocks and verify timestamps are roughly 2s apart.
latest_block=$(cast block-number --rpc-url "$L2_RPC_URL")
if [[ "$latest_block" -lt 5 ]]; then
skip "Not enough blocks to measure cadence"
fi
timestamps=()
for i in $(seq 0 4); do
block_num=$(( latest_block - i ))
ts=$(cast block "$block_num" --json --rpc-url "$L2_RPC_URL" | jq -r '.timestamp' | xargs printf "%d\n")
timestamps+=("$ts")
done
# Check intervals between consecutive blocks (timestamps are newest-first)
total_diff=0
for i in $(seq 0 3); do
diff=$(( timestamps[i] - timestamps[i+1] ))
total_diff=$(( total_diff + diff ))
echo "Block gap $i: ${diff}s" >&3
done
avg_diff=$(( total_diff / 4 ))
echo "Average block interval: ${avg_diff}s" >&3
# Allow 1-4 seconds average to account for network jitter
if [[ "$avg_diff" -lt 1 || "$avg_diff" -gt 4 ]]; then
echo "Block cadence out of expected range: avg=${avg_diff}s (expected ~2s)" >&2
return 1
fi
}
# bats test_tags=execution-specs,bor-specific,evm-gas
@test "base fee adjusts between blocks following EIP-1559 dynamics" {
# Verify that Bor implements EIP-1559 base fee adjustment.
latest_block=$(cast block-number --rpc-url "$L2_RPC_URL")
if [[ "$latest_block" -lt 2 ]]; then
skip "Not enough blocks to compare base fees"
fi
base_fee_current=$(cast block "$latest_block" --json --rpc-url "$L2_RPC_URL" | jq -r '.baseFeePerGas' | xargs printf "%d\n")
base_fee_prev=$(cast block "$(( latest_block - 1 ))" --json --rpc-url "$L2_RPC_URL" | jq -r '.baseFeePerGas' | xargs printf "%d\n")
echo "Block $latest_block baseFee: $base_fee_current" >&3
echo "Block $(( latest_block - 1 )) baseFee: $base_fee_prev" >&3
# Base fee must be positive
if [[ "$base_fee_current" -le 0 ]]; then
echo "baseFeePerGas is not positive: $base_fee_current" >&2
return 1
fi
# EIP-1559: base fee can change by at most 12.5% per block
# max_change = prev / 8
if [[ "$base_fee_prev" -gt 0 ]]; then
max_change=$(( base_fee_prev / 8 ))
actual_change=$(( base_fee_current - base_fee_prev ))
# Absolute value
if [[ "$actual_change" -lt 0 ]]; then
actual_change=$(( -actual_change ))
fi
if [[ "$actual_change" -gt $(( max_change + 1 )) ]]; then
echo "Base fee changed by more than 12.5%:" >&2
echo " prev=$base_fee_prev current=$base_fee_current change=$actual_change max_allowed=$max_change" >&2
return 1
fi
fi
}