Skip to content

Commit

Permalink
feat(ctb + proposer): Send initial bond when submitting outputs to DGF (
Browse files Browse the repository at this point in the history
ethereum-optimism#8821)

* Add initial bond to `DisputeGameFactory`

* Update proposer

* fdg semver

* fmt

* bindings + semver + slither

* @refcell nit
  • Loading branch information
clabby authored Jan 5, 2024
1 parent e496caa commit 19c79fe
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 34 deletions.
209 changes: 207 additions & 2 deletions op-bindings/bindings/disputegamefactory.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions op-bindings/bindings/disputegamefactory_more.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion op-bindings/bindings/faultdisputegame.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion op-bindings/bindings/faultdisputegame_more.go

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions op-proposer/proposer/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,16 @@ func proposeL2OutputTxData(abi *abi.ABI, output *eth.OutputResponse) ([]byte, er
new(big.Int).SetUint64(output.Status.CurrentL1.Number))
}

func (l *L2OutputSubmitter) ProposeL2OutputDGFTxData(output *eth.OutputResponse) ([]byte, error) {
return proposeL2OutputDGFTxData(l.dgfABI, l.Cfg.DisputeGameType, output)
func (l *L2OutputSubmitter) ProposeL2OutputDGFTxData(output *eth.OutputResponse) ([]byte, *big.Int, error) {
bond, err := l.dgfContract.InitBonds(&bind.CallOpts{}, l.Cfg.DisputeGameType)
if err != nil {
return nil, nil, err
}
data, err := proposeL2OutputDGFTxData(l.dgfABI, l.Cfg.DisputeGameType, output)
if err != nil {
return nil, nil, err
}
return data, bond, err
}

// proposeL2OutputDGFTxData creates the transaction data for the DisputeGameFactory's `create` function
Expand Down Expand Up @@ -356,14 +364,15 @@ func (l *L2OutputSubmitter) sendTransaction(ctx context.Context, output *eth.Out

var receipt *types.Receipt
if l.Cfg.DisputeGameFactoryAddr != nil {
data, err := l.ProposeL2OutputDGFTxData(output)
data, bond, err := l.ProposeL2OutputDGFTxData(output)
if err != nil {
return err
}
receipt, err = l.Txmgr.Send(ctx, txmgr.TxCandidate{
TxData: data,
To: l.Cfg.DisputeGameFactoryAddr,
GasLimit: 0,
Value: bond,
})
if err != nil {
return err
Expand Down
8 changes: 4 additions & 4 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@
"sourceCodeHash": "0x1afb1d392e8f6a58ff86ea7f648e0d1756d4ba8d0d964279d58a390deaa53b7e"
},
"src/dispute/DisputeGameFactory.sol": {
"initCodeHash": "0x79ede9274590494d776c9e2ecdf006cb7106f97490841ea85b52fc8eb18cd7b9",
"sourceCodeHash": "0x6e3a697764840cd30ca4b3e514528b832ce80bb412ac958b5ec6b751a07d735f"
"initCodeHash": "0xf6ab546550f8d3d85e9bde748eeea21f7fb16db3d63b8312c04598ca64c1be16",
"sourceCodeHash": "0x01c757935c87dcf2faa7f16bcf29cfb29b90c671a874f983067614670147b11d"
},
"src/dispute/FaultDisputeGame.sol": {
"initCodeHash": "0xe3c50aa8ab4ba7a4b4b6d5752a3a3acc2ca0efccbc086df8e00f85d4d696ab15",
"sourceCodeHash": "0xbc5e4e6ec9d4438494cacd6ed284456b41a9b6e090fa4b39a59cf122c8c3667d"
"initCodeHash": "0x2f30aa96d5c4d126d93dfe15bbfc90e1bbd6bc6208bcc4a49e250790c2a0918d",
"sourceCodeHash": "0x315508f5b100dcb6b81077fb8d6999de425b47f0dfd0330ac1973ee4df166f4a"
},
"src/legacy/DeployerWhitelist.sol": {
"initCodeHash": "0x8de80fb23b26dd9d849f6328e56ea7c173cd9e9ce1f05c9beea559d1720deb3d",
Expand Down
10 changes: 5 additions & 5 deletions packages/contracts-bedrock/slither-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@
"impact": "Medium",
"confidence": "Medium",
"check": "reentrancy-no-eth",
"description": "Reentrancy in FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191):\n\tExternal calls:\n\t- validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw() (src/dispute/FaultDisputeGame.sol#184)\n\tState variables written after the call(s):\n\t- parent.counteredBy = msg.sender (src/dispute/FaultDisputeGame.sol#190)\n\tFaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66) can be used in cross function reentrancies:\n\t- FaultDisputeGame._findStartingAndDisputedOutputs(uint256) (src/dispute/FaultDisputeGame.sol#664-723)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#637-655)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#512-514)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#454-509)\n\t- FaultDisputeGame.move(uint256,Claim,bool) (src/dispute/FaultDisputeGame.sol#197-288)\n\t- FaultDisputeGame.resolve() (src/dispute/FaultDisputeGame.sol#353-365)\n\t- FaultDisputeGame.resolveClaim(uint256) (src/dispute/FaultDisputeGame.sol#368-428)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"description": "Reentrancy in FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191):\n\tExternal calls:\n\t- validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw() (src/dispute/FaultDisputeGame.sol#184)\n\tState variables written after the call(s):\n\t- parent.counteredBy = msg.sender (src/dispute/FaultDisputeGame.sol#190)\n\tFaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66) can be used in cross function reentrancies:\n\t- FaultDisputeGame._findStartingAndDisputedOutputs(uint256) (src/dispute/FaultDisputeGame.sol#662-721)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#635-653)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#510-512)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#454-507)\n\t- FaultDisputeGame.move(uint256,Claim,bool) (src/dispute/FaultDisputeGame.sol#197-288)\n\t- FaultDisputeGame.resolve() (src/dispute/FaultDisputeGame.sol#353-365)\n\t- FaultDisputeGame.resolveClaim(uint256) (src/dispute/FaultDisputeGame.sol#368-428)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"type": "function",
"name": "step",
"start": 4981,
Expand All @@ -964,7 +964,7 @@
"impact": "Medium",
"confidence": "Medium",
"check": "reentrancy-no-eth",
"description": "Reentrancy in FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191):\n\tExternal calls:\n\t- validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw() (src/dispute/FaultDisputeGame.sol#184)\n\tState variables written after the call(s):\n\t- parent.counteredBy = msg.sender (src/dispute/FaultDisputeGame.sol#190)\n\tFaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66) can be used in cross function reentrancies:\n\t- FaultDisputeGame._findStartingAndDisputedOutputs(uint256) (src/dispute/FaultDisputeGame.sol#664-723)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#637-655)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#512-514)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#454-509)\n\t- FaultDisputeGame.move(uint256,Claim,bool) (src/dispute/FaultDisputeGame.sol#197-288)\n\t- FaultDisputeGame.resolve() (src/dispute/FaultDisputeGame.sol#353-365)\n\t- FaultDisputeGame.resolveClaim(uint256) (src/dispute/FaultDisputeGame.sol#368-428)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"description": "Reentrancy in FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191):\n\tExternal calls:\n\t- validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw() (src/dispute/FaultDisputeGame.sol#184)\n\tState variables written after the call(s):\n\t- parent.counteredBy = msg.sender (src/dispute/FaultDisputeGame.sol#190)\n\tFaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66) can be used in cross function reentrancies:\n\t- FaultDisputeGame._findStartingAndDisputedOutputs(uint256) (src/dispute/FaultDisputeGame.sol#662-721)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#635-653)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#510-512)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#454-507)\n\t- FaultDisputeGame.move(uint256,Claim,bool) (src/dispute/FaultDisputeGame.sol#197-288)\n\t- FaultDisputeGame.resolve() (src/dispute/FaultDisputeGame.sol#353-365)\n\t- FaultDisputeGame.resolveClaim(uint256) (src/dispute/FaultDisputeGame.sol#368-428)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"type": "node",
"name": "validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw()",
"start": 8761,
Expand All @@ -976,7 +976,7 @@
"impact": "Medium",
"confidence": "Medium",
"check": "reentrancy-no-eth",
"description": "Reentrancy in FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191):\n\tExternal calls:\n\t- validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw() (src/dispute/FaultDisputeGame.sol#184)\n\tState variables written after the call(s):\n\t- parent.counteredBy = msg.sender (src/dispute/FaultDisputeGame.sol#190)\n\tFaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66) can be used in cross function reentrancies:\n\t- FaultDisputeGame._findStartingAndDisputedOutputs(uint256) (src/dispute/FaultDisputeGame.sol#664-723)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#637-655)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#512-514)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#454-509)\n\t- FaultDisputeGame.move(uint256,Claim,bool) (src/dispute/FaultDisputeGame.sol#197-288)\n\t- FaultDisputeGame.resolve() (src/dispute/FaultDisputeGame.sol#353-365)\n\t- FaultDisputeGame.resolveClaim(uint256) (src/dispute/FaultDisputeGame.sol#368-428)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"description": "Reentrancy in FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191):\n\tExternal calls:\n\t- validStep = VM.step(_stateData,_proof,uuid.raw()) == postState.claim.raw() (src/dispute/FaultDisputeGame.sol#184)\n\tState variables written after the call(s):\n\t- parent.counteredBy = msg.sender (src/dispute/FaultDisputeGame.sol#190)\n\tFaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66) can be used in cross function reentrancies:\n\t- FaultDisputeGame._findStartingAndDisputedOutputs(uint256) (src/dispute/FaultDisputeGame.sol#662-721)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#635-653)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#510-512)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#454-507)\n\t- FaultDisputeGame.move(uint256,Claim,bool) (src/dispute/FaultDisputeGame.sol#197-288)\n\t- FaultDisputeGame.resolve() (src/dispute/FaultDisputeGame.sol#353-365)\n\t- FaultDisputeGame.resolveClaim(uint256) (src/dispute/FaultDisputeGame.sol#368-428)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"type": "node",
"name": "parent.counteredBy = msg.sender",
"start": 9171,
Expand Down Expand Up @@ -1072,10 +1072,10 @@
"impact": "Medium",
"confidence": "Medium",
"check": "uninitialized-local",
"description": "FaultDisputeGame._findStartingAndDisputedOutputs(uint256).currentDepth (src/dispute/FaultDisputeGame.sol#682) is a local variable never initialized\n",
"description": "FaultDisputeGame._findStartingAndDisputedOutputs(uint256).currentDepth (src/dispute/FaultDisputeGame.sol#680) is a local variable never initialized\n",
"type": "variable",
"name": "currentDepth",
"start": 32191,
"start": 32115,
"length": 20,
"filename_relative": "src/dispute/FaultDisputeGame.sol"
},
Expand Down
61 changes: 61 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/DisputeGameFactory.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "",
"type": "uint8"
}
],
"name": "initBonds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -208,6 +227,24 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "_initBond",
"type": "uint256"
}
],
"name": "setInitBond",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -278,6 +315,25 @@
"name": "ImplementationSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
},
{
"indexed": true,
"internalType": "uint256",
"name": "newBond",
"type": "uint256"
}
],
"name": "InitBondUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand Down Expand Up @@ -321,6 +377,11 @@
"name": "GameAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientBond",
"type": "error"
},
{
"inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,23 @@
},
{
"bytes": "32",
"label": "_disputeGames",
"label": "initBonds",
"offset": 0,
"slot": "102",
"type": "mapping(GameType => uint256)"
},
{
"bytes": "32",
"label": "_disputeGames",
"offset": 0,
"slot": "103",
"type": "mapping(Hash => GameId)"
},
{
"bytes": "32",
"label": "_disputeGameList",
"offset": 0,
"slot": "103",
"slot": "104",
"type": "GameId[]"
}
]
20 changes: 16 additions & 4 deletions packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver
/// @dev Allows for the creation of clone proxies with immutable arguments.
using ClonesWithImmutableArgs for address;

