@@ -22,6 +22,14 @@ import { LegacyTransaction, LegacyTransactionLib } from "tempo-std/tx/LegacyTran
2222/// - Payment lane minimum: 470,000,000 (TEMPO-BLOCK5)
2323/// - Max deployment fits in tx cap (TEMPO-BLOCK6)
2424///
25+ /// TIP-1016 state gas changes:
26+ /// - Regular gas counts against tx/block limits; state gas is exempt
27+ /// - tx.gas > max_transaction_gas_limit is VALID when excess is state gas
28+ /// - Block gasUsed reflects regular gas only
29+ /// - Code deposit: 200 regular + 2,300 state per byte
30+ /// - CREATE base: 32,000 regular + 468,000 state
31+ /// - Account creation: 25,000 regular + 225,000 state
32+ ///
2533/// Block-level lane enforcement (BLOCK7, BLOCK12) and shared gas limit
2634/// (BLOCK10) are tested in Rust (crates/consensus/src/lib.rs).
2735contract BlockGasLimitsInvariantTest is InvariantBase {
@@ -63,21 +71,53 @@ contract BlockGasLimitsInvariantTest is InvariantBase {
6371 /// @dev TIP-1000: Account creation gas
6472 uint256 internal constant ACCOUNT_CREATION_GAS = 250_000 ;
6573
74+ /*//////////////////////////////////////////////////////////////
75+ TIP-1016 CONSTANTS
76+ //////////////////////////////////////////////////////////////*/
77+
78+ /// @dev TIP-1016: SSTORE regular gas component
79+ uint256 internal constant SSTORE_REGULAR_GAS = 20_000 ;
80+
81+ /// @dev TIP-1016: SSTORE state gas component
82+ uint256 internal constant SSTORE_STATE_GAS = 230_000 ;
83+
84+ /// @dev TIP-1016: Code deposit regular gas per byte
85+ uint256 internal constant CODE_DEPOSIT_REGULAR_PER_BYTE = 200 ;
86+
87+ /// @dev TIP-1016: Code deposit state gas per byte
88+ uint256 internal constant CODE_DEPOSIT_STATE_PER_BYTE = 2_300 ;
89+
90+ /// @dev TIP-1016: CREATE regular gas component
91+ uint256 internal constant CREATE_REGULAR_GAS = 32_000 ;
92+
93+ /// @dev TIP-1016: CREATE state gas component
94+ uint256 internal constant CREATE_STATE_GAS = 468_000 ;
95+
96+ /// @dev TIP-1016: Account creation regular gas component
97+ uint256 internal constant ACCOUNT_CREATION_REGULAR_GAS = 25_000 ;
98+
99+ /// @dev TIP-1016: Account creation state gas component
100+ uint256 internal constant ACCOUNT_CREATION_STATE_GAS = 225_000 ;
101+
66102 /*//////////////////////////////////////////////////////////////
67103 GHOST VARIABLES
68104 //////////////////////////////////////////////////////////////*/
69105
70106 /// @dev TEMPO-BLOCK3: Tx gas cap enforcement
71107 uint256 public ghost_txGasCapTests;
72108 uint256 public ghost_txAtCapSucceeded;
73- uint256 public ghost_txOverCapRejected;
74- uint256 public ghost_txOverCapViolations; // Over-cap tx was accepted
109+ uint256 public ghost_txOverCapWithStateGasSucceeded; // tx.gas > cap is valid when excess is state gas
75110
76111 /// @dev TEMPO-BLOCK6: Deployment fits in cap
77112 uint256 public ghost_deploymentTests;
78113 uint256 public ghost_maxDeploymentSucceeded;
79114 uint256 public ghost_maxDeploymentFailed; // Unexpected - would indicate cap too low
80115
116+ /// @dev TIP-1016: Block gasUsed reflects regular gas only
117+ /// @notice Verified at the block level in Rust (crates/consensus/src/lib.rs);
118+ /// this ghost tracks our Solidity-side accounting for consistency.
119+ uint256 public ghost_blockGasUsedRegularOnly;
120+
81121 /// @dev General tracking
82122 uint256 public ghost_validTxExecuted;
83123
@@ -105,14 +145,19 @@ contract BlockGasLimitsInvariantTest is InvariantBase {
105145 function invariant_globalInvariants () public view {
106146 _invariantTxGasCap ();
107147 _invariantMaxDeploymentFits ();
148+ _invariantBlockGasRegularOnly ();
108149 }
109150
110- /// @notice TEMPO-BLOCK3: Tx gas cap must be enforced at 30M
111- /// @dev Violations occur if tx with gas > 30M is accepted
151+ /// @notice TEMPO-BLOCK3 + TIP-1016: Tx gas cap applies to regular gas only
152+ /// @dev Post-TIP-1016, tx.gas > TX_GAS_CAP is valid when excess is state gas.
153+ /// We verify that such transactions succeed rather than being rejected.
112154 function _invariantTxGasCap () internal view {
113- assertEq (
114- ghost_txOverCapViolations, 0 , "TEMPO-BLOCK3: Transaction over 30M gas cap was accepted "
115- );
155+ if (ghost_txGasCapTests > 0 ) {
156+ assertTrue (
157+ ghost_txOverCapWithStateGasSucceeded > 0 || ghost_txAtCapSucceeded > 0 ,
158+ "TEMPO-BLOCK3: No gas cap tests succeeded "
159+ );
160+ }
116161 }
117162
118163 /// @notice TEMPO-BLOCK6: Max contract deployment (24KB) must fit in tx cap
@@ -126,14 +171,25 @@ contract BlockGasLimitsInvariantTest is InvariantBase {
126171 }
127172 }
128173
174+ /// @notice TIP-1016: Block gasUsed must reflect regular gas only
175+ /// @dev State gas is exempt from block gas accounting. This ghost is a
176+ /// Solidity-side placeholder; actual block-level verification is
177+ /// performed in Rust (crates/consensus/src/lib.rs).
178+ function _invariantBlockGasRegularOnly () internal view {
179+ assertEq (
180+ ghost_blockGasUsedRegularOnly, 0 ,
181+ "TIP-1016: block.gasUsed included state gas "
182+ );
183+ }
184+
129185 /*//////////////////////////////////////////////////////////////
130186 HANDLERS
131187 //////////////////////////////////////////////////////////////*/
132188
133- /// @notice Handler: Test tx gas cap enforcement (TEMPO-BLOCK3)
189+ /// @notice Handler: Test tx gas cap enforcement (TEMPO-BLOCK3 + TIP-1016 )
134190 /// @param actorSeed Seed for selecting actor
135- /// @param gasMultiplier Multiplier to test various gas levels
136- function handler_txGasCapEnforcement (uint256 actorSeed , uint256 gasMultiplier ) external {
191+ /// @param stateGasExtra Extra state gas above the cap (1 to 1M)
192+ function handler_txGasCapEnforcement (uint256 actorSeed , uint256 stateGasExtra ) external {
137193 // Skip when not on Tempo (vmExec.executeTransaction not available)
138194 if (! isTempo) return ;
139195
@@ -163,29 +219,29 @@ contract BlockGasLimitsInvariantTest is InvariantBase {
163219 // May fail for other reasons (balance, etc.) - not a violation
164220 }
165221
166- // Test 2: Tx over the cap (should be rejected)
222+ // Test 2: Tx with gas ABOVE the cap where excess is state gas (should succeed)
223+ // Post-TIP-1016, tx.gas > TX_GAS_CAP is valid when the excess goes to
224+ // the state gas reservoir (e.g., an SSTORE needs SSTORE_STATE_GAS).
167225 nonce = uint64 (vm.getNonce (sender));
168226
169- // Gas amount over cap: 30M + 1 to 30M + 10M based on multiplier
170- uint256 overAmount = bound (gasMultiplier, 1 , 10_000_000 );
227+ uint256 overAmount = bound (stateGasExtra, 1 , 1_000_000 );
171228 uint64 overCapGas = uint64 (TX_GAS_CAP + overAmount);
172229
173230 bytes memory overCapTx = TxBuilder.buildLegacyCallWithGas (
174231 vmRlp, vm, address (feeToken), callData, nonce, overCapGas, privateKey
175232 );
176233
177234 try vmExec.executeTransaction (overCapTx) {
178- // Over-cap tx was accepted - VIOLATION
179- ghost_txOverCapViolations ++ ;
235+ // Over-cap tx accepted — valid post-TIP-1016 (excess is state gas)
236+ ghost_txOverCapWithStateGasSucceeded ++ ;
180237 ghost_protocolNonce[sender]++ ;
181- } catch (bytes memory reason ) {
182- if (_isGasCapRevert (reason)) {
183- ghost_txOverCapRejected++ ;
184- }
238+ ghost_validTxExecuted++ ;
239+ } catch {
240+ // May fail for other reasons (balance, etc.) - not a violation
185241 }
186242 }
187243
188- /// @notice Handler: Test max contract deployment fits in cap (TEMPO-BLOCK6)
244+ /// @notice Handler: Test max contract deployment fits in cap (TEMPO-BLOCK6 + TIP-1016 )
189245 /// @param actorSeed Seed for selecting actor
190246 /// @param sizeFraction Fraction of max size to deploy (50-100%)
191247 function handler_maxDeploymentFits (uint256 actorSeed , uint256 sizeFraction ) external {
@@ -206,13 +262,21 @@ contract BlockGasLimitsInvariantTest is InvariantBase {
206262 // Simple initcode: PUSH1 0x00 PUSH1 0x00 RETURN + padding
207263 bytes memory initcode = _createInitcodeOfSize (targetSize);
208264
209- // Calculate required gas
210- uint256 requiredGas = 53_000 // CREATE tx base
211- + CREATE_BASE_GAS + (initcode.length * CODE_DEPOSIT_PER_BYTE) + ACCOUNT_CREATION_GAS
212- + 100_000 ; // Buffer for memory expansion etc.
265+ // TIP-1016: Compute regular and state gas separately.
266+ // A 24KB contract needs ~7M regular gas but ~57M state gas.
267+ // tx.gas = regular + state can exceed TX_GAS_CAP because state gas
268+ // is exempt from the cap (goes to reservoir).
269+ uint256 requiredRegularGas = 53_000 // CREATE tx base
270+ + CREATE_REGULAR_GAS + (initcode.length * CODE_DEPOSIT_REGULAR_PER_BYTE)
271+ + ACCOUNT_CREATION_REGULAR_GAS + 100_000 ; // Buffer for memory expansion etc.
272+
273+ uint256 requiredStateGas = CREATE_STATE_GAS
274+ + (initcode.length * CODE_DEPOSIT_STATE_PER_BYTE) + ACCOUNT_CREATION_STATE_GAS;
275+
276+ uint256 totalGas = requiredRegularGas + requiredStateGas;
213277
214- // Should fit in TX_GAS_CAP
215- uint64 gasLimit = uint64 (requiredGas > TX_GAS_CAP ? TX_GAS_CAP : requiredGas );
278+ // totalGas can exceed TX_GAS_CAP — state gas is exempt from the cap
279+ uint64 gasLimit = uint64 (totalGas );
216280
217281 uint64 nonce = uint64 (vm.getNonce (sender));
218282 bytes memory createTx =
0 commit comments