Commit 5d711a05 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #5555 from ethereum-optimism/refcell/delete/output

feat(contracts-bedrock): L2OutputOracle Scripts
parents 08aec270 45c71abe
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol";
import { LibSort } from "../libraries/LibSort.sol";
import { IGnosisSafe, Enum } from "../interfaces/IGnosisSafe.sol";
import { SafeBuilder } from "../universal/SafeBuilder.sol";
import { Types } from "../../contracts/libraries/Types.sol";
import { FeeVault } from "../../contracts/universal/FeeVault.sol";
import { L2OutputOracle } from "../../contracts/L1/L2OutputOracle.sol";
import { Predeploys } from "../../contracts/libraries/Predeploys.sol";
/**
* @title DeleteOutput
* @notice Deletes an output root from the L2OutputOracle.
* @notice Example usage is provided in the README documentation.
*/
contract DeleteOutput is SafeBuilder {
/**
* @notice A set of contract addresses for the script.
*/
struct ContractSet {
address Safe;
address ProxyAdmin;
address L2OutputOracleProxy;
}
/**
* @notice A mapping of chainid to a ContractSet.
*/
mapping(uint256 => ContractSet) internal _contracts;
/**
* @notice The l2 output index we will delete.
*/
uint256 internal index;
/**
* @notice The address of the L2OutputOracle to target.
*/
address internal oracle;
/**
* @notice Place the contract addresses in storage for ux.
*/
function setUp() external {
_contracts[GOERLI] = ContractSet({
Safe: 0xBc1233d0C3e6B5d53Ab455cF65A6623F6dCd7e4f,
ProxyAdmin: 0x01d3670863c3F4b24D7b107900f0b75d4BbC6e0d,
L2OutputOracleProxy: 0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0
});
}
/**
* @notice Returns the ContractSet for the defined block chainid.
*
* @dev Reverts if no ContractSet is defined.
*/
function contracts() public view returns (ContractSet memory) {
ContractSet memory cs = _contracts[block.chainid];
if (cs.Safe == address(0) || cs.ProxyAdmin == address(0) || cs.L2OutputOracleProxy == address(0)) {
revert("Missing Contract Set for the given block.chainid");
}
return cs;
}
/**
* @notice Executes the gnosis safe transaction to delete an L2 Output Root.
*/
function run(uint256 _index) external returns (bool) {
address _safe = contracts().Safe;
address _proxyAdmin = contracts().ProxyAdmin;
index = _index;
return run(_safe, _proxyAdmin);
}
/**
* @notice Follow up assertions to ensure that the script ran to completion.
*/
function _postCheck() internal view override {
L2OutputOracle l2oo = L2OutputOracle(contracts().L2OutputOracleProxy);
Types.OutputProposal memory proposal = l2oo.getL2Output(index);
require(proposal.l2BlockNumber == 0, "DeleteOutput: Output deletion failed.");
}
/**
* @notice Test coverage of the script.
*/
function test_script_succeeds() skipWhenNotForking external {
uint256 _index = getLatestIndex();
require(_index != 0, "DeleteOutput: No outputs to delete.");
index = _index;
address safe = contracts().Safe;
require(safe != address(0), "DeleteOutput: Invalid safe address.");
address proxyAdmin = contracts().ProxyAdmin;
require(proxyAdmin != address(0), "DeleteOutput: Invalid proxy admin address.");
address[] memory owners = IGnosisSafe(payable(safe)).getOwners();
for (uint256 i; i < owners.length; i++) {
address owner = owners[i];
vm.startBroadcast(owner);
bool success = _run(safe, proxyAdmin);
vm.stopBroadcast();
if (success) {
console.log("tx success");
break;
}
}
_postCheck();
}
function buildCalldata(address _proxyAdmin) internal view override returns (bytes memory) {
IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](1);
calls[0] = IMulticall3.Call3({
target: oracle,
allowFailure: false,
callData: abi.encodeCall(
L2OutputOracle.deleteL2Outputs,
(index)
)
});
return abi.encodeCall(IMulticall3.aggregate3, (calls));
}
/**
* @notice Computes the safe transaction hash.
*/
function computeSafeTransactionHash(uint256 _index) public returns (bytes32) {
ContractSet memory cs = contracts();
address _safe = cs.Safe;
address _proxyAdmin = cs.ProxyAdmin;
index = _index;
oracle = cs.L2OutputOracleProxy;
return _getTransactionHash(_safe, _proxyAdmin);
}
/**
* @notice Returns the challenger for the L2OutputOracle.
*/
function getChallenger() public view returns (address) {
L2OutputOracle l2oo = L2OutputOracle(contracts().L2OutputOracleProxy);
return l2oo.CHALLENGER();
}
/**
* @notice Returns the L2 Block Number for the given index.
*/
function getL2BlockNumber(uint256 _index) public view returns (uint256) {
L2OutputOracle l2oo = L2OutputOracle(contracts().L2OutputOracleProxy);
return l2oo.getL2Output(_index).l2BlockNumber;
}
/**
* @notice Returns the output root for the given index.
*/
function getOutputFromIndex(uint256 _index) public view returns (bytes32) {
L2OutputOracle l2oo = L2OutputOracle(contracts().L2OutputOracleProxy);
return l2oo.getL2Output(_index).outputRoot;
}
/**
* @notice Returns the output root with the corresponding to the L2 Block Number.
*/
function getOutputFromL2BlockNumber(uint256 l2BlockNumber) public view returns (bytes32) {
L2OutputOracle l2oo = L2OutputOracle(contracts().L2OutputOracleProxy);
return l2oo.getL2OutputAfter(l2BlockNumber).outputRoot;
}
/**
* @notice Returns the latest l2 output index.
*/
function getLatestIndex() public view returns (uint256) {
L2OutputOracle l2oo = L2OutputOracle(contracts().L2OutputOracleProxy);
return l2oo.latestOutputIndex();
}
}
## L2 Output Oracle Scripts
A collection of scripts to interact with the L2OutputOracle.
### Output Deletion
[DeleteOutput](./DeleteOutput.s.sol) contains a variety of functions that deal
with deleting an output root from the [L2OutputOracle](../../contracts/L1/L2OutputOracle.sol).
To delete an output root, the script can be run as follows, where `<L2_OUTPUT_INDEX>` is
the index of the posted output to delete.
```bash
$ forge script scripts/output/DeleteOutput.s.sol \
--sig "run(uint256)" \
--rpc-url $ETH_RPC_URL \
--broadcast \
--private-key $PRIVATE_KEY \
<L2_OUTPUT_INDEX>
```
To find and confirm the output index, there are a variety of helper functions that
can be run using the script `--sig` flag, passing the function signatures in as arguments.
These are outlined below.
### Retrieving an L2 Block Number
The output's associated L2 block number can be retrieved using the following command, where
`<L2_OUTPUT_INDEX>` is the index of the output in the [L2OutputOracle](../../contracts/L1/L2OutputOracle.sol).
```bash
$ forge script scripts/output/DeleteOutput.s.sol \
--sig "getL2BlockNumber(uint256)" \
--rpc-url $ETH_RPC_URL \
<L2_OUTPUT_INDEX>
```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { Script } from "forge-std/Script.sol";
import { Semver } from "../../contracts/universal/Semver.sol";
/**
* @title EnhancedScript
* @notice Enhances forge-std' Script.sol with some additional application-specific functionality.
* Logs simulation links using Tenderly.
*/
abstract contract EnhancedScript is Script {
/**
* @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 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);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/**
* @title GlobalConstants
* @notice A set of constants.
*/
contract GlobalConstants {
/**
* @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;
}
......@@ -2,11 +2,12 @@
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 { LibSort } from "../libraries/LibSort.sol";
import { IGnosisSafe, Enum } from "../interfaces/IGnosisSafe.sol";
import { EnhancedScript } from "../universal/EnhancedScript.sol";
import { GlobalConstants } from "../universal/GlobalConstants.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
/**
......@@ -21,22 +22,7 @@ import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
* 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;
abstract contract SafeBuilder is EnhancedScript, GlobalConstants {
/**
* @notice Interface for multicall3.
*/
......@@ -50,7 +36,7 @@ abstract contract SafeBuilder is Script {
/**
* @notice The entrypoint to this script.
*/
function run(address _safe, address _proxyAdmin) external returns (bool) {
function run(address _safe, address _proxyAdmin) public returns (bool) {
vm.startBroadcast();
bool success = _run(_safe, _proxyAdmin);
if (success) _postCheck();
......@@ -58,12 +44,19 @@ abstract contract SafeBuilder is Script {
}
/**
* @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.
* @notice Follow up assertions to ensure that the script ran to completion.
*/
function _run(address _safe, address _proxyAdmin) public returns (bool) {
function _postCheck() internal virtual view;
/**
* @notice Creates the calldata
*/
function buildCalldata(address _proxyAdmin) internal virtual view returns (bytes memory);
/**
* @notice Internal helper function to compute the safe transaction hash.
*/
function _getTransactionHash(address _safe, address _proxyAdmin) internal returns (bytes32) {
// 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");
......@@ -88,6 +81,23 @@ abstract contract SafeBuilder is Script {
_nonce: nonce
});
return hash;
}
/**
* @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) {
IGnosisSafe safe = IGnosisSafe(payable(_safe));
bytes memory data = buildCalldata(_proxyAdmin);
// Compute the safe transaction hash
bytes32 hash = _getTransactionHash(_safe, _proxyAdmin);
// Send a transaction to approve the hash
safe.approveHash(hash);
......@@ -158,52 +168,6 @@ abstract contract SafeBuilder is Script {
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.
......@@ -226,9 +190,5 @@ abstract contract SafeBuilder is Script {
return signatures;
}
/**
* @notice Creates the calldata
*/
function buildCalldata(address _proxyAdmin) internal virtual view returns (bytes memory);
}
......@@ -2,10 +2,10 @@
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { SafeBuilder } from "./SafeBuilder.sol";
import { SafeBuilder } from "../universal/SafeBuilder.sol";
import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol";
import { IGnosisSafe, Enum } from "./IGnosisSafe.sol";
import { LibSort } from "./LibSort.sol";
import { IGnosisSafe, Enum } from "../interfaces/IGnosisSafe.sol";
import { LibSort } from "../libraries/LibSort.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
import { Constants } from "../../contracts/libraries/Constants.sol";
import { SystemConfig } from "../../contracts/L1/SystemConfig.sol";
......
......@@ -2,8 +2,8 @@
pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { SafeBuilder } from "./SafeBuilder.sol";
import { IGnosisSafe, Enum } from "./IGnosisSafe.sol";
import { SafeBuilder } from "../universal/SafeBuilder.sol";
import { IGnosisSafe, Enum } from "../libraries/IGnosisSafe.sol";
import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol";
import { Predeploys } from "../../contracts/libraries/Predeploys.sol";
import { ProxyAdmin } from "../../contracts/universal/ProxyAdmin.sol";
......
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