Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ contract GenerateNUTBundle is Script {
/// @notice Version of the upgrade bundle.
string internal constant BUNDLE_VERSION = "1.0.0";

/// @notice EIP-7825 per-transaction gas limit cap (2 ** 24).
uint256 public constant MAX_TX_GAS_LIMIT = 16777216;

/// @notice Output containing generated transactions.
/// @param txns Array of Network Upgrade Transactions to execute.
struct Output {
Expand Down Expand Up @@ -113,7 +116,7 @@ contract GenerateNUTBundle is Script {
output_.txns[i] = txns[i];
}

_assertValidOutput(output_);
assertValidOutput(output_);

// Write transactions to artifact with metadata
NetworkUpgradeTxns.BundleMetadata memory metadata =
Expand All @@ -123,7 +126,7 @@ contract GenerateNUTBundle is Script {

/// @notice Asserts the output is valid.
/// @param _output The output to assert.
function _assertValidOutput(Output memory _output) internal pure {
function assertValidOutput(Output memory _output) public pure {
Comment thread
maurelian marked this conversation as resolved.
Outdated
uint256 transactionCount = UpgradeUtils.getTransactionCount();
uint256 txnsLength = _output.txns.length;
require(txnsLength == transactionCount, "GenerateNUTBundle: invalid transaction count");
Expand All @@ -132,13 +135,16 @@ contract GenerateNUTBundle is Script {
require(_output.txns[i].data.length > 0, "GenerateNUTBundle: invalid transaction data");
require(bytes(_output.txns[i].intent).length > 0, "GenerateNUTBundle: invalid transaction intent");
require(_output.txns[i].to != address(0), "GenerateNUTBundle: invalid transaction to");
require(_output.txns[i].gasLimit > 0, "GenerateNUTBundle: invalid transaction gasLimit");

// EIP-7623: op-geth rejects the tx (ErrFloorDataGas) if gasLimit < floorDataGas.
// Lower bound: EIP-7623 calldata floor (op-geth rejects with ErrFloorDataGas below this).
// Upper bound: EIP-7825 per-tx gas cap (2 ** 24). The floor dominates `> 0`, so the
// floor is the only lower bound we need here, assuming every NUT is a CALL, which is
// guaranteed by the `to != address(0)` check above.
uint64 floorDataGas = UpgradeUtils.computeFloorDataGas(_output.txns[i].data);
require(
_output.txns[i].gasLimit >= floorDataGas,
string.concat("GenerateNUTBundle: gasLimit below EIP-7623 floor for ", _output.txns[i].intent)
_output.txns[i].gasLimit >= floorDataGas && _output.txns[i].gasLimit <= MAX_TX_GAS_LIMIT,
string.concat(
"GenerateNUTBundle: gasLimit outside [EIP-7623 floor, EIP-7825 cap] for ", _output.txns[i].intent
)
);

if (_output.txns[i].from == address(0)) {
Expand Down
126 changes: 126 additions & 0 deletions packages/contracts-bedrock/test/scripts/GenerateNUTBundle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NetworkUpgradeTxns } from "src/libraries/NetworkUpgradeTxns.sol";
import { UpgradeUtils } from "scripts/libraries/UpgradeUtils.sol";
import { Constants } from "src/libraries/Constants.sol";
import { L2ContractsManagerTypes } from "src/libraries/L2ContractsManagerTypes.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";

/// @title GenerateNUTBundleTest
/// @notice Tests that GenerateNUTBundle correctly generates Network Upgrade Transaction bundles
Expand Down Expand Up @@ -137,4 +138,129 @@ contract GenerateNUTBundleTest is Test {
string[] memory names = UpgradeUtils.getImplementationsNamesToUpgrade();
assertEq(names.length, structFieldCount, "Deployment list must equal Implementations struct field count");
}

/// @notice Tests that a bundle with an incorrect number of transactions is rejected.
/// @dev Builds a valid bundle, then mutates the array length to trigger the assertion.
function testFuzz_assertValidOutput_transactionCountMismatch_reverts(uint256 _newLength) public {
GenerateNUTBundle.Output memory output = script.run();

_newLength = bound(_newLength, 0, output.txns.length - 1);
NetworkUpgradeTxns.NetworkUpgradeTxn[] memory txns = output.txns;
assembly {
mstore(txns, _newLength)
}

vm.expectRevert("GenerateNUTBundle: invalid transaction count");
script.assertValidOutput(output);
}

/// @notice Tests that a transaction with empty data is rejected.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_emptyData_reverts(uint256 _index) public {
GenerateNUTBundle.Output memory output = script.run();

_index = bound(_index, 0, output.txns.length - 1);
output.txns[_index].data = new bytes(0);

vm.expectRevert("GenerateNUTBundle: invalid transaction data");
script.assertValidOutput(output);
}

/// @notice Tests that a transaction with an empty intent is rejected.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_emptyIntent_reverts(uint256 _index) public {
GenerateNUTBundle.Output memory output = script.run();

_index = bound(_index, 0, output.txns.length - 1);
output.txns[_index].intent = "";

vm.expectRevert("GenerateNUTBundle: invalid transaction intent");
script.assertValidOutput(output);
}

/// @notice Tests that a transaction with a zero destination address is rejected.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_zeroTo_reverts(uint256 _index) public {
GenerateNUTBundle.Output memory output = script.run();

_index = bound(_index, 0, output.txns.length - 1);
output.txns[_index].to = address(0);

vm.expectRevert("GenerateNUTBundle: invalid transaction to");
script.assertValidOutput(output);
}

/// @notice Tests that a transaction exceeding the EIP-7825 per-tx gas limit cap is rejected.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_gasLimitExceedsMax_reverts(uint256 _index, uint64 _gasLimit) public {
Comment thread
0xniha marked this conversation as resolved.
GenerateNUTBundle.Output memory output = script.run();

_index = bound(_index, 0, output.txns.length - 1);
_gasLimit = uint64(bound(_gasLimit, script.MAX_TX_GAS_LIMIT() + 1, type(uint64).max));
output.txns[_index].gasLimit = _gasLimit;

vm.expectRevert(
bytes(
string.concat(
"GenerateNUTBundle: gasLimit outside [EIP-7623 floor, EIP-7825 cap] for ",
output.txns[_index].intent
)
)
);
script.assertValidOutput(output);
}

/// @notice Tests that a transaction with a zero gas limit is rejected by the EIP-7623 floor.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_zeroGasLimit_reverts(uint256 _index) public {
GenerateNUTBundle.Output memory output = script.run();

_index = bound(_index, 0, output.txns.length - 1);
output.txns[_index].gasLimit = 0;

vm.expectRevert(
bytes(
string.concat(
"GenerateNUTBundle: gasLimit outside [EIP-7623 floor, EIP-7825 cap] for ",
output.txns[_index].intent
)
)
);
script.assertValidOutput(output);
}

/// @notice Tests that a transaction whose gasLimit is one below the EIP-7623 floor is rejected.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_gasLimitBelowFloor_reverts(uint256 _index) public {
GenerateNUTBundle.Output memory output = script.run();

_index = bound(_index, 0, output.txns.length - 1);
uint64 floor = UpgradeUtils.computeFloorDataGas(output.txns[_index].data);
output.txns[_index].gasLimit = floor - 1;

vm.expectRevert(
bytes(
string.concat(
"GenerateNUTBundle: gasLimit outside [EIP-7623 floor, EIP-7825 cap] for ",
output.txns[_index].intent
)
)
);
script.assertValidOutput(output);
}

/// @notice Tests that a transaction with a zero sender and a non-privileged destination is rejected.
/// @dev Builds a valid bundle, then mutates one transaction to trigger the assertion.
function testFuzz_assertValidOutput_zeroFromNonPrivilegedTo_reverts(uint256 _index, address _to) public {
GenerateNUTBundle.Output memory output = script.run();

vm.assume(_to != address(0));
vm.assume(_to != Predeploys.PROXY_ADMIN && _to != Predeploys.CONDITIONAL_DEPLOYER);
_index = bound(_index, 0, output.txns.length - 1);
output.txns[_index].from = address(0);
output.txns[_index].to = _to;

vm.expectRevert("GenerateNUTBundle: invalid transaction from");
script.assertValidOutput(output);
}
}
Loading