Commit b36be32b authored by clabby's avatar clabby

Only allow checkpointing 1 block back in the `BlockOracle`

parent e0c0f867
...@@ -36,8 +36,8 @@ type BlockOracleBlockInfo struct { ...@@ -36,8 +36,8 @@ type BlockOracleBlockInfo struct {
// BlockOracleMetaData contains all meta data concerning the BlockOracle contract. // BlockOracleMetaData contains all meta data concerning the BlockOracle contract.
var BlockOracleMetaData = &bind.MetaData{ var BlockOracleMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[],\"name\":\"BlockHashNotPresent\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BlockNumberOOB\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_blockNumber\",\"type\":\"uint256\"}],\"name\":\"load\",\"outputs\":[{\"components\":[{\"internalType\":\"Hash\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"internalType\":\"Timestamp\",\"name\":\"timestamp\",\"type\":\"uint64\"}],\"internalType\":\"structBlockOracle.BlockInfo\",\"name\":\"blockInfo_\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_blockNumber\",\"type\":\"uint256\"}],\"name\":\"store\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", ABI: "[{\"inputs\":[],\"name\":\"BlockHashNotPresent\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"checkpoint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber_\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_blockNumber\",\"type\":\"uint256\"}],\"name\":\"load\",\"outputs\":[{\"components\":[{\"internalType\":\"Hash\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"internalType\":\"Timestamp\",\"name\":\"timestamp\",\"type\":\"uint64\"}],\"internalType\":\"structBlockOracle.BlockInfo\",\"name\":\"blockInfo_\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
Bin: "0x608060405234801561001057600080fd5b50610279806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636057361d1461003b57806399d548aa14610050575b600080fd5b61004e6100493660046101d0565b61008c565b005b61006361005e3660046101d0565b610151565b604080518251815260209283015167ffffffffffffffff16928101929092520160405180910390f35b804060008190036100c9576040517fd82756d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006100d58343610218565b6100e090600d61022f565b6100ea9042610218565b60408051808201825293845267ffffffffffffffff918216602080860191825260009687528690529420925183559251600190920180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016929093169190911790915550565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff1692840192909252036101cb576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b6000602082840312156101e257600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008282101561022a5761022a6101e9565b500390565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610267576102676101e9565b50029056fea164736f6c634300080f000a", Bin: "0x608060405234801561001057600080fd5b506101fb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806399d548aa1461003b578063c2c4c5c114610078575b600080fd5b61004e610049366004610197565b61008e565b604080518251815260209283015167ffffffffffffffff1692810192909252015b60405180910390f35b61008061010d565b60405190815260200161006f565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff169284019290925203610108576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b60008061011b6001436101b0565b9150508040600061012d600d426101b0565b60408051808201825293845267ffffffffffffffff9182166020808601918252600087815290819052919091209351845551600190930180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001693909116929092179091555090565b6000602082840312156101a957600080fd5b5035919050565b6000828210156101e9577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a",
} }
// BlockOracleABI is the input ABI used to generate the binding from. // BlockOracleABI is the input ABI used to generate the binding from.
...@@ -238,23 +238,23 @@ func (_BlockOracle *BlockOracleCallerSession) Load(_blockNumber *big.Int) (Block ...@@ -238,23 +238,23 @@ func (_BlockOracle *BlockOracleCallerSession) Load(_blockNumber *big.Int) (Block
return _BlockOracle.Contract.Load(&_BlockOracle.CallOpts, _blockNumber) return _BlockOracle.Contract.Load(&_BlockOracle.CallOpts, _blockNumber)
} }
// Store is a paid mutator transaction binding the contract method 0x6057361d. // Checkpoint is a paid mutator transaction binding the contract method 0xc2c4c5c1.
// //
// Solidity: function store(uint256 _blockNumber) returns() // Solidity: function checkpoint() returns(uint256 blockNumber_)
func (_BlockOracle *BlockOracleTransactor) Store(opts *bind.TransactOpts, _blockNumber *big.Int) (*types.Transaction, error) { func (_BlockOracle *BlockOracleTransactor) Checkpoint(opts *bind.TransactOpts) (*types.Transaction, error) {
return _BlockOracle.contract.Transact(opts, "store", _blockNumber) return _BlockOracle.contract.Transact(opts, "checkpoint")
} }
// Store is a paid mutator transaction binding the contract method 0x6057361d. // Checkpoint is a paid mutator transaction binding the contract method 0xc2c4c5c1.
// //
// Solidity: function store(uint256 _blockNumber) returns() // Solidity: function checkpoint() returns(uint256 blockNumber_)
func (_BlockOracle *BlockOracleSession) Store(_blockNumber *big.Int) (*types.Transaction, error) { func (_BlockOracle *BlockOracleSession) Checkpoint() (*types.Transaction, error) {
return _BlockOracle.Contract.Store(&_BlockOracle.TransactOpts, _blockNumber) return _BlockOracle.Contract.Checkpoint(&_BlockOracle.TransactOpts)
} }
// Store is a paid mutator transaction binding the contract method 0x6057361d. // Checkpoint is a paid mutator transaction binding the contract method 0xc2c4c5c1.
// //
// Solidity: function store(uint256 _blockNumber) returns() // Solidity: function checkpoint() returns(uint256 blockNumber_)
func (_BlockOracle *BlockOracleTransactorSession) Store(_blockNumber *big.Int) (*types.Transaction, error) { func (_BlockOracle *BlockOracleTransactorSession) Checkpoint() (*types.Transaction, error) {
return _BlockOracle.Contract.Store(&_BlockOracle.TransactOpts, _blockNumber) return _BlockOracle.Contract.Checkpoint(&_BlockOracle.TransactOpts)
} }
...@@ -9,11 +9,11 @@ import ( ...@@ -9,11 +9,11 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
) )
const BlockOracleStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"src/dispute/BlockOracle.sol:BlockOracle\",\"label\":\"blockHashes\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_mapping(t_uint256,t_struct(BlockInfo)1001_storage)\"}],\"types\":{\"t_mapping(t_uint256,t_struct(BlockInfo)1001_storage)\":{\"encoding\":\"mapping\",\"label\":\"mapping(uint256 =\u003e struct BlockOracle.BlockInfo)\",\"numberOfBytes\":\"32\",\"key\":\"t_uint256\",\"value\":\"t_struct(BlockInfo)1001_storage\"},\"t_struct(BlockInfo)1001_storage\":{\"encoding\":\"inplace\",\"label\":\"struct BlockOracle.BlockInfo\",\"numberOfBytes\":\"64\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_userDefinedValueType(Hash)1002\":{\"encoding\":\"inplace\",\"label\":\"Hash\",\"numberOfBytes\":\"32\"},\"t_userDefinedValueType(Timestamp)1003\":{\"encoding\":\"inplace\",\"label\":\"Timestamp\",\"numberOfBytes\":\"8\"}}}" const BlockOracleStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"src/dispute/BlockOracle.sol:BlockOracle\",\"label\":\"blocks\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_mapping(t_uint256,t_struct(BlockInfo)1001_storage)\"}],\"types\":{\"t_mapping(t_uint256,t_struct(BlockInfo)1001_storage)\":{\"encoding\":\"mapping\",\"label\":\"mapping(uint256 =\u003e struct BlockOracle.BlockInfo)\",\"numberOfBytes\":\"32\",\"key\":\"t_uint256\",\"value\":\"t_struct(BlockInfo)1001_storage\"},\"t_struct(BlockInfo)1001_storage\":{\"encoding\":\"inplace\",\"label\":\"struct BlockOracle.BlockInfo\",\"numberOfBytes\":\"64\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_userDefinedValueType(Hash)1002\":{\"encoding\":\"inplace\",\"label\":\"Hash\",\"numberOfBytes\":\"32\"},\"t_userDefinedValueType(Timestamp)1003\":{\"encoding\":\"inplace\",\"label\":\"Timestamp\",\"numberOfBytes\":\"8\"}}}"
var BlockOracleStorageLayout = new(solc.StorageLayout) var BlockOracleStorageLayout = new(solc.StorageLayout)
var BlockOracleDeployedBin = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80636057361d1461003b57806399d548aa14610050575b600080fd5b61004e6100493660046101d0565b61008c565b005b61006361005e3660046101d0565b610151565b604080518251815260209283015167ffffffffffffffff16928101929092520160405180910390f35b804060008190036100c9576040517fd82756d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006100d58343610218565b6100e090600d61022f565b6100ea9042610218565b60408051808201825293845267ffffffffffffffff918216602080860191825260009687528690529420925183559251600190920180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016929093169190911790915550565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff1692840192909252036101cb576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b6000602082840312156101e257600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008282101561022a5761022a6101e9565b500390565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610267576102676101e9565b50029056fea164736f6c634300080f000a" var BlockOracleDeployedBin = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806399d548aa1461003b578063c2c4c5c114610078575b600080fd5b61004e610049366004610197565b61008e565b604080518251815260209283015167ffffffffffffffff1692810192909252015b60405180910390f35b61008061010d565b60405190815260200161006f565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff169284019290925203610108576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b60008061011b6001436101b0565b9150508040600061012d600d426101b0565b60408051808201825293845267ffffffffffffffff9182166020808601918252600087815290819052919091209351845551600190930180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001693909116929092179091555090565b6000602082840312156101a957600080fd5b5035919050565b6000828210156101e9577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a"
func init() { func init() {
if err := json.Unmarshal([]byte(BlockOracleStorageLayoutJSON), BlockOracleStorageLayout); err != nil { if err := json.Unmarshal([]byte(BlockOracleStorageLayoutJSON), BlockOracleStorageLayout); err != nil {
......
...@@ -17,7 +17,7 @@ import ( ...@@ -17,7 +17,7 @@ import (
// It configures the alphabet fault game as game type 0 (faultGameType) // It configures the alphabet fault game as game type 0 (faultGameType)
// If/when the dispute game factory becomes a predeployed contract this can be removed and just use the // If/when the dispute game factory becomes a predeployed contract this can be removed and just use the
// predeployed version // predeployed version
func deployDisputeGameContracts(require *require.Assertions, ctx context.Context, client *ethclient.Client, opts *bind.TransactOpts, gameDuration uint64) (*bindings.DisputeGameFactory, uint64) { func deployDisputeGameContracts(require *require.Assertions, ctx context.Context, client *ethclient.Client, opts *bind.TransactOpts, gameDuration uint64) (*bindings.DisputeGameFactory, *big.Int) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel() defer cancel()
// Deploy the proxy // Deploy the proxy
...@@ -51,14 +51,6 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context ...@@ -51,14 +51,6 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context
alphaVMAddr, err := bind.WaitDeployed(ctx, client, tx) alphaVMAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err) require.NoError(err)
// Deploy the block hash oracle
_, tx, _, err = bindings.DeployBlockOracle(opts, client)
require.NoError(err)
blockHashOracleAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
blockHashOracle, err := bindings.NewBlockOracle(blockHashOracleAddr, client)
require.NoError(err)
// Deploy the L2 output oracle // Deploy the L2 output oracle
_, tx, _, err = bindings.DeployL2OutputOracle( _, tx, _, err = bindings.DeployL2OutputOracle(
opts, opts,
...@@ -77,15 +69,17 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context ...@@ -77,15 +69,17 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context
l2OutputOracle, err := bindings.NewL2OutputOracle(l2OutputOracleAddr, client) l2OutputOracle, err := bindings.NewL2OutputOracle(l2OutputOracleAddr, client)
require.NoError(err) require.NoError(err)
// Deploy the fault dispute game implementation // Deploy the block hash oracle
_, tx, _, err = bindings.DeployFaultDisputeGame(opts, client, alphabetVMAbsolutePrestateClaim, big.NewInt(alphabetGameDepth), gameDuration, alphaVMAddr, l2OutputOracleAddr, blockHashOracleAddr) _, tx, _, err = bindings.DeployBlockOracle(opts, client)
require.NoError(err) require.NoError(err)
faultDisputeGameAddr, err := bind.WaitDeployed(ctx, client, tx) blockHashOracleAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
blockHashOracle, err := bindings.NewBlockOracle(blockHashOracleAddr, client)
require.NoError(err) require.NoError(err)
// Propose 2 outputs // Propose 2 outputs
for i := uint8(0); i < 2; i++ { for i := uint8(0); i < 2; i++ {
nextBlockNumber, err := l2OutputOracle.NextBlockNumber(nil) nextBlockNumber, err := l2OutputOracle.NextBlockNumber(&bind.CallOpts{Pending: true, Context: ctx})
require.NoError(err) require.NoError(err)
block, err := client.BlockByNumber(ctx, big.NewInt(int64(i))) block, err := client.BlockByNumber(ctx, big.NewInt(int64(i)))
require.NoError(err) require.NoError(err)
...@@ -97,18 +91,36 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context ...@@ -97,18 +91,36 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context
} }
// Store the current block in the oracle // Store the current block in the oracle
blockNo, err := client.BlockNumber(ctx) tx, err = blockHashOracle.Checkpoint(opts)
require.NoError(err)
tx, err = blockHashOracle.Store(opts, big.NewInt(int64(blockNo)))
require.NoError(err) require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash()) r, err := utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err, "failed to store block in blockoracle") require.NoError(err, "failed to store block in blockoracle")
_, tx, _, err = bindings.DeployMIPS(opts, client)
require.NoError(err)
_, err = bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Deploy the fault dispute game implementation
_, tx, _, err = bindings.DeployFaultDisputeGame(
opts,
client,
alphabetVMAbsolutePrestateClaim,
big.NewInt(alphabetGameDepth),
gameDuration,
alphaVMAddr,
l2OutputOracleAddr,
blockHashOracleAddr,
)
require.NoError(err)
faultDisputeGameAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Set the fault game type implementation // Set the fault game type implementation
tx, err = factory.SetImplementation(opts, faultGameType, faultDisputeGameAddr) tx, err = factory.SetImplementation(opts, faultGameType, faultDisputeGameAddr)
require.NoError(err) require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash()) _, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err, "wait for final transaction to be included and OK") require.NoError(err, "wait for final transaction to be included and OK")
return factory, blockNo return factory, new(big.Int).Sub(r.BlockNumber, big.NewInt(1))
} }
...@@ -44,7 +44,7 @@ type FactoryHelper struct { ...@@ -44,7 +44,7 @@ type FactoryHelper struct {
client *ethclient.Client client *ethclient.Client
opts *bind.TransactOpts opts *bind.TransactOpts
factory *bindings.DisputeGameFactory factory *bindings.DisputeGameFactory
l1Head uint64 l1Head *big.Int
} }
func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Client, gameDuration uint64) *FactoryHelper { func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Client, gameDuration uint64) *FactoryHelper {
...@@ -74,7 +74,7 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -74,7 +74,7 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
h.require.NoError(err, "get root claim") h.require.NoError(err, "get root claim")
extraData := make([]byte, 64) extraData := make([]byte, 64)
binary.BigEndian.PutUint64(extraData[24:], uint64(3600)) binary.BigEndian.PutUint64(extraData[24:], uint64(3600))
binary.BigEndian.PutUint64(extraData[56:], h.l1Head) binary.BigEndian.PutUint64(extraData[56:], h.l1Head.Uint64())
tx, err := h.factory.Create(h.opts, faultGameType, rootClaim, extraData) tx, err := h.factory.Create(h.opts, faultGameType, rootClaim, extraData)
h.require.NoError(err, "create fault dispute game") h.require.NoError(err, "create fault dispute game")
rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash()) rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
......
...@@ -16,7 +16,8 @@ AssetReceiverTest:test_withdrawETHwithAmount_unauthorized_reverts() (gas: 10738) ...@@ -16,7 +16,8 @@ AssetReceiverTest:test_withdrawETHwithAmount_unauthorized_reverts() (gas: 10738)
AttestationStationTest:test_attest_bulk_succeeds() (gas: 703740) AttestationStationTest:test_attest_bulk_succeeds() (gas: 703740)
AttestationStationTest:test_attest_individual_succeeds() (gas: 632078) AttestationStationTest:test_attest_individual_succeeds() (gas: 632078)
AttestationStationTest:test_attest_single_succeeds() (gas: 651316) AttestationStationTest:test_attest_single_succeeds() (gas: 651316)
BlockOracle_Test:test_load_noBlockHash_reverts() (gas: 12849) BlockOracle_Test:test_checkpointAndLoad_succeeds() (gas: 51618)
BlockOracle_Test:test_load_noBlockHash_reverts() (gas: 12805)
Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9413) Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9413)
Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1430) Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1430)
Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17240) Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17240)
...@@ -102,7 +103,7 @@ FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10410) ...@@ -102,7 +103,7 @@ FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10410)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8266) FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8266)
FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 58226) FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 58226)
FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210532) FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210532)
FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 276399) FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228360)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415921) FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415921)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26428) FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26428)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13322) FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13322)
......
...@@ -14,33 +14,31 @@ contract BlockOracle { ...@@ -14,33 +14,31 @@ contract BlockOracle {
} }
/// @notice Maps block numbers to block hashes and timestamps /// @notice Maps block numbers to block hashes and timestamps
mapping(uint256 => BlockInfo) internal blockHashes; mapping(uint256 => BlockInfo) internal blocks;
/// @notice Loads a block hash for a given block number, assuming that the block number /// @notice Loads a block hash for a given block number, assuming that the block number
/// has been stored in the oracle. /// has been stored in the oracle.
/// @param _blockNumber The block number to load the block hash and timestamp for. /// @param _blockNumber The block number to load the block hash and timestamp for.
/// @return blockInfo_ The block hash and timestamp for the given block number. /// @return blockInfo_ The block hash and timestamp for the given block number.
function load(uint256 _blockNumber) external view returns (BlockInfo memory blockInfo_) { function load(uint256 _blockNumber) external view returns (BlockInfo memory blockInfo_) {
blockInfo_ = blockHashes[_blockNumber]; blockInfo_ = blocks[_blockNumber];
if (Hash.unwrap(blockInfo_.hash) == 0) revert BlockHashNotPresent(); if (Hash.unwrap(blockInfo_.hash) == 0) revert BlockHashNotPresent();
} }
/// @notice Stores a block hash for a given block number, assuming that the block number /// @notice Stores a block hash for the previous block number, assuming that the block number
/// is within the acceptable range of [tip - 256, tip]. /// is within the acceptable range of [tip - 256, tip].
/// @param _blockNumber The block number to persist the block hash for. function checkpoint() external returns (uint256 blockNumber_) {
function store(uint256 _blockNumber) external {
// Fetch the block hash for the given block number and revert if it is out of // Fetch the block hash for the given block number and revert if it is out of
// the `BLOCKHASH` opcode's range. // the `BLOCKHASH` opcode's range.
bytes32 blockHash = blockhash(_blockNumber); // SAFETY: This block hash will always be accessible by the `BLOCKHASH` opcode,
if (blockHash == 0) revert BlockNumberOOB(); // and in the case of `block.number = 0`, we'll underflow.
bytes32 blockHash = blockhash(blockNumber_ = block.number - 1);
// Estimate the timestamp of the block assuming an average block time of 13 seconds. // Estimate the timestamp of the block assuming an average block time of 13 seconds.
Timestamp estimatedTimestamp = Timestamp.wrap( Timestamp estimatedTimestamp = Timestamp.wrap(uint64(block.timestamp - 13));
uint64(block.timestamp - ((block.number - _blockNumber) * 13))
);
// Persist the block information. // Persist the block information.
blockHashes[_blockNumber] = BlockInfo({ blocks[blockNumber_] = BlockInfo({
hash: Hash.wrap(blockHash), hash: Hash.wrap(blockHash),
timestamp: estimatedTimestamp timestamp: estimatedTimestamp
}); });
......
...@@ -478,7 +478,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver { ...@@ -478,7 +478,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
// the game cannot be played. // the game cannot be played.
// TODO(clabby): The block timestamp in the oracle is an estimate that assumes a 13 // TODO(clabby): The block timestamp in the oracle is an estimate that assumes a 13
// second block time. Should we add a buffer here to ensure that the // second block time. Should we add a buffer here to ensure that the
// estimation has room for error? This invariant cannot break. // estimation has room for error? This invariant cannot break. We could
// add the L1 block number to the `Types.OutputProposal` type as well,
// which would remove the need for estimation.
if (Timestamp.unwrap(blockInfo.timestamp) < disputed.timestamp) revert L1HeadTooOld(); if (Timestamp.unwrap(blockInfo.timestamp) < disputed.timestamp) revert L1HeadTooOld();
// Persist the output proposals fetched from the oracle. These outputs will be referenced // Persist the output proposals fetched from the oracle. These outputs will be referenced
......
...@@ -11,36 +11,19 @@ contract BlockOracle_Test is Test { ...@@ -11,36 +11,19 @@ contract BlockOracle_Test is Test {
function setUp() public { function setUp() public {
oracle = new BlockOracle(); oracle = new BlockOracle();
// Roll the chain forward 255 blocks. // Roll the chain forward 1 block.
vm.roll(block.number + 255); vm.roll(block.number + 1);
// Set the time to a realistic date. vm.warp(block.timestamp + 13);
vm.warp(1690906994);
} }
/// @notice Tests that loading a block hash for a block number within the range of the /// @notice Tests that checkpointing a block and loading its information succeeds.
/// `BLOCKHASH` opcode succeeds. function test_checkpointAndLoad_succeeds() public {
function testFuzz_store_succeeds(uint256 _blockNumber) public { oracle.checkpoint();
_blockNumber = bound(_blockNumber, 0, 255); uint256 blockNumber = block.number - 1;
oracle.store(_blockNumber); BlockOracle.BlockInfo memory res = oracle.load(blockNumber);
BlockOracle.BlockInfo memory res = oracle.load(_blockNumber); assertEq(Hash.unwrap(res.hash), blockhash(blockNumber));
assertEq(Hash.unwrap(res.hash), blockhash(_blockNumber)); assertEq(Timestamp.unwrap(res.timestamp), block.timestamp - 13);
assertEq(Timestamp.unwrap(res.timestamp), block.timestamp - ((block.number - _blockNumber) * 13));
}
/// @notice Tests that loading a block hash for a block number outside the range of the
/// `BLOCKHASH` opcode fails.
function testFuzz_store_oob_reverts(uint256 _blockNumber) public {
// Fast forward another 256 blocks.
vm.roll(block.number + 256);
// Bound the block number to the set { 0, ..., 255 } ∪ { 512, ..., type(uint256).max }
_blockNumber = _blockNumber % 2 == 0
? bound(_blockNumber, 0, 255)
: bound(_blockNumber, 512, type(uint256).max);
// Attempt to load the block hash, which should fail.
vm.expectRevert(BlockNumberOOB.selector);
oracle.store(_blockNumber);
} }
/// @notice Tests that the `load` function reverts if the block hash for the given block /// @notice Tests that the `load` function reverts if the block hash for the given block
......
...@@ -53,7 +53,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init { ...@@ -53,7 +53,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
// Deploy a new block hash oracle and store the block hash for the genesis block. // Deploy a new block hash oracle and store the block hash for the genesis block.
BlockOracle blockOracle = new BlockOracle(); BlockOracle blockOracle = new BlockOracle();
blockOracle.store(block.number - 1); blockOracle.checkpoint();
// Set the extra data for the game creation // Set the extra data for the game creation
extraData = abi.encode(oracle.SUBMISSION_INTERVAL() * 2, block.number - 1); extraData = abi.encode(oracle.SUBMISSION_INTERVAL() * 2, block.number - 1);
...@@ -127,8 +127,9 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -127,8 +127,9 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// @dev Tests that a game cannot be created by the factory if the L1 head hash does not /// @dev Tests that a game cannot be created by the factory if the L1 head hash does not
/// contain the disputed L2 output root. /// contain the disputed L2 output root.
function test_initialize_l1HeadTooOld_reverts() public { function test_initialize_l1HeadTooOld_reverts() public {
gameProxy.BLOCK_ORACLE().store(block.number - 128); // Store a mock block hash for the genesis block. The timestamp will default to 0.
bytes memory _extraData = abi.encode(oracle.SUBMISSION_INTERVAL() * 2, block.number - 128); vm.store(address(gameImpl.BLOCK_ORACLE()), keccak256(abi.encode(0, 0)), bytes32(uint256(1)));
bytes memory _extraData = abi.encode(oracle.SUBMISSION_INTERVAL() * 2, 0);
vm.expectRevert(L1HeadTooOld.selector); vm.expectRevert(L1HeadTooOld.selector);
factory.create(GAME_TYPE, ROOT_CLAIM, _extraData); factory.create(GAME_TYPE, ROOT_CLAIM, _extraData);
......
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