/// @notice Semantic version.
/// @custom:semver 0.0.8
string public constant version = "0.0.8";

/// @inheritdoc IDisputeGameFactory
mapping(GameType => IDisputeGame) public gameImpls;

/// @inheritdoc IDisputeGameFactory
mapping(GameType => uint256) public initBonds;

/// @notice Mapping of a hash of `gameType || rootClaim || extraData` to
/// the deployed `IDisputeGame` clone.
/// @dev Note: `||` denotes concatenation.
Expand All @@ -36,10 +43,6 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver
/// track dispute games
GameId[] internal _disputeGameList;

/// @notice Semantic version.
/// @custom:semver 0.0.7
string public constant version = "0.0.7";

/// @notice constructs a new DisputeGameFactory contract.
constructor() OwnableUpgradeable() {
initialize(address(0));
Expand Down Expand Up @@ -96,6 +99,9 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver
// If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) revert NoImplementation(_gameType);

// If the required initialization bond is not met, revert.
if (msg.value < initBonds[_gameType]) revert InsufficientBond();

// Clone the implementation contract and initialize it with the given parameters.
proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(_rootClaim, _extraData)));
proxy_.initialize{ value: msg.value }();
Expand Down Expand Up @@ -132,4 +138,10 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver
gameImpls[_gameType] = _impl;
emit ImplementationSet(address(_impl), _gameType);
}

