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
4fd8406c
Unverified
Commit
4fd8406c
authored
Apr 27, 2023
by
OptimismBot
Committed by
GitHub
Apr 27, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5552 from ethereum-optimism/feat/upgrade-script
contracts-bedrock: upgrade scripts
parents
fb768df1
50959382
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
317 additions
and
477 deletions
+317
-477
Enum.sol
packages/contracts-bedrock/scripts/upgrades/Enum.sol
+0
-13
IGnosisSafe.sol
packages/contracts-bedrock/scripts/upgrades/IGnosisSafe.sol
+14
-1
PostSherlock.s.sol
...ges/contracts-bedrock/scripts/upgrades/PostSherlock.s.sol
+38
-224
PostSherlockL2.s.sol
...s/contracts-bedrock/scripts/upgrades/PostSherlockL2.s.sol
+31
-239
SafeBuilder.sol
packages/contracts-bedrock/scripts/upgrades/SafeBuilder.sol
+234
-0
No files found.
packages/contracts-bedrock/scripts/upgrades/Enum.sol
deleted
100644 → 0
View file @
fb768df1
// 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
}
}
packages/contracts-bedrock/scripts/upgrades/IGnosisSafe.sol
View file @
4fd8406c
// 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);
...
...
packages/contracts-bedrock/scripts/upgrades/PostSherlock.s.sol
View file @
4fd8406c
...
...
@@ -2,11 +2,10 @@
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { S
cript } from "forge-std/Script
.sol";
import { S
afeBuilder } 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 PostSherlock
L1
* @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 PostSherlock
L1 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: 0x
fa37a4b2D49E21De63fa2b13D6dB213081E020b3
,
L1StandardBridge: 0x
79179704077E3324CC745A24a5CcC2a80A9B6842
,
L2OutputOracle: 0x
47bBB9054823f27B9B6A71F5cb0eBc785692FF2
E,
OptimismMintableERC20Factory: 0x
F516Fa87f89E4AC7C299aE28263e9EB851dE4781
,
OptimismPortal: 0x
a24A444C6ceeb1d4Fc19D1B78913C22B9d03BbC9
,
SystemConfig: 0x
2FFfe603caA9FA2C20E7F349138475a43284a6b1
,
L1ERC721Bridge: 0x
b460323429B08B9d1d427e6b8A450532988d5fe8
L1CrossDomainMessenger: 0x
9D1dACf9d9299D17EFFE1aAd559c06bb3Fbf9BC4
,
L1StandardBridge: 0x
022Fc3EBAA3d53F8f9b270CC4ABe1B0e4A406253
,
L2OutputOracle: 0x
0C2b6590De9D61b37094617b5e6f794Ae118176
E,
OptimismMintableERC20Factory: 0x
0EebA1A5da867EB3bc0956f6389d490d0F4b8086
,
OptimismPortal: 0x
9e760aBd847E48A56b4a348Cba56Ae7267FeCE80
,
SystemConfig: 0x
821EE96B88dAA1569F41cD46b0EA87fA89714b45
,
L1ERC721Bridge: 0x
015609dC8cBF8f9947ba571432Bc0d9837c583a4
});
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();
...
...
packages/contracts-bedrock/scripts/upgrades/PostSherlockL2.s.sol
View file @
4fd8406c
...
...
@@ -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: 0x
EcBb01757B6b7799465a422aD0fC7Fd5F5179F0a
,
GasPriceOracle: 0x
79f09f735B2d1a42fF864C014d3bD4aA5FAA6A5E
,
L1Block: 0x
d5F2B9f6Ee80065b2Ce18bF1e629c5aC1C98c7F6
,
L1FeeVault: 0x
9bA5E286934F0A29fb2f8421f60d3eE8A853447C
,
L2CrossDomainMessenger: 0x
De90fE30325588D895Ee4c2E862E703e165a01c7
,
L2ERC721Bridge: 0x
777adA49d40DAC02AE5b4FdC292feDf9066435A3
,
L2StandardBridge: 0x
3EA657c5aA0E4Bce1D8919dC7f248724d7B0987a
,
L2ToL1MessagePasser: 0x
EF2ec5A5465f075E010BE70966a8667c94BCe15a
,
SequencerFeeVault: 0x
4781674AAe242bbDf6C58b81Cf4F06F1534cd37d
,
OptimismMintableERC20Factory: 0x
eDF90ac13642e6445955b79CdDA321ecB136b29B
,
OptimismMintableERC721Factory: 0x
795F355F75f9B28AEC6cC6A887704191e630065b
BaseFeeVault: 0x
984eBeFb32A5c2862e92ce90EA0C81Ab69F026B5
,
GasPriceOracle: 0x
b43C412454f5D1e58Fe895B1a832B6700ADB5FA7
,
L1Block: 0x
6dF83A19647A398d48e77a6835F4A28EB7e2f7c0
,
L1FeeVault: 0x
C7d3389726374B8BFF53116585e20A483415f6f6
,
L2CrossDomainMessenger: 0x
3305a8110469eB7168870126b26BDAD56067C679
,
L2ERC721Bridge: 0x
5Eb0EE8d7f29856F50b5c97F9CF1225491404bF1
,
L2StandardBridge: 0x
26A77636eD9A97BBDE9740bed362bFCE5CaB8e10
,
L2ToL1MessagePasser: 0x
50CcA47c1e06084459dc83c9E964F4a158cB28Ae
,
SequencerFeeVault: 0x
9eE472aB07Aa92bAe20a9d3E2d29beD3248b9075
,
OptimismMintableERC20Factory: 0x
3F94732CFd48eE3597d7cEDfb853cfB2De31219c
,
OptimismMintableERC721Factory: 0x
13DcfC403eCEF3E8Eab66C00dC64e793dc40Be1d
});
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();
...
...
packages/contracts-bedrock/scripts/upgrades/SafeBuilder.sol
0 → 100644
View file @
4fd8406c
// 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);
}
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