Commit 7e389c7d authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jg/handle_l2_reorgs_cleanly

parents 66f9e8e8 4c3c1621
---
'@eth-optimism/contracts-bedrock': minor
---
Increase precision in `SafeCall.hasMinGas`
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
...@@ -18,6 +18,7 @@ var ( ...@@ -18,6 +18,7 @@ var (
) )
type OracleL1Client struct { type OracleL1Client struct {
logger log.Logger
oracle Oracle oracle Oracle
head eth.L1BlockRef head eth.L1BlockRef
hashByNum map[uint64]common.Hash hashByNum map[uint64]common.Hash
...@@ -28,6 +29,7 @@ func NewOracleL1Client(logger log.Logger, oracle Oracle, l1Head common.Hash) *Or ...@@ -28,6 +29,7 @@ func NewOracleL1Client(logger log.Logger, oracle Oracle, l1Head common.Hash) *Or
head := eth.InfoToL1BlockRef(oracle.HeaderByBlockHash(l1Head)) head := eth.InfoToL1BlockRef(oracle.HeaderByBlockHash(l1Head))
logger.Info("L1 head loaded", "hash", head.Hash, "number", head.Number) logger.Info("L1 head loaded", "hash", head.Hash, "number", head.Number)
return &OracleL1Client{ return &OracleL1Client{
logger: logger,
oracle: oracle, oracle: oracle,
head: head, head: head,
hashByNum: map[uint64]common.Hash{head.Number: head.Hash}, hashByNum: map[uint64]common.Hash{head.Number: head.Hash},
...@@ -52,6 +54,7 @@ func (o *OracleL1Client) L1BlockRefByNumber(ctx context.Context, number uint64) ...@@ -52,6 +54,7 @@ func (o *OracleL1Client) L1BlockRefByNumber(ctx context.Context, number uint64)
return o.L1BlockRefByHash(ctx, hash) return o.L1BlockRefByHash(ctx, hash)
} }
block := o.earliestIndexedBlock block := o.earliestIndexedBlock
o.logger.Info("Extending block by number lookup", "from", block.Number, "to", number)
for block.Number > number { for block.Number > number {
block = eth.InfoToL1BlockRef(o.oracle.HeaderByBlockHash(block.ParentHash)) block = eth.InfoToL1BlockRef(o.oracle.HeaderByBlockHash(block.ParentHash))
o.hashByNum[block.Number] = block.Hash o.hashByNum[block.Number] = block.Hash
......
...@@ -26,11 +26,14 @@ func Main(logger log.Logger) { ...@@ -26,11 +26,14 @@ func Main(logger log.Logger) {
log.Info("Starting fault proof program client") log.Info("Starting fault proof program client")
preimageOracle := CreatePreimageChannel() preimageOracle := CreatePreimageChannel()
preimageHinter := CreateHinterChannel() preimageHinter := CreateHinterChannel()
err := RunProgram(logger, preimageOracle, preimageHinter) if err := RunProgram(logger, preimageOracle, preimageHinter); errors.Is(err, cldr.ErrClaimNotValid) {
if err != nil { log.Error("Claim is invalid", "err", err)
log.Error("Program failed", "err", err)
os.Exit(1) os.Exit(1)
} else if err != nil {
log.Error("Program failed", "err", err)
os.Exit(2)
} else { } else {
log.Info("Claim successfully verified")
os.Exit(0) os.Exit(0)
} }
} }
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
cl "github.com/ethereum-optimism/optimism/op-program/client"
"github.com/ethereum-optimism/optimism/op-program/client/driver" "github.com/ethereum-optimism/optimism/op-program/client/driver"
"github.com/ethereum-optimism/optimism/op-program/host" "github.com/ethereum-optimism/optimism/op-program/host"
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
...@@ -36,6 +37,11 @@ var VersionWithMeta = func() string { ...@@ -36,6 +37,11 @@ var VersionWithMeta = func() string {
}() }()
func main() { func main() {
if host.RunningProgramInClient() {
logger := oplog.NewLogger(oplog.DefaultCLIConfig())
cl.Main(logger)
panic("Client main should have exited process")
}
args := os.Args args := os.Args
if err := run(args, host.FaultProofProgram); errors.Is(err, driver.ErrClaimNotValid) { if err := run(args, host.FaultProofProgram); errors.Is(err, driver.ErrClaimNotValid) {
log.Crit("Claim is invalid", "err", err) log.Crit("Claim is invalid", "err", err)
......
...@@ -36,11 +36,6 @@ func RunningProgramInClient() bool { ...@@ -36,11 +36,6 @@ func RunningProgramInClient() bool {
// FaultProofProgram is the programmatic entry-point for the fault proof program // FaultProofProgram is the programmatic entry-point for the fault proof program
func FaultProofProgram(logger log.Logger, cfg *config.Config) error { func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
if RunningProgramInClient() {
cl.Main(logger)
panic("Client main should have exited process")
}
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config: %w", err) return fmt.Errorf("invalid config: %w", err)
} }
...@@ -102,7 +97,7 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error { ...@@ -102,7 +97,7 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
var cmd *exec.Cmd var cmd *exec.Cmd
if cfg.Detached { if cfg.Detached {
cmd = exec.CommandContext(ctx, os.Args[0], os.Args[1:]...) cmd = exec.CommandContext(ctx, os.Args[0])
cmd.ExtraFiles = make([]*os.File, cl.MaxFd-3) // not including stdin, stdout and stderr cmd.ExtraFiles = make([]*os.File, cl.MaxFd-3) // not including stdin, stdout and stderr
cmd.ExtraFiles[cl.HClientRFd-3] = hClientRW.Reader() cmd.ExtraFiles[cl.HClientRFd-3] = hClientRW.Reader()
cmd.ExtraFiles[cl.HClientWFd-3] = hClientRW.Writer() cmd.ExtraFiles[cl.HClientWFd-3] = hClientRW.Writer()
......
...@@ -98,8 +98,6 @@ services: ...@@ -98,8 +98,6 @@ services:
OP_PROPOSER_ROLLUP_RPC: http://op-node:8545 OP_PROPOSER_ROLLUP_RPC: http://op-node:8545
OP_PROPOSER_POLL_INTERVAL: 1s OP_PROPOSER_POLL_INTERVAL: 1s
OP_PROPOSER_NUM_CONFIRMATIONS: 1 OP_PROPOSER_NUM_CONFIRMATIONS: 1
OP_PROPOSER_SAFE_ABORT_NONCE_TOO_LOW_COUNT: 3
OP_PROPOSER_RESUBMISSION_TIMEOUT: 30s
OP_PROPOSER_MNEMONIC: test test test test test test test test test test test junk OP_PROPOSER_MNEMONIC: test test test test test test test test test test test junk
OP_PROPOSER_L2_OUTPUT_HD_PATH: "m/44'/60'/0'/0/1" OP_PROPOSER_L2_OUTPUT_HD_PATH: "m/44'/60'/0'/0/1"
OP_PROPOSER_L2OO_ADDRESS: "${L2OO_ADDRESS}" OP_PROPOSER_L2OO_ADDRESS: "${L2OO_ADDRESS}"
...@@ -125,15 +123,9 @@ services: ...@@ -125,15 +123,9 @@ services:
OP_BATCHER_ROLLUP_RPC: http://op-node:8545 OP_BATCHER_ROLLUP_RPC: http://op-node:8545
OFFLINE_GAS_ESTIMATION: false OFFLINE_GAS_ESTIMATION: false
OP_BATCHER_MAX_CHANNEL_DURATION: 1 OP_BATCHER_MAX_CHANNEL_DURATION: 1
OP_BATCHER_MAX_L1_TX_SIZE_BYTES: 120000
OP_BATCHER_TARGET_L1_TX_SIZE_BYTES: 100000
OP_BATCHER_TARGET_NUM_FRAMES: 1
OP_BATCHER_APPROX_COMPR_RATIO: 0.4
OP_BATCHER_SUB_SAFETY_MARGIN: 4 # SWS is 15, ChannelTimeout is 40 OP_BATCHER_SUB_SAFETY_MARGIN: 4 # SWS is 15, ChannelTimeout is 40
OP_BATCHER_POLL_INTERVAL: 1s OP_BATCHER_POLL_INTERVAL: 1s
OP_BATCHER_NUM_CONFIRMATIONS: 1 OP_BATCHER_NUM_CONFIRMATIONS: 1
OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT: 3
OP_BATCHER_RESUBMISSION_TIMEOUT: 30s
OP_BATCHER_MNEMONIC: test test test test test test test test test test test junk OP_BATCHER_MNEMONIC: test test test test test test test test test test test junk
OP_BATCHER_SEQUENCER_HD_PATH: "m/44'/60'/0'/0/2" OP_BATCHER_SEQUENCER_HD_PATH: "m/44'/60'/0'/0/2"
OP_BATCHER_PPROF_ENABLED: "true" OP_BATCHER_PPROF_ENABLED: "true"
......
This diff is collapsed.
This diff is collapsed.
...@@ -20,12 +20,12 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, Semver { ...@@ -20,12 +20,12 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, Semver {
OptimismPortal public immutable PORTAL; OptimismPortal public immutable PORTAL;
/** /**
* @custom:semver 1.3.0 * @custom:semver 1.4.0
* *
* @param _portal Address of the OptimismPortal contract on this network. * @param _portal Address of the OptimismPortal contract on this network.
*/ */
constructor(OptimismPortal _portal) constructor(OptimismPortal _portal)
Semver(1, 3, 0) Semver(1, 4, 0)
CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER)
{ {
PORTAL = _portal; PORTAL = _portal;
......
...@@ -20,13 +20,13 @@ contract L1ERC721Bridge is ERC721Bridge, Semver { ...@@ -20,13 +20,13 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; mapping(address => mapping(address => mapping(uint256 => bool))) public deposits;
/** /**
* @custom:semver 1.0.0 * @custom:semver 1.1.1
* *
* @param _messenger Address of the CrossDomainMessenger on this network. * @param _messenger Address of the CrossDomainMessenger on this network.
* @param _otherBridge Address of the ERC721 bridge on the other network. * @param _otherBridge Address of the ERC721 bridge on the other network.
*/ */
constructor(address _messenger, address _otherBridge) constructor(address _messenger, address _otherBridge)
Semver(1, 1, 0) Semver(1, 1, 1)
ERC721Bridge(_messenger, _otherBridge) ERC721Bridge(_messenger, _otherBridge)
{} {}
......
...@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
} }
/** /**
* @custom:semver 1.4.0 * @custom:semver 1.6.0
* *
* @param _l2Oracle Address of the L2OutputOracle contract. * @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals. * @param _guardian Address that can pause deposits and withdrawals.
...@@ -152,7 +152,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -152,7 +152,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
address _guardian, address _guardian,
bool _paused, bool _paused,
SystemConfig _config SystemConfig _config
) Semver(1, 4, 0) { ) Semver(1, 6, 0) {
L2_ORACLE = _l2Oracle; L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian; GUARDIAN = _guardian;
SYSTEM_CONFIG = _config; SYSTEM_CONFIG = _config;
...@@ -186,6 +186,17 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -186,6 +186,17 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
emit Unpaused(msg.sender); emit Unpaused(msg.sender);
} }
/**
* @notice Computes the minimum gas limit for a deposit. The minimum gas limit
* linearly increases based on the size of the calldata. This is to prevent
* users from creating L2 resource usage without paying for it. This function
* can be used when interacting with the portal to ensure forwards compatibility.
*
*/
function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
return _byteCount * 16 + 21000;
}
/** /**
* @notice Accepts value so that users can send ETH directly to this contract and have the * @notice Accepts value so that users can send ETH directly to this contract and have the
* funds be deposited to their address on L2. This is intended as a convenience * funds be deposited to their address on L2. This is intended as a convenience
...@@ -436,8 +447,18 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -436,8 +447,18 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
); );
} }
// Prevent depositing transactions that have too small of a gas limit. // Prevent depositing transactions that have too small of a gas limit. Users should pay
require(_gasLimit >= 21_000, "OptimismPortal: gas limit must cover instrinsic gas cost"); // more for more resource usage.
require(
_gasLimit >= minimumGasLimit(uint64(_data.length)),
"OptimismPortal: gas limit too small"
);
// Prevent the creation of deposit transactions that have too much calldata. This gives an
// upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
// that the transaction can fit into the p2p network policy of 128kb even though deposit
// transactions are not gossipped over the p2p network.
require(_data.length <= 120_000, "OptimismPortal: data too large");
// Transform the from-address to its alias if the caller is a contract. // Transform the from-address to its alias if the caller is a contract.
address from = msg.sender; address from = msg.sender;
......
...@@ -17,12 +17,12 @@ import { L2ToL1MessagePasser } from "./L2ToL1MessagePasser.sol"; ...@@ -17,12 +17,12 @@ import { L2ToL1MessagePasser } from "./L2ToL1MessagePasser.sol";
*/ */
contract L2CrossDomainMessenger is CrossDomainMessenger, Semver { contract L2CrossDomainMessenger is CrossDomainMessenger, Semver {
/** /**
* @custom:semver 1.3.0 * @custom:semver 1.4.0
* *
* @param _l1CrossDomainMessenger Address of the L1CrossDomainMessenger contract. * @param _l1CrossDomainMessenger Address of the L1CrossDomainMessenger contract.
*/ */
constructor(address _l1CrossDomainMessenger) constructor(address _l1CrossDomainMessenger)
Semver(1, 3, 0) Semver(1, 4, 0)
CrossDomainMessenger(_l1CrossDomainMessenger) CrossDomainMessenger(_l1CrossDomainMessenger)
{ {
initialize(); initialize();
......
...@@ -63,8 +63,9 @@ library SafeCall { ...@@ -63,8 +63,9 @@ library SafeCall {
function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) { function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) {
bool _hasMinGas; bool _hasMinGas;
assembly { assembly {
// Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas)
_hasMinGas := iszero( _hasMinGas := iszero(
lt(gas(), add(div(mul(_minGas, 64), 63), add(40000, _reservedGas))) lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63)))
) )
} }
return _hasMinGas; return _hasMinGas;
......
...@@ -22,6 +22,21 @@ contract CrossDomainMessenger_BaseGas_Test is Messenger_Initializer { ...@@ -22,6 +22,21 @@ contract CrossDomainMessenger_BaseGas_Test is Messenger_Initializer {
function testFuzz_baseGas_succeeds(uint32 _minGasLimit) external view { function testFuzz_baseGas_succeeds(uint32 _minGasLimit) external view {
L1Messenger.baseGas(hex"ff", _minGasLimit); L1Messenger.baseGas(hex"ff", _minGasLimit);
} }
/**
* @notice The baseGas function should always return a value greater than
* or equal to the minimum gas limit value on the OptimismPortal.
* This guarantees that the messengers will always pass sufficient
* gas to the OptimismPortal.
*/
function testFuzz_baseGas_portalMinGasLimit_succeeds(bytes memory _data, uint32 _minGasLimit)
external
{
vm.assume(_data.length <= type(uint64).max);
uint64 baseGas = L1Messenger.baseGas(_data, _minGasLimit);
uint64 minGasLimit = op.minimumGasLimit(uint64(_data.length));
assertTrue(baseGas >= minGasLimit);
}
} }
/** /**
......
...@@ -56,7 +56,7 @@ contract CrossDomainOwnableThroughPortal_Test is Portal_Initializer { ...@@ -56,7 +56,7 @@ contract CrossDomainOwnableThroughPortal_Test is Portal_Initializer {
op.depositTransaction({ op.depositTransaction({
_to: address(setter), _to: address(setter),
_value: 0, _value: 0,
_gasLimit: 21_000, _gasLimit: 30_000,
_isCreation: false, _isCreation: false,
_data: abi.encodeWithSelector(XDomainSetter.set.selector, 1) _data: abi.encodeWithSelector(XDomainSetter.set.selector, 1)
}); });
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { ERC721, IERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {
IERC721Enumerable
} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ERC721Bridge_Initializer } from "./CommonTest.t.sol"; import { ERC721Bridge_Initializer } from "./CommonTest.t.sol";
import { OptimismMintableERC721 } from "../universal/OptimismMintableERC721.sol"; import {
OptimismMintableERC721,
IOptimismMintableERC721
} from "../universal/OptimismMintableERC721.sol";
contract OptimismMintableERC721_Test is ERC721Bridge_Initializer { contract OptimismMintableERC721_Test is ERC721Bridge_Initializer {
ERC721 internal L1Token; ERC721 internal L1Token;
...@@ -44,7 +50,21 @@ contract OptimismMintableERC721_Test is ERC721Bridge_Initializer { ...@@ -44,7 +50,21 @@ contract OptimismMintableERC721_Test is ERC721Bridge_Initializer {
assertEq(L2Token.REMOTE_TOKEN(), address(L1Token)); assertEq(L2Token.REMOTE_TOKEN(), address(L1Token));
assertEq(L2Token.BRIDGE(), address(L2Bridge)); assertEq(L2Token.BRIDGE(), address(L2Bridge));
assertEq(L2Token.REMOTE_CHAIN_ID(), 1); assertEq(L2Token.REMOTE_CHAIN_ID(), 1);
assertEq(L2Token.version(), "1.0.0"); assertEq(L2Token.version(), "1.1.0");
}
/**
* @notice Ensure that the contract supports the expected interfaces.
*/
function test_supportsInterfaces_succeeds() external {
// Checks if the contract supports the IOptimismMintableERC721 interface.
assertTrue(L2Token.supportsInterface(type(IOptimismMintableERC721).interfaceId));
// Checks if the contract supports the IERC721Enumerable interface.
assertTrue(L2Token.supportsInterface(type(IERC721Enumerable).interfaceId));
// Checks if the contract supports the IERC721 interface.
assertTrue(L2Token.supportsInterface(type(IERC721).interfaceId));
// Checks if the contract supports the IERC165 interface.
assertTrue(L2Token.supportsInterface(type(IERC165).interfaceId));
} }
function test_safeMint_succeeds() external { function test_safeMint_succeeds() external {
......
...@@ -110,12 +110,29 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -110,12 +110,29 @@ contract OptimismPortal_Test is Portal_Initializer {
op.depositTransaction(address(1), 1, 0, true, hex""); op.depositTransaction(address(1), 1, 0, true, hex"");
} }
/**
* @notice Prevent deposits from being too large to have a sane upper bound
* on unsafe blocks sent over the p2p network.
*/
function test_depositTransaction_largeData_reverts() external {
uint256 size = 120_001;
uint64 gasLimit = op.minimumGasLimit(uint64(size));
vm.expectRevert("OptimismPortal: data too large");
op.depositTransaction({
_to: address(0),
_value: 0,
_gasLimit: gasLimit,
_isCreation: false,
_data: new bytes(size)
});
}
/** /**
* @notice Prevent gasless deposits from being force processed in L2 by * @notice Prevent gasless deposits from being force processed in L2 by
* ensuring that they have a large enough gas limit set. * ensuring that they have a large enough gas limit set.
*/ */
function test_depositTransaction_smallGasLimit_reverts() external { function test_depositTransaction_smallGasLimit_reverts() external {
vm.expectRevert("OptimismPortal: gas limit must cover instrinsic gas cost"); vm.expectRevert("OptimismPortal: gas limit too small");
op.depositTransaction({ op.depositTransaction({
_to: address(1), _to: address(1),
_value: 0, _value: 0,
...@@ -125,6 +142,40 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -125,6 +142,40 @@ contract OptimismPortal_Test is Portal_Initializer {
}); });
} }
/**
* @notice Fuzz for too small of gas limits
*/
function testFuzz_depositTransaction_smallGasLimit_succeeds(
bytes memory _data,
bool _shouldFail
) external {
vm.assume(_data.length <= type(uint64).max);
uint64 gasLimit = op.minimumGasLimit(uint64(_data.length));
if (_shouldFail) {
gasLimit = uint64(bound(gasLimit, 0, gasLimit - 1));
vm.expectRevert("OptimismPortal: gas limit too small");
}
op.depositTransaction({
_to: address(0x40),
_value: 0,
_gasLimit: gasLimit,
_isCreation: false,
_data: _data
});
}
/**
* @notice Ensure that the 0 calldata case is covered and there is a linearly
* increasing gas limit for larger calldata sizes.
*/
function test_minimumGasLimit_succeeds() external {
assertEq(op.minimumGasLimit(0), 21_000);
assertTrue(op.minimumGasLimit(2) > op.minimumGasLimit(1));
assertTrue(op.minimumGasLimit(3) > op.minimumGasLimit(2));
}
// Test: depositTransaction should emit the correct log when an EOA deposits a tx with 0 value // Test: depositTransaction should emit the correct log when an EOA deposits a tx with 0 value
function test_depositTransaction_noValueEOA_succeeds() external { function test_depositTransaction_noValueEOA_succeeds() external {
// EOA emulation // EOA emulation
...@@ -1129,7 +1180,7 @@ contract OptimismPortalResourceFuzz_Test is Portal_Initializer { ...@@ -1129,7 +1180,7 @@ contract OptimismPortalResourceFuzz_Test is Portal_Initializer {
// Bound resource config // Bound resource config
_maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8)); _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8));
_gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit)); _gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit));
_prevBaseFee = uint128(bound(_prevBaseFee, 0, 5 gwei)); _prevBaseFee = uint128(bound(_prevBaseFee, 0, 3 gwei));
// Prevent values that would cause reverts // Prevent values that would cause reverts
vm.assume(gasLimit >= _gasLimit); vm.assume(gasLimit >= _gasLimit);
vm.assume(_minimumBaseFee < _maximumBaseFee); vm.assume(_minimumBaseFee < _maximumBaseFee);
......
...@@ -94,9 +94,9 @@ contract SafeCall_Test is CommonTest { ...@@ -94,9 +94,9 @@ contract SafeCall_Test is CommonTest {
for (uint64 i = 40_000; i < 100_000; i++) { for (uint64 i = 40_000; i < 100_000; i++) {
uint256 snapshot = vm.snapshot(); uint256 snapshot = vm.snapshot();
// 65_903 is the exact amount of gas required to make the safe call // 65_907 is the exact amount of gas required to make the safe call
// successfully. // successfully.
if (i < 65_903) { if (i < 65_907) {
assertFalse(caller.makeSafeCall(i, 25_000)); assertFalse(caller.makeSafeCall(i, 25_000));
} else { } else {
vm.expectCallMinGas( vm.expectCallMinGas(
...@@ -118,9 +118,9 @@ contract SafeCall_Test is CommonTest { ...@@ -118,9 +118,9 @@ contract SafeCall_Test is CommonTest {
for (uint64 i = 15_200_000; i < 15_300_000; i++) { for (uint64 i = 15_200_000; i < 15_300_000; i++) {
uint256 snapshot = vm.snapshot(); uint256 snapshot = vm.snapshot();
// 15_278_602 is the exact amount of gas required to make the safe call // 15_278_606 is the exact amount of gas required to make the safe call
// successfully. // successfully.
if (i < 15_278_602) { if (i < 15_278_606) {
assertFalse(caller.makeSafeCall(i, 15_000_000)); assertFalse(caller.makeSafeCall(i, 15_000_000));
} else { } else {
vm.expectCallMinGas( vm.expectCallMinGas(
......
...@@ -33,16 +33,16 @@ contract CrossDomainMessengerLegacySpacer0 { ...@@ -33,16 +33,16 @@ contract CrossDomainMessengerLegacySpacer0 {
contract CrossDomainMessengerLegacySpacer1 { contract CrossDomainMessengerLegacySpacer1 {
/** /**
* @custom:legacy * @custom:legacy
* @custom:spacer __gap * @custom:spacer ContextUpgradable's __gap
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin * @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* ContextUpgradable via OwnableUpgradeable. * ContextUpgradable.
* *
*/ */
uint256[50] private spacer_1_0_1600; uint256[50] private spacer_1_0_1600;
/** /**
* @custom:legacy * @custom:legacy
* @custom:spacer _owner * @custom:spacer OwnableUpgradeable's _owner
* @notice Spacer for backwards compatibility. * @notice Spacer for backwards compatibility.
* Come from OpenZeppelin OwnableUpgradeable. * Come from OpenZeppelin OwnableUpgradeable.
*/ */
...@@ -50,15 +50,15 @@ contract CrossDomainMessengerLegacySpacer1 { ...@@ -50,15 +50,15 @@ contract CrossDomainMessengerLegacySpacer1 {
/** /**
* @custom:legacy * @custom:legacy
* @custom:spacer __gap * @custom:spacer OwnableUpgradeable's __gap
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin * @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* ContextUpgradable via PausableUpgradable. * OwnableUpgradeable.
*/ */
uint256[49] private spacer_52_0_1568; uint256[49] private spacer_52_0_1568;
/** /**
* @custom:legacy * @custom:legacy
* @custom:spacer _paused * @custom:spacer PausableUpgradable's _paused
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin * @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* PausableUpgradable. * PausableUpgradable.
*/ */
...@@ -66,7 +66,7 @@ contract CrossDomainMessengerLegacySpacer1 { ...@@ -66,7 +66,7 @@ contract CrossDomainMessengerLegacySpacer1 {
/** /**
* @custom:legacy * @custom:legacy
* @custom:spacer __gap * @custom:spacer PausableUpgradable's __gap
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin * @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* PausableUpgradable. * PausableUpgradable.
*/ */
...@@ -75,15 +75,16 @@ contract CrossDomainMessengerLegacySpacer1 { ...@@ -75,15 +75,16 @@ contract CrossDomainMessengerLegacySpacer1 {
/** /**
* @custom:legacy * @custom:legacy
* @custom:spacer ReentrancyGuardUpgradeable's `_status` field. * @custom:spacer ReentrancyGuardUpgradeable's `_status` field.
* @notice Spacer for backwards compatibility * @notice Spacer for backwards compatibility.
*/ */
uint256 private spacer_151_0_32; uint256 private spacer_151_0_32;
/** /**
* @custom:spacer ReentrancyGuardUpgradeable * @custom:legacy
* @notice Spacer for backwards compatibility * @custom:spacer ReentrancyGuardUpgradeable's __gap
* @notice Spacer for backwards compatibility.
*/ */
uint256[49] private __gap_reentrancy_guard; uint256[49] private spacer_152_0_1568;
/** /**
* @custom:legacy * @custom:legacy
......
...@@ -46,7 +46,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se ...@@ -46,7 +46,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se
} }
/** /**
* @custom:semver 1.0.0 * @custom:semver 1.1.0
* *
* @param _bridge Address of the bridge on this network. * @param _bridge Address of the bridge on this network.
* @param _remoteChainId Chain ID where the remote token is deployed. * @param _remoteChainId Chain ID where the remote token is deployed.
...@@ -60,7 +60,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se ...@@ -60,7 +60,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se
address _remoteToken, address _remoteToken,
string memory _name, string memory _name,
string memory _symbol string memory _symbol
) ERC721(_name, _symbol) Semver(1, 0, 0) { ) ERC721(_name, _symbol) Semver(1, 1, 0) {
require(_bridge != address(0), "OptimismMintableERC721: bridge cannot be address(0)"); require(_bridge != address(0), "OptimismMintableERC721: bridge cannot be address(0)");
require(_remoteChainId != 0, "OptimismMintableERC721: remote chain id cannot be zero"); require(_remoteChainId != 0, "OptimismMintableERC721: remote chain id cannot be zero");
require( require(
...@@ -137,12 +137,8 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se ...@@ -137,12 +137,8 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se
override(ERC721Enumerable, IERC165) override(ERC721Enumerable, IERC165)
returns (bool) returns (bool)
{ {
bytes4 iface1 = type(IERC165).interfaceId; bytes4 iface = type(IOptimismMintableERC721).interfaceId;
bytes4 iface2 = type(IOptimismMintableERC721).interfaceId; return _interfaceId == iface || super.supportsInterface(_interfaceId);
return
_interfaceId == iface1 ||
_interfaceId == iface2 ||
super.supportsInterface(_interfaceId);
} }
/** /**
......
...@@ -38,7 +38,7 @@ contract OptimismMintableERC721Factory is Semver { ...@@ -38,7 +38,7 @@ contract OptimismMintableERC721Factory is Semver {
); );
/** /**
* @custom:semver 1.1.0 * @custom:semver 1.2.0
* @notice The semver MUST be bumped any time that there is a change in * @notice The semver MUST be bumped any time that there is a change in
* the OptimismMintableERC721 token contract since this contract * the OptimismMintableERC721 token contract since this contract
* is responsible for deploying OptimismMintableERC721 contracts. * is responsible for deploying OptimismMintableERC721 contracts.
...@@ -46,7 +46,7 @@ contract OptimismMintableERC721Factory is Semver { ...@@ -46,7 +46,7 @@ contract OptimismMintableERC721Factory is Semver {
* @param _bridge Address of the ERC721 bridge on this network. * @param _bridge Address of the ERC721 bridge on this network.
* @param _remoteChainId Chain ID for the remote network. * @param _remoteChainId Chain ID for the remote network.
*/ */
constructor(address _bridge, uint256 _remoteChainId) Semver(1, 1, 0) { constructor(address _bridge, uint256 _remoteChainId) Semver(1, 2, 0) {
BRIDGE = _bridge; BRIDGE = _bridge;
REMOTE_CHAIN_ID = _remoteChainId; REMOTE_CHAIN_ID = _remoteChainId;
} }
......
// 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; 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 { interface IGnosisSafe {
event AddedOwner(address owner); event AddedOwner(address owner);
event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event ApproveHash(bytes32 indexed approvedHash, address indexed owner);
......
// 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);
}
...@@ -245,7 +245,7 @@ const check = { ...@@ -245,7 +245,7 @@ const check = {
await assertSemver( await assertSemver(
L2CrossDomainMessenger, L2CrossDomainMessenger,
'L2CrossDomainMessenger', 'L2CrossDomainMessenger',
'1.3.0' '1.4.0'
) )
const xDomainMessageSenderSlot = await signer.provider.getStorageAt( const xDomainMessageSenderSlot = await signer.provider.getStorageAt(
...@@ -557,7 +557,7 @@ const check = { ...@@ -557,7 +557,7 @@ const check = {
await assertSemver( await assertSemver(
OptimismMintableERC721Factory, OptimismMintableERC721Factory,
'OptimismMintableERC721Factory', 'OptimismMintableERC721Factory',
'1.1.0' '1.2.0'
) )
const BRIDGE = await OptimismMintableERC721Factory.BRIDGE() const BRIDGE = await OptimismMintableERC721Factory.BRIDGE()
......
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