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

Merge branch 'develop' into inphi/fpp-detached

parents a98d456f c6d3e78c
---
'@eth-optimism/sdk': patch
---
Update the migrated withdrawal gas limit for non goerli networks
...@@ -364,6 +364,13 @@ jobs: ...@@ -364,6 +364,13 @@ jobs:
environment: environment:
FOUNDRY_PROFILE: ci FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run:
name: validate deploy configs
command: |
yarn validate-deploy-configs || echo "export DEPLOY_CONFIG_STATUS=1" >> "$BASH_ENV"
environment:
FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock
- run: - run:
name: storage snapshot name: storage snapshot
command: | command: |
...@@ -387,6 +394,10 @@ jobs: ...@@ -387,6 +394,10 @@ jobs:
FAILED=1 FAILED=1
echo "Gas snapshot failed, see job output for details." echo "Gas snapshot failed, see job output for details."
fi fi
if [[ "$DEPLOY_CONFIG_STATUS" -ne 0 ]]; then
FAILED=1
echo "Deploy configs invalid, see job output for details."
fi
if [[ "$STORAGE_SNAPSHOT_STATUS" -ne 0 ]]; then if [[ "$STORAGE_SNAPSHOT_STATUS" -ne 0 ]]; then
echo "Storage snapshot failed, see job output for details." echo "Storage snapshot failed, see job output for details."
FAILED=1 FAILED=1
......
This diff is collapsed.
This diff is collapsed.
...@@ -181,6 +181,7 @@ func main() { ...@@ -181,6 +181,7 @@ func main() {
migrationData, migrationData,
&config.L1CrossDomainMessengerProxy, &config.L1CrossDomainMessengerProxy,
config.L1ChainID, config.L1ChainID,
config.L2ChainID,
config.FinalSystemOwner, config.FinalSystemOwner,
config.ProxyAdminOwner, config.ProxyAdminOwner,
&derive.L1BlockInfo{ &derive.L1BlockInfo{
......
...@@ -223,6 +223,7 @@ func main() { ...@@ -223,6 +223,7 @@ func main() {
migrationData, migrationData,
&config.L1CrossDomainMessengerProxy, &config.L1CrossDomainMessengerProxy,
config.L1ChainID, config.L1ChainID,
config.L2ChainID,
config.FinalSystemOwner, config.FinalSystemOwner,
config.ProxyAdminOwner, config.ProxyAdminOwner,
&derive.L1BlockInfo{ &derive.L1BlockInfo{
......
...@@ -141,6 +141,10 @@ func main() { ...@@ -141,6 +141,10 @@ func main() {
if err != nil { if err != nil {
return err return err
} }
l2ChainID, err := clients.L2Client.ChainID(context.Background())
if err != nil {
return err
}
// create the set of withdrawals // create the set of withdrawals
wds, err := newWithdrawals(ctx, l1ChainID) wds, err := newWithdrawals(ctx, l1ChainID)
...@@ -212,7 +216,7 @@ func main() { ...@@ -212,7 +216,7 @@ func main() {
log.Info("Processing withdrawal", "index", i) log.Info("Processing withdrawal", "index", i)
// migrate the withdrawal // migrate the withdrawal
withdrawal, err := crossdomain.MigrateWithdrawal(wd, &l1xdmAddr) withdrawal, err := crossdomain.MigrateWithdrawal(wd, &l1xdmAddr, l2ChainID)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -20,7 +20,13 @@ var ( ...@@ -20,7 +20,13 @@ var (
) )
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB. // MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1CrossDomainMessenger *common.Address, noCheck bool) error { func MigrateWithdrawals(
withdrawals SafeFilteredWithdrawals,
db vm.StateDB,
l1CrossDomainMessenger *common.Address,
noCheck bool,
chainID *big.Int,
) error {
for i, legacy := range withdrawals { for i, legacy := range withdrawals {
legacySlot, err := legacy.StorageSlot() legacySlot, err := legacy.StorageSlot()
if err != nil { if err != nil {
...@@ -34,7 +40,7 @@ func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1Cr ...@@ -34,7 +40,7 @@ func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1Cr
} }
} }
withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger) withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger, chainID)
if err != nil { if err != nil {
return err return err
} }
...@@ -52,7 +58,11 @@ func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1Cr ...@@ -52,7 +58,11 @@ func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1Cr
// MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock // MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock
// style Withdrawal. // style Withdrawal.
func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *common.Address) (*Withdrawal, error) { func MigrateWithdrawal(
withdrawal *LegacyWithdrawal,
l1CrossDomainMessenger *common.Address,
chainID *big.Int,
) (*Withdrawal, error) {
// Attempt to parse the value // Attempt to parse the value
value, err := withdrawal.Value() value, err := withdrawal.Value()
if err != nil { if err != nil {
...@@ -83,7 +93,7 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com ...@@ -83,7 +93,7 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
return nil, fmt.Errorf("cannot abi encode relayMessage: %w", err) return nil, fmt.Errorf("cannot abi encode relayMessage: %w", err)
} }
gasLimit := MigrateWithdrawalGasLimit(data) gasLimit := MigrateWithdrawalGasLimit(data, chainID)
w := NewWithdrawal( w := NewWithdrawal(
versionedNonce, versionedNonce,
...@@ -97,13 +107,21 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com ...@@ -97,13 +107,21 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
} }
// MigrateWithdrawalGasLimit computes the gas limit for the migrated withdrawal. // MigrateWithdrawalGasLimit computes the gas limit for the migrated withdrawal.
func MigrateWithdrawalGasLimit(data []byte) uint64 { // The chain id is used to determine the overhead.
func MigrateWithdrawalGasLimit(data []byte, chainID *big.Int) uint64 {
// Compute the upper bound on the gas limit. This could be more // Compute the upper bound on the gas limit. This could be more
// accurate if individual 0 bytes and non zero bytes were accounted // accurate if individual 0 bytes and non zero bytes were accounted
// for. // for.
dataCost := uint64(len(data)) * params.TxDataNonZeroGasEIP2028 dataCost := uint64(len(data)) * params.TxDataNonZeroGasEIP2028
// Goerli has a lower gas limit than other chains.
overhead := uint64(200_000)
if chainID.Cmp(big.NewInt(420)) != 0 {
overhead = 1_000_000
}
// Set the outer gas limit. This cannot be zero // Set the outer gas limit. This cannot be zero
gasLimit := dataCost + 200_000 gasLimit := dataCost + overhead
// Cap the gas limit to be 25 million to prevent creating withdrawals // Cap the gas limit to be 25 million to prevent creating withdrawals
// that go over the block gas limit. // that go over the block gas limit.
if gasLimit > 25_000_000 { if gasLimit > 25_000_000 {
......
...@@ -12,7 +12,10 @@ import ( ...@@ -12,7 +12,10 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var big25Million = big.NewInt(25_000_000) var (
big25Million = big.NewInt(25_000_000)
bigGoerliChainID = big.NewInt(420)
)
func TestMigrateWithdrawal(t *testing.T) { func TestMigrateWithdrawal(t *testing.T) {
withdrawals := make([]*crossdomain.LegacyWithdrawal, 0) withdrawals := make([]*crossdomain.LegacyWithdrawal, 0)
...@@ -27,7 +30,7 @@ func TestMigrateWithdrawal(t *testing.T) { ...@@ -27,7 +30,7 @@ func TestMigrateWithdrawal(t *testing.T) {
l1CrossDomainMessenger := common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1") l1CrossDomainMessenger := common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1")
for i, legacy := range withdrawals { for i, legacy := range withdrawals {
t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger) withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger, bigGoerliChainID)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, withdrawal) require.NotNil(t, withdrawal)
...@@ -50,7 +53,7 @@ func TestMigrateWithdrawalGasLimitMax(t *testing.T) { ...@@ -50,7 +53,7 @@ func TestMigrateWithdrawalGasLimitMax(t *testing.T) {
data[i] = 0xff data[i] = 0xff
} }
result := crossdomain.MigrateWithdrawalGasLimit(data) result := crossdomain.MigrateWithdrawalGasLimit(data, bigGoerliChainID)
require.Equal(t, result, big25Million.Uint64()) require.Equal(t, result, big25Million.Uint64())
} }
...@@ -84,7 +87,7 @@ func TestMigrateWithdrawalGasLimit(t *testing.T) { ...@@ -84,7 +87,7 @@ func TestMigrateWithdrawalGasLimit(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
result := crossdomain.MigrateWithdrawalGasLimit(test.input) result := crossdomain.MigrateWithdrawalGasLimit(test.input, bigGoerliChainID)
require.Equal(t, test.output, result) require.Equal(t, test.output, result)
} }
} }
...@@ -101,6 +101,7 @@ func PostCheckMigratedDB( ...@@ -101,6 +101,7 @@ func PostCheckMigratedDB(
migrationData crossdomain.MigrationData, migrationData crossdomain.MigrationData,
l1XDM *common.Address, l1XDM *common.Address,
l1ChainID uint64, l1ChainID uint64,
l2ChainID uint64,
finalSystemOwner common.Address, finalSystemOwner common.Address,
proxyAdminOwner common.Address, proxyAdminOwner common.Address,
info *derive.L1BlockInfo, info *derive.L1BlockInfo,
...@@ -163,7 +164,7 @@ func PostCheckMigratedDB( ...@@ -163,7 +164,7 @@ func PostCheckMigratedDB(
} }
log.Info("checked legacy eth") log.Info("checked legacy eth")
if err := CheckWithdrawalsAfter(db, migrationData, l1XDM); err != nil { if err := CheckWithdrawalsAfter(db, migrationData, l1XDM, new(big.Int).SetUint64(l2ChainID)); err != nil {
return err return err
} }
log.Info("checked withdrawals") log.Info("checked withdrawals")
...@@ -557,7 +558,7 @@ func PostCheckL1Block(db *state.StateDB, info *derive.L1BlockInfo) error { ...@@ -557,7 +558,7 @@ func PostCheckL1Block(db *state.StateDB, info *derive.L1BlockInfo) error {
return nil return nil
} }
func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address) error { func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address, l2ChainID *big.Int) error {
wds, invalidMessages, err := data.ToWithdrawals() wds, invalidMessages, err := data.ToWithdrawals()
if err != nil { if err != nil {
return err return err
...@@ -570,7 +571,7 @@ func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1 ...@@ -570,7 +571,7 @@ func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1
wdsByOldSlot := make(map[common.Hash]*crossdomain.LegacyWithdrawal) wdsByOldSlot := make(map[common.Hash]*crossdomain.LegacyWithdrawal)
invalidMessagesByOldSlot := make(map[common.Hash]crossdomain.InvalidMessage) invalidMessagesByOldSlot := make(map[common.Hash]crossdomain.InvalidMessage)
for _, wd := range wds { for _, wd := range wds {
migrated, err := crossdomain.MigrateWithdrawal(wd, l1CrossDomainMessenger) migrated, err := crossdomain.MigrateWithdrawal(wd, l1CrossDomainMessenger, l2ChainID)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -186,7 +186,8 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m ...@@ -186,7 +186,8 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
// the LegacyMessagePasser contract. Here we operate on the list of withdrawals that we // the LegacyMessagePasser contract. Here we operate on the list of withdrawals that we
// previously filtered and verified. // previously filtered and verified.
log.Info("Starting to migrate withdrawals", "no-check", noCheck) log.Info("Starting to migrate withdrawals", "no-check", noCheck)
err = crossdomain.MigrateWithdrawals(filteredWithdrawals, db, &config.L1CrossDomainMessengerProxy, noCheck) l2ChainID := new(big.Int).SetUint64(config.L2ChainID)
err = crossdomain.MigrateWithdrawals(filteredWithdrawals, db, &config.L1CrossDomainMessengerProxy, noCheck, l2ChainID)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot migrate withdrawals: %w", err) return nil, fmt.Errorf("cannot migrate withdrawals: %w", err)
} }
......
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.2.0 * @custom:semver 1.3.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, 2, 0) Semver(1, 3, 0)
CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER)
{ {
PORTAL = _portal; PORTAL = _portal;
......
...@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
} }
/** /**
* @custom:semver 1.3.1 * @custom:semver 1.4.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, 3, 1) { ) Semver(1, 4, 0) {
L2_ORACLE = _l2Oracle; L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian; GUARDIAN = _guardian;
SYSTEM_CONFIG = _config; SYSTEM_CONFIG = _config;
...@@ -388,11 +388,9 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -388,11 +388,9 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
// SafeCall.callWithMinGas to ensure two key properties // SafeCall.callWithMinGas to ensure two key properties
// 1. Target contracts cannot force this call to run out of gas by returning a very large // 1. Target contracts cannot force this call to run out of gas by returning a very large
// amount of data (and this is OK because we don't care about the returndata here). // amount of data (and this is OK because we don't care about the returndata here).
// 2. The amount of gas provided to the call to the target contract is at least the gas // 2. The amount of gas provided to the execution context of the target is at least the
// limit specified by the user. If there is not enough gas in the callframe to // gas limit specified by the user. If there is not enough gas in the current context
// accomplish this, `callWithMinGas` will revert. // to accomplish this, `callWithMinGas` will revert.
// Additionally, if there is not enough gas remaining to complete the execution after the
// call returns, this function will revert.
bool success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data); bool success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
// Reset the l2Sender back to the default value. // Reset the l2Sender back to the default value.
......
...@@ -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.2.0 * @custom:semver 1.3.0
* *
* @param _l1CrossDomainMessenger Address of the L1CrossDomainMessenger contract. * @param _l1CrossDomainMessenger Address of the L1CrossDomainMessenger contract.
*/ */
constructor(address _l1CrossDomainMessenger) constructor(address _l1CrossDomainMessenger)
Semver(1, 2, 0) Semver(1, 3, 0)
CrossDomainMessenger(_l1CrossDomainMessenger) CrossDomainMessenger(_l1CrossDomainMessenger)
{ {
initialize(); initialize();
......
...@@ -35,6 +35,41 @@ library SafeCall { ...@@ -35,6 +35,41 @@ library SafeCall {
return _success; return _success;
} }
/**
* @notice Helper function to determine if there is sufficient gas remaining within the context
* to guarantee that the minimum gas requirement for a call will be met as well as
* optionally reserving a specified amount of gas for after the call has concluded.
* @param _minGas The minimum amount of gas that may be passed to the target context.
* @param _reservedGas Optional amount of gas to reserve for the caller after the execution
* of the target context.
* @return `true` if there is enough gas remaining to safely supply `_minGas` to the target
* context as well as reserve `_reservedGas` for the caller after the execution of
* the target context.
* @dev !!!!! FOOTGUN ALERT !!!!!
* 1.) The 40_000 base buffer is to account for the worst case of the dynamic cost of the
* `CALL` opcode's `address_access_cost`, `positive_value_cost`, and
* `value_to_empty_account_cost` factors with an added buffer of 5,700 gas. It is
* still possible to self-rekt by initiating a withdrawal with a minimum gas limit
* that does not account for the `memory_expansion_cost` & `code_execution_cost`
* factors of the dynamic cost of the `CALL` opcode.
* 2.) This function should *directly* precede the external call if possible. There is an
* added buffer to account for gas consumed between this check and the call, but it
* is only 5,700 gas.
* 3.) Because EIP-150 ensures that a maximum of 63/64ths of the remaining gas in the call
* frame may be passed to a subcontext, we need to ensure that the gas will not be
* truncated.
* 4.) Use wisely. This function is not a silver bullet.
*/
function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) {
bool _hasMinGas;
assembly {
_hasMinGas := iszero(
lt(gas(), add(div(mul(_minGas, 64), 63), add(40000, _reservedGas)))
)
}
return _hasMinGas;
}
/** /**
* @notice Perform a low level call without copying any returndata. This function * @notice Perform a low level call without copying any returndata. This function
* will revert if the call cannot be performed with the specified minimum * will revert if the call cannot be performed with the specified minimum
...@@ -52,16 +87,10 @@ library SafeCall { ...@@ -52,16 +87,10 @@ library SafeCall {
bytes memory _calldata bytes memory _calldata
) internal returns (bool) { ) internal returns (bool) {
bool _success; bool _success;
bool _hasMinGas = hasMinGas(_minGas, 0);
assembly { assembly {
// Assertion: gasleft() >= ((_minGas + 200) * 64) / 63 // Assertion: gasleft() >= (_minGas * 64) / 63 + 40_000
// if iszero(_hasMinGas) {
// Because EIP-150 ensures that, a maximum of 63/64ths of the remaining gas in the call
// frame may be passed to a subcontext, we need to ensure that the gas will not be
// truncated to hold this function's invariant: "If a call is performed by
// `callWithMinGas`, it must receive at least the specified minimum gas limit." In
// addition, exactly 51 gas is consumed between the below `GAS` opcode and the `CALL`
// opcode, so it is factored in with some extra room for error.
if lt(gas(), div(mul(64, add(_minGas, 200)), 63)) {
// Store the "Error(string)" selector in scratch space. // Store the "Error(string)" selector in scratch space.
mstore(0, 0x08c379a0) mstore(0, 0x08c379a0)
// Store the pointer to the string length in scratch space. // Store the pointer to the string length in scratch space.
...@@ -82,13 +111,11 @@ library SafeCall { ...@@ -82,13 +111,11 @@ library SafeCall {
revert(28, 100) revert(28, 100)
} }
// The call will be supplied at least (((_minGas + 200) * 64) / 63) - 49 gas due to the // The call will be supplied at least ((_minGas * 64) / 63) gas due to the
// above assertion. This ensures that, in all circumstances, the call will // above assertion. This ensures that, in all circumstances (except for when the
// receive at least the minimum amount of gas specified. // `_minGas` does not account for the `memory_expansion_cost` and `code_execution_cost`
// We can prove this property by solving the inequalities: // factors of the dynamic cost of the `CALL` opcode), the call will receive at least
// ((((_minGas + 200) * 64) / 63) - 49) >= _minGas // the minimum amount of gas specified.
// ((((_minGas + 200) * 64) / 63) - 51) * (63 / 64) >= _minGas
// Both inequalities hold true for all possible values of `_minGas`.
_success := call( _success := call(
gas(), // gas gas(), // gas
_target, // recipient _target, // recipient
......
...@@ -4,7 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { CommonTest } from "./CommonTest.t.sol"; import { CommonTest } from "./CommonTest.t.sol";
import { SafeCall } from "../libraries/SafeCall.sol"; import { SafeCall } from "../libraries/SafeCall.sol";
contract SafeCall_call_Test is CommonTest { contract SafeCall_Test is CommonTest {
function testFuzz_call_succeeds( function testFuzz_call_succeeds(
address from, address from,
address to, address to,
...@@ -63,6 +63,8 @@ contract SafeCall_call_Test is CommonTest { ...@@ -63,6 +63,8 @@ contract SafeCall_call_Test is CommonTest {
vm.assume(to != address(0x000000000000000000636F6e736F6c652e6c6f67)); vm.assume(to != address(0x000000000000000000636F6e736F6c652e6c6f67));
// don't call the create2 deployer // don't call the create2 deployer
vm.assume(to != address(0x4e59b44847b379578588920cA78FbF26c0B4956C)); vm.assume(to != address(0x4e59b44847b379578588920cA78FbF26c0B4956C));
// don't call the FFIInterface
vm.assume(to != address(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f));
assertEq(from.balance, 0, "from balance is 0"); assertEq(from.balance, 0, "from balance is 0");
vm.deal(from, value); vm.deal(from, value);
...@@ -89,12 +91,12 @@ contract SafeCall_call_Test is CommonTest { ...@@ -89,12 +91,12 @@ contract SafeCall_call_Test is CommonTest {
function test_callWithMinGas_noLeakageLow_succeeds() external { function test_callWithMinGas_noLeakageLow_succeeds() external {
SimpleSafeCaller caller = new SimpleSafeCaller(); SimpleSafeCaller caller = new SimpleSafeCaller();
for (uint64 i = 5000; i < 50_000; i++) { for (uint64 i = 40_000; i < 100_000; i++) {
uint256 snapshot = vm.snapshot(); uint256 snapshot = vm.snapshot();
// 26,071 is the exact amount of gas required to make the safe call // 65_903 is the exact amount of gas required to make the safe call
// successfully. // successfully.
if (i < 26_071) { if (i < 65_903) {
assertFalse(caller.makeSafeCall(i, 25_000)); assertFalse(caller.makeSafeCall(i, 25_000));
} else { } else {
vm.expectCallMinGas( vm.expectCallMinGas(
...@@ -116,9 +118,9 @@ contract SafeCall_call_Test is CommonTest { ...@@ -116,9 +118,9 @@ contract SafeCall_call_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,238,769 is the exact amount of gas required to make the safe call // 15_278_602 is the exact amount of gas required to make the safe call
// successfully. // successfully.
if (i < 15_238_769) { if (i < 15_278_602) {
assertFalse(caller.makeSafeCall(i, 15_000_000)); assertFalse(caller.makeSafeCall(i, 15_000_000));
} else { } else {
vm.expectCallMinGas( vm.expectCallMinGas(
......
...@@ -7,6 +7,7 @@ import { L1CrossDomainMessenger } from "../../L1/L1CrossDomainMessenger.sol"; ...@@ -7,6 +7,7 @@ import { L1CrossDomainMessenger } from "../../L1/L1CrossDomainMessenger.sol";
import { Messenger_Initializer } from "../CommonTest.t.sol"; import { Messenger_Initializer } from "../CommonTest.t.sol";
import { Types } from "../../libraries/Types.sol"; import { Types } from "../../libraries/Types.sol";
import { Predeploys } from "../../libraries/Predeploys.sol"; import { Predeploys } from "../../libraries/Predeploys.sol";
import { Constants } from "../../libraries/Constants.sol";
import { Encoding } from "../../libraries/Encoding.sol"; import { Encoding } from "../../libraries/Encoding.sol";
import { Hashing } from "../../libraries/Hashing.sol"; import { Hashing } from "../../libraries/Hashing.sol";
...@@ -21,38 +22,53 @@ contract RelayActor is StdUtils { ...@@ -21,38 +22,53 @@ contract RelayActor is StdUtils {
OptimismPortal op; OptimismPortal op;
L1CrossDomainMessenger xdm; L1CrossDomainMessenger xdm;
Vm vm; Vm vm;
bool doFail;
constructor( constructor(
OptimismPortal _op, OptimismPortal _op,
L1CrossDomainMessenger _xdm, L1CrossDomainMessenger _xdm,
Vm _vm Vm _vm,
bool _doFail
) { ) {
op = _op; op = _op;
xdm = _xdm; xdm = _xdm;
vm = _vm; vm = _vm;
doFail = _doFail;
} }
/** /**
* Relays a message to the `L1CrossDomainMessenger` with a random `version`, `_minGasLimit` * Relays a message to the `L1CrossDomainMessenger` with a random `version`, and `_message`.
* and `_message`.
*/ */
function relay( function relay(
uint16 _version, uint8 _version,
uint32 _minGasLimit, uint8 _value,
bytes memory _message bytes memory _message
) external { ) external {
address target = address(0x04); // ID precompile address target = address(0x04); // ID precompile
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
// Set the minimum gas limit to the cost of the identity precompile's execution for
// the given message.
// ID Precompile cost can be determined by calculating: 15 + 3 * data_word_length
uint32 minGasLimit = uint32(15 + 3 * ((_message.length + 31) / 32));
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger. // set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(op), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); vm.store(address(op), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Restrict `_minGasLimit` to a number in the range of the block gas limit.
_minGasLimit = uint32(bound(_minGasLimit, 0, block.gaslimit));
// Restrict version to the range of [0, 1] // Restrict version to the range of [0, 1]
_version = _version % 2; _version = _version % 2;
// Restrict the value to the range of [0, 1]
// This is just so we get variance of calls with and without value. The ID precompile
// will not reject value being sent to it.
_value = _value % 2;
// If the message should succeed, supply it `baseGas`. If not, supply it an amount of
// gas that is too low to complete the call.
uint256 gas = doFail
? bound(minGasLimit, 60_000, 80_000)
: xdm.baseGas(_message, minGasLimit);
// Compute the cross domain message hash and store it in `hashes`. // Compute the cross domain message hash and store it in `hashes`.
// The `relayMessage` function will always encode the message as a version 1 // The `relayMessage` function will always encode the message as a version 1
// message after checking that the V0 hash has not already been relayed. // message after checking that the V0 hash has not already been relayed.
...@@ -60,22 +76,29 @@ contract RelayActor is StdUtils { ...@@ -60,22 +76,29 @@ contract RelayActor is StdUtils {
Encoding.encodeVersionedNonce(0, _version), Encoding.encodeVersionedNonce(0, _version),
sender, sender,
target, target,
0, // value _value,
_minGasLimit, minGasLimit,
_message _message
); );
hashes.push(_hash);
numHashes += 1;
// Make sure we've got a fresh message.
vm.assume(xdm.successfulMessages(_hash) == false && xdm.failedMessages(_hash) == false);
// Act as the optimism portal and call `relayMessage` on the `L1CrossDomainMessenger` with // Act as the optimism portal and call `relayMessage` on the `L1CrossDomainMessenger` with
// the outer min gas limit. // the outer min gas limit.
vm.startPrank(address(op)); vm.startPrank(address(op));
vm.expectCall(target, _message); if (!doFail) {
vm.expectCallMinGas(address(0x04), _value, minGasLimit, _message);
}
try try
xdm.relayMessage{ gas: xdm.baseGas(_message, _minGasLimit) }( xdm.relayMessage{ gas: gas, value: _value }(
Encoding.encodeVersionedNonce(0, _version), Encoding.encodeVersionedNonce(0, _version),
sender, sender,
target, target,
0, // value _value,
_minGasLimit, minGasLimit,
_message _message
) )
{} catch { {} catch {
...@@ -85,34 +108,81 @@ contract RelayActor is StdUtils { ...@@ -85,34 +108,81 @@ contract RelayActor is StdUtils {
reverted = true; reverted = true;
} }
vm.stopPrank(); vm.stopPrank();
hashes.push(_hash);
numHashes += 1;
} }
} }
contract XDM_MinGasLimits is Messenger_Initializer { contract XDM_MinGasLimits is Messenger_Initializer {
RelayActor actor; RelayActor actor;
function setUp() public virtual override { function init(bool doFail) public virtual {
// Set up the `L1CrossDomainMessenger` and `OptimismPortal` contracts. // Set up the `L1CrossDomainMessenger` and `OptimismPortal` contracts.
super.setUp(); super.setUp();
// Deploy a relay actor // Deploy a relay actor
actor = new RelayActor(op, L1Messenger, vm); actor = new RelayActor(op, L1Messenger, vm, doFail);
// Give the portal some ether to send to `relayMessage`
vm.deal(address(op), type(uint128).max);
// Target the `RelayActor` contract // Target the `RelayActor` contract
targetContract(address(actor)); targetContract(address(actor));
// Don't allow the estimation address to be the sender
excludeSender(Constants.ESTIMATION_ADDRESS);
// Target the actor's `relay` function // Target the actor's `relay` function
bytes4[] memory selectors = new bytes4[](1); bytes4[] memory selectors = new bytes4[](1);
selectors[0] = actor.relay.selector; selectors[0] = actor.relay.selector;
targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors }));
} }
}
contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits {
function setUp() public override {
// Don't fail
super.init(false);
}
/**
* @custom:invariant A call to `relayMessage` should succeed if at least the minimum gas limit
* can be supplied to the target context, there is enough gas to complete
* execution of `relayMessage` after the target context's execution is
* finished, and the target context did not revert.
*
* There are two minimum gas limits here:
*
* - The outer min gas limit is for the call from the `OptimismPortal` to the
* `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function
* with the `message` and inner limit.
*
* - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target
* contract.
*/
function invariant_minGasLimits() external {
uint256 length = actor.numHashes();
for (uint256 i = 0; i < length; ++i) {
bytes32 hash = actor.hashes(i);
// The message hash is set in the successfulMessages mapping
assertTrue(L1Messenger.successfulMessages(hash));
// The message hash is not set in the failedMessages mapping
assertFalse(L1Messenger.failedMessages(hash));
}
assertFalse(actor.reverted());
}
}
contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits {
function setUp() public override {
// Do fail
super.init(true);
}
/** /**
* @custom:invariant A call to `relayMessage` should never revert if at least the proper minimum * @custom:invariant A call to `relayMessage` should assign the message hash to the
* gas limits are supplied. * `failedMessages` mapping if not enough gas is supplied to forward
* `minGasLimit` to the target context or if there is not enough gas to
* complete execution of `relayMessage` after the target context's execution
* is finished.
* *
* There are two minimum gas limits here: * There are two minimum gas limits here:
* *
...@@ -123,19 +193,15 @@ contract XDM_MinGasLimits is Messenger_Initializer { ...@@ -123,19 +193,15 @@ contract XDM_MinGasLimits is Messenger_Initializer {
* - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target * - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target
* contract. * contract.
*/ */
function invariant_minGasLimits() public { function invariant_minGasLimits() external {
/////////////////////////////////////////////////////////////////// uint256 length = actor.numHashes();
// ~ DEV ~ // for (uint256 i = 0; i < length; ++i) {
// This test is temporarily disabled, it is being fixed in #5470 // bytes32 hash = actor.hashes(i);
/////////////////////////////////////////////////////////////////// // The message hash is not set in the successfulMessages mapping
// uint256 length = actor.numHashes(); assertFalse(L1Messenger.successfulMessages(hash));
// for (uint256 i = 0; i < length; ++i) { // The message hash is set in the failedMessages mapping
// bytes32 hash = actor.hashes(i); assertTrue(L1Messenger.failedMessages(hash));
// // the message hash is in the successfulMessages mapping }
// assertTrue(L1Messenger.successfulMessages(hash)); assertFalse(actor.reverted());
// // it is not in the received messages mapping
// assertFalse(L1Messenger.failedMessages(hash));
// }
// assertFalse(actor.reverted());
} }
} }
...@@ -18,6 +18,9 @@ contract SafeCall_Succeeds_Invariants is Test { ...@@ -18,6 +18,9 @@ contract SafeCall_Succeeds_Invariants is Test {
// Target the safe caller actor. // Target the safe caller actor.
targetContract(address(actor)); targetContract(address(actor));
// Give the actor some ETH to work with
vm.deal(address(actor), type(uint128).max);
} }
/** /**
...@@ -31,8 +34,8 @@ contract SafeCall_Succeeds_Invariants is Test { ...@@ -31,8 +34,8 @@ contract SafeCall_Succeeds_Invariants is Test {
assertEq(actor.numCalls(), 0, "no failed calls allowed"); assertEq(actor.numCalls(), 0, "no failed calls allowed");
} }
function performSafeCallMinGas(uint64 minGas) external { function performSafeCallMinGas(address to, uint64 minGas) external payable {
SafeCall.callWithMinGas(address(0), minGas, 0, hex""); SafeCall.callWithMinGas(to, minGas, msg.value, hex"");
} }
} }
...@@ -48,6 +51,9 @@ contract SafeCall_Fails_Invariants is Test { ...@@ -48,6 +51,9 @@ contract SafeCall_Fails_Invariants is Test {
// Target the safe caller actor. // Target the safe caller actor.
targetContract(address(actor)); targetContract(address(actor));
// Give the actor some ETH to work with
vm.deal(address(actor), type(uint128).max);
} }
/** /**
...@@ -62,8 +68,8 @@ contract SafeCall_Fails_Invariants is Test { ...@@ -62,8 +68,8 @@ contract SafeCall_Fails_Invariants is Test {
assertEq(actor.numCalls(), 0, "no successful calls allowed"); assertEq(actor.numCalls(), 0, "no successful calls allowed");
} }
function performSafeCallMinGas(uint64 minGas) external { function performSafeCallMinGas(address to, uint64 minGas) external payable {
SafeCall.callWithMinGas(address(0), minGas, 0, hex""); SafeCall.callWithMinGas(to, minGas, msg.value, hex"");
} }
} }
...@@ -78,25 +84,39 @@ contract SafeCaller_Actor is StdUtils { ...@@ -78,25 +84,39 @@ contract SafeCaller_Actor is StdUtils {
FAILS = _fails; FAILS = _fails;
} }
function performSafeCallMinGas(uint64 gas, uint64 minGas) external { function performSafeCallMinGas(
uint64 gas,
uint64 minGas,
address to,
uint8 value
) external {
// Only send to EOAs - we exclude the console as it has no code but reverts when called
// with a selector that doesn't exist due to the foundry hook.
vm.assume(to.code.length == 0 && to != 0x000000000000000000636F6e736F6c652e6c6f67);
// Bound the minimum gas amount to [2500, type(uint48).max]
minGas = uint64(bound(minGas, 2500, type(uint48).max));
if (FAILS) { if (FAILS) {
// Bound the minimum gas amount to [2500, type(uint48).max] // Bound the gas passed to [minGas, ((minGas * 64) / 63)]
minGas = uint64(bound(minGas, 2500, type(uint48).max)); gas = uint64(bound(gas, minGas, (minGas * 64) / 63));
// Bound the gas passed to [minGas, (((minGas + 200) * 64) / 63)]
gas = uint64(bound(gas, minGas, (((minGas + 200) * 64) / 63)));
} else { } else {
// Bound the minimum gas amount to [2500, type(uint48).max] // Bound the gas passed to
minGas = uint64(bound(minGas, 2500, type(uint48).max)); // [((minGas * 64) / 63) + 40_000 + 1000, type(uint64).max]
// Bound the gas passed to [(((minGas + 200) * 64) / 63) + 500, type(uint64).max] // The extra 1000 gas is to account for the gas used by the `SafeCall.call` call
gas = uint64(bound(gas, (((minGas + 200) * 64) / 63) + 500, type(uint64).max)); // itself.
gas = uint64(bound(gas, ((minGas * 64) / 63) + 40_000 + 1000, type(uint64).max));
} }
vm.expectCallMinGas(address(0x00), 0, minGas, hex""); vm.expectCallMinGas(to, value, minGas, hex"");
bool success = SafeCall.call( bool success = SafeCall.call(
msg.sender, msg.sender,
gas, gas,
0, value,
abi.encodeWithSelector(0x2ae57a41, minGas) abi.encodeWithSelector(
SafeCall_Succeeds_Invariants.performSafeCallMinGas.selector,
to,
minGas
)
); );
if (success && FAILS) numCalls++; if (success && FAILS) numCalls++;
......
...@@ -124,23 +124,39 @@ abstract contract CrossDomainMessenger is ...@@ -124,23 +124,39 @@ abstract contract CrossDomainMessenger is
/** /**
* @notice Constant overhead added to the base gas for a message. * @notice Constant overhead added to the base gas for a message.
*/ */
uint64 public constant MIN_GAS_CONSTANT_OVERHEAD = 200_000; uint64 public constant RELAY_CONSTANT_OVERHEAD = 200_000;
/** /**
* @notice Numerator for dynamic overhead added to the base gas for a message. * @notice Numerator for dynamic overhead added to the base gas for a message.
*/ */
uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR = 1016; uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR = 64;
/** /**
* @notice Denominator for dynamic overhead added to the base gas for a message. * @notice Denominator for dynamic overhead added to the base gas for a message.
*/ */
uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = 1000; uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = 63;
/** /**
* @notice Extra gas added to base gas for each byte of calldata in a message. * @notice Extra gas added to base gas for each byte of calldata in a message.
*/ */
uint64 public constant MIN_GAS_CALLDATA_OVERHEAD = 16; uint64 public constant MIN_GAS_CALLDATA_OVERHEAD = 16;
/**
* @notice Gas reserved for performing the external call in `relayMessage`.
*/
uint64 public constant RELAY_CALL_OVERHEAD = 40_000;
/**
* @notice Gas reserved for finalizing the execution of `relayMessage` after the safe call.
*/
uint64 public constant RELAY_RESERVED_GAS = 40_000;
/**
* @notice Gas reserved for the execution between the `hasMinGas` check and the external
* call in `relayMessage`.
*/
uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
/** /**
* @notice Address of the paired CrossDomainMessenger contract on the other chain. * @notice Address of the paired CrossDomainMessenger contract on the other chain.
*/ */
...@@ -345,17 +361,36 @@ abstract contract CrossDomainMessenger is ...@@ -345,17 +361,36 @@ abstract contract CrossDomainMessenger is
"CrossDomainMessenger: message has already been relayed" "CrossDomainMessenger: message has already been relayed"
); );
// If there is not enough gas left to perform the external call and finish the execution,
// return early and assign the message to the failedMessages mapping.
// We are asserting that we have enough gas to:
// 1. Call the target contract (_minGasLimit + RELAY_CALL_OVERHEAD + RELAY_GAS_CHECK_BUFFER)
// 1.a. The RELAY_CALL_OVERHEAD is included in `hasMinGas`.
// 2. Finish the execution after the external call (RELAY_RESERVED_GAS).
//
// If `xDomainMsgSender` is not the default L2 sender, this function // If `xDomainMsgSender` is not the default L2 sender, this function
// is being re-entered. This marks the message as failed to allow it // is being re-entered. This marks the message as failed to allow it to be replayed.
// to be replayed. if (
if (xDomainMsgSender != Constants.DEFAULT_L2_SENDER) { !SafeCall.hasMinGas(_minGasLimit, RELAY_RESERVED_GAS + RELAY_GAS_CHECK_BUFFER) ||
xDomainMsgSender != Constants.DEFAULT_L2_SENDER
) {
failedMessages[versionedHash] = true; failedMessages[versionedHash] = true;
emit FailedRelayedMessage(versionedHash); emit FailedRelayedMessage(versionedHash);
// Revert in this case if the transaction was triggered by the estimation address. This
// should only be possible during gas estimation or we have bigger problems. Reverting
// here will make the behavior of gas estimation change such that the gas limit
// computed will be the amount required to relay the message, even if that amount is
// greater than the minimum gas limit specified by the user.
if (tx.origin == Constants.ESTIMATION_ADDRESS) {
revert("CrossDomainMessenger: failed to relay message");
}
return; return;
} }
xDomainMsgSender = _sender; xDomainMsgSender = _sender;
bool success = SafeCall.callWithMinGas(_target, _minGasLimit, _value, _message); bool success = SafeCall.call(_target, gasleft() - RELAY_RESERVED_GAS, _value, _message);
xDomainMsgSender = Constants.DEFAULT_L2_SENDER; xDomainMsgSender = Constants.DEFAULT_L2_SENDER;
if (success) { if (success) {
...@@ -415,17 +450,23 @@ abstract contract CrossDomainMessenger is ...@@ -415,17 +450,23 @@ abstract contract CrossDomainMessenger is
* @return Amount of gas required to guarantee message receipt. * @return Amount of gas required to guarantee message receipt.
*/ */
function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) { function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) {
// We peform the following math on uint64s to avoid overflow errors. Multiplying the
// by MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR would otherwise limit the _minGasLimit to
// type(uint32).max / MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR ~= 4.2m.
return return
// Dynamic overhead // Constant overhead
((uint64(_minGasLimit) * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / RELAY_CONSTANT_OVERHEAD +
MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) +
// Calldata overhead // Calldata overhead
(uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) +
// Constant overhead // Dynamic overhead (EIP-150)
MIN_GAS_CONSTANT_OVERHEAD; ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) /
MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) +
// Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas
// factors. (Conservative)
RELAY_CALL_OVERHEAD +
// Relay reserved gas (to ensure execution of `relayMessage` completes after the
// subcontext finishes executing) (Conservative)
RELAY_RESERVED_GAS +
// Gas reserved for the execution between the `hasMinGas` check and the `CALL`
// opcode. (Conservative)
RELAY_GAS_CHECK_BUFFER;
} }
/** /**
......
...@@ -3,40 +3,29 @@ ...@@ -3,40 +3,29 @@
"portalGuardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "portalGuardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"controller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "controller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"proxyAdminOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "proxyAdminOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"l1StartingBlockTag": "earliest", "l1StartingBlockTag": "earliest",
"l1ChainID": 900, "l1ChainID": 900,
"l2ChainID": 901, "l2ChainID": 901,
"l2BlockTime": 2, "l2BlockTime": 2,
"maxSequencerDrift": 300, "maxSequencerDrift": 300,
"sequencerWindowSize": 15, "sequencerWindowSize": 15,
"channelTimeout": 40, "channelTimeout": 40,
"p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"batchInboxAddress": "0xff00000000000000000000000000000000000000", "batchInboxAddress": "0xff00000000000000000000000000000000000000",
"batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"l2OutputOracleSubmissionInterval": 6, "l2OutputOracleSubmissionInterval": 6,
"l2OutputOracleStartingTimestamp": 0, "l2OutputOracleStartingTimestamp": 0,
"l2OutputOracleStartingBlockNumber": 0, "l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleChallenger": "0x6925B8704Ff96DEe942623d6FB5e946EF5884b63", "l2OutputOracleChallenger": "0x6925B8704Ff96DEe942623d6FB5e946EF5884b63",
"l2GenesisBlockBaseFeePerGas": "0x3B9ACA00", "l2GenesisBlockBaseFeePerGas": "0x3B9ACA00",
"l2GenesisBlockGasLimit": "0x17D7840", "l2GenesisBlockGasLimit": "0x17D7840",
"baseFeeVaultRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "baseFeeVaultRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"l1FeeVaultRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "l1FeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788",
"sequencerFeeVaultRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"l1FeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788",
"sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a",
"finalizationPeriodSeconds": 2, "finalizationPeriodSeconds": 2,
"numDeployConfirmations": 1 "numDeployConfirmations": 1
} }
\ No newline at end of file
# `CrossDomainMessenger` Invariants # `CrossDomainMessenger` Invariants
## A call to `relayMessage` should never revert if at least the proper minimum gas limits are supplied. ## A call to `relayMessage` should succeed if at least the minimum gas limit can be supplied to the target context, there is enough gas to complete execution of `relayMessage` after the target context's execution is finished, and the target context did not revert.
**Test:** [`CrossDomainMessenger.t.sol#L126`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L126) **Test:** [`CrossDomainMessenger.t.sol#L161`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L161)
There are two minimum gas limits here:
- The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit.
- The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target contract.
## A call to `relayMessage` should assign the message hash to the `failedMessages` mapping if not enough gas is supplied to forward `minGasLimit` to the target context or if there is not enough gas to complete execution of `relayMessage` after the target context's execution is finished.
**Test:** [`CrossDomainMessenger.t.sol#L196`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L196)
There are two minimum gas limits here: There are two minimum gas limits here:
- The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit. - The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit.
......
# `SafeCall` Invariants # `SafeCall` Invariants
## If `callWithMinGas` performs a call, then it must always provide at least the specified minimum gas limit to the subcontext. ## If `callWithMinGas` performs a call, then it must always provide at least the specified minimum gas limit to the subcontext.
**Test:** [`SafeCall.t.sol#L30`](../contracts/test/invariants/SafeCall.t.sol#L30) **Test:** [`SafeCall.t.sol#L33`](../contracts/test/invariants/SafeCall.t.sol#L33)
If the check for remaining gas in `SafeCall.callWithMinGas` passes, the subcontext of the call below it must be provided at least `minGas` gas. If the check for remaining gas in `SafeCall.callWithMinGas` passes, the subcontext of the call below it must be provided at least `minGas` gas.
## `callWithMinGas` reverts if there is not enough gas to pass to the subcontext. ## `callWithMinGas` reverts if there is not enough gas to pass to the subcontext.
**Test:** [`SafeCall.t.sol#L61`](../contracts/test/invariants/SafeCall.t.sol#L61) **Test:** [`SafeCall.t.sol#L67`](../contracts/test/invariants/SafeCall.t.sol#L67)
If there is not enough gas in the callframe to ensure that `callWithMinGas` can provide the specified minimum gas limit to the subcontext of the call, then `callWithMinGas` must revert. If there is not enough gas in the callframe to ensure that `callWithMinGas` can provide the specified minimum gas limit to the subcontext of the call, then `callWithMinGas` must revert.
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
"coverage:lcov": "yarn build:differential && yarn build:fuzz && forge coverage --report lcov", "coverage:lcov": "yarn build:differential && yarn build:fuzz && forge coverage --report lcov",
"gas-snapshot": "yarn build:differential && yarn build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'", "gas-snapshot": "yarn build:differential && yarn build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'",
"storage-snapshot": "./scripts/storage-snapshot.sh", "storage-snapshot": "./scripts/storage-snapshot.sh",
"validate-deploy-configs": "hardhat compile && hardhat generate-deploy-config && ./scripts/validate-deploy-configs.sh",
"validate-spacers": "hardhat compile && hardhat validate-spacers", "validate-spacers": "hardhat compile && hardhat validate-spacers",
"slither": "./scripts/slither.sh", "slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh", "slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
......
#!/usr/bin/env bash
set -e
dir=$(dirname "$0")
echo "Validating deployment configurations...\n"
for config in $dir/../deploy-config/*.json
do
echo "Found file: $config\n"
git diff --exit-code $config
done
echo "Deployment configs in $dir/../deploy-config validated!\n"
...@@ -245,7 +245,7 @@ const check = { ...@@ -245,7 +245,7 @@ const check = {
await assertSemver( await assertSemver(
L2CrossDomainMessenger, L2CrossDomainMessenger,
'L2CrossDomainMessenger', 'L2CrossDomainMessenger',
'1.2.0' '1.3.0'
) )
const xDomainMessageSenderSlot = await signer.provider.getStorageAt( const xDomainMessageSenderSlot = await signer.provider.getStorageAt(
...@@ -274,9 +274,9 @@ const check = { ...@@ -274,9 +274,9 @@ const check = {
const MIN_GAS_CALLDATA_OVERHEAD = const MIN_GAS_CALLDATA_OVERHEAD =
await L2CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD() await L2CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()
console.log(` - MIN_GAS_CALLDATA_OVERHEAD: ${MIN_GAS_CALLDATA_OVERHEAD}`) console.log(` - MIN_GAS_CALLDATA_OVERHEAD: ${MIN_GAS_CALLDATA_OVERHEAD}`)
const MIN_GAS_CONSTANT_OVERHEAD = const RELAY_CONSTANT_OVERHEAD =
await L2CrossDomainMessenger.MIN_GAS_CONSTANT_OVERHEAD() await L2CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
console.log(` - MIN_GAS_CONSTANT_OVERHEAD: ${MIN_GAS_CONSTANT_OVERHEAD}`) console.log(` - RELAY_CONSTANT_OVERHEAD: ${RELAY_CONSTANT_OVERHEAD}`)
const MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = const MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR =
await L2CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR() await L2CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
console.log( console.log(
...@@ -287,6 +287,14 @@ const check = { ...@@ -287,6 +287,14 @@ const check = {
console.log( console.log(
` - MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR: ${MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR}` ` - MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR: ${MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR}`
) )
const RELAY_CALL_OVERHEAD =
await L2CrossDomainMessenger.RELAY_CALL_OVERHEAD()
console.log(` - RELAY_CALL_OVERHEAD: ${RELAY_CALL_OVERHEAD}`)
const RELAY_RESERVED_GAS = await L2CrossDomainMessenger.RELAY_RESERVED_GAS()
console.log(` - RELAY_RESERVED_GAS: ${RELAY_RESERVED_GAS}`)
const RELAY_GAS_CHECK_BUFFER =
await L2CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
console.log(` - RELAY_GAS_CHECK_BUFFER: ${RELAY_GAS_CHECK_BUFFER}`)
const slot = await signer.provider.getStorageAt( const slot = await signer.provider.getStorageAt(
predeploys.L2CrossDomainMessenger, predeploys.L2CrossDomainMessenger,
......
...@@ -26,6 +26,7 @@ import { ...@@ -26,6 +26,7 @@ import {
BedrockCrossChainMessageProof, BedrockCrossChainMessageProof,
decodeVersionedNonce, decodeVersionedNonce,
encodeVersionedNonce, encodeVersionedNonce,
getChainId,
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
import { getContractInterface, predeploys } from '@eth-optimism/contracts' import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import * as rlp from 'rlp' import * as rlp from 'rlp'
...@@ -403,7 +404,8 @@ export class CrossChainMessenger { ...@@ -403,7 +404,8 @@ export class CrossChainMessenger {
let gasLimit: BigNumber let gasLimit: BigNumber
let messageNonce: BigNumber let messageNonce: BigNumber
if (version.eq(0)) { if (version.eq(0)) {
gasLimit = migratedWithdrawalGasLimit(encoded) const chainID = await getChainId(this.l2Provider)
gasLimit = migratedWithdrawalGasLimit(encoded, chainID)
messageNonce = resolved.messageNonce messageNonce = resolved.messageNonce
} else { } else {
const receipt = await this.l2Provider.getTransactionReceipt( const receipt = await this.l2Provider.getTransactionReceipt(
......
...@@ -41,10 +41,17 @@ export const hashMessageHash = (messageHash: string): string => { ...@@ -41,10 +41,17 @@ export const hashMessageHash = (messageHash: string): string => {
/** /**
* Compute the min gas limit for a migrated withdrawal. * Compute the min gas limit for a migrated withdrawal.
*/ */
export const migratedWithdrawalGasLimit = (data: string): BigNumber => { export const migratedWithdrawalGasLimit = (
data: string,
chainID: number
): BigNumber => {
// Compute the gas limit and cap at 25 million // Compute the gas limit and cap at 25 million
const dataCost = BigNumber.from(hexDataLength(data)).mul(16) const dataCost = BigNumber.from(hexDataLength(data)).mul(16)
let minGasLimit = dataCost.add(200_000) let overhead = 200_000
if (chainID !== 420) {
overhead = 1_000_000
}
let minGasLimit = dataCost.add(overhead)
if (minGasLimit.gt(25_000_000)) { if (minGasLimit.gt(25_000_000)) {
minGasLimit = BigNumber.from(25_000_000) minGasLimit = BigNumber.from(25_000_000)
} }
......
...@@ -7,11 +7,13 @@ import { ...@@ -7,11 +7,13 @@ import {
hashMessageHash, hashMessageHash,
} from '../../src/utils/message-utils' } from '../../src/utils/message-utils'
const goerliChainID = 420
describe('Message Utils', () => { describe('Message Utils', () => {
describe('migratedWithdrawalGasLimit', () => { describe('migratedWithdrawalGasLimit', () => {
it('should have a max of 25 million', () => { it('should have a max of 25 million', () => {
const data = '0x' + 'ff'.repeat(15_000_000) const data = '0x' + 'ff'.repeat(15_000_000)
const result = migratedWithdrawalGasLimit(data) const result = migratedWithdrawalGasLimit(data, goerliChainID)
expect(result).to.eq(BigNumber.from(25_000_000)) expect(result).to.eq(BigNumber.from(25_000_000))
}) })
...@@ -25,7 +27,7 @@ describe('Message Utils', () => { ...@@ -25,7 +27,7 @@ describe('Message Utils', () => {
] ]
for (const test of tests) { for (const test of tests) {
const result = migratedWithdrawalGasLimit(test.input) const result = migratedWithdrawalGasLimit(test.input, goerliChainID)
expect(result).to.eq(test.result) expect(result).to.eq(test.result)
} }
}) })
......
...@@ -178,17 +178,21 @@ Note that hints may produce multiple pre-images: ...@@ -178,17 +178,21 @@ Note that hints may produce multiple pre-images:
e.g. a hint for an ethereum block with transaction list may prepare pre-images for the header, e.g. a hint for an ethereum block with transaction list may prepare pre-images for the header,
each of the transactions, and the intermediate merkle-nodes that form the transactions-list Merkle Patricia Trie. each of the transactions, and the intermediate merkle-nodes that form the transactions-list Merkle Patricia Trie.
Hinting is implemented with a minimal wire-protocol over a blocking one-way stream: Hinting is implemented with a request-acknowledgement wire-protocol over a blocking two-way stream:
```text ```text
<request> := <length prefix> <hint bytes> <end> <request> := <length prefix> <hint bytes>
<repsonse> := <ack>
<length prefix> := big-endian uint32 # length of <hint bytes> <length prefix> := big-endian uint32 # length of <hint bytes>
<hint bytes> := byte sequence <hint bytes> := byte sequence
<end> := 0 byte <ack> := 1-byte zero value
``` ```
The `<end>` trailing zero byte allows the server to block the program The ack informs the client that the hint has been processed. Servers may respond to hints and pre-image (see below)
(since the communication is blocking) until the hint is processed. requests asynchronously as they are on separate streams. To avoid requesting pre-images that are not yet fetched,
clients should request the pre-image only after it has observed the hint acknowledgement.
### Pre-image communication ### Pre-image communication
...@@ -201,7 +205,6 @@ This protocol can be implemented with blocking read/write syscalls. ...@@ -201,7 +205,6 @@ This protocol can be implemented with blocking read/write syscalls.
<response> := <length prefix> <pre-image bytes> <response> := <length prefix> <pre-image bytes>
<length prefix> := big-endian uint64 # length of <pre-image bytes>, note: uint64 <length prefix> := big-endian uint64 # length of <pre-image bytes>, note: uint64
<hint bytes> := byte sequence #
``` ```
The `<length prefix>` here may be arbitrarily high: The `<length prefix>` here may be arbitrarily high:
......
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