Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
8cd36c2f
Unverified
Commit
8cd36c2f
authored
Nov 21, 2023
by
clabby
Committed by
GitHub
Nov 21, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #8198 from ethereum-optimism/cl/ctb/reinit-tests
feat(ctb): Refactor L1 initializer tests
parents
af6b5d90
74aa6d16
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
204 additions
and
26 deletions
+204
-26
.gas-snapshot
packages/contracts-bedrock/.gas-snapshot
+0
-1
package.json
packages/contracts-bedrock/package.json
+2
-2
Executables.sol
packages/contracts-bedrock/scripts/Executables.sol
+1
-0
Initializable.t.sol
packages/contracts-bedrock/test/Initializable.t.sol
+201
-23
No files found.
packages/contracts-bedrock/.gas-snapshot
View file @
8cd36c2f
...
@@ -160,7 +160,6 @@ GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 110940)
...
@@ -160,7 +160,6 @@ GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 110940)
GovernanceToken_Test:test_transferFrom_succeeds() (gas: 151340)
GovernanceToken_Test:test_transferFrom_succeeds() (gas: 151340)
GovernanceToken_Test:test_transfer_succeeds() (gas: 142867)
GovernanceToken_Test:test_transfer_succeeds() (gas: 142867)
Hashing_hashDepositSource_Test:test_hashDepositSource_succeeds() (gas: 700)
Hashing_hashDepositSource_Test:test_hashDepositSource_succeeds() (gas: 700)
Initializer_Test:test_cannotReinitializeL1_succeeds() (gas: 44041)
L1BlockNumberTest:test_fallback_succeeds() (gas: 18677)
L1BlockNumberTest:test_fallback_succeeds() (gas: 18677)
L1BlockNumberTest:test_getL1BlockNumber_succeeds() (gas: 10647)
L1BlockNumberTest:test_getL1BlockNumber_succeeds() (gas: 10647)
L1BlockNumberTest:test_receive_succeeds() (gas: 25384)
L1BlockNumberTest:test_receive_succeeds() (gas: 25384)
...
...
packages/contracts-bedrock/package.json
View file @
8cd36c2f
...
@@ -21,7 +21,7 @@
...
@@ -21,7 +21,7 @@
"coverage"
:
"pnpm build:go-ffi && forge coverage"
,
"coverage"
:
"pnpm build:go-ffi && forge coverage"
,
"coverage:lcov"
:
"pnpm build:go-ffi && forge coverage --report lcov"
,
"coverage:lcov"
:
"pnpm build:go-ffi && forge coverage --report lcov"
,
"deploy"
:
"./scripts/deploy.sh"
,
"deploy"
:
"./scripts/deploy.sh"
,
"gas-snapshot:no-build"
:
"forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'"
,
"gas-snapshot:no-build"
:
"forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'
--no-match-contract 'Initializer_Test'
"
,
"gas-snapshot"
:
"pnpm build:go-ffi && pnpm gas-snapshot:no-build"
,
"gas-snapshot"
:
"pnpm build:go-ffi && pnpm gas-snapshot:no-build"
,
"storage-snapshot"
:
"./scripts/storage-snapshot.sh"
,
"storage-snapshot"
:
"./scripts/storage-snapshot.sh"
,
"semver-lock"
:
"forge script scripts/SemverLock.s.sol"
,
"semver-lock"
:
"forge script scripts/SemverLock.s.sol"
,
...
@@ -50,4 +50,4 @@
...
@@ -50,4 +50,4 @@
"tsx"
:
"^4.1.1"
,
"tsx"
:
"^4.1.1"
,
"typescript"
:
"^5.2.2"
"typescript"
:
"^5.2.2"
}
}
}
}
\ No newline at end of file
packages/contracts-bedrock/scripts/Executables.sol
View file @
8cd36c2f
...
@@ -10,4 +10,5 @@ library Executables {
...
@@ -10,4 +10,5 @@ library Executables {
string internal constant forge = "forge";
string internal constant forge = "forge";
string internal constant echo = "echo";
string internal constant echo = "echo";
string internal constant sed = "sed";
string internal constant sed = "sed";
string internal constant find = "find";
}
}
packages/contracts-bedrock/test/Initializable.t.sol
View file @
8cd36c2f
...
@@ -2,40 +2,218 @@
...
@@ -2,40 +2,218 @@
pragma solidity 0.8.15;
pragma solidity 0.8.15;
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
import { Executables } from "scripts/Executables.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import "src/L1/ProtocolVersions.sol";
/// @title Initializer_Test
/// @title Initializer_Test
/// @dev Ensures that the `initialize()` function on contracts cannot be called more than
/// @dev Ensures that the `initialize()` function on contracts cannot be called more than
/// once. This contract inherits from `ERC721Bridge_Initializer` because it is the
/// once. This contract inherits from `ERC721Bridge_Initializer` because it is the
/// deepest contract in the inheritance chain for setting up the system contracts.
/// deepest contract in the inheritance chain for setting up the system contracts.
contract Initializer_Test is Bridge_Initializer {
contract Initializer_Test is Bridge_Initializer {
function test_cannotReinitializeL1_succeeds() public {
/// @notice Contains the address of an `Initializable` contract and the calldata
vm.expectRevert("Initializable: contract is already initialized");
/// used to initialize it.
l2OutputOracle.initialize(0, 0);
struct InitializeableContract {
address target;
vm.expectRevert("Initializable: contract is already initialized");
bytes initCalldata;
optimismPortal.initialize(false);
StorageSlot initializedSlot;
}
vm.expectRevert("Initializable: contract is already initialized");
systemConfig.initialize({
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
_owner: address(0xdEaD),
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
_overhead: 0,
struct StorageSlot {
_scalar: 0,
uint256 astId;
_batcherHash: bytes32(0),
string _contract;
_gasLimit: 1,
string label;
_unsafeBlockSigner: address(0),
uint256 offset;
_config: ResourceMetering.ResourceConfig({
string slot;
maxResourceLimit: 1,
string _type;
elasticityMultiplier: 1,
}
baseFeeMaxChangeDenominator: 2,
minimumBaseFee: 0,
/// @notice Contains the addresses of the contracts to test as well as the calldata
systemTxMaxGas: 0,
/// used to initialize them.
maximumBaseFee: 0
InitializeableContract[] contracts;
function setUp() public override {
// Run the `Bridge_Initializer`'s `setUp()` function.
super.setUp();
// Initialize the `contracts` array with the addresses of the contracts to test, the
// calldata used to initialize them, and the storage slot of their `_initialized` flag.
// L1CrossDomainMessenger
contracts.push(
InitializeableContract({
target: address(l1CrossDomainMessenger),
initCalldata: abi.encodeCall(l1CrossDomainMessenger.initialize, ()),
initializedSlot: _getInitializedSlot("L1CrossDomainMessenger")
})
})
});
);
// L2OutputOracle
contracts.push(
InitializeableContract({
target: address(l2OutputOracle),
initCalldata: abi.encodeCall(l2OutputOracle.initialize, (0, 0)),
initializedSlot: _getInitializedSlot("L2OutputOracle")
})
);
// OptimismPortal
contracts.push(
InitializeableContract({
target: address(optimismPortal),
initCalldata: abi.encodeCall(optimismPortal.initialize, (false)),
initializedSlot: _getInitializedSlot("OptimismPortal")
})
);
// SystemConfig
contracts.push(
InitializeableContract({
target: address(systemConfig),
initCalldata: abi.encodeCall(
systemConfig.initialize,
(
address(0xdead),
0,
0,
bytes32(0),
1,
address(0),
ResourceMetering.ResourceConfig({
maxResourceLimit: 1,
elasticityMultiplier: 1,
baseFeeMaxChangeDenominator: 2,
minimumBaseFee: 0,
systemTxMaxGas: 0,
maximumBaseFee: 0
})
)
),
initializedSlot: _getInitializedSlot("SystemConfig")
})
);
// ProtocolVersions
contracts.push(
InitializeableContract({
target: address(protocolVersions),
initCalldata: abi.encodeCall(
protocolVersions.initialize, (address(0), ProtocolVersion.wrap(1), ProtocolVersion.wrap(2))
),
initializedSlot: _getInitializedSlot("ProtocolVersions")
})
);
}
/// @notice Tests that:
/// 1. All `Initializable` contracts in `src/L1` are accounted for in the `contracts` array.
/// 2. The `_initialized` flag of each contract is properly set to `1`, signifying that the
/// contracts are initialized.
/// 3. The `initialize()` function of each contract cannot be called more than once.
function test_cannotReinitializeL1_succeeds() public {
// Ensure that all L1 `Initializable` contracts are accounted for.
assertEq(_getNumL1Initializable(), contracts.length);
// Attempt to re-initialize all contracts within the `contracts` array.
for (uint256 i; i < contracts.length; i++) {
InitializeableContract memory _contract = contracts[i];
// Load the `_initialized` slot from the storage of the target contract.
uint256 initSlotOffset = _contract.initializedSlot.offset;
bytes32 initSlotVal = vm.load(_contract.target, bytes32(vm.parseUint(_contract.initializedSlot.slot)));
// Pull out the 8-bit `_initialized` flag from the storage slot. The offset in forge artifacts is
// relative to the least-significant bit and signifies the *byte offset*, so we need to shift the
// value to the right by the offset * 8 and then mask out the low-order byte to retrieve the flag.
uint8 init = uint8((uint256(initSlotVal) >> (initSlotOffset * 8)) & 0xFF);
assertEq(init, 1);
// Then, attempt to re-initialize the contract. This should fail.
(bool success, bytes memory returnData) = _contract.target.call(_contract.initCalldata);
assertFalse(success);
assertEq(_extractErrorString(returnData), "Initializable: contract is already initialized");
}
}
/// @dev Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function _getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @dev Returns the number of contracts that are `Initializable` in `src/L1`.
function _getNumL1Initializable() internal returns (uint256 numContracts_) {
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.find,
" src/L1 -type f -exec basename {} \\;",
" | ",
Executables.sed,
" 's/\\.[^.]*$//'",
" | ",
Executables.jq,
" -R -s 'split(\"\n\")[:-1]'"
);
string[] memory contractNames = abi.decode(vm.parseJson(string(vm.ffi(command))), (string[]));
for (uint256 i; i < contractNames.length; i++) {
string memory contractName = contractNames[i];
string memory contractAbi = getAbi(contractName);
// Query the contract's ABI for an `initialize()` function.
command[2] = string.concat(
Executables.echo,
" '",
contractAbi,
"'",
" | ",
Executables.jq,
" '.[] | select(.name == \"initialize\" and .type == \"function\")'"
);
bytes memory res = vm.ffi(command);
// If the contract has an `initialize()` function, the resulting query will be non-empty.
// In this case, increment the number of `Initializable` contracts.
if (res.length > 0) {
numContracts_++;
}
}
}
/// @dev Extracts the revert string from returndata encoded in the form of `Error(string)`.
function _extractErrorString(bytes memory _returnData) internal pure returns (string memory error_) {
// The first 4 bytes of the return data should be the selector for `Error(string)`. If not, revert.
if (bytes4(_returnData) == 0x08c379a0) {
// Extract the error string from the returndata. The error string is located 68 bytes after
// the pointer to `returnData`.
//
// 32 bytes: `returnData` length
// 4 bytes: `Error(string)` selector
// 32 bytes: ABI encoding metadata; String offset
// = 68 bytes
assembly {
error_ := add(_returnData, 0x44)
}
} else {
revert("Initializer_Test: Invalid returndata format. Expected `Error(string)`");
}
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment