Commit 356effb9 authored by Mark Tyneway's avatar Mark Tyneway

contracts-bedrock: `StorageSetter` contract

Add + test a `StorageSetter` contract that can be
used by upgrades and storage migrations. This contract
exists to decouple migrations from implementations.
The existing contracts use `Initialize` which is a leaky
abstraction because it includes the fact that the implementation
is behind a proxy in the implementation code itself.
The logic in `initialize` must include logic that can
ensure the storage is correct for any version upgrading
to any version as well as going from empty storage for
new deployments, adding a lot of complexity. This contract
should be used as an intermediate step in upgrades, so that
this contract can be used to migrate storage between
different implementations.
parent cb42a610
...@@ -40,5 +40,6 @@ ...@@ -40,5 +40,6 @@
"Safe", "Safe",
"SafeProxyFactory", "SafeProxyFactory",
"DelayedVetoable", "DelayedVetoable",
"ISemver" "ISemver",
"StorageSetter"
] ]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package bindings
import (
"encoding/json"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
)
const StorageSetterStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
var StorageSetterStorageLayout = new(solc.StorageLayout)
var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a6ed563e1161005b578063a6ed563e1461011c578063bd02d0f51461011c578063ca446dd914610138578063e2a4853a146100bf57600080fd5b806321f8a721146100825780634e91db08146100bf57806354fd4d50146100d3575b600080fd5b610095610090366004610156565b610146565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d16100cd36600461016f565b9055565b005b61010f6040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b69190610191565b61012a610090366004610156565b6040519081526020016100b6565b6100d16100cd366004610204565b6000610150825490565b92915050565b60006020828403121561016857600080fd5b5035919050565b6000806040838503121561018257600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156101be578581018301518582016040015282016101a2565b818111156101d0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561021757600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461024257600080fd5b80915050925092905056fea164736f6c634300080f000a"
func init() {
if err := json.Unmarshal([]byte(StorageSetterStorageLayoutJSON), StorageSetterStorageLayout); err != nil {
panic(err)
}
layouts["StorageSetter"] = StorageSetterStorageLayout
deployedBytecodes["StorageSetter"] = StorageSetterDeployedBin
}
...@@ -658,9 +658,9 @@ SequencerFeeVault_Test:test_withdraw_toL1_succeeds() (gas: 171675) ...@@ -658,9 +658,9 @@ SequencerFeeVault_Test:test_withdraw_toL1_succeeds() (gas: 171675)
SetPrevBaseFee_Test:test_setPrevBaseFee_succeeds() (gas: 11549) SetPrevBaseFee_Test:test_setPrevBaseFee_succeeds() (gas: 11549)
StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 49936) StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 49936)
StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33072) StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33072)
Storage_Roundtrip_Test:test_setGetAddress_succeeds(bytes32,address) (runs: 64, μ: 31799, ~: 31799) Storage_Roundtrip_Test:test_setGetAddress_succeeds(bytes32,address) (runs: 64, μ: 31199, ~: 31821)
Storage_Roundtrip_Test:test_setGetBytes32_succeeds(bytes32,bytes32) (runs: 64, μ: 31643, ~: 31643) Storage_Roundtrip_Test:test_setGetBytes32_succeeds(bytes32,bytes32) (runs: 64, μ: 31598, ~: 31598)
Storage_Roundtrip_Test:test_setGetUint_succeeds(bytes32,uint256) (runs: 64, μ: 30998, ~: 31620) Storage_Roundtrip_Test:test_setGetUint_succeeds(bytes32,uint256) (runs: 64, μ: 31042, ~: 31664)
SystemConfig_Initialize_Test:test_initialize_events_succeeds() (gas: 71972) SystemConfig_Initialize_Test:test_initialize_events_succeeds() (gas: 71972)
SystemConfig_Initialize_Test:test_initialize_startBlockNoop_reverts() (gas: 81247) SystemConfig_Initialize_Test:test_initialize_startBlockNoop_reverts() (gas: 81247)
SystemConfig_Initialize_Test:test_initialize_startBlockOverride_succeeds() (gas: 65143) SystemConfig_Initialize_Test:test_initialize_startBlockOverride_succeeds() (gas: 65143)
......
...@@ -35,6 +35,7 @@ import { MIPS } from "src/cannon/MIPS.sol"; ...@@ -35,6 +35,7 @@ import { MIPS } from "src/cannon/MIPS.sol";
import { BlockOracle } from "src/dispute/BlockOracle.sol"; import { BlockOracle } from "src/dispute/BlockOracle.sol";
import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol";
import { StorageSetter } from "src/universal/StorageSetter.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
import { Chains } from "./Chains.sol"; import { Chains } from "./Chains.sol";
...@@ -999,4 +1000,13 @@ contract Deploy is Deployer { ...@@ -999,4 +1000,13 @@ contract Deploy is Deployer {
); );
} }
} }
/// @notice Deploy the StorageSetter contract, used for upgrades.
function deployStorageSetter() broadcast public returns (address addr_) {
StorageSetter setter = new StorageSetter{ salt: implSalt() }();
console.log("StorageSetter deployed at: %s", address(setter));
string memory version = setter.version();
console.log("StorageSetter version: %s", version);
addr_ = address(setter);
}
} }
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ISemver } from "src/universal/ISemver.sol";
import { Storage } from "src/libraries/Storage.sol";
/// @title StorageSetter
/// @notice A simple contract that allows setting arbitrary storage slots.
/// WARNING: this contract is not safe to be called by untrusted parties.
/// It is only meant as an intermediate step during upgrades.
contract StorageSetter is ISemver {
/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";
/// @notice Stores a bytes32 `_value` at `_slot`. Any storage slots that
/// are packed should be set through this interface.
function setBytes32(bytes32 _slot, bytes32 _value) public {
Storage.setBytes32(_slot, _value);
}
/// @notice Retrieves a bytes32 value from `_slot`.
function getBytes32(bytes32 _slot) external view returns (bytes32) {
return Storage.getBytes32(_slot);
}
/// @notice Stores a uint256 `_value` at `_slot`.
function setUint(bytes32 _slot, uint256 _value) public {
Storage.setUint(_slot, _value);
}
/// @notice Retrieves a uint256 value from `_slot`.
function getUint(bytes32 _slot) external view returns (uint256) {
return Storage.getUint(_slot);
}
/// @notice Stores an address `_value` at `_slot`.
function setAddress(bytes32 _slot, address _address) public {
Storage.setAddress(_slot, _address);
}
/// @notice Retrieves an address value from `_slot`.
function getAddress(bytes32 _slot) external view returns (address) {
return Storage.getAddress(_slot);
}
}
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity 0.8.15;
// Target contract // Target contract
import { Storage } from "src/libraries/Storage.sol"; import { Storage } from "src/libraries/Storage.sol";
import { StorageSetter } from "src/universal/StorageSetter.sol";
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { console } from "forge-std/console.sol";
/// @title StorageWrapper
/// @notice StorageWrapper wraps the Storage library for testing purposes.
/// It exists to prevent storage collisions with the `Test` contract.
contract StorageWrapper {
function getAddress(bytes32 _slot) external view returns (address) {
return Storage.getAddress(_slot);
}
function setAddress(bytes32 _slot, address _address) external {
Storage.setAddress(_slot, _address);
}
function getUint(bytes32 _slot) external view returns (uint256) {
return Storage.getUint(_slot);
}
function setUint(bytes32 _slot, uint256 _value) external {
Storage.setUint(_slot, _value);
}
function getBytes32(bytes32 _slot) external view returns (bytes32) {
return Storage.getBytes32(_slot);
}
function setBytes32(bytes32 _slot, bytes32 _value) external {
Storage.setBytes32(_slot, _value);
}
}
/// @title Storage_Roundtrip_Test
/// @notice Tests the storage setting and getting through the StorageSetter contract.
/// This contract simply wraps the Storage library, this is required as to
/// not poison the storage of the `Test` contract.
contract Storage_Roundtrip_Test is Test { contract Storage_Roundtrip_Test is Test {
StorageWrapper wrapper; StorageSetter setter;
function setUp() external { function setUp() external {
wrapper = new StorageWrapper(); setter = new StorageSetter();
} }
function test_setGetUint_succeeds(bytes32 slot, uint256 num) external { function test_setGetUint_succeeds(bytes32 slot, uint256 num) external {
wrapper.setUint(slot, num); setter.setUint(slot, num);
assertEq(wrapper.getUint(slot), num); assertEq(setter.getUint(slot), num);
assertEq(num, uint256(vm.load(address(wrapper), slot))); assertEq(num, uint256(vm.load(address(setter), slot)));
} }
function test_setGetAddress_succeeds(bytes32 slot, address addr) external { function test_setGetAddress_succeeds(bytes32 slot, address addr) external {
wrapper.setAddress(slot, addr); setter.setAddress(slot, addr);
assertEq(wrapper.getAddress(slot), addr); assertEq(setter.getAddress(slot), addr);
assertEq(addr, address(uint160(uint256(vm.load(address(wrapper), slot))))); assertEq(addr, address(uint160(uint256(vm.load(address(setter), slot)))));
} }
function test_setGetBytes32_succeeds(bytes32 slot, bytes32 hash) external { function test_setGetBytes32_succeeds(bytes32 slot, bytes32 hash) external {
wrapper.setBytes32(slot, hash); setter.setBytes32(slot, hash);
assertEq(wrapper.getBytes32(slot), hash); assertEq(setter.getBytes32(slot), hash);
assertEq(hash, vm.load(address(wrapper), slot)); assertEq(hash, vm.load(address(setter), slot));
} }
} }
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