Commit ffa25d19 authored by Mark Tyneway's avatar Mark Tyneway

contracts-bedrock: upgrade scripts

Run the tests with the following commands:

For L2
```bash
forge test -vvvv --contracts scripts/upgrades --mc PostSherlockL2 --rpc-url $ETH_RPC_URL
```

For L1
```bash
forge test -vvvv --contracts scripts/upgrades --mc PostSherlockL1 --rpc-url $ETH_RPC_URL
```

Ensure that the `ETH_RPC_URL` env var is set for the correct env var.
parent 5661cb3e
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Enum - Collection of enums used in Safe contracts.
* @author Richard Meissner - @rmeissner
*/
abstract contract Enum {
enum Operation {
Call,
DelegateCall
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.10;
import { Enum } from "./Enum.sol";
/**
* @title Enum - Collection of enums used in Safe contracts.
* @author Richard Meissner - @rmeissner
*/
abstract contract Enum {
enum Operation {
Call,
DelegateCall
}
}
/**
* @title IGnosisSafe - Gnosis Safe Interface
*/
interface IGnosisSafe {
event AddedOwner(address owner);
event ApproveHash(bytes32 indexed approvedHash, address indexed owner);
......
......@@ -2,11 +2,10 @@
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { Script } from "forge-std/Script.sol";
import { SafeBuilder } from "./SafeBuilder.sol";
import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol";
import { IGnosisSafe } from "./IGnosisSafe.sol";
import { IGnosisSafe, Enum } from "./IGnosisSafe.sol";
import { LibSort } from "./LibSort.sol";
import { Enum } from "./Enum.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
import { Constants } from "../../contracts/libraries/Constants.sol";
import { SystemConfig } from "../../contracts/L1/SystemConfig.sol";
......@@ -14,32 +13,14 @@ import { ResourceMetering } from "../../contracts/L1/ResourceMetering.sol";
import { Semver } from "../../contracts/universal/Semver.sol";
/**
* @title PostSherlock
* @title PostSherlockL1
* @notice Upgrade script for upgrading the L1 contracts after the sherlock audit.
* Assumes that a gnosis safe is used as the privileged account and the same
* gnosis safe is the owner of the system config and the proxy admin.
* This could be optimized by checking for the number of approvals up front
* and not submitting the final approval as `execTransaction` can be called when
* there are `threshold - 1` approvals.
* Uses the "approved hashes" method of interacting with the gnosis safe. Allows
* for the most simple user experience when using automation and no indexer.
* Run the command without the `--broadcast` flag and it will print a tenderly URL.
*/
contract PostSherlock is Script {
contract PostSherlockL1 is SafeBuilder {
/**
* @notice Interface for multicall3.
* @notice Address of the ProxyAdmin, passed in via constructor of `run`.
*/
IMulticall3 private constant multicall = IMulticall3(MULTICALL3_ADDRESS);
/**
* @notice Mainnet chain id.
*/
uint256 constant MAINNET = 1;
/**
* @notice Goerli chain id.
*/
uint256 constant GOERLI = 5;
ProxyAdmin internal PROXY_ADMIN;
/**
* @notice Represents a set of L1 contracts. Used to represent a set of
......@@ -65,34 +46,29 @@ contract PostSherlock is Script {
*/
mapping(uint256 => ContractSet) internal proxies;
/**
* @notice An array of approvals, used to generate the execution transaction.
*/
address[] internal approvals;
/**
* @notice The expected versions for the contracts to be upgraded to.
*/
string constant internal L1CrossDomainMessenger_Version = "1.1.0";
string constant internal L1CrossDomainMessenger_Version = "1.4.0";
string constant internal L1StandardBridge_Version = "1.1.0";
string constant internal L2OutputOracle_Version = "1.2.0";
string constant internal L2OutputOracle_Version = "1.3.0";
string constant internal OptimismMintableERC20Factory_Version = "1.1.0";
string constant internal OptimismPortal_Version = "1.3.1";
string constant internal SystemConfig_Version = "1.2.0";
string constant internal L1ERC721Bridge_Version = "1.1.0";
string constant internal OptimismPortal_Version = "1.6.0";
string constant internal SystemConfig_Version = "1.3.0";
string constant internal L1ERC721Bridge_Version = "1.1.1";
/**
* @notice Place the contract addresses in storage so they can be used when building calldata.
*/
function setUp() external {
implementations[GOERLI] = ContractSet({
L1CrossDomainMessenger: 0xfa37a4b2D49E21De63fa2b13D6dB213081E020b3,
L1StandardBridge: 0x79179704077E3324CC745A24a5CcC2a80A9B6842,
L2OutputOracle: 0x47bBB9054823f27B9B6A71F5cb0eBc785692FF2E,
OptimismMintableERC20Factory: 0xF516Fa87f89E4AC7C299aE28263e9EB851dE4781,
OptimismPortal: 0xa24A444C6ceeb1d4Fc19D1B78913C22B9d03BbC9,
SystemConfig: 0x2FFfe603caA9FA2C20E7F349138475a43284a6b1,
L1ERC721Bridge: 0xb460323429B08B9d1d427e6b8A450532988d5fe8
L1CrossDomainMessenger: 0x9D1dACf9d9299D17EFFE1aAd559c06bb3Fbf9BC4,
L1StandardBridge: 0x022Fc3EBAA3d53F8f9b270CC4ABe1B0e4A406253,
L2OutputOracle: 0x0C2b6590De9D61b37094617b5e6f794Ae118176E,
OptimismMintableERC20Factory: 0x0EebA1A5da867EB3bc0956f6389d490d0F4b8086,
OptimismPortal: 0x9e760aBd847E48A56b4a348Cba56Ae7267FeCE80,
SystemConfig: 0x821EE96B88dAA1569F41cD46b0EA87fA89714b45,
L1ERC721Bridge: 0x015609dC8cBF8f9947ba571432Bc0d9837c583a4
});
proxies[GOERLI] = ContractSet({
......@@ -106,174 +82,32 @@ contract PostSherlock is Script {
});
}
/**
* @notice The entrypoint to this script.
*/
function run(address _safe, address _proxyAdmin) external returns (bool) {
vm.startBroadcast();
bool success = _run(_safe, _proxyAdmin);
if (success) _postCheck();
return success;
}
/**
* @notice The implementation of the upgrade. Split into its own function
* to allow for testability. This is subject to a race condition if
* the nonce changes by a different transaction finalizing while not
* all of the signers have used this script.
*/
function _run(address _safe, address _proxyAdmin) public returns (bool) {
// Ensure that the required contracts exist
require(address(multicall).code.length > 0, "multicall3 not deployed");
require(_safe.code.length > 0, "no code at safe address");
require(_proxyAdmin.code.length > 0, "no code at proxy admin address");
IGnosisSafe safe = IGnosisSafe(payable(_safe));
uint256 nonce = safe.nonce();
bytes memory data = buildCalldata(_proxyAdmin);
// Compute the safe transaction hash
bytes32 hash = safe.getTransactionHash({
to: address(multicall),
value: 0,
data: data,
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: address(0),
_nonce: nonce
});
// Send a transaction to approve the hash
safe.approveHash(hash);
logSimulationLink({
_to: address(safe),
_from: msg.sender,
_data: abi.encodeCall(safe.approveHash, (hash))
});
uint256 threshold = safe.getThreshold();
address[] memory owners = safe.getOwners();
for (uint256 i; i < owners.length; i++) {
address owner = owners[i];
uint256 approved = safe.approvedHashes(owner, hash);
if (approved == 1) {
approvals.push(owner);
}
}
if (approvals.length >= threshold) {
bytes memory signatures = buildSignatures();
bool success = safe.execTransaction({
to: address(multicall),
value: 0,
data: data,
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(address(0)),
signatures: signatures
});
logSimulationLink({
_to: address(safe),
_from: msg.sender,
_data: abi.encodeCall(
safe.execTransaction,
(
address(multicall),
0,
data,
Enum.Operation.DelegateCall,
0,
0,
0,
address(0),
payable(address(0)),
signatures
)
)
});
require(success, "call not successful");
return true;
} else {
console.log("not enough approvals");
}
// Reset the approvals because they are only used transiently.
assembly {
sstore(approvals.slot, 0)
}
return false;
}
/**
* @notice Log a tenderly simulation link. The TENDERLY_USERNAME and TENDERLY_PROJECT
* environment variables will be used if they are present. The vm is staticcall'ed
* because of a compiler issue with the higher level ABI.
*/
function logSimulationLink(address _to, bytes memory _data, address _from) public view {
(, bytes memory projData) = VM_ADDRESS.staticcall(
abi.encodeWithSignature("envOr(string,string)", "TENDERLY_PROJECT", "TENDERLY_PROJECT")
);
string memory proj = abi.decode(projData, (string));
(, bytes memory userData) = VM_ADDRESS.staticcall(
abi.encodeWithSignature("envOr(string,string)", "TENDERLY_USERNAME", "TENDERLY_USERNAME")
);
string memory username = abi.decode(userData, (string));
string memory str = string.concat(
"https://dashboard.tenderly.co/",
username,
"/",
proj,
"/simulator/new?network=",
vm.toString(block.chainid),
"&contractAddress=",
vm.toString(_to),
"&rawFunctionInput=",
vm.toString(_data),
"&from=",
vm.toString(_from)
);
console.log(str);
}
/**
* @notice Follow up assertions to ensure that the script ran to completion.
*/
function _postCheck() internal view {
function _postCheck() internal override view {
ContractSet memory prox = getProxies();
require(_versionHash(prox.L1CrossDomainMessenger) == keccak256(bytes(L1CrossDomainMessenger_Version)));
require(_versionHash(prox.L1StandardBridge) == keccak256(bytes(L1StandardBridge_Version)));
require(_versionHash(prox.L2OutputOracle) == keccak256(bytes(L2OutputOracle_Version)));
require(_versionHash(prox.OptimismMintableERC20Factory) == keccak256(bytes(OptimismMintableERC20Factory_Version)));
require(_versionHash(prox.OptimismPortal) == keccak256(bytes(OptimismPortal_Version)));
require(_versionHash(prox.SystemConfig) == keccak256(bytes(SystemConfig_Version)));
require(_versionHash(prox.L1ERC721Bridge) == keccak256(bytes(L1ERC721Bridge_Version)));
require(_versionHash(prox.L1CrossDomainMessenger) == keccak256(bytes(L1CrossDomainMessenger_Version)), "L1CrossDomainMessenger");
require(_versionHash(prox.L1StandardBridge) == keccak256(bytes(L1StandardBridge_Version)), "L1StandardBridge");
require(_versionHash(prox.L2OutputOracle) == keccak256(bytes(L2OutputOracle_Version)), "L2OutputOracle");
require(_versionHash(prox.OptimismMintableERC20Factory) == keccak256(bytes(OptimismMintableERC20Factory_Version)), "OptimismMintableERC20Factory");
require(_versionHash(prox.OptimismPortal) == keccak256(bytes(OptimismPortal_Version)), "OptimismPortal");
require(_versionHash(prox.SystemConfig) == keccak256(bytes(SystemConfig_Version)), "SystemConfig");
require(_versionHash(prox.L1ERC721Bridge) == keccak256(bytes(L1ERC721Bridge_Version)), "L1ERC721Bridge");
ResourceMetering.ResourceConfig memory rcfg = SystemConfig(prox.SystemConfig).resourceConfig();
ResourceMetering.ResourceConfig memory dflt = Constants.DEFAULT_RESOURCE_CONFIG();
require(keccak256(abi.encode(rcfg)) == keccak256(abi.encode(dflt)));
}
/**
* @notice Helper function used to compute the hash of Semver's version string to be used in a
* comparison.
*/
function _versionHash(address _addr) internal view returns (bytes32) {
return keccak256(bytes(Semver(_addr).version()));
// Check that the codehashes of all implementations match the proxies set implementations.
ContractSet memory impl = getImplementations();
require(PROXY_ADMIN.getProxyImplementation(prox.L1CrossDomainMessenger).codehash == impl.L1CrossDomainMessenger.codehash, "L1CrossDomainMessenger codehash");
require(PROXY_ADMIN.getProxyImplementation(prox.L1StandardBridge).codehash == impl.L1StandardBridge.codehash, "L1StandardBridge codehash");
require(PROXY_ADMIN.getProxyImplementation(prox.L2OutputOracle).codehash == impl.L2OutputOracle.codehash, "L2OutputOracle codehash");
require(PROXY_ADMIN.getProxyImplementation(prox.OptimismMintableERC20Factory).codehash == impl.OptimismMintableERC20Factory.codehash, "OptimismMintableERC20Factory codehash");
require(PROXY_ADMIN.getProxyImplementation(prox.OptimismPortal).codehash == impl.OptimismPortal.codehash, "OptimismPortal codehash");
require(PROXY_ADMIN.getProxyImplementation(prox.SystemConfig).codehash == impl.SystemConfig.codehash, "SystemConfig codehash");
require(PROXY_ADMIN.getProxyImplementation(prox.L1ERC721Bridge).codehash == impl.L1ERC721Bridge.codehash, "L1ERC721Bridge codehash");
}
/**
......@@ -287,6 +121,8 @@ contract PostSherlock is Script {
if (block.chainid == GOERLI) {
safe = 0xBc1233d0C3e6B5d53Ab455cF65A6623F6dCd7e4f;
proxyAdmin = 0x01d3670863c3F4b24D7b107900f0b75d4BbC6e0d;
// Set the proxy admin for the `_postCheck` function
PROXY_ADMIN = ProxyAdmin(proxyAdmin);
}
require(safe != address(0) && proxyAdmin != address(0));
......@@ -308,34 +144,12 @@ contract PostSherlock is Script {
_postCheck();
}
/**
* @notice Builds the signatures by tightly packing them together.
* Ensures that they are sorted.
*/
function buildSignatures() internal view returns (bytes memory) {
address[] memory addrs = new address[](approvals.length);
for (uint256 i; i < approvals.length; i++) {
addrs[i] = approvals[i];
}
LibSort.sort(addrs);
bytes memory signatures;
uint8 v = 1;
bytes32 s = bytes32(0);
for (uint256 i; i < addrs.length; i++) {
bytes32 r = bytes32(uint256(uint160(addrs[i])));
signatures = bytes.concat(signatures, abi.encodePacked(r, s, v));
}
return signatures;
}
/**
* @notice Builds the calldata that the multisig needs to make for the upgrade to happen.
* A total of 8 calls are made, 7 upgrade implementations and 1 sets the resource
* config to the default value in the SystemConfig contract.
*/
function buildCalldata(address _proxyAdmin) internal view returns (bytes memory) {
function buildCalldata(address _proxyAdmin) internal override view returns (bytes memory) {
IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](8);
ContractSet memory impl = getImplementations();
......
......@@ -2,46 +2,17 @@
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { Script } from "forge-std/Script.sol";
import { SafeBuilder } from "./SafeBuilder.sol";
import { IGnosisSafe, Enum } from "./IGnosisSafe.sol";
import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol";
import { IGnosisSafe } from "./IGnosisSafe.sol";
import { LibSort } from "./LibSort.sol";
import { Enum } from "./Enum.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
import { Constants } from "../../contracts/libraries/Constants.sol";
import { Predeploys } from "../../contracts/libraries/Predeploys.sol";
import { SystemConfig } from "../../contracts/L1/SystemConfig.sol";
import { ResourceMetering } from "../../contracts/L1/ResourceMetering.sol";
import { Semver } from "../../contracts/universal/Semver.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
/**
* @title PostSherlockL2
* @notice Upgrade script for upgrading the L2 predeploy implementations after the sherlock audit.
* Assumes that a gnosis safe is used as the privileged account and the same
* gnosis safe is the owner the proxy admin.
* This could be optimized by checking for the number of approvals up front
* and not submitting the final approval as `execTransaction` can be called when
* there are `threshold - 1` approvals.
* Uses the "approved hashes" method of interacting with the gnosis safe. Allows
* for the most simple user experience when using automation and no indexer.
* Run the command without the `--broadcast` flag and it will print a tenderly URL.
* @notice Upgrades the L2 contracts.
*/
contract PostSherlockL2 is Script {
/**
* @notice Interface for multicall3.
*/
IMulticall3 private constant multicall = IMulticall3(MULTICALL3_ADDRESS);
/**
* @notice OP Mainnet chain id.
*/
uint256 constant OP_MAINNET = 10;
/**
* @notice OP Goerli chain id.
*/
uint256 constant OP_GOERLI = 420;
contract PostSherlockL2 is SafeBuilder {
/**
* @notice The proxy admin predeploy on L2.
*/
......@@ -75,11 +46,6 @@ contract PostSherlockL2 is Script {
*/
mapping(uint256 => ContractSet) internal proxies;
/**
* @notice An array of approvals, used to generate the execution transaction.
*/
address[] internal approvals;
/**
* @notice The expected versions for the contracts to be upgraded to.
*/
......@@ -87,30 +53,30 @@ contract PostSherlockL2 is Script {
string constant internal GasPriceOracle_Version = "1.0.0";
string constant internal L1Block_Version = "1.0.0";
string constant internal L1FeeVault_Version = "1.1.0";
string constant internal L2CrossDomainMessenger_Version = "1.1.0";
string constant internal L2CrossDomainMessenger_Version = "1.4.0";
string constant internal L2ERC721Bridge_Version = "1.1.0";
string constant internal L2StandardBridge_Version = "1.1.0";
string constant internal L2ToL1MessagePasser_Version = "1.0.0";
string constant internal SequencerFeeVault_Version = "1.1.0";
string constant internal OptimismMintableERC20Factory_Version = "1.1.0";
string constant internal OptimismMintableERC721Factory_Version = "1.1.0";
string constant internal OptimismMintableERC721Factory_Version = "1.2.0";
/**
* @notice Place the contract addresses in storage so they can be used when building calldata.
*/
function setUp() external {
implementations[OP_GOERLI] = ContractSet({
BaseFeeVault: 0xEcBb01757B6b7799465a422aD0fC7Fd5F5179F0a,
GasPriceOracle: 0x79f09f735B2d1a42fF864C014d3bD4aA5FAA6A5E,
L1Block: 0xd5F2B9f6Ee80065b2Ce18bF1e629c5aC1C98c7F6,
L1FeeVault: 0x9bA5E286934F0A29fb2f8421f60d3eE8A853447C,
L2CrossDomainMessenger: 0xDe90fE30325588D895Ee4c2E862E703e165a01c7,
L2ERC721Bridge: 0x777adA49d40DAC02AE5b4FdC292feDf9066435A3,
L2StandardBridge: 0x3EA657c5aA0E4Bce1D8919dC7f248724d7B0987a,
L2ToL1MessagePasser: 0xEF2ec5A5465f075E010BE70966a8667c94BCe15a,
SequencerFeeVault: 0x4781674AAe242bbDf6C58b81Cf4F06F1534cd37d,
OptimismMintableERC20Factory: 0xeDF90ac13642e6445955b79CdDA321ecB136b29B,
OptimismMintableERC721Factory: 0x795F355F75f9B28AEC6cC6A887704191e630065b
BaseFeeVault: 0x984eBeFb32A5c2862e92ce90EA0C81Ab69F026B5,
GasPriceOracle: 0xb43C412454f5D1e58Fe895B1a832B6700ADB5FA7,
L1Block: 0x6dF83A19647A398d48e77a6835F4A28EB7e2f7c0,
L1FeeVault: 0xC7d3389726374B8BFF53116585e20A483415f6f6,
L2CrossDomainMessenger: 0x3305a8110469eB7168870126b26BDAD56067C679,
L2ERC721Bridge: 0x5Eb0EE8d7f29856F50b5c97F9CF1225491404bF1,
L2StandardBridge: 0x26A77636eD9A97BBDE9740bed362bFCE5CaB8e10,
L2ToL1MessagePasser: 0x50CcA47c1e06084459dc83c9E964F4a158cB28Ae,
SequencerFeeVault: 0x9eE472aB07Aa92bAe20a9d3E2d29beD3248b9075,
OptimismMintableERC20Factory: 0x3F94732CFd48eE3597d7cEDfb853cfB2De31219c,
OptimismMintableERC721Factory: 0x13DcfC403eCEF3E8Eab66C00dC64e793dc40Be1d
});
proxies[OP_GOERLI] = ContractSet({
......@@ -128,166 +94,22 @@ contract PostSherlockL2 is Script {
});
}
/**
* @notice The entrypoint to this script.
*/
function run(address _safe, address _proxyAdmin) external returns (bool) {
vm.startBroadcast();
bool success = _run(_safe, _proxyAdmin);
if (success) _postCheck();
return success;
}
/**
* @notice The implementation of the upgrade. Split into its own function
* to allow for testability. This is subject to a race condition if
* the nonce changes by a different transaction finalizing while not
* all of the signers have used this script.
*/
function _run(address _safe, address _proxyAdmin) public returns (bool) {
// Ensure that the required contracts exist
require(address(multicall).code.length > 0, "multicall3 not deployed");
require(_safe.code.length > 0, "no code at safe address");
require(_proxyAdmin.code.length > 0, "no code at proxy admin address");
IGnosisSafe safe = IGnosisSafe(payable(_safe));
uint256 nonce = safe.nonce();
bytes memory data = buildCalldata(_proxyAdmin);
// Compute the safe transaction hash
bytes32 hash = safe.getTransactionHash({
to: address(multicall),
value: 0,
data: data,
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: address(0),
_nonce: nonce
});
// Send a transaction to approve the hash
safe.approveHash(hash);
logSimulationLink({
_to: address(safe),
_from: msg.sender,
_data: abi.encodeCall(safe.approveHash, (hash))
});
uint256 threshold = safe.getThreshold();
address[] memory owners = safe.getOwners();
for (uint256 i; i < owners.length; i++) {
address owner = owners[i];
uint256 approved = safe.approvedHashes(owner, hash);
if (approved == 1) {
approvals.push(owner);
}
}
if (approvals.length >= threshold) {
bytes memory signatures = buildSignatures();
bool success = safe.execTransaction({
to: address(multicall),
value: 0,
data: data,
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(address(0)),
signatures: signatures
});
logSimulationLink({
_to: address(safe),
_from: msg.sender,
_data: abi.encodeCall(
safe.execTransaction,
(
address(multicall),
0,
data,
Enum.Operation.DelegateCall,
0,
0,
0,
address(0),
payable(address(0)),
signatures
)
)
});
require(success, "call not successful");
return true;
} else {
console.log("not enough approvals");
}
// Reset the approvals because they are only used transiently.
assembly {
sstore(approvals.slot, 0)
}
return false;
}
/**
* @notice Log a tenderly simulation link. The TENDERLY_USERNAME and TENDERLY_PROJECT
* environment variables will be used if they are present. The vm is staticcall'ed
* because of a compiler issue with the higher level ABI.
*/
function logSimulationLink(address _to, bytes memory _data, address _from) public view {
(, bytes memory projData) = VM_ADDRESS.staticcall(
abi.encodeWithSignature("envOr(string,string)", "TENDERLY_PROJECT", "TENDERLY_PROJECT")
);
string memory proj = abi.decode(projData, (string));
(, bytes memory userData) = VM_ADDRESS.staticcall(
abi.encodeWithSignature("envOr(string,string)", "TENDERLY_USERNAME", "TENDERLY_USERNAME")
);
string memory username = abi.decode(userData, (string));
string memory str = string.concat(
"https://dashboard.tenderly.co/",
username,
"/",
proj,
"/simulator/new?network=",
vm.toString(block.chainid),
"&contractAddress=",
vm.toString(_to),
"&rawFunctionInput=",
vm.toString(_data),
"&from=",
vm.toString(_from)
);
console.log(str);
}
/**
* @notice Follow up assertions to ensure that the script ran to completion.
*/
function _postCheck() internal view {
function _postCheck() internal override view {
ContractSet memory prox = getProxies();
require(_versionHash(prox.BaseFeeVault) == keccak256(bytes(BaseFeeVault_Version)));
require(_versionHash(prox.GasPriceOracle) == keccak256(bytes(GasPriceOracle_Version)));
require(_versionHash(prox.L1Block) == keccak256(bytes(L1Block_Version)));
require(_versionHash(prox.L1FeeVault) == keccak256(bytes(L1FeeVault_Version)));
require(_versionHash(prox.L2CrossDomainMessenger) == keccak256(bytes(L2CrossDomainMessenger_Version)));
require(_versionHash(prox.L2ERC721Bridge) == keccak256(bytes(L2ERC721Bridge_Version)));
require(_versionHash(prox.L2StandardBridge) == keccak256(bytes(L2StandardBridge_Version)));
require(_versionHash(prox.L2ToL1MessagePasser) == keccak256(bytes(L2ToL1MessagePasser_Version)));
require(_versionHash(prox.SequencerFeeVault) == keccak256(bytes(SequencerFeeVault_Version)));
require(_versionHash(prox.OptimismMintableERC20Factory) == keccak256(bytes(OptimismMintableERC20Factory_Version)));
require(_versionHash(prox.OptimismMintableERC721Factory) == keccak256(bytes(OptimismMintableERC721Factory_Version)));
require(_versionHash(prox.BaseFeeVault) == keccak256(bytes(BaseFeeVault_Version)), "BaseFeeVault");
require(_versionHash(prox.GasPriceOracle) == keccak256(bytes(GasPriceOracle_Version)), "GasPriceOracle");
require(_versionHash(prox.L1Block) == keccak256(bytes(L1Block_Version)), "L1Block");
require(_versionHash(prox.L1FeeVault) == keccak256(bytes(L1FeeVault_Version)), "L1FeeVault");
require(_versionHash(prox.L2CrossDomainMessenger) == keccak256(bytes(L2CrossDomainMessenger_Version)), "L2CrossDomainMessenger");
require(_versionHash(prox.L2ERC721Bridge) == keccak256(bytes(L2ERC721Bridge_Version)), "L2ERC721Bridge");
require(_versionHash(prox.L2StandardBridge) == keccak256(bytes(L2StandardBridge_Version)), "L2StandardBridge");
require(_versionHash(prox.L2ToL1MessagePasser) == keccak256(bytes(L2ToL1MessagePasser_Version)), "L2ToL1MessagePasser");
require(_versionHash(prox.SequencerFeeVault) == keccak256(bytes(SequencerFeeVault_Version)), "SequencerFeeVault");
require(_versionHash(prox.OptimismMintableERC20Factory) == keccak256(bytes(OptimismMintableERC20Factory_Version)), "OptimismMintableERC20Factory");
require(_versionHash(prox.OptimismMintableERC721Factory) == keccak256(bytes(OptimismMintableERC721Factory_Version)), "OptimismMintableERC721Factory");
// Check that the codehashes of all implementations match the proxies set implementations.
ContractSet memory impl = getImplementations();
......@@ -304,14 +126,6 @@ contract PostSherlockL2 is Script {
require(PROXY_ADMIN.getProxyImplementation(prox.OptimismMintableERC721Factory).codehash == impl.OptimismMintableERC721Factory.codehash);
}
/**
* @notice Helper function used to compute the hash of Semver's version string to be used in a
* comparison.
*/
function _versionHash(address _addr) internal view returns (bytes32) {
return keccak256(bytes(Semver(_addr).version()));
}
/**
* @notice Test coverage of the logic. Should only run on goerli but other chains
* could be added.
......@@ -344,34 +158,12 @@ contract PostSherlockL2 is Script {
_postCheck();
}
/**
* @notice Builds the signatures by tightly packing them together.
* Ensures that they are sorted.
*/
function buildSignatures() internal view returns (bytes memory) {
address[] memory addrs = new address[](approvals.length);
for (uint256 i; i < approvals.length; i++) {
addrs[i] = approvals[i];
}
LibSort.sort(addrs);
bytes memory signatures;
uint8 v = 1;
bytes32 s = bytes32(0);
for (uint256 i; i < addrs.length; i++) {
bytes32 r = bytes32(uint256(uint160(addrs[i])));
signatures = bytes.concat(signatures, abi.encodePacked(r, s, v));
}
return signatures;
}
/**
* @notice Builds the calldata that the multisig needs to make for the upgrade to happen.
* A total of 9 calls are made to the proxy admin to upgrade the implementations
* of the predeploys.
*/
function buildCalldata(address _proxyAdmin) internal view returns (bytes memory) {
function buildCalldata(address _proxyAdmin) internal override view returns (bytes memory) {
IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](11);
ContractSet memory impl = getImplementations();
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { Script } from "forge-std/Script.sol";
import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol";
import { IGnosisSafe, Enum } from "./IGnosisSafe.sol";
import { LibSort } from "./LibSort.sol";
import { Semver } from "../../contracts/universal/Semver.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
/**
* @title SafeBuilder
* @notice Builds SafeTransactions
* Assumes that a gnosis safe is used as the privileged account and the same
* gnosis safe is the owner the proxy admin.
* This could be optimized by checking for the number of approvals up front
* and not submitting the final approval as `execTransaction` can be called when
* there are `threshold - 1` approvals.
* Uses the "approved hashes" method of interacting with the gnosis safe. Allows
* for the most simple user experience when using automation and no indexer.
* Run the command without the `--broadcast` flag and it will print a tenderly URL.
*/
abstract contract SafeBuilder is Script {
/**
* @notice Mainnet chain id.
*/
uint256 constant MAINNET = 1;
/**
* @notice Goerli chain id.
*/
uint256 constant GOERLI = 5;
/**
* @notice Optimism Goerli chain id.
*/
uint256 constant OP_GOERLI = 420;
/**
* @notice Interface for multicall3.
*/
IMulticall3 internal constant multicall = IMulticall3(MULTICALL3_ADDRESS);
/**
* @notice An array of approvals, used to generate the execution transaction.
*/
address[] internal approvals;
/**
* @notice The entrypoint to this script.
*/
function run(address _safe, address _proxyAdmin) external returns (bool) {
vm.startBroadcast();
bool success = _run(_safe, _proxyAdmin);
if (success) _postCheck();
return success;
}
/**
* @notice The implementation of the upgrade. Split into its own function
* to allow for testability. This is subject to a race condition if
* the nonce changes by a different transaction finalizing while not
* all of the signers have used this script.
*/
function _run(address _safe, address _proxyAdmin) public returns (bool) {
// Ensure that the required contracts exist
require(address(multicall).code.length > 0, "multicall3 not deployed");
require(_safe.code.length > 0, "no code at safe address");
require(_proxyAdmin.code.length > 0, "no code at proxy admin address");
IGnosisSafe safe = IGnosisSafe(payable(_safe));
uint256 nonce = safe.nonce();
bytes memory data = buildCalldata(_proxyAdmin);
// Compute the safe transaction hash
bytes32 hash = safe.getTransactionHash({
to: address(multicall),
value: 0,
data: data,
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: address(0),
_nonce: nonce
});
// Send a transaction to approve the hash
safe.approveHash(hash);
logSimulationLink({
_to: address(safe),
_from: msg.sender,
_data: abi.encodeCall(safe.approveHash, (hash))
});
uint256 threshold = safe.getThreshold();
address[] memory owners = safe.getOwners();
for (uint256 i; i < owners.length; i++) {
address owner = owners[i];
uint256 approved = safe.approvedHashes(owner, hash);
if (approved == 1) {
approvals.push(owner);
}
}
if (approvals.length >= threshold) {
bytes memory signatures = buildSignatures();
bool success = safe.execTransaction({
to: address(multicall),
value: 0,
data: data,
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(address(0)),
signatures: signatures
});
logSimulationLink({
_to: address(safe),
_from: msg.sender,
_data: abi.encodeCall(
safe.execTransaction,
(
address(multicall),
0,
data,
Enum.Operation.DelegateCall,
0,
0,
0,
address(0),
payable(address(0)),
signatures
)
)
});
require(success, "call not successful");
return true;
} else {
console.log("not enough approvals");
}
// Reset the approvals because they are only used transiently.
assembly {
sstore(approvals.slot, 0)
}
return false;
}
/**
* @notice Log a tenderly simulation link. The TENDERLY_USERNAME and TENDERLY_PROJECT
* environment variables will be used if they are present. The vm is staticcall'ed
* because of a compiler issue with the higher level ABI.
*/
function logSimulationLink(address _to, bytes memory _data, address _from) public view {
(, bytes memory projData) = VM_ADDRESS.staticcall(
abi.encodeWithSignature("envOr(string,string)", "TENDERLY_PROJECT", "TENDERLY_PROJECT")
);
string memory proj = abi.decode(projData, (string));
(, bytes memory userData) = VM_ADDRESS.staticcall(
abi.encodeWithSignature("envOr(string,string)", "TENDERLY_USERNAME", "TENDERLY_USERNAME")
);
string memory username = abi.decode(userData, (string));
string memory str = string.concat(
"https://dashboard.tenderly.co/",
username,
"/",
proj,
"/simulator/new?network=",
vm.toString(block.chainid),
"&contractAddress=",
vm.toString(_to),
"&rawFunctionInput=",
vm.toString(_data),
"&from=",
vm.toString(_from)
);
console.log(str);
}
/**
* @notice Follow up assertions to ensure that the script ran to completion.
*/
function _postCheck() internal virtual view;
/**
* @notice Helper function used to compute the hash of Semver's version string to be used in a
* comparison.
*/
function _versionHash(address _addr) internal view returns (bytes32) {
return keccak256(bytes(Semver(_addr).version()));
}
/**
* @notice Builds the signatures by tightly packing them together.
* Ensures that they are sorted.
*/
function buildSignatures() internal view returns (bytes memory) {
address[] memory addrs = new address[](approvals.length);
for (uint256 i; i < approvals.length; i++) {
addrs[i] = approvals[i];
}
LibSort.sort(addrs);
bytes memory signatures;
uint8 v = 1;
bytes32 s = bytes32(0);
for (uint256 i; i < addrs.length; i++) {
bytes32 r = bytes32(uint256(uint160(addrs[i])));
signatures = bytes.concat(signatures, abi.encodePacked(r, s, v));
}
return signatures;
}
/**
* @notice Creates the calldata
*/
function buildCalldata(address _proxyAdmin) internal virtual view returns (bytes memory);
}
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