Commit 60faf8b5 authored by Diego's avatar Diego Committed by GitHub

feat/interop: update `L1BlockInterop` (#10584)

* contracts-bedrock: create diff tests for StaticConfig

* contracts-bedrock: refactor encodeGasPayingToken

* contracts-bedrock: define uint256Type for differential testing

* contracts-bedrock: fix missing line in differential-testing

* contracts-bedrock: return packed in utils

* contracts-bedrock: define args as global vars in differential-testing

* contracts-bedrock: improve args for encodeGasPayingToken in diff testing

* contracts-bedrock: update L1BlockInterop

* contracts-bedrock: remove duplicates in differential-testing

* contracts-bedrock: use SET_GAS_PAYING_TOKEN in L1BlockInterop

* contracts-bedrock: update semver-lock
parent 7ade7f23
...@@ -340,10 +340,17 @@ contract L2Genesis is Deployer { ...@@ -340,10 +340,17 @@ contract L2Genesis is Deployer {
/// @notice This predeploy is following the safety invariant #1. /// @notice This predeploy is following the safety invariant #1.
function setL1Block() public { function setL1Block() public {
if (cfg.useInterop()) {
string memory cname = "L1BlockInterop";
address impl = Predeploys.predeployToCodeNamespace(Predeploys.L1_BLOCK_ATTRIBUTES);
console.log("Setting %s implementation at: %s", cname, impl);
vm.etch(impl, vm.getDeployedCode(string.concat(cname, ".sol:", cname)));
} else {
_setImplementationCode(Predeploys.L1_BLOCK_ATTRIBUTES); _setImplementationCode(Predeploys.L1_BLOCK_ATTRIBUTES);
// Note: L1 block attributes are set to 0. // Note: L1 block attributes are set to 0.
// Before the first user-tx the state is overwritten with actual L1 attributes. // Before the first user-tx the state is overwritten with actual L1 attributes.
} }
}
/// @notice This predeploy is following the safety invariant #1. /// @notice This predeploy is following the safety invariant #1.
function setGasPriceOracle() public { function setGasPriceOracle() public {
......
...@@ -64,12 +64,12 @@ ...@@ -64,12 +64,12 @@
"sourceCodeHash": "0x5529ee28aae94904a1c08a8b188f51a39a0f51fbd3b43f1abd4fee7bba57998c" "sourceCodeHash": "0x5529ee28aae94904a1c08a8b188f51a39a0f51fbd3b43f1abd4fee7bba57998c"
}, },
"src/L2/L1Block.sol": { "src/L2/L1Block.sol": {
"initCodeHash": "0x00961e82f3ed7f7755115c897304063e283bff0bed1200d50e0abe5c59424069", "initCodeHash": "0xfd099da051edf13b147f4382ab4bed9db546d0c48157736ba298fb7e178b20d9",
"sourceCodeHash": "0x9c19260697d5ed5f85318098dead3f5882d77f1ee87233da1b47d1e87c88bce8" "sourceCodeHash": "0x24db623574743432626ed0d7dd938bbd2149b570a00328c772debd7eb179ff1d"
}, },
"src/L2/L1BlockInterop.sol": { "src/L2/L1BlockInterop.sol": {
"initCodeHash": "0x2929683ae3e4f76fca87462f0217b6e24c418bc964328f8d095f0e90343d2cae", "initCodeHash": "0x6833a323934b3be1e5a5c7491c652b6e90bc5102416ddbb255b5f65aa6d5d4a1",
"sourceCodeHash": "0x78ca58451d973e9896eeb9d19699943b7a4e96c36da2724f47d5aafdf243abcd" "sourceCodeHash": "0xd8ec2f814690d1ffd55e5b8496bca5a179d6d1772d61f71cdf8296c9058dc2c6"
}, },
"src/L2/L1FeeVault.sol": { "src/L2/L1FeeVault.sol": {
"initCodeHash": "0x2744d34573be83206d1b75d049d18a7bb37f9058e68c0803e5008c46b0dc2474", "initCodeHash": "0x2744d34573be83206d1b75d049d18a7bb37f9058e68c0803e5008c46b0dc2474",
......
...@@ -77,25 +77,6 @@ ...@@ -77,25 +77,6 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "dependencySet",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "dependencySetSize", "name": "dependencySetSize",
...@@ -250,6 +231,24 @@ ...@@ -250,6 +231,24 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "enum ConfigType",
"name": "_type",
"type": "uint8"
},
{
"internalType": "bytes",
"name": "_value",
"type": "bytes"
}
],
"name": "setConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -333,13 +332,6 @@ ...@@ -333,13 +332,6 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "setL1BlockValuesInterop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "timestamp", "name": "timestamp",
...@@ -366,6 +358,32 @@ ...@@ -366,6 +358,32 @@
"stateMutability": "pure", "stateMutability": "pure",
"type": "function" "type": "function"
}, },
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
}
],
"name": "DependencyAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
}
],
"name": "DependencyRemoved",
"type": "event"
},
{ {
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
...@@ -397,6 +415,26 @@ ...@@ -397,6 +415,26 @@
"name": "GasPayingTokenSet", "name": "GasPayingTokenSet",
"type": "event" "type": "event"
}, },
{
"inputs": [],
"name": "AlreadyDependency",
"type": "error"
},
{
"inputs": [],
"name": "CantRemovedDependency",
"type": "error"
},
{
"inputs": [],
"name": "DependencySetSizeTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "NotDependency",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "NotDepositor", "name": "NotDepositor",
......
...@@ -77,10 +77,10 @@ ...@@ -77,10 +77,10 @@
"type": "uint256" "type": "uint256"
}, },
{ {
"bytes": "32", "bytes": "64",
"label": "dependencySet", "label": "dependencySet",
"offset": 0, "offset": 0,
"slot": "8", "slot": "8",
"type": "uint256[]" "type": "struct EnumerableSet.UintSet"
} }
] ]
\ No newline at end of file
...@@ -4,6 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { Constants } from "src/libraries/Constants.sol"; import { Constants } from "src/libraries/Constants.sol";
import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol"; import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol";
import "src/libraries/L1BlockErrors.sol";
/// @custom:proxied /// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000015 /// @custom:predeploy 0x4200000000000000000000000000000000000015
...@@ -13,9 +14,6 @@ import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol"; ...@@ -13,9 +14,6 @@ import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol";
/// set by the "depositor" account, a special system address. Depositor account transactions /// set by the "depositor" account, a special system address. Depositor account transactions
/// are created by the protocol whenever we move to a new epoch. /// are created by the protocol whenever we move to a new epoch.
contract L1Block is ISemver, IGasToken { contract L1Block is ISemver, IGasToken {
/// @notice Error returns when a non-depositor account tries to set L1 block values.
error NotDepositor();
/// @notice Event emitted when the gas paying token is set. /// @notice Event emitted when the gas paying token is set.
event GasPayingTokenSet(address indexed token, uint8 indexed decimals, bytes32 name, bytes32 symbol); event GasPayingTokenSet(address indexed token, uint8 indexed decimals, bytes32 name, bytes32 symbol);
...@@ -59,9 +57,9 @@ contract L1Block is ISemver, IGasToken { ...@@ -59,9 +57,9 @@ contract L1Block is ISemver, IGasToken {
/// @notice The latest L1 blob base fee. /// @notice The latest L1 blob base fee.
uint256 public blobBaseFee; uint256 public blobBaseFee;
/// @custom:semver 1.4.0 /// @custom:semver 1.4.1-beta.1
function version() public pure virtual returns (string memory) { function version() public pure virtual returns (string memory) {
return "1.4.0"; return "1.4.1-beta.1";
} }
/// @notice Returns the gas paying token, its decimals, name and symbol. /// @notice Returns the gas paying token, its decimals, name and symbol.
......
...@@ -2,108 +2,103 @@ ...@@ -2,108 +2,103 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { L1Block } from "src/L2/L1Block.sol"; import { L1Block } from "src/L2/L1Block.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { GasPayingToken } from "src/libraries/GasPayingToken.sol";
import { StaticConfig } from "src/libraries/StaticConfig.sol";
import "src/libraries/L1BlockErrors.sol";
/// @notice Thrown when a non-depositor account attempts to set L1 block values. /// @notice Enum representing different types of configurations that can be set on L1BlockInterop.
error NotDepositor(); /// @custom:value SET_GAS_PAYING_TOKEN Represents the config type for setting the gas paying token.
/// @custom:value ADD_DEPENDENCY Represents the config type for adding a chain to the interop dependency set.
/// @notice Thrown when dependencySetSize does not match the length of the dependency set. /// @custom:value REMOVE_DEPENDENCY Represents the config type for removing a chain from the interop dependency set.
error DependencySetSizeMismatch(); enum ConfigType {
SET_GAS_PAYING_TOKEN,
ADD_DEPENDENCY,
REMOVE_DEPENDENCY
}
/// @custom:proxied /// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000015 /// @custom:predeploy 0x4200000000000000000000000000000000000015
/// @title L1BlockInterop /// @title L1BlockInterop
/// @notice Interop extenstions of L1Block. /// @notice Interop extenstions of L1Block.
contract L1BlockInterop is L1Block { contract L1BlockInterop is L1Block {
/// @notice The chain IDs of the interop dependency set. using EnumerableSet for EnumerableSet.UintSet;
uint256[] public dependencySet;
/// @custom:semver 1.3.0+interop /// @notice Event emitted when a new dependency is added to the interop dependency set.
function version() public pure override returns (string memory) { event DependencyAdded(uint256 indexed chainId);
return string.concat(super.version(), "+interop");
}
/// @notice Updates the L1 block values for an Interop upgraded chain. /// @notice Event emitted when a dependency is removed from the interop dependency set.
/// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size. event DependencyRemoved(uint256 indexed chainId);
/// Params are expected to be in the following order:
/// 1. _baseFeeScalar L1 base fee scalar
/// 2. _blobBaseFeeScalar L1 blob base fee scalar
/// 3. _sequenceNumber Number of L2 blocks since epoch start.
/// 4. _timestamp L1 timestamp.
/// 5. _number L1 blocknumber.
/// 6. _basefee L1 base fee.
/// 7. _blobBaseFee L1 blob base fee.
/// 8. _hash L1 blockhash.
/// 9. _batcherHash Versioned hash to authenticate batcher by.
/// 10. _dependencySetSize Size of the interop dependency set.
/// 11. _dependencySet Array of chain IDs for the interop dependency set.
function setL1BlockValuesInterop() external {
address depositor = DEPOSITOR_ACCOUNT();
assembly {
// Revert if the caller is not the depositor account.
if xor(caller(), depositor) {
mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()"
revert(0x1C, 0x04) // returns the stored 4-byte selector from above
}
// sequencenum (uint64), blobBaseFeeScalar (uint32), baseFeeScalar (uint32)
sstore(sequenceNumber.slot, shr(128, calldataload(4)))
// number (uint64) and timestamp (uint64)
sstore(number.slot, shr(128, calldataload(20)))
sstore(basefee.slot, calldataload(36)) // uint256
sstore(blobBaseFee.slot, calldataload(68)) // uint256
sstore(hash.slot, calldataload(100)) // bytes32
sstore(batcherHash.slot, calldataload(132)) // bytes32
// Load dependencySetSize from calldata (at offset 164 after calldata for setL1BlockValuesEcotone ends)
let dependencySetSize_ := shr(248, calldataload(164))
// Revert if dependencySetSize_ doesn't match the length of dependencySet in calldata
if xor(add(165, mul(dependencySetSize_, 0x20)), calldatasize()) {
mstore(0x00, 0x44165b6a) // 0x44165b6a is the 4-byte selector of "DependencySetSizeMismatch()"
revert(0x1C, 0x04) // returns the stored 4-byte selector from above
}
// Use memory to hash and get the start index of dependencySet /// @notice The interop dependency set, containing the chain IDs in it.
mstore(0x00, dependencySet.slot) EnumerableSet.UintSet dependencySet;
let dependencySetStartIndex := keccak256(0x00, 0x20)
// Iterate over calldata dependencySet and write to store dependencySet /// @custom:semver +interop
for { let i := 0 } lt(i, dependencySetSize_) { i := add(i, 1) } { function version() public pure override returns (string memory) {
// Load value from calldata and write to storage (dependencySet) at index return string.concat(super.version(), "+interop");
let val := calldataload(add(165, mul(i, 0x20)))
sstore(add(dependencySetStartIndex, i), val)
}
// Update length of dependencySet array
sstore(dependencySet.slot, dependencySetSize_)
}
} }
/// @notice Returns true if a chain ID is in the interop dependency set and false otherwise. /// @notice Returns true if a chain ID is in the interop dependency set and false otherwise.
/// Every chain ID is in the interop dependency set of itself. /// The chain's chain ID is always considered to be in the dependency set.
/// @param _chainId The chain ID to check. /// @param _chainId The chain ID to check.
/// @return True if the chain ID to check is in the interop dependency set. False otherwise. /// @return True if the chain ID to check is in the interop dependency set. False otherwise.
function isInDependencySet(uint256 _chainId) public view returns (bool) { function isInDependencySet(uint256 _chainId) public view returns (bool) {
// Every chain ID is in the interop dependency set of itself. return _chainId == block.chainid || dependencySet.contains(_chainId);
if (_chainId == block.chainid) { }
return true;
/// @notice Returns the size of the interop dependency set.
/// @return The size of the interop dependency set.
function dependencySetSize() external view returns (uint8) {
return uint8(dependencySet.length());
} }
uint256 length = dependencySet.length; /// @notice Sets static configuration options for the L2 system. Can only be called by the special
for (uint256 i = 0; i < length;) { /// depositor account.
if (dependencySet[i] == _chainId) { /// @param _type The type of configuration to set.
return true; /// @param _value The encoded value with which to set the configuration.
function setConfig(ConfigType _type, bytes calldata _value) external {
if (msg.sender != DEPOSITOR_ACCOUNT()) revert NotDepositor();
if (_type == ConfigType.SET_GAS_PAYING_TOKEN) {
_setGasPayingToken(_value);
} else if (_type == ConfigType.ADD_DEPENDENCY) {
_addDependency(_value);
} else if (_type == ConfigType.REMOVE_DEPENDENCY) {
_removeDependency(_value);
} }
unchecked {
i++;
} }
/// @notice Internal method to set the gas paying token.
/// @param _value The encoded value with which to set the gas paying token.
function _setGasPayingToken(bytes calldata _value) internal {
(address token, uint8 decimals, bytes32 name, bytes32 symbol) = StaticConfig.decodeSetGasPayingToken(_value);
GasPayingToken.set({ _token: token, _decimals: decimals, _name: name, _symbol: symbol });
emit GasPayingTokenSet({ token: token, decimals: decimals, name: name, symbol: symbol });
} }
return false; /// @notice Internal method to add a dependency to the interop dependency set.
/// @param _value The encoded value with which to add the dependency.
function _addDependency(bytes calldata _value) internal {
uint256 chainId = StaticConfig.decodeAddDependency(_value);
if (dependencySet.length() == type(uint8).max) revert DependencySetSizeTooLarge();
if (chainId == block.chainid || !dependencySet.add(chainId)) revert AlreadyDependency();
emit DependencyAdded(chainId);
} }
/// @notice Returns the size of the interop dependency set. /// @notice Internal method to remove a dependency from the interop dependency set.
/// @return The size of the interop dependency set. /// @param _value The encoded value with which to remove the dependency.
function dependencySetSize() external view returns (uint8) { function _removeDependency(bytes calldata _value) internal {
return uint8(dependencySet.length); uint256 chainId = StaticConfig.decodeRemoveDependency(_value);
if (chainId == block.chainid) revert CantRemovedDependency();
if (!dependencySet.remove(chainId)) revert NotDependency();
emit DependencyRemoved(chainId);
} }
} }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Error returns when a non-depositor account tries to set L1 block values.
error NotDepositor();
/// @notice Error when a chain ID is not in the interop dependency set.
error NotDependency();
/// @notice Error when the interop dependency set size is too large.
error DependencySetSizeTooLarge();
/// @notice Error when a chain ID already in the interop dependency set is attempted to be added.
error AlreadyDependency();
/// @notice Error when the chain's chain ID is attempted to be removed from the interop dependency set.
error CantRemovedDependency();
...@@ -10,6 +10,7 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -10,6 +10,7 @@ import { Constants } from "src/libraries/Constants.sol";
// Target contract // Target contract
import { L1Block } from "src/L2/L1Block.sol"; import { L1Block } from "src/L2/L1Block.sol";
import "src/libraries/L1BlockErrors.sol";
contract L1BlockTest is CommonTest { contract L1BlockTest is CommonTest {
address depositor; address depositor;
...@@ -200,7 +201,7 @@ contract L1BlockCustomGasToken_Test is L1BlockTest { ...@@ -200,7 +201,7 @@ contract L1BlockCustomGasToken_Test is L1BlockTest {
} }
function test_setGasPayingToken_isDepositor_reverts() external { function test_setGasPayingToken_isDepositor_reverts() external {
vm.expectRevert(L1Block.NotDepositor.selector); vm.expectRevert(NotDepositor.selector);
l1Block.setGasPayingToken(address(this), 18, "Test", "TST"); l1Block.setGasPayingToken(address(this), 18, "Test", "TST");
} }
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; // Testing utilities
import { Encoding } from "src/libraries/Encoding.sol"; import { CommonTest } from "test/setup/CommonTest.sol";
import { L1BlockInterop, DependencySetSizeMismatch, NotDepositor } from "src/L2/L1BlockInterop.sol";
// Libraries
contract L1BlockInteropTest is Test { import { StaticConfig } from "src/libraries/StaticConfig.sol";
L1BlockInterop l1Block;
address depositor; // Target contract dependencies
import { L1BlockInterop, ConfigType } from "src/L2/L1BlockInterop.sol";
function setUp() public { import "src/libraries/L1BlockErrors.sol";
l1Block = new L1BlockInterop();
depositor = l1Block.DEPOSITOR_ACCOUNT(); contract L1BlockInteropTest is CommonTest {
} event GasPayingTokenSet(address indexed token, uint8 indexed decimals, bytes32 name, bytes32 symbol);
event DependencyAdded(uint256 indexed chainId);
/// @dev Tests that setL1BlockValuesInterop updates the values appropriately. event DependencyRemoved(uint256 indexed chainId);
function testFuzz_setL1BlockValuesInterop_succeeds(
uint32 _baseFeeScalar, modifier prankDepositor() {
uint32 _blobBaseFeeScalar, vm.startPrank(l1Block.DEPOSITOR_ACCOUNT());
uint64 _sequenceNumber, _;
uint64 _timestamp, vm.stopPrank();
uint64 _number, }
uint256 _baseFee,
uint256 _blobBaseFee, /// @notice Marked virtual to be overridden in
bytes32 _hash, /// test/kontrol/deployment/DeploymentSummary.t.sol
bytes32 _batcherHash, function setUp() public virtual override {
uint256[] calldata _dependencySet super.enableInterop();
) super.setUp();
external }
{
vm.assume(_dependencySet.length <= type(uint8).max); /// @dev Tests that an arbitrary chain ID can be added to the dependency set.
vm.assume(uint160(uint256(_batcherHash)) == uint256(_batcherHash)); function testFuzz_isInDependencySet_succeeds(uint256 _chainId) public prankDepositor {
vm.assume(_chainId != block.chainid);
bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesInterop({
_baseFeeScalar: _baseFeeScalar, _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId));
_blobBaseFeeScalar: _blobBaseFeeScalar,
_sequenceNumber: _sequenceNumber, assertTrue(_l1BlockInterop().isInDependencySet(_chainId));
_timestamp: _timestamp, }
_number: _number,
_baseFee: _baseFee, /// @dev Tests that `isInDependencySet` returns true when the chain's chain ID is passed as the input.
_blobBaseFee: _blobBaseFee, function test_isInDependencySet_chainChainId_succeeds() public view {
_hash: _hash, assertTrue(_l1BlockInterop().isInDependencySet(block.chainid));
_batcherHash: _batcherHash, }
_dependencySet: _dependencySet
}); /// @dev Tests that `isInDependencySet` reverts when the input chain ID is not in the dependency set
/// and is not the chain's chain ID.
vm.prank(depositor); function testFuzz_isInDependencySet_notDependency_reverts(uint256 _chainId) public view {
(bool success,) = address(l1Block).call(functionCallDataPacked); vm.assume(_chainId != block.chainid);
assertTrue(success, "Function call failed");
// Check that the chain ID is not in the dependency set
assertEq(l1Block.baseFeeScalar(), _baseFeeScalar); assertFalse(_l1BlockInterop().isInDependencySet(_chainId));
assertEq(l1Block.blobBaseFeeScalar(), _blobBaseFeeScalar); }
assertEq(l1Block.sequenceNumber(), _sequenceNumber);
assertEq(l1Block.timestamp(), _timestamp); /// @dev Tests that `isInDependencySet` returns false when the dependency set is empty.
assertEq(l1Block.number(), _number); function testFuzz_isInDependencySet_dependencySetEmpty_succeeds(uint256 _chainId) public view {
assertEq(l1Block.basefee(), _baseFee); vm.assume(_chainId != block.chainid);
assertEq(l1Block.blobBaseFee(), _blobBaseFee);
assertEq(l1Block.hash(), _hash); assertEq(_l1BlockInterop().dependencySetSize(), 0);
assertEq(l1Block.batcherHash(), _batcherHash);
assertEq(l1Block.dependencySetSize(), _dependencySet.length); assertFalse(_l1BlockInterop().isInDependencySet(_chainId));
for (uint256 i = 0; i < _dependencySet.length; i++) { }
assertEq(l1Block.dependencySet(i), _dependencySet[i]);
assertTrue(l1Block.isInDependencySet(_dependencySet[i])); /// @dev Tests that the dependency set size is correct when adding an arbitrary number of chain IDs.
} function testFuzz_dependencySetSize_succeeds(uint8 _dependencySetSize) public prankDepositor {
vm.assume(_dependencySetSize <= type(uint8).max);
// ensure we didn't accidentally pollute the 128 bits of the sequencenum+scalars slot that
// should be empty uint256 uniqueCount = 0;
bytes32 scalarsSlot = vm.load(address(l1Block), bytes32(uint256(3)));
bytes32 mask128 = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"; for (uint256 i = 0; i < _dependencySetSize; i++) {
if (i == block.chainid) continue;
assertEq(0, scalarsSlot & mask128); _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(i));
uniqueCount++;
// ensure we didn't accidentally pollute the 128 bits of the number & timestamp slot that }
// should be empty
bytes32 numberTimestampSlot = vm.load(address(l1Block), bytes32(uint256(0))); assertEq(_l1BlockInterop().dependencySetSize(), uniqueCount);
assertEq(0, numberTimestampSlot & mask128); }
}
/// @dev Tests that the dependency set size is correct when the dependency set is empty.
/// @dev Tests that `setL1BlockValuesInterop` succeeds if sender address is the depositor function test_dependencySetSize_dependencySetEmpty_succeeds() public view {
function test_setL1BlockValuesInterop_isDepositor_succeeds() external { assertEq(_l1BlockInterop().dependencySetSize(), 0);
bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesInterop({ }
_baseFeeScalar: type(uint32).max,
_blobBaseFeeScalar: type(uint32).max, /// @dev Tests that the config for setting the gas paying token succeeds.
_sequenceNumber: type(uint64).max, function testFuzz_setConfig_gasPayingToken_succeeds(
_timestamp: type(uint64).max, address _token,
_number: type(uint64).max, uint8 _decimals,
_baseFee: type(uint256).max, bytes32 _name,
_blobBaseFee: type(uint256).max, bytes32 _symbol
_hash: bytes32(type(uint256).max),
_batcherHash: bytes32(0),
_dependencySet: new uint256[](0)
});
vm.prank(depositor);
(bool success,) = address(l1Block).call(functionCallDataPacked);
assertTrue(success, "function call failed");
}
/// @dev Tests that `setL1BlockValuesInterop` reverts if sender address is not the depositor
function test_setL1BlockValuesInterop_isDepositor_reverts() external {
bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesInterop({
_baseFeeScalar: type(uint32).max,
_blobBaseFeeScalar: type(uint32).max,
_sequenceNumber: type(uint64).max,
_timestamp: type(uint64).max,
_number: type(uint64).max,
_baseFee: type(uint256).max,
_blobBaseFee: type(uint256).max,
_hash: bytes32(type(uint256).max),
_batcherHash: bytes32(0),
_dependencySet: new uint256[](0)
});
(bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked);
assertTrue(!success, "function call should have failed");
// make sure return value is the expected function selector for "NotDepositor()"
assertEq(bytes4(data), NotDepositor.selector);
}
/// @dev Tests that `setL1BlockValuesInterop` reverts if _dependencySetSize is not the same as
/// the length of _dependencySet. (bad path)
function testFuzz_setL1BlockValuesInterop_dependencySetSizeMatch_reverts(
uint8 _notDependencySetSize,
uint256[] calldata _dependencySet
) )
external public
prankDepositor
{ {
vm.assume(_dependencySet.length <= type(uint8).max); vm.assume(_token != address(vm));
vm.assume(_notDependencySetSize != _dependencySet.length);
vm.expectEmit(address(l1Block));
bytes memory functionCallDataPacked = abi.encodePacked( emit GasPayingTokenSet({ token: _token, decimals: _decimals, name: _name, symbol: _symbol });
bytes4(keccak256("setL1BlockValuesInterop()")),
type(uint32).max, _l1BlockInterop().setConfig(
type(uint32).max, ConfigType.SET_GAS_PAYING_TOKEN,
type(uint64).max, StaticConfig.encodeSetGasPayingToken({ _token: _token, _decimals: _decimals, _name: _name, _symbol: _symbol })
type(uint64).max,
type(uint64).max,
type(uint256).max,
type(uint256).max,
bytes32(type(uint256).max),
bytes32(type(uint256).max),
_notDependencySetSize,
_dependencySet
); );
}
vm.prank(depositor); /// @dev Tests that setting the gas paying token config as not the depositor reverts.
(bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked); function testFuzz_setConfig_gasPayingToken_notDepositor_reverts(
assertTrue(!success, "function call should have failed"); address _token,
// make sure return value is the expected function selector for "DependencySetSizeMismatch()" uint8 _decimals,
assertEq(bytes4(data), DependencySetSizeMismatch.selector); bytes32 _name,
} bytes32 _symbol
/// @dev Tests that an arbitrary dependency set can be set and that ìsInDependencySet returns
/// the expected results.
function testFuzz_isInDependencySet_succeeds(
uint32 _baseFeeScalar,
uint32 _blobBaseFeeScalar,
uint64 _sequenceNumber,
uint64 _timestamp,
uint64 _number,
uint256 _baseFee,
uint256 _blobBaseFee,
bytes32 _hash,
bytes32 _batcherHash,
uint256[] calldata _dependencySet
) )
external public
{ {
vm.assume(_dependencySet.length <= type(uint8).max); vm.assume(_token != address(vm));
vm.assume(uint160(uint256(_batcherHash)) == uint256(_batcherHash));
bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesInterop({ vm.expectRevert(NotDepositor.selector);
_baseFeeScalar: _baseFeeScalar, _l1BlockInterop().setConfig(
_blobBaseFeeScalar: _blobBaseFeeScalar, ConfigType.SET_GAS_PAYING_TOKEN,
_sequenceNumber: _sequenceNumber, StaticConfig.encodeSetGasPayingToken({ _token: _token, _decimals: _decimals, _name: _name, _symbol: _symbol })
_timestamp: _timestamp, );
_number: _number, }
_baseFee: _baseFee,
_blobBaseFee: _blobBaseFee,
_hash: _hash,
_batcherHash: _batcherHash,
_dependencySet: _dependencySet
});
vm.prank(depositor); /// @dev Tests that the config for adding a dependency can be set.
(bool success,) = address(l1Block).call(functionCallDataPacked); function testFuzz_setConfig_addDependency_succeeds(uint256 _chainId) public prankDepositor {
assertTrue(success, "Function call failed"); vm.assume(_chainId != block.chainid);
assertEq(l1Block.dependencySetSize(), _dependencySet.length); vm.expectEmit(address(l1Block));
emit DependencyAdded(_chainId);
for (uint256 i = 0; i < _dependencySet.length; i++) { _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId));
assertTrue(l1Block.isInDependencySet(_dependencySet[i]));
} }
/// @dev Tests that adding a dependency reverts if it's the chain's chain id
function test_setConfig_addDependency_chainChainId_reverts() public prankDepositor {
vm.expectRevert(AlreadyDependency.selector);
_l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(block.chainid));
} }
/// @dev Tests that `isInDependencySet` returns true when the current chain ID is passed as the input /// @dev Tests that adding a dependency already in the set reverts
function test_isInDependencySet_isChainId_succeeds() external view { function test_setConfig_addDependency_alreadyDependency_reverts(uint256 _chainId) public prankDepositor {
assertTrue(l1Block.isInDependencySet(block.chainid)); vm.assume(_chainId != block.chainid);
_l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId));
vm.expectRevert(AlreadyDependency.selector);
_l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId));
} }
/// @dev Tests that `isInDependencySet` reverts when the input chain ID is not in the dependency set /// @dev Tests that setting the add dependency config as not the depositor reverts.
function testFuzz_isInDependencySet_reverts(uint256 _chainId) external { function testFuzz_setConfig_addDependency_notDepositor_reverts(uint256 _chainId) public {
vm.assume(_chainId != 1); vm.expectRevert(NotDepositor.selector);
_l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId));
uint256[] memory dependencySet = new uint256[](1); }
dependencySet[0] = 1;
/// @dev Tests that setting the add dependency config when the dependency set size is too large reverts.
bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesInterop({ function test_setConfig_addDependency_dependencySetSizeTooLarge_reverts() public prankDepositor {
_baseFeeScalar: 0, for (uint256 i = 0; i < type(uint8).max; i++) {
_blobBaseFeeScalar: 0, _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(i));
_sequenceNumber: 0, }
_timestamp: 0,
_number: 0, assertEq(_l1BlockInterop().dependencySetSize(), type(uint8).max);
_baseFee: 0,
_blobBaseFee: 0, vm.expectRevert(DependencySetSizeTooLarge.selector);
_hash: bytes32(0), _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(1));
_batcherHash: bytes32(0), }
_dependencySet: dependencySet
}); /// @dev Tests that the config for removing a dependency can be set.
function testFuzz_setConfig_removeDependency_succeeds(uint256 _chainId) public prankDepositor {
vm.prank(depositor); vm.assume(_chainId != block.chainid);
(bool success,) = address(l1Block).call(functionCallDataPacked);
assertTrue(success, "Function call failed"); // Add the chain ID to the dependency set before removing it
_l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId));
assertFalse(l1Block.isInDependencySet(_chainId));
} vm.expectEmit(address(l1Block));
emit DependencyRemoved(_chainId);
/// @dev Tests that `isInDependencySet` returns false when the dependency set is empty
function testFuzz_isInDependencySet_dependencySetEmpty_succeeds(uint256 _chainId) external view { _l1BlockInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(_chainId));
assertTrue(l1Block.dependencySetSize() == 0); }
assertFalse(l1Block.isInDependencySet(_chainId));
/// @dev Tests that setting the remove dependency config as not the depositor reverts.
function testFuzz_setConfig_removeDependency_notDepositor_reverts(uint256 _chainId) public {
vm.expectRevert(NotDepositor.selector);
_l1BlockInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(_chainId));
}
/// @dev Tests that setting the remove dependency config for the chain's chain ID reverts.
function test_setConfig_removeDependency_chainChainId_reverts() public prankDepositor {
vm.expectRevert(CantRemovedDependency.selector);
_l1BlockInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(block.chainid));
}
/// @dev Tests that setting the remove dependency config for a chain ID that is not in the dependency set reverts.
function testFuzz_setConfig_removeDependency_notDependency_reverts(uint256 _chainId) public prankDepositor {
vm.assume(_chainId != block.chainid);
vm.expectRevert(NotDependency.selector);
_l1BlockInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(_chainId));
}
/// @dev Returns the L1BlockInterop instance.
function _l1BlockInterop() internal view returns (L1BlockInterop) {
return L1BlockInterop(address(l1Block));
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment