|
26 | 26 | Storage, |
27 | 27 | Transaction, |
28 | 28 | TransactionException, |
| 29 | + TransactionReceipt, |
29 | 30 | ) |
30 | 31 |
|
| 32 | +from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 |
| 33 | + |
31 | 34 | from .spec import ref_spec_8037 |
32 | 35 |
|
33 | 36 | REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path |
@@ -1253,3 +1256,183 @@ def test_existing_auth_refund_survives_top_level_revert( |
1253 | 1256 | tx=tx, |
1254 | 1257 | blockchain_test_header_verify=Header(gas_used=expected_gas_used), |
1255 | 1258 | ) |
| 1259 | + |
| 1260 | + |
| 1261 | +@pytest.mark.parametrize( |
| 1262 | + "failure_mode", |
| 1263 | + [ |
| 1264 | + pytest.param("revert", id="revert"), |
| 1265 | + pytest.param("halt", id="halt"), |
| 1266 | + pytest.param("oog", id="oog"), |
| 1267 | + ], |
| 1268 | +) |
| 1269 | +@pytest.mark.parametrize( |
| 1270 | + "authority_exists", |
| 1271 | + [ |
| 1272 | + pytest.param(False, id="new_account"), |
| 1273 | + pytest.param(True, id="existing_account"), |
| 1274 | + ], |
| 1275 | +) |
| 1276 | +@pytest.mark.valid_from("EIP8037") |
| 1277 | +def test_auth_state_gas_in_header_after_failure( |
| 1278 | + state_test: StateTestFiller, |
| 1279 | + pre: Alloc, |
| 1280 | + fork: Fork, |
| 1281 | + failure_mode: str, |
| 1282 | + authority_exists: bool, |
| 1283 | +) -> None: |
| 1284 | + """ |
| 1285 | + Verify block header reflects intrinsic state gas from a 7702 |
| 1286 | + authorization when the top-level tx fails. |
| 1287 | +
|
| 1288 | + Execution state gas is zeroed on failure but intrinsic state gas |
| 1289 | + is preserved. For existing-account auths the spec subtracts the |
| 1290 | + auth refund from `tx_state_gas`, reducing the state component. |
| 1291 | + The delegation indicator persists (set before the execution |
| 1292 | + snapshot). Parametrized across all failure modes (revert/halt/oog) |
| 1293 | + and authority states (new vs existing). |
| 1294 | + """ |
| 1295 | + gas_limit_cap = fork.transaction_gas_limit_cap() |
| 1296 | + assert gas_limit_cap is not None |
| 1297 | + |
| 1298 | + auth_intrinsic_state = fork.transaction_intrinsic_state_gas( |
| 1299 | + authorization_count=1, |
| 1300 | + ) |
| 1301 | + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() |
| 1302 | + intrinsic_total = intrinsic_cost(authorization_list_or_count=1) |
| 1303 | + intrinsic_regular = intrinsic_total - auth_intrinsic_state |
| 1304 | + auth_refund = ( |
| 1305 | + fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT |
| 1306 | + if authority_exists |
| 1307 | + else 0 |
| 1308 | + ) |
| 1309 | + |
| 1310 | + delegate = pre.deploy_contract(code=Op.STOP) |
| 1311 | + |
| 1312 | + if failure_mode == "revert": |
| 1313 | + revert_code = Op.REVERT(0, 0) |
| 1314 | + target = pre.deploy_contract(code=revert_code) |
| 1315 | + elif failure_mode == "halt": |
| 1316 | + target = pre.deploy_contract(code=Op.INVALID) |
| 1317 | + else: |
| 1318 | + target = pre.deploy_contract(code=Op.JUMPDEST + Op.JUMP(0x0)) |
| 1319 | + |
| 1320 | + if authority_exists: |
| 1321 | + signer = pre.fund_eoa() |
| 1322 | + else: |
| 1323 | + signer = pre.fund_eoa(0) |
| 1324 | + |
| 1325 | + tx_gas = gas_limit_cap + auth_intrinsic_state |
| 1326 | + |
| 1327 | + tx = Transaction( |
| 1328 | + ty=4, |
| 1329 | + to=target, |
| 1330 | + gas_limit=tx_gas, |
| 1331 | + sender=pre.fund_eoa(), |
| 1332 | + authorization_list=[ |
| 1333 | + AuthorizationTuple( |
| 1334 | + address=delegate, |
| 1335 | + nonce=0, |
| 1336 | + signer=signer, |
| 1337 | + ), |
| 1338 | + ], |
| 1339 | + ) |
| 1340 | + |
| 1341 | + if failure_mode == "revert": |
| 1342 | + block_regular = intrinsic_regular + revert_code.gas_cost(fork) |
| 1343 | + else: |
| 1344 | + block_regular = tx_gas - auth_intrinsic_state |
| 1345 | + |
| 1346 | + expected_gas_used = max(block_regular, auth_intrinsic_state - auth_refund) |
| 1347 | + |
| 1348 | + state_test( |
| 1349 | + pre=pre, |
| 1350 | + post={ |
| 1351 | + signer: Account( |
| 1352 | + code=Spec7702.delegation_designation(delegate), |
| 1353 | + ), |
| 1354 | + }, |
| 1355 | + tx=tx, |
| 1356 | + blockchain_test_header_verify=Header(gas_used=expected_gas_used), |
| 1357 | + ) |
| 1358 | + |
| 1359 | + |
| 1360 | +@pytest.mark.parametrize( |
| 1361 | + "authority_exists", |
| 1362 | + [ |
| 1363 | + pytest.param(False, id="new_account"), |
| 1364 | + pytest.param(True, id="existing_account"), |
| 1365 | + ], |
| 1366 | +) |
| 1367 | +@pytest.mark.valid_from("EIP8037") |
| 1368 | +def test_auth_sender_billing_after_failure( |
| 1369 | + state_test: StateTestFiller, |
| 1370 | + pre: Alloc, |
| 1371 | + fork: Fork, |
| 1372 | + authority_exists: bool, |
| 1373 | +) -> None: |
| 1374 | + """ |
| 1375 | + Verify sender billing distinguishes new vs existing account auth |
| 1376 | + on top-level failure. |
| 1377 | +
|
| 1378 | + For existing accounts, set_delegation refunds new-account state |
| 1379 | + gas to the reservoir. On REVERT, the restored reservoir reduces |
| 1380 | + the sender's bill via the billing formula. The sender pays less |
| 1381 | + than in the new-account case by exactly the refund amount. |
| 1382 | + """ |
| 1383 | + gas_limit_cap = fork.transaction_gas_limit_cap() |
| 1384 | + assert gas_limit_cap is not None |
| 1385 | + |
| 1386 | + auth_intrinsic_state = fork.transaction_intrinsic_state_gas( |
| 1387 | + authorization_count=1, |
| 1388 | + ) |
| 1389 | + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() |
| 1390 | + intrinsic_total = intrinsic_cost(authorization_list_or_count=1) |
| 1391 | + intrinsic_regular = intrinsic_total - auth_intrinsic_state |
| 1392 | + new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT |
| 1393 | + |
| 1394 | + delegate = pre.deploy_contract(code=Op.STOP) |
| 1395 | + target = pre.deploy_contract(code=Op.REVERT(0, 0)) |
| 1396 | + |
| 1397 | + if authority_exists: |
| 1398 | + signer = pre.fund_eoa() |
| 1399 | + else: |
| 1400 | + signer = pre.fund_eoa(0) |
| 1401 | + |
| 1402 | + tx_gas = gas_limit_cap + auth_intrinsic_state |
| 1403 | + |
| 1404 | + revert_gas = (Op.REVERT(0, 0)).gas_cost(fork) |
| 1405 | + auth_refund = new_account_refund if authority_exists else 0 |
| 1406 | + expected_cumulative = intrinsic_total + revert_gas - auth_refund |
| 1407 | + expected_gas_used = max( |
| 1408 | + intrinsic_regular + revert_gas, |
| 1409 | + auth_intrinsic_state - auth_refund, |
| 1410 | + ) |
| 1411 | + |
| 1412 | + tx = Transaction( |
| 1413 | + ty=4, |
| 1414 | + to=target, |
| 1415 | + gas_limit=tx_gas, |
| 1416 | + sender=pre.fund_eoa(), |
| 1417 | + authorization_list=[ |
| 1418 | + AuthorizationTuple( |
| 1419 | + address=delegate, |
| 1420 | + nonce=0, |
| 1421 | + signer=signer, |
| 1422 | + ), |
| 1423 | + ], |
| 1424 | + expected_receipt=TransactionReceipt( |
| 1425 | + cumulative_gas_used=expected_cumulative, |
| 1426 | + ), |
| 1427 | + ) |
| 1428 | + |
| 1429 | + state_test( |
| 1430 | + pre=pre, |
| 1431 | + post={ |
| 1432 | + signer: Account( |
| 1433 | + code=Spec7702.delegation_designation(delegate), |
| 1434 | + ), |
| 1435 | + }, |
| 1436 | + tx=tx, |
| 1437 | + blockchain_test_header_verify=Header(gas_used=expected_gas_used), |
| 1438 | + ) |
0 commit comments