/// @inheritdoc IDisputeGameFactory
function setInitBond(GameType _gameType, uint256 _initBond) external onlyOwner {
initBonds[_gameType] = _initBond;
emit InitBondUpdated(_gameType, _initBond);
}
}
8 changes: 3 additions & 5 deletions packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
bool internal initialized;

/// @notice Semantic version.
/// @custom:semver 0.0.23
string public constant version = "0.0.23";
/// @custom:semver 0.0.24
string public constant version = "0.0.24";

/// @param _gameType The type ID of the game.
/// @param _absolutePrestate The absolute prestate of the instruction trace.
Expand Down Expand Up @@ -457,6 +457,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
//
// Implicit assumptions:
// - The `gameStatus` state variable defaults to 0, which is `GameStatus.IN_PROGRESS`
// - The dispute game factory will enforce the required bond to initialize the game.
//
// Explicit checks:
// - The game must not have already been initialized.
Expand All @@ -482,9 +483,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
}
}

// INVARIANT: The `msg.value` must be sufficient to cover the required bond.
if (getRequiredBond(ROOT_POSITION) > msg.value) revert InsufficientBond();

// Set the root claim
claimData.push(
ClaimData({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ interface IDisputeGameFactory {
/// @param gameType The type of the DisputeGame.
event ImplementationSet(address indexed impl, GameType indexed gameType);

/// @notice Emitted when a game type's initialization bond is updated
/// @param gameType The type of the DisputeGame.
/// @param newBond The new bond (in wei) for initializing the game type.
event InitBondUpdated(GameType indexed gameType, uint256 indexed newBond);

/// @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_);
Expand Down Expand Up @@ -60,6 +65,11 @@ interface IDisputeGameFactory {
/// Will be cloned on creation of a new dispute game with the given `gameType`.
function gameImpls(GameType _gameType) external view returns (IDisputeGame impl_);

/// @notice Returns the required bonds for initializing a dispute game of the given type.
/// @param _gameType The type of the dispute game.
/// @return bond_ The required bond for initializing a dispute game of the given type.
function initBonds(GameType _gameType) external view returns (uint256 bond_);

/// @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.
Expand All @@ -80,6 +90,12 @@ interface IDisputeGameFactory {
/// @param _impl The implementation contract for the given `GameType`.
function setImplementation(GameType _gameType, IDisputeGame _impl) external;

/// @notice Sets the bond (in wei) for initializing a game type.
/// @dev May only be called by the `owner`.
/// @param _gameType The type of the DisputeGame.
/// @param _initBond The bond (in wei) for initializing a game type.
function setInitBond(GameType _gameType, uint256 _initBond) external;

/// @notice Returns a unique identifier for the given dispute game parameters.
/// @dev Hashes the concatenation of `gameType . rootClaim . extraData`
/// without expanding memory.
Expand Down
Loading

0 comments on commit 19c79fe

Please sign in to comment.