Commit 9d9a79a3 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6900 from ethereum-optimism/clabby/ctb/dispute-game-type-storage

feat(ctb): Pack `GameType` in DGF's `GameId` type
parents 4b700774 f077424a
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -32,10 +32,30 @@ type TraceType string
const (
TraceTypeAlphabet TraceType = "alphabet"
TraceTypeCannon TraceType = "cannon"
// Devnet game IDs
DevnetGameIDAlphabet = uint8(0)
DevnetGameIDCannon = uint8(1)
// Mainnet game IDs
MainnetGameIDFault = uint8(0)
)
var TraceTypes = []TraceType{TraceTypeAlphabet, TraceTypeCannon}
// GameIdToString maps game IDs to their string representation on a per-network basis.
var GameIdToString = map[uint64]map[uint8]string{
// Mainnet
1: {
MainnetGameIDFault: "fault-cannon",
},
// Devnet
900: {
DevnetGameIDAlphabet: "fault-alphabet",
DevnetGameIDCannon: "fault-cannon",
},
}
func (t TraceType) String() string {
return string(t)
}
......
......@@ -19,14 +19,16 @@ var (
type MinimalDisputeGameFactoryCaller interface {
GameCount(opts *bind.CallOpts) (*big.Int, error)
GameAtIndex(opts *bind.CallOpts, _index *big.Int) (struct {
GameType uint8
Timestamp uint64
Proxy common.Address
Timestamp *big.Int
}, error)
}
type FaultDisputeGame struct {
GameType uint8
Timestamp uint64
Proxy common.Address
Timestamp *big.Int
}
// GameLoader is a minimal interface for fetching on chain dispute games.
......
......@@ -90,7 +90,7 @@ func generateMockGames(count uint64) []FaultDisputeGame {
for i := uint64(0); i < count; i++ {
games[i] = FaultDisputeGame{
Proxy: common.BigToAddress(big.NewInt(int64(i))),
Timestamp: big.NewInt(int64(i)),
Timestamp: i,
}
}
......@@ -151,22 +151,26 @@ func (m *mockMinimalDisputeGameFactoryCaller) GameCount(opts *bind.CallOpts) (*b
}
func (m *mockMinimalDisputeGameFactoryCaller) GameAtIndex(opts *bind.CallOpts, _index *big.Int) (struct {
GameType uint8
Timestamp uint64
Proxy common.Address
Timestamp *big.Int
}, error) {
index := _index.Uint64()
if m.indexErrors[index] {
return struct {
GameType uint8
Timestamp uint64
Proxy common.Address
Timestamp *big.Int
}{}, gameIndexErr
}
return struct {
GameType uint8
Timestamp uint64
Proxy common.Address
Timestamp *big.Int
}{
Proxy: m.games[index].Proxy,
GameType: m.games[index].GameType,
Timestamp: m.games[index].Timestamp,
Proxy: m.games[index].Proxy,
}, nil
}
......@@ -8,6 +8,8 @@ import { Semver } from "src/universal/Semver.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { LibGameId } from "src/dispute/lib/LibGameId.sol";
import "src/libraries/DisputeTypes.sol";
import "src/libraries/DisputeErrors.sol";
......@@ -35,7 +37,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
GameId[] internal _disputeGameList;
/// @notice constructs a new DisputeGameFactory contract.
constructor() OwnableUpgradeable() Semver(0, 0, 4) {
constructor() OwnableUpgradeable() Semver(0, 0, 5) {
initialize(address(0));
}
......@@ -59,61 +61,67 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
)
external
view
returns (IDisputeGame proxy_, uint256 timestamp_)
returns (IDisputeGame proxy_, Timestamp timestamp_)
{
Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
GameId slot = _disputeGames[uuid];
(address addr, uint256 timestamp) = _unpackSlot(slot);
proxy_ = IDisputeGame(addr);
timestamp_ = timestamp;
(, timestamp_, proxy_) = _disputeGames[uuid].unpack();
}
/// @inheritdoc IDisputeGameFactory
function gameAtIndex(uint256 _index) external view returns (IDisputeGame proxy_, uint256 timestamp_) {
GameId slot = _disputeGameList[_index];
(address addr, uint256 timestamp) = _unpackSlot(slot);
proxy_ = IDisputeGame(addr);
timestamp_ = timestamp;
function gameAtIndex(uint256 _index)
external
view
returns (GameType gameType_, Timestamp timestamp_, IDisputeGame proxy_)
{
(gameType_, timestamp_, proxy_) = _disputeGameList[_index].unpack();
}
/// @inheritdoc IDisputeGameFactory
function create(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
GameType _gameType,
Claim _rootClaim,
bytes calldata _extraData
)
external
returns (IDisputeGame proxy)
{
// Grab the implementation contract for the given `GameType`.
IDisputeGame impl = gameImpls[gameType];
IDisputeGame impl = gameImpls[_gameType];
// If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) revert NoImplementation(gameType);
if (address(impl) == address(0)) revert NoImplementation(_gameType);
// Clone the implementation contract and initialize it with the given parameters.
proxy = IDisputeGame(address(impl).clone(abi.encodePacked(rootClaim, extraData)));
proxy = IDisputeGame(address(impl).clone(abi.encodePacked(_rootClaim, _extraData)));
proxy.initialize();
// Compute the unique identifier for the dispute game.
Hash uuid = getGameUUID(gameType, rootClaim, extraData);
Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
// If a dispute game with the same UUID already exists, revert.
if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);
GameId slot = _packSlot(address(proxy), block.timestamp);
GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), proxy);
// Store the dispute game in the mapping & emit the `DisputeGameCreated` event.
_disputeGames[uuid] = slot;
_disputeGameList.push(slot);
emit DisputeGameCreated(address(proxy), gameType, rootClaim);
// Store the dispute game id in the mapping & emit the `DisputeGameCreated` event.
_disputeGames[uuid] = id;
_disputeGameList.push(id);
emit DisputeGameCreated(address(proxy), _gameType, _rootClaim);
}
/// @inheritdoc IDisputeGameFactory
function getGameUUID(GameType gameType, Claim rootClaim, bytes memory extraData) public pure returns (Hash _uuid) {
function getGameUUID(
GameType _gameType,
Claim _rootClaim,
bytes memory _extraData
)
public
pure
returns (Hash _uuid)
{
assembly {
// Grab the offsets of the other memory locations we will need to temporarily overwrite.
let gameTypeOffset := sub(extraData, 0x60)
let gameTypeOffset := sub(_extraData, 0x60)
let rootClaimOffset := add(gameTypeOffset, 0x20)
let pointerOffset := add(rootClaimOffset, 0x20)
......@@ -124,13 +132,13 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
let tempC := mload(pointerOffset)
// Overwrite the memory with the data we want to hash
mstore(gameTypeOffset, gameType)
mstore(rootClaimOffset, rootClaim)
mstore(gameTypeOffset, _gameType)
mstore(rootClaimOffset, _rootClaim)
mstore(pointerOffset, 0x60)
// Compute the length of the memory to hash
// `0x60 + 0x20 + extraData.length` rounded to the *next* multiple of 32.
let hashLen := and(add(mload(extraData), 0x9F), not(0x1F))
let hashLen := and(add(mload(_extraData), 0x9F), not(0x1F))
// Hash the memory to produce the UUID digest
_uuid := keccak256(gameTypeOffset, hashLen)
......@@ -143,24 +151,8 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
}
/// @inheritdoc IDisputeGameFactory
function setImplementation(GameType gameType, IDisputeGame impl) external onlyOwner {
gameImpls[gameType] = impl;
emit ImplementationSet(address(impl), gameType);
}
/// @dev Packs an address and a uint256 into a single bytes32 slot. This
/// is only safe for up to uint96 values.
function _packSlot(address _addr, uint256 _num) internal pure returns (GameId slot_) {
assembly {
slot_ := or(shl(0xa0, _num), _addr)
}
}
/// @dev Unpacks an address and a uint256 from a single bytes32 slot.
function _unpackSlot(GameId _slot) internal pure returns (address addr_, uint256 num_) {
assembly {
addr_ := and(_slot, 0xffffffffffffffffffffffffffffffffffffffff)
num_ := shr(0xa0, _slot)
}
function setImplementation(GameType _gameType, IDisputeGame _impl) external onlyOwner {
gameImpls[_gameType] = _impl;
emit ImplementationSet(address(_impl), _gameType);
}
}
......@@ -20,8 +20,8 @@ interface IDisputeGameFactory {
event ImplementationSet(address indexed impl, GameType indexed gameType);
/// @notice The total number of dispute games created by this factory.
/// @return _gameCount The total number of dispute games created by this factory.
function gameCount() external view returns (uint256 _gameCount);
/// @return gameCount_ The total number of dispute games created by this factory.
function gameCount() external view returns (uint256 gameCount_);
/// @notice `games` queries an internal mapping that maps the hash of
/// `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
......@@ -29,9 +29,9 @@ interface IDisputeGameFactory {
/// @param gameType The type of the DisputeGame - used to decide the proxy implementation
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @return _proxy The clone of the `DisputeGame` created with the given parameters.
/// @return proxy_ The clone of the `DisputeGame` created with the given parameters.
/// Returns `address(0)` if nonexistent.
/// @return _timestamp The timestamp of the creation of the dispute game.
/// @return timestamp_ The timestamp of the creation of the dispute game.
function games(
GameType gameType,
Claim rootClaim,
......@@ -39,53 +39,57 @@ interface IDisputeGameFactory {
)
external
view
returns (IDisputeGame _proxy, uint256 _timestamp);
returns (IDisputeGame proxy_, Timestamp timestamp_);
/// @notice `gameAtIndex` returns the dispute game contract address and its creation timestamp
/// at the given index. Each created dispute game increments the underlying index.
/// @param _index The index of the dispute game.
/// @return _proxy The clone of the `DisputeGame` created with the given parameters.
/// @return gameType_ The type of the DisputeGame - used to decide the proxy implementation.
/// @return timestamp_ The timestamp of the creation of the dispute game.
/// @return proxy_ The clone of the `DisputeGame` created with the given parameters.
/// Returns `address(0)` if nonexistent.
/// @return _timestamp The timestamp of the creation of the dispute game.
function gameAtIndex(uint256 _index) external view returns (IDisputeGame _proxy, uint256 _timestamp);
function gameAtIndex(uint256 _index)
external
view
returns (GameType gameType_, Timestamp timestamp_, IDisputeGame proxy_);
/// @notice `gameImpls` is a mapping that maps `GameType`s to their respective
/// `IDisputeGame` implementations.
/// @param gameType The type of the dispute game.
/// @param _gameType The type of the dispute game.
/// @return _impl The address of the implementation of the game type.
/// Will be cloned on creation of a new dispute game with the given `gameType`.
function gameImpls(GameType gameType) external view returns (IDisputeGame _impl);
function gameImpls(GameType _gameType) external view returns (IDisputeGame _impl);
/// @notice Creates a new DisputeGame proxy contract.
/// @param gameType The type of the DisputeGame - used to decide the proxy implementation.
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @return proxy The address of the created DisputeGame proxy.
/// @param _gameType The type of the DisputeGame - used to decide the proxy implementation.
/// @param _rootClaim The root claim of the DisputeGame.
/// @param _extraData Any extra data that should be provided to the created dispute game.
/// @return proxy_ The address of the created DisputeGame proxy.
function create(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
GameType _gameType,
Claim _rootClaim,
bytes calldata _extraData
)
external
returns (IDisputeGame proxy);
returns (IDisputeGame proxy_);
/// @notice Sets the implementation contract for a specific `GameType`.
/// @dev May only be called by the `owner`.
/// @param gameType The type of the DisputeGame.
/// @param impl The implementation contract for the given `GameType`.
function setImplementation(GameType gameType, IDisputeGame impl) external;
/// @param _gameType The type of the DisputeGame.
/// @param _impl The implementation contract for the given `GameType`.
function setImplementation(GameType _gameType, IDisputeGame _impl) external;
/// @notice Returns a unique identifier for the given dispute game parameters.
/// @dev Hashes the concatenation of `gameType . rootClaim . extraData`
/// without expanding memory.
/// @param gameType The type of the DisputeGame.
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @param _gameType The type of the DisputeGame.
/// @param _rootClaim The root claim of the DisputeGame.
/// @param _extraData Any extra data that should be provided to the created dispute game.
/// @return _uuid The unique identifier for the given dispute game parameters.
function getGameUUID(
GameType gameType,
Claim rootClaim,
bytes memory extraData
GameType _gameType,
Claim _rootClaim,
bytes memory _extraData
)
external
pure
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "src/libraries/DisputeTypes.sol";
import "src/dispute/interfaces/IDisputeGame.sol";
/// @title LibGameId
/// @notice Utility functions for packing and unpacking GameIds.
library LibGameId {
/// @notice Packs values into a 32 byte GameId type.
/// @param _gameType The game type.
/// @param _timestamp The timestamp of the game's creation.
/// @param _gameProxy The game proxy address.
/// @return gameId_ The packed GameId.
function pack(
GameType _gameType,
Timestamp _timestamp,
IDisputeGame _gameProxy
)
internal
pure
returns (GameId gameId_)
{
assembly {
gameId_ := or(or(shl(248, _gameType), shl(184, _timestamp)), _gameProxy)
}
}
/// @notice Unpacks values from a 32 byte GameId type.
/// @param _gameId The packed GameId.
/// @return gameType_ The game type.
/// @return timestamp_ The timestamp of the game's creation.
/// @return gameProxy_ The game proxy address.
function unpack(GameId _gameId)
internal
pure
returns (GameType gameType_, Timestamp timestamp_, IDisputeGame gameProxy_)
{
assembly {
gameType_ := shr(248, _gameId)
timestamp_ := shr(184, and(_gameId, not(shl(248, 0xff))))
gameProxy_ := and(_gameId, 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
......@@ -4,10 +4,12 @@ pragma solidity ^0.8.15;
import { LibHashing } from "../dispute/lib/LibHashing.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol";
import { LibClock } from "../dispute/lib/LibClock.sol";
import { LibGameId } from "../dispute/lib/LibGameId.sol";
using LibHashing for Claim global;
using LibPosition for Position global;
using LibClock for Clock global;
using LibGameId for GameId global;
/// @notice A custom type for a generic hash.
type Hash is bytes32;
......@@ -29,14 +31,15 @@ type Timestamp is uint64;
/// @dev Unit: seconds
type Duration is uint64;
/// @notice A `GameId` represents a packed 12 byte timestamp and a 20 byte address.
/// @notice A `GameId` represents a packed 1 byte game ID, an 11 byte timestamp, and a 20 byte address.
/// @dev The packed layout of this type is as follows:
/// ┌────────────┬────────────────┐
/// │ Bits │ Value │
/// ├────────────┼────────────────┤
/// │ [0, 96) │ Timestamp │
/// │ [96, 256) │ Address │
/// └────────────┴────────────────┘
/// ┌───────────┬───────────┐
/// │ Bits │ Value │
/// ├───────────┼───────────┤
/// │ [0, 8) │ Game Type │
/// │ [8, 96) │ Timestamp │
/// │ [96, 256) │ Address │
/// └───────────┴───────────┘
type GameId is bytes32;
/// @notice A `Clock` represents a packed `Duration` and `Timestamp`
......
......@@ -51,16 +51,16 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
emit DisputeGameCreated(address(0), gt, rootClaim);
IDisputeGame proxy = factory.create(gt, rootClaim, extraData);
(IDisputeGame game, uint256 timestamp) = factory.games(gt, rootClaim, extraData);
(IDisputeGame game, Timestamp timestamp) = factory.games(gt, rootClaim, extraData);
// Ensure that the dispute game was assigned to the `disputeGames` mapping.
assertEq(address(game), address(proxy));
assertEq(timestamp, block.timestamp);
assertEq(Timestamp.unwrap(timestamp), block.timestamp);
assertEq(factory.gameCount(), 1);
(IDisputeGame game2, uint256 timestamp2) = factory.gameAtIndex(0);
(, Timestamp timestamp2, IDisputeGame game2) = factory.gameAtIndex(0);
assertEq(address(game2), address(proxy));
assertEq(timestamp2, block.timestamp);
assertEq(Timestamp.unwrap(timestamp2), block.timestamp);
}
/// @dev Tests that the `create` function reverts when there is no implementation
......@@ -88,10 +88,10 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
emit DisputeGameCreated(address(0), gt, rootClaim);
IDisputeGame proxy = factory.create(gt, rootClaim, extraData);
(IDisputeGame game, uint256 timestamp) = factory.games(gt, rootClaim, extraData);
(IDisputeGame game, Timestamp timestamp) = factory.games(gt, rootClaim, extraData);
// Ensure that the dispute game was assigned to the `disputeGames` mapping.
assertEq(address(game), address(proxy));
assertEq(timestamp, block.timestamp);
assertEq(Timestamp.unwrap(timestamp), block.timestamp);
// Ensure that the `create` function reverts when called with parameters that would result in the same UUID.
vm.expectRevert(
......@@ -161,37 +161,6 @@ contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init {
}
}
/// @title PackingTester
/// @notice Exposes the internal packing functions so that they can be fuzzed
/// in a roundtrip manner.
contract PackingTester is DisputeGameFactory {
function packSlot(address _addr, uint256 _num) external pure returns (GameId) {
return _packSlot(_addr, _num);
}
function unpackSlot(GameId _slot) external pure returns (address, uint256) {
return _unpackSlot(_slot);
}
}
/// @title DisputeGameFactory_PackSlot_Test
/// @notice Fuzzes the PackingTester contract
contract DisputeGameFactory_PackSlot_Test is Test {
PackingTester tester;
function setUp() public {
tester = new PackingTester();
}
/// @dev Tests that the `packSlot` and `unpackSlot` functions roundtrip correctly.
function testFuzz_packSlot_succeeds(address _addr, uint96 _num) public {
GameId slot = tester.packSlot(_addr, uint256(_num));
(address addr, uint256 num) = tester.unpackSlot(slot);
assertEq(addr, _addr);
assertEq(num, _num);
}
}
/// @dev A fake clone used for testing the `DisputeGameFactory` contract's `create` function.
contract FakeClone {
function initialize() external {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { LibGameId } from "src/dispute/lib/LibGameId.sol";
import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol";
import "src/libraries/DisputeTypes.sol";
contract LibGameId_Test is Test {
/// @dev Tests that a round trip of packing and unpacking a GameId maintains the same values.
function testFuzz_gameId_roundTrip_succeeds(
GameType _gameType,
Timestamp _timestamp,
IDisputeGame _gameProxy
)
public
{
GameId gameId = LibGameId.pack(_gameType, _timestamp, _gameProxy);
(GameType gameType_, Timestamp timestamp_, IDisputeGame gameProxy_) = LibGameId.unpack(gameId);
assertEq(GameType.unwrap(gameType_), GameType.unwrap(_gameType));
assertEq(Timestamp.unwrap(timestamp_), Timestamp.unwrap(_timestamp));
assertEq(address(gameProxy_), address(_gameProxy));
}
}
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