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

Merge branch 'develop' into aj/report-hive-tests

parents 6f51ab6d c264ed2f
...@@ -685,6 +685,10 @@ jobs: ...@@ -685,6 +685,10 @@ jobs:
image: ubuntu-2204:2022.07.1 image: ubuntu-2204:2022.07.1
steps: steps:
- checkout - checkout
- run:
name: Install pnpm package manager
command: |
npm i pnpm --global
- run: - run:
name: Lint check name: Lint check
command: | command: |
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
) )
var remapTypeRe = regexp.MustCompile(`^(t_[\w_]+\([\w]+\))([\d]+)(_[\w]+)?$`) var remapTypeRe = regexp.MustCompile(`^(t_[\w_]+\([\w]+\))([\d]+)(_[\w]+)?$`)
var remapAstIdStorage = regexp.MustCompile(`(t_(struct|userDefinedValueType))\(([\w]+)\)([\d]+)_storage`)
// typeRemapping represents a mapping between an a type generated by solc // typeRemapping represents a mapping between an a type generated by solc
// and a canonicalized type. This is because solc inserts the ast id into // and a canonicalized type. This is because solc inserts the ast id into
...@@ -63,8 +64,11 @@ func CanonicalizeASTIDs(in *solc.StorageLayout) *solc.StorageLayout { ...@@ -63,8 +64,11 @@ func CanonicalizeASTIDs(in *solc.StorageLayout) *solc.StorageLayout {
// The storage types include the size when its a fixed size. // The storage types include the size when its a fixed size.
// This is subject to breaking in the future if a type with // This is subject to breaking in the future if a type with
// an ast id is added in a fixed storage type // an ast id is added in a fixed storage type. We don't want
if strings.Contains(oldType, "storage") { // to skip a type with `_storage` in it if it has a subtype
// with an ast id or it has an astid itself.
skip := len(remapAstIdStorage.FindAllStringSubmatch(oldType, -1)) == 0
if strings.Contains(oldType, "storage") && skip {
continue continue
} }
......
This diff is collapsed.
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
) )
const L2OutputOracleStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"startingBlockNumber\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_uint256\"},{\"astId\":1003,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"startingTimestamp\",\"offset\":0,\"slot\":\"2\",\"type\":\"t_uint256\"},{\"astId\":1004,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"l2Outputs\",\"offset\":0,\"slot\":\"3\",\"type\":\"t_array(t_struct(OutputProposal)42701_storage)dyn_storage\"}],\"types\":{\"t_array(t_struct(OutputProposal)42701_storage)dyn_storage\":{\"encoding\":\"dynamic_array\",\"label\":\"struct Types.OutputProposal[]\",\"numberOfBytes\":\"32\",\"base\":\"t_struct(OutputProposal)42701_storage\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_struct(OutputProposal)42701_storage\":{\"encoding\":\"inplace\",\"label\":\"struct Types.OutputProposal\",\"numberOfBytes\":\"64\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}" const L2OutputOracleStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"startingBlockNumber\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_uint256\"},{\"astId\":1003,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"startingTimestamp\",\"offset\":0,\"slot\":\"2\",\"type\":\"t_uint256\"},{\"astId\":1004,\"contract\":\"contracts/L1/L2OutputOracle.sol:L2OutputOracle\",\"label\":\"l2Outputs\",\"offset\":0,\"slot\":\"3\",\"type\":\"t_array(t_struct(OutputProposal)1005_storage)dyn_storage\"}],\"types\":{\"t_array(t_struct(OutputProposal)1005_storage)dyn_storage\":{\"encoding\":\"dynamic_array\",\"label\":\"struct Types.OutputProposal[]\",\"numberOfBytes\":\"32\",\"base\":\"t_struct(OutputProposal)1005_storage\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_struct(OutputProposal)1005_storage\":{\"encoding\":\"inplace\",\"label\":\"struct Types.OutputProposal\",\"numberOfBytes\":\"64\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}"
var L2OutputOracleStorageLayout = new(solc.StorageLayout) var L2OutputOracleStorageLayout = new(solc.StorageLayout)
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
) )
const OptimismPortalStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"params\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_struct(ResourceParams)34941_storage\"},{\"astId\":1003,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"2\",\"type\":\"t_array(t_uint256)48_storage\"},{\"astId\":1004,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"l2Sender\",\"offset\":0,\"slot\":\"50\",\"type\":\"t_address\"},{\"astId\":1005,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"finalizedWithdrawals\",\"offset\":0,\"slot\":\"51\",\"type\":\"t_mapping(t_bytes32,t_bool)\"},{\"astId\":1006,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"provenWithdrawals\",\"offset\":0,\"slot\":\"52\",\"type\":\"t_mapping(t_bytes32,t_struct(ProvenWithdrawal)34280_storage)\"},{\"astId\":1007,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"paused\",\"offset\":0,\"slot\":\"53\",\"type\":\"t_bool\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_array(t_uint256)48_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[48]\",\"numberOfBytes\":\"1536\",\"base\":\"t_uint256\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_mapping(t_bytes32,t_bool)\":{\"encoding\":\"mapping\",\"label\":\"mapping(bytes32 =\u003e bool)\",\"numberOfBytes\":\"32\",\"key\":\"t_bytes32\",\"value\":\"t_bool\"},\"t_mapping(t_bytes32,t_struct(ProvenWithdrawal)34280_storage)\":{\"encoding\":\"mapping\",\"label\":\"mapping(bytes32 =\u003e struct OptimismPortal.ProvenWithdrawal)\",\"numberOfBytes\":\"32\",\"key\":\"t_bytes32\",\"value\":\"t_struct(ProvenWithdrawal)34280_storage\"},\"t_struct(ProvenWithdrawal)34280_storage\":{\"encoding\":\"inplace\",\"label\":\"struct OptimismPortal.ProvenWithdrawal\",\"numberOfBytes\":\"64\"},\"t_struct(ResourceParams)34941_storage\":{\"encoding\":\"inplace\",\"label\":\"struct ResourceMetering.ResourceParams\",\"numberOfBytes\":\"32\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint64\":{\"encoding\":\"inplace\",\"label\":\"uint64\",\"numberOfBytes\":\"8\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}" const OptimismPortalStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"params\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_struct(ResourceParams)1009_storage\"},{\"astId\":1003,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"2\",\"type\":\"t_array(t_uint256)48_storage\"},{\"astId\":1004,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"l2Sender\",\"offset\":0,\"slot\":\"50\",\"type\":\"t_address\"},{\"astId\":1005,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"finalizedWithdrawals\",\"offset\":0,\"slot\":\"51\",\"type\":\"t_mapping(t_bytes32,t_bool)\"},{\"astId\":1006,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"provenWithdrawals\",\"offset\":0,\"slot\":\"52\",\"type\":\"t_mapping(t_bytes32,t_struct(ProvenWithdrawal)1008_storage)\"},{\"astId\":1007,\"contract\":\"contracts/L1/OptimismPortal.sol:OptimismPortal\",\"label\":\"paused\",\"offset\":0,\"slot\":\"53\",\"type\":\"t_bool\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_array(t_uint256)48_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[48]\",\"numberOfBytes\":\"1536\",\"base\":\"t_uint256\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_mapping(t_bytes32,t_bool)\":{\"encoding\":\"mapping\",\"label\":\"mapping(bytes32 =\u003e bool)\",\"numberOfBytes\":\"32\",\"key\":\"t_bytes32\",\"value\":\"t_bool\"},\"t_mapping(t_bytes32,t_struct(ProvenWithdrawal)1008_storage)\":{\"encoding\":\"mapping\",\"label\":\"mapping(bytes32 =\u003e struct OptimismPortal.ProvenWithdrawal)\",\"numberOfBytes\":\"32\",\"key\":\"t_bytes32\",\"value\":\"t_struct(ProvenWithdrawal)1008_storage\"},\"t_struct(ProvenWithdrawal)1008_storage\":{\"encoding\":\"inplace\",\"label\":\"struct OptimismPortal.ProvenWithdrawal\",\"numberOfBytes\":\"64\"},\"t_struct(ResourceParams)1009_storage\":{\"encoding\":\"inplace\",\"label\":\"struct ResourceMetering.ResourceParams\",\"numberOfBytes\":\"32\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint64\":{\"encoding\":\"inplace\",\"label\":\"uint64\",\"numberOfBytes\":\"8\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}"
var OptimismPortalStorageLayout = new(solc.StorageLayout) var OptimismPortalStorageLayout = new(solc.StorageLayout)
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
) )
const SystemConfigStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_array(t_uint256)50_storage\"},{\"astId\":1003,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_owner\",\"offset\":0,\"slot\":\"51\",\"type\":\"t_address\"},{\"astId\":1004,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"52\",\"type\":\"t_array(t_uint256)49_storage\"},{\"astId\":1005,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"overhead\",\"offset\":0,\"slot\":\"101\",\"type\":\"t_uint256\"},{\"astId\":1006,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"scalar\",\"offset\":0,\"slot\":\"102\",\"type\":\"t_uint256\"},{\"astId\":1007,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"batcherHash\",\"offset\":0,\"slot\":\"103\",\"type\":\"t_bytes32\"},{\"astId\":1008,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"gasLimit\",\"offset\":0,\"slot\":\"104\",\"type\":\"t_uint64\"},{\"astId\":1009,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_resourceConfig\",\"offset\":0,\"slot\":\"105\",\"type\":\"t_struct(ResourceConfig)34954_storage\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_array(t_uint256)49_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[49]\",\"numberOfBytes\":\"1568\",\"base\":\"t_uint256\"},\"t_array(t_uint256)50_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[50]\",\"numberOfBytes\":\"1600\",\"base\":\"t_uint256\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_struct(ResourceConfig)34954_storage\":{\"encoding\":\"inplace\",\"label\":\"struct ResourceMetering.ResourceConfig\",\"numberOfBytes\":\"32\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint32\":{\"encoding\":\"inplace\",\"label\":\"uint32\",\"numberOfBytes\":\"4\"},\"t_uint64\":{\"encoding\":\"inplace\",\"label\":\"uint64\",\"numberOfBytes\":\"8\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}" const SystemConfigStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_array(t_uint256)50_storage\"},{\"astId\":1003,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_owner\",\"offset\":0,\"slot\":\"51\",\"type\":\"t_address\"},{\"astId\":1004,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"52\",\"type\":\"t_array(t_uint256)49_storage\"},{\"astId\":1005,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"overhead\",\"offset\":0,\"slot\":\"101\",\"type\":\"t_uint256\"},{\"astId\":1006,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"scalar\",\"offset\":0,\"slot\":\"102\",\"type\":\"t_uint256\"},{\"astId\":1007,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"batcherHash\",\"offset\":0,\"slot\":\"103\",\"type\":\"t_bytes32\"},{\"astId\":1008,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"gasLimit\",\"offset\":0,\"slot\":\"104\",\"type\":\"t_uint64\"},{\"astId\":1009,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_resourceConfig\",\"offset\":0,\"slot\":\"105\",\"type\":\"t_struct(ResourceConfig)1010_storage\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_array(t_uint256)49_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[49]\",\"numberOfBytes\":\"1568\",\"base\":\"t_uint256\"},\"t_array(t_uint256)50_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[50]\",\"numberOfBytes\":\"1600\",\"base\":\"t_uint256\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_struct(ResourceConfig)1010_storage\":{\"encoding\":\"inplace\",\"label\":\"struct ResourceMetering.ResourceConfig\",\"numberOfBytes\":\"32\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint32\":{\"encoding\":\"inplace\",\"label\":\"uint32\",\"numberOfBytes\":\"4\"},\"t_uint64\":{\"encoding\":\"inplace\",\"label\":\"uint64\",\"numberOfBytes\":\"8\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}"
var SystemConfigStorageLayout = new(solc.StorageLayout) var SystemConfigStorageLayout = new(solc.StorageLayout)
......
...@@ -78,3 +78,24 @@ func WaitFor(ctx context.Context, rate time.Duration, cb func() (bool, error)) e ...@@ -78,3 +78,24 @@ func WaitFor(ctx context.Context, rate time.Duration, cb func() (bool, error)) e
} }
} }
} }
func WaitAndGet[T interface{}](ctx context.Context, pollRate time.Duration, get func() (T, error), predicate func(T) bool) (T, error) {
tick := time.NewTicker(pollRate)
defer tick.Stop()
var nilT T
for {
select {
case <-ctx.Done():
return nilT, ctx.Err()
case <-tick.C:
val, err := get()
if err != nil {
return nilT, err
}
if predicate(val) {
return val, nil
}
}
}
}
...@@ -511,7 +511,7 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -511,7 +511,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
// Wait for the finalization period, then we can finalize this withdrawal. // Wait for the finalization period, then we can finalize this withdrawal.
ctx, cancel = context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber) blockNumber, err := withdrawals.WaitForOutputRootPublished(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber)
cancel() cancel()
require.Nil(t, err) require.Nil(t, err)
...@@ -642,7 +642,7 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -642,7 +642,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
// Wait for finalization and then create the Finalized Withdrawal Transaction // Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel = context.WithTimeout(context.Background(), 45*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 45*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
_, err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, header.Number) err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, header.Number)
require.Nil(t, err) require.Nil(t, err)
// Finalize withdrawal // Finalize withdrawal
......
...@@ -79,7 +79,7 @@ func defaultWithdrawalTxOpts() *WithdrawalTxOpts { ...@@ -79,7 +79,7 @@ func defaultWithdrawalTxOpts() *WithdrawalTxOpts {
func ProveAndFinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Node *node.Node, ethPrivKey *ecdsa.PrivateKey, l2WithdrawalReceipt *types.Receipt) (*types.Receipt, *types.Receipt) { func ProveAndFinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Node *node.Node, ethPrivKey *ecdsa.PrivateKey, l2WithdrawalReceipt *types.Receipt) (*types.Receipt, *types.Receipt) {
params, proveReceipt := ProveWithdrawal(t, cfg, l1Client, l2Node, ethPrivKey, l2WithdrawalReceipt) params, proveReceipt := ProveWithdrawal(t, cfg, l1Client, l2Node, ethPrivKey, l2WithdrawalReceipt)
finalizeReceipt := FinalizeWithdrawal(t, cfg, l1Client, ethPrivKey, l2WithdrawalReceipt, params) finalizeReceipt := FinalizeWithdrawal(t, cfg, l1Client, ethPrivKey, proveReceipt, params)
return proveReceipt, finalizeReceipt return proveReceipt, finalizeReceipt
} }
...@@ -87,7 +87,8 @@ func ProveWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, ...@@ -87,7 +87,8 @@ func ProveWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client,
// Get l2BlockNumber for proof generation // Get l2BlockNumber for proof generation
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, l2WithdrawalReceipt.BlockNumber)
blockNumber, err := withdrawals.WaitForOutputRootPublished(ctx, l1Client, predeploys.DevOptimismPortalAddr, l2WithdrawalReceipt.BlockNumber)
require.Nil(t, err) require.Nil(t, err)
rpcClient, err := rpc.Dial(l2Node.WSEndpoint()) rpcClient, err := rpc.Dial(l2Node.WSEndpoint())
...@@ -142,7 +143,7 @@ func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Clie ...@@ -142,7 +143,7 @@ func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Clie
// Wait for finalization and then create the Finalized Withdrawal Transaction // Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
_, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, withdrawalReceipt.BlockNumber) err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, withdrawalReceipt.BlockNumber)
require.Nil(t, err) require.Nil(t, err)
opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L1ChainIDBig()) opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L1ChainIDBig())
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -22,103 +23,74 @@ import ( ...@@ -22,103 +23,74 @@ import (
var MessagePassedTopic = crypto.Keccak256Hash([]byte("MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)")) var MessagePassedTopic = crypto.Keccak256Hash([]byte("MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)"))
// WaitForFinalizationPeriod waits until there is OutputProof for an L2 block number larger than the supplied l2BlockNumber // WaitForOutputRootPublished waits until there is an output published for an L2 block number larger than the supplied l2BlockNumber
// and that the output is finalized. // This function polls and can block for a very long time if used on mainnet.
// This functions polls and can block for a very long time if used on mainnet. // This returns the block number to use for proof generation.
// This returns the block number to use for the proof generation. func WaitForOutputRootPublished(ctx context.Context, client *ethclient.Client, portalAddr common.Address, l2BlockNumber *big.Int) (uint64, error) {
func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, portalAddr common.Address, l2BlockNumber *big.Int) (uint64, error) {
l2BlockNumber = new(big.Int).Set(l2BlockNumber) // Don't clobber caller owned l2BlockNumber l2BlockNumber = new(big.Int).Set(l2BlockNumber) // Don't clobber caller owned l2BlockNumber
opts := &bind.CallOpts{Context: ctx} opts := &bind.CallOpts{Context: ctx}
portal, err := bindings.NewOptimismPortalCaller(portalAddr, client) l2OO, err := createL2OOCaller(ctx, client, portalAddr)
if err != nil {
return 0, err
}
l2OOAddress, err := portal.L2ORACLE(opts)
if err != nil { if err != nil {
return 0, err return 0, err
} }
l2OO, err := bindings.NewL2OutputOracleCaller(l2OOAddress, client)
if err != nil {
return 0, err
}
submissionInterval, err := l2OO.SUBMISSIONINTERVAL(opts)
if err != nil {
return 0, err
}
// Convert blockNumber to submission interval boundary
rem := new(big.Int)
l2BlockNumber, rem = l2BlockNumber.DivMod(l2BlockNumber, submissionInterval, rem)
if rem.Cmp(common.Big0) != 0 {
l2BlockNumber = l2BlockNumber.Add(l2BlockNumber, common.Big1)
}
l2BlockNumber = l2BlockNumber.Mul(l2BlockNumber, submissionInterval)
finalizationPeriod, err := l2OO.FINALIZATIONPERIODSECONDS(opts) getL2BlockFromLatestOutput := func() (*big.Int, error) { return l2OO.LatestBlockNumber(opts) }
if err != nil { outputBlockNum, err := e2eutils.WaitAndGet[*big.Int](ctx, time.Second, getL2BlockFromLatestOutput, func(latest *big.Int) bool {
return 0, err return latest.Cmp(l2BlockNumber) >= 0
} })
latest, err := l2OO.LatestBlockNumber(opts)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return outputBlockNum.Uint64(), nil
}
// Now poll for the output to be submitted on chain // WaitForFinalizationPeriod waits until the L1 chain has progressed far enough that l1ProvingBlockNum has completed
var ticker *time.Ticker // the finalization period.
diff := new(big.Int).Sub(l2BlockNumber, latest) // This functions polls and can block for a very long time if used on mainnet.
if diff.Cmp(big.NewInt(10)) > 0 { func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, portalAddr common.Address, l1ProvingBlockNum *big.Int) error {
ticker = time.NewTicker(time.Minute) l1ProvingBlockNum = new(big.Int).Set(l1ProvingBlockNum) // Don't clobber caller owned l1ProvingBlockNum
} else { opts := &bind.CallOpts{Context: ctx}
ticker = time.NewTicker(time.Second)
}
loop: // Load finalization period
for { l2OO, err := createL2OOCaller(ctx, client, portalAddr)
select {
case <-ticker.C:
latest, err = l2OO.LatestBlockNumber(opts)
if err != nil { if err != nil {
return 0, err return fmt.Errorf("create L2OOCaller: %w", err)
}
// Already passed the submitted block (likely just equals rather than >= here).
if latest.Cmp(l2BlockNumber) >= 0 {
break loop
}
case <-ctx.Done():
return 0, ctx.Err()
} }
finalizationPeriod, err := l2OO.FINALIZATIONPERIODSECONDS(opts)
if err != nil {
return fmt.Errorf("get finalization period: %w", err)
} }
// Now wait for it to be finalized provingHeader, err := client.HeaderByNumber(ctx, l1ProvingBlockNum)
output, err := l2OO.GetL2OutputAfter(opts, l2BlockNumber)
if err != nil { if err != nil {
return 0, err return fmt.Errorf("retrieve proving header: %w", err)
}
if output.OutputRoot == [32]byte{} {
return 0, errors.New("empty output root. likely no proposal at timestamp")
} }
targetTimestamp := new(big.Int).Add(output.Timestamp, finalizationPeriod)
targetTimestamp := new(big.Int).Add(new(big.Int).SetUint64(provingHeader.Time), finalizationPeriod)
targetTime := time.Unix(targetTimestamp.Int64(), 0) targetTime := time.Unix(targetTimestamp.Int64(), 0)
// Assume clock is relatively correct // Assume clock is relatively correct
time.Sleep(time.Until(targetTime)) time.Sleep(time.Until(targetTime))
// Poll for L1 Block to have a time greater than the target time // Poll for L1 Block to have a time greater than the target time
ticker = time.NewTicker(time.Second) return e2eutils.WaitFor(ctx, time.Second, func() (bool, error) {
for {
select {
case <-ticker.C:
header, err := client.HeaderByNumber(ctx, nil) header, err := client.HeaderByNumber(ctx, nil)
if err != nil { if err != nil {
return 0, err return false, fmt.Errorf("retrieve latest header: %w", err)
}
if header.Time > targetTimestamp.Uint64() {
return l2BlockNumber.Uint64(), nil
} }
case <-ctx.Done(): return header.Time > targetTimestamp.Uint64(), nil
return 0, ctx.Err() })
}
func createL2OOCaller(ctx context.Context, client *ethclient.Client, portalAddr common.Address) (*bindings.L2OutputOracleCaller, error) {
portal, err := bindings.NewOptimismPortalCaller(portalAddr, client)
if err != nil {
return nil, fmt.Errorf("create OptimismPortalCaller: %w", err)
} }
l2OOAddress, err := portal.L2ORACLE(&bind.CallOpts{Context: ctx})
if err != nil {
return nil, fmt.Errorf("create L2ORACLE: %w", err)
} }
return bindings.NewL2OutputOracleCaller(l2OOAddress, client)
} }
type ProofClient interface { type ProofClient interface {
......
...@@ -32,30 +32,32 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert ...@@ -32,30 +32,32 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert
DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243) DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 498826) FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 502174)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 500705) FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 504053)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 488169) FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 491517)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 497701) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 500937)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 499580) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502816)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 487044) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 490280)
FaultDisputeGame_Test:test_defendRoot_invalidMove_reverts() (gas: 13294)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17426) FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17426)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17917) FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17917)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10315) FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10315)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8282) FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8282)
FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17691) FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17669)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415981)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26387) FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26387)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13360)
FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103230) FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103230)
FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 408100) FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 408100)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10923) FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10968)
FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24655) FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24655)
FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 221201) FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107344)
FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9679) FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224789)
FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 106068) FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9657)
FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 23692) FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109754)
FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 391875) FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21422)
FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27256)
FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395447)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8181) FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8181)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107388)
FeeVault_Test:test_constructor_succeeds() (gas: 18185) FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352113) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352113)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950320) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950320)
......
...@@ -34,7 +34,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -34,7 +34,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
IBigStepper public immutable VM; IBigStepper public immutable VM;
/// @notice The duration of the game. /// @notice The duration of the game.
/// @dev TODO: Account for resolution buffer. (?)
Duration internal constant GAME_DURATION = Duration.wrap(7 days); Duration internal constant GAME_DURATION = Duration.wrap(7 days);
/// @notice The root claim's position is always at gindex 1. /// @notice The root claim's position is always at gindex 1.
...@@ -275,10 +274,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -275,10 +274,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
/// @inheritdoc IDisputeGame /// @inheritdoc IDisputeGame
function resolve() external returns (GameStatus status_) { function resolve() external returns (GameStatus status_) {
// TODO: Do not allow resolution before clocks run out.
if (status != GameStatus.IN_PROGRESS) {
// If the game is not in progress, it cannot be resolved. // If the game is not in progress, it cannot be resolved.
if (status != GameStatus.IN_PROGRESS) {
revert GameNotInProgress(); revert GameNotInProgress();
} }
...@@ -298,7 +295,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -298,7 +295,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
// If the claim is not a dangling node above the bottom of the tree, // If the claim is not a dangling node above the bottom of the tree,
// we can skip over it. These nodes are not relevant to the game resolution. // we can skip over it. These nodes are not relevant to the game resolution.
Position claimPos = claim.position;
if (claim.countered) { if (claim.countered) {
continue; continue;
} }
...@@ -306,7 +302,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -306,7 +302,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
// If the claim is a dangling node, we can check if it is the left-most // If the claim is a dangling node, we can check if it is the left-most
// dangling node we've come across so far. If it is, we can update the // dangling node we've come across so far. If it is, we can update the
// left-most trace index. // left-most trace index.
uint256 traceIndex = claimPos.traceIndex(MAX_GAME_DEPTH); uint256 traceIndex = claim.position.traceIndex(MAX_GAME_DEPTH);
if (traceIndex < leftMostTraceIndex) { if (traceIndex < leftMostTraceIndex) {
leftMostTraceIndex = traceIndex; leftMostTraceIndex = traceIndex;
unchecked { unchecked {
...@@ -315,12 +311,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -315,12 +311,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
} }
} }
// Create a reference to the left most uncontested claim and its parent.
ClaimData storage leftMostUncontested = claimData[leftMostIndex];
// If the left most uncontested claim's parent has not expired their clock, the game
// cannot be resolved. If the left most uncontested claim is the root, no nodes qualified,
// and we check if 3.5 days has passed since the root claim's creation.
uint256 parentIndex = leftMostUncontested.parentIndex;
Clock opposingClock = parentIndex == type(uint32).max
? leftMostUncontested.clock
: claimData[parentIndex].clock;
if (
Duration.unwrap(opposingClock.duration()) +
(block.timestamp - Timestamp.unwrap(opposingClock.timestamp())) <=
Duration.unwrap(GAME_DURATION) >> 1
) {
revert ClockNotExpired();
}
// If the left-most dangling node is at an even depth, the defender wins. // If the left-most dangling node is at an even depth, the defender wins.
// Otherwise, the challenger wins and the root claim is deemed invalid. // Otherwise, the challenger wins and the root claim is deemed invalid.
if ( if (
// slither-disable-next-line weak-prng // slither-disable-next-line weak-prng
claimData[leftMostIndex].position.depth() % 2 == 0 && leftMostUncontested.position.depth() % 2 == 0 && leftMostTraceIndex != type(uint128).max
leftMostTraceIndex != type(uint128).max
) { ) {
status_ = GameStatus.DEFENDER_WINS; status_ = GameStatus.DEFENDER_WINS;
} else { } else {
......
...@@ -39,6 +39,9 @@ error GameNotInProgress(); ...@@ -39,6 +39,9 @@ error GameNotInProgress();
/// @notice Thrown when a move is attempted to be made after the clock has timed out. /// @notice Thrown when a move is attempted to be made after the clock has timed out.
error ClockTimeExceeded(); error ClockTimeExceeded();
/// @notice Thrown when the game is attempted to be resolved too early.
error ClockNotExpired();
/// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game. /// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
error GameDepthExceeded(); error GameDepthExceeded();
......
...@@ -130,7 +130,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -130,7 +130,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
} }
/// @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error. /// @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error.
function test_defendRoot_invalidMove_reverts() public { function test_move_defendRoot_reverts() public {
vm.expectRevert(CannotDefendRootClaim.selector); vm.expectRevert(CannotDefendRootClaim.selector);
gameProxy.defend(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.defend(0, Claim.wrap(bytes32(uint256(5))));
} }
...@@ -175,6 +175,49 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -175,6 +175,49 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
} }
/// @notice Static unit test for the correctness of the chess clock incrementation.
function test_move_clockCorrectness_succeeds() public {
(, , , , Clock clock) = gameProxy.claimData(0);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))))
);
Claim claim = Claim.wrap(bytes32(uint256(5)));
vm.warp(block.timestamp + 15);
gameProxy.attack(0, claim);
(, , , , clock) = gameProxy.claimData(1);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(15), Timestamp.wrap(uint64(block.timestamp))))
);
vm.warp(block.timestamp + 10);
gameProxy.attack(1, claim);
(, , , , clock) = gameProxy.claimData(2);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(10), Timestamp.wrap(uint64(block.timestamp))))
);
vm.warp(block.timestamp + 10);
gameProxy.attack(2, claim);
(, , , , clock) = gameProxy.claimData(3);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(25), Timestamp.wrap(uint64(block.timestamp))))
);
vm.warp(block.timestamp + 10);
gameProxy.attack(3, claim);
(, , , , clock) = gameProxy.claimData(4);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(20), Timestamp.wrap(uint64(block.timestamp))))
);
}
/// @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should /// @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should
/// revert with the `ClaimAlreadyExists` error. /// revert with the `ClaimAlreadyExists` error.
function test_move_duplicateClaim_reverts() public { function test_move_duplicateClaim_reverts() public {
...@@ -189,7 +232,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -189,7 +232,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
} }
/// @dev Static unit test for the correctness of an opening attack. /// @dev Static unit test for the correctness of an opening attack.
function test_simpleAttack_succeeds() public { function test_move_simpleAttack_succeeds() public {
// Warp ahead 5 seconds. // Warp ahead 5 seconds.
vm.warp(block.timestamp + 5); vm.warp(block.timestamp + 5);
...@@ -237,11 +280,19 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -237,11 +280,19 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// @dev Static unit test for the correctness an uncontested root resolution. /// @dev Static unit test for the correctness an uncontested root resolution.
function test_resolve_rootUncontested_succeeds() public { function test_resolve_rootUncontested_succeeds() public {
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve(); GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
} }
/// @dev Static unit test for the correctness an uncontested root resolution.
function test_resolve_rootUncontestedClockNotExpired_succeeds() public {
vm.warp(block.timestamp + 3 days + 12 hours);
vm.expectRevert(ClockNotExpired.selector);
gameProxy.resolve();
}
/// @dev Static unit test asserting that resolve reverts when the game state is /// @dev Static unit test asserting that resolve reverts when the game state is
/// not in progress. /// not in progress.
function test_resolve_notInProgress_reverts() public { function test_resolve_notInProgress_reverts() public {
...@@ -263,6 +314,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -263,6 +314,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
function test_resolve_rootContested_succeeds() public { function test_resolve_rootContested_succeeds() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve(); GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
...@@ -273,6 +326,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -273,6 +326,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6)))); gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve(); GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
...@@ -285,6 +340,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -285,6 +340,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6)))); gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(7)))); gameProxy.defend(1, Claim.wrap(bytes32(uint256(7))));
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve(); GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
...@@ -499,6 +556,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena { ...@@ -499,6 +556,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
// Play the game until a step is forced. // Play the game until a step is forced.
challenger.play(0); challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the honest player challenged the root // Resolve the game and assert that the honest player challenged the root
// claim successfully. // claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
...@@ -517,6 +577,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena { ...@@ -517,6 +577,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
// Play the game until a step is forced. // Play the game until a step is forced.
challenger.play(0); challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the dishonest player challenged the root // Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully. // claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
...@@ -535,6 +598,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena { ...@@ -535,6 +598,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
// Play the game until a step is forced. // Play the game until a step is forced.
challenger.play(0); challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the honest player challenged the root // Resolve the game and assert that the honest player challenged the root
// claim successfully. // claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
...@@ -553,6 +619,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena { ...@@ -553,6 +619,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
// Play the game until a step is forced. // Play the game until a step is forced.
challenger.play(0); challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the dishonest player challenged the root // Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully. // claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
...@@ -571,6 +640,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena { ...@@ -571,6 +640,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
// Play the game until a step is forced. // Play the game until a step is forced.
challenger.play(0); challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the honest player challenged the root // Resolve the game and assert that the honest player challenged the root
// claim successfully. // claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
...@@ -589,6 +661,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena { ...@@ -589,6 +661,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena {
// Play the game until a step is forced. // Play the game until a step is forced.
challenger.play(0); challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the dishonest player challenged the root // Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully. // claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
......
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