-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(contracts): implement OPSuccinctEntryPoint.sol
#371
Conversation
|
4a2474c
to
82fa049
Compare
Proposers can now create a game with a proof. If a valid proof is provided for game creation, the game can be resolved right after the game creation.
Now, in case of a game retirement and new game type being deployed, you can reference retired game type's games as a parent of new game type's games.
Allows to sweep funds across multiple games, instead of having to send claimReward tx for every game.
…s with no parents" This reverts commit 50b9768.
e2db1c4
to
b5bb6ca
Compare
* 1) Stores whitelisted addresses for proposers and challengers. | ||
* 2) Calls the `disputeGameFactory` to create, challenge, prove, | ||
* and resolve `OPSuccinctFaultDisputeGame`s. | ||
* 3) Holds credits for users when games are resolved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* 3) Holds credits for users when games are resolved. | |
* 3) Holds credits for users, which can be claimed by parties once games are resolved. |
//////////////////////////////////////////////////////////////// | ||
|
||
/// @notice Modifier to check if the caller is a whitelisted proposer. | ||
/// @dev Whitelisting zero address allows permissionless proposer system. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @dev Whitelisting zero address allows permissionless proposer system. | |
/// @dev If the zero address is marked as `true`, then permissionless proposing is enabled. |
} | ||
|
||
/// @notice Modifier to check if the caller is a whitelisted challenger. | ||
/// @dev Whitelisting zero address allows permissionless challenger system. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @dev Whitelisting zero address allows permissionless challenger system. | |
/// @dev If the zero address is marked as `true`, then permissionless challenging is enabled. |
|
||
/// @notice Allows the owner to recover by pulling ETHs out of the contract. | ||
function recover() external { | ||
require(msg.sender == owner(), "OPSuccinctEntryPoint: not owner"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an onlyOwner
modifier for this
OPSuccinctFaultDisputeGame(address(_game)).resolve(); | ||
} | ||
|
||
/// @notice Allows the owner to recover by pulling ETHs out of the contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allows the sender to recover all ETH from the contract.
OPSuccinctFaultDisputeGame(address(_game)).resolve(); | ||
} | ||
|
||
/// @notice Allows the owner to recover by pulling ETHs out of the contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add another method which allows reclaiming ETH from a specific user that's onlyOwner
gated, mirroring the logic inside of the OP FP contracts.
@@ -124,6 +127,9 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver { | |||
/// is the amount of the bond that is distributed to the prover when proven with a valid proof. | |||
uint256 internal immutable PROOF_REWARD; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't there be different rewards? Shouldn't these be configurable in the entrypoint?
OPSuccinctEntryPoint.addCredit.selector, claimData.counteredBy, address(this).balance | ||
) | ||
); | ||
if (!success) revert BondTransferFailed(); | ||
|
||
emit Resolved(status); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a comment after this function that at this point we know parentGameStatus = GameStatus.DEFENDER_WINS
.
if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); | ||
|
||
// INVARIANT: If the parent game's claim is invalid, then the current game's claim is invalid. | ||
// INVARIANT: If there is no challenger, i.e. `claimData.counteredBy == address(0)`, the bond is burnt. | ||
if (parentGameStatus == GameStatus.CHALLENGER_WINS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use if, else-if to make this more clear
OPSuccinctEntryPoint.addCredit.selector, claimData.counteredBy, address(this).balance | ||
) | ||
); | ||
if (!success) revert BondTransferFailed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add reasons for failure to transfer above this line.
(bool success,) = ENTRY_POINT.call{value: address(this).balance}( | ||
abi.encodeWithSelector(OPSuccinctEntryPoint.addCredit.selector, claimant(), address(this).balance) | ||
); | ||
if (!success) revert BondTransferFailed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add failure cases
OPSuccinctEntryPoint.addCredit.selector, claimData.counteredBy, address(this).balance | ||
) | ||
); | ||
if (!success) revert BondTransferFailed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add failure cases
OPSuccinctEntryPoint.addCredit.selector, claimData.counteredBy, address(this).balance | ||
) | ||
); | ||
if (!success) revert BondTransferFailed(); | ||
|
||
emit Resolved(status); | ||
} else if (claimData.status == ProposalStatus.UnchallengedAndValidProofProvided) { | ||
claimData.status = ProposalStatus.Resolved; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a clarifying comment here about the fact that when the game state is unchallenged, there's no reward for a third-party prover. The proposer just gets re-funded the bond. This prevents front-running attacks on the proposer.
@@ -304,7 +359,7 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver { | |||
if (msg.value != PROOF_REWARD) revert IncorrectBondAmount(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The challengeBond
is the prover's reward.
Proposer incentive: Don't create bad proposals.
Challenger incentive: Don't challenge proposals (otherwise you lose proof reward, which should be greater than cost to generate proof by significant amount).
Prover Incentive: Generate proofs if PROOF_REWARD
is high enough.
@@ -377,13 +438,19 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver { | |||
if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); | |||
|
|||
// INVARIANT: If the parent game's claim is invalid, then the current game's claim is invalid. | |||
// INVARIANT: If there is no challenger, i.e. `claimData.counteredBy == address(0)`, the bond is burnt. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// INVARIANT: If there is no challenger, i.e. `claimData.counteredBy == address(0)`, the bond is burnt. | |
// INVARIANT: If there is no challenger, i.e. `claimData.counteredBy == address(0)`, the bond will be stored in `address(0)` in the entrypoint. Can only be recovered by the owner of the entrypoint contract. |
|
||
emit Resolved(status); | ||
} else if (claimData.status == ProposalStatus.ChallengedAndValidProofProvided) { | ||
claimData.status = ProposalStatus.Resolved; | ||
status = GameStatus.DEFENDER_WINS; | ||
resolvedAt = Timestamp.wrap(uint64(block.timestamp)); | ||
|
||
// Record the proof reward for the prover | ||
credit[claimData.prover] += PROOF_REWARD; | ||
// Add prover's reward to the entry point contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Add prover's reward to the entry point contract. | |
// Send challenger's proving reward to the entry point contract for the prover to claim. |
|
||
// Record the remaining balance (proposer's bond) for the proposer | ||
credit[gameCreator()] += address(this).balance - PROOF_REWARD; | ||
// Return the proposer's bond to the entry point contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Return the proposer's bond to the entry point contract. | |
// Return the proposer's bond to the entry point contract where the proposer can claim it. |
@@ -19,3 +19,6 @@ error ParentGameNotResolved(); | |||
|
|||
/// @notice Thrown when the claim has already been proven. | |||
error AlreadyProven(); | |||
|
|||
/// @notice Thrown when actions are attempted without going through the entry point. | |||
error NotThroughEntryPoint(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error NotThroughEntryPoint(); | |
error SenderNotEntrypoint(); |
|
||
emit Resolved(status); | ||
} else if (claimData.status == ProposalStatus.UnchallengedAndValidProofProvided) { | ||
claimData.status = ProposalStatus.Resolved; | ||
status = GameStatus.DEFENDER_WINS; | ||
resolvedAt = Timestamp.wrap(uint64(block.timestamp)); | ||
|
||
// Record the proposer's reward | ||
credit[gameCreator()] += address(this).balance; | ||
// Return the proposer's bond to the entry point contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be explicit when saying it's returned to the entry point contract about who it's being returned to.
@@ -397,8 +464,11 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver { | |||
status = GameStatus.DEFENDER_WINS; | |||
resolvedAt = Timestamp.wrap(uint64(block.timestamp)); | |||
|
|||
// Record the proposer's reward | |||
credit[gameCreator()] += address(this).balance; | |||
// Return the proposer's bond to the entry point contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Return the proposer's bond to the entry point contract. | |
// Send proposer's bond to the entrypoint contract so the proposer can claim it. |
@@ -407,51 +477,50 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver { | |||
status = GameStatus.CHALLENGER_WINS; | |||
resolvedAt = Timestamp.wrap(uint64(block.timestamp)); | |||
|
|||
// Record the challenger's reward | |||
credit[claimData.counteredBy] += address(this).balance; | |||
// Add challenger's reward to the entry point contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Add challenger's reward to the entry point contract. | |
// Send the proposer's bond to the entry point contract so the challenger can claim the reward for a valid challenge. |
|
||
emit Resolved(status); | ||
} else if (claimData.status == ProposalStatus.UnchallengedAndValidProofProvided) { | ||
claimData.status = ProposalStatus.Resolved; | ||
status = GameStatus.DEFENDER_WINS; | ||
resolvedAt = Timestamp.wrap(uint64(block.timestamp)); | ||
|
||
// Record the proposer's reward | ||
credit[gameCreator()] += address(this).balance; | ||
// Return the proposer's bond to the entry point contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Return the proposer's bond to the entry point contract. | |
// Send proposer's bond to the entrypoint contract so the proposer can claim it. |
// Append address(msg.sender) to the extraData | ||
bytes memory extraDataWithClaimant = abi.encodePacked(_extraData, msg.sender); | ||
|
||
// Call the factory to create the game | ||
// Append address(msg.sender) to the extraData to record the claimant | ||
IDisputeGame newGame = disputeGameFactory.create{value: msg.value}(gameType, _rootClaim, extraDataWithClaimant); | ||
|
||
// Emit an event with the new game address and the rootClaim |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add periods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pending review from OP Labs, LGTM
* @dev Credit is added when a game is resolved. | ||
*/ | ||
function addCredit(address _user, uint256 _amount) external payable { | ||
credit[_user] += _amount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should check that the msg.value
is sent before adding _amount
.
* | ||
* @dev Credit is added when a game is resolved. | ||
*/ | ||
function addCredit(address _user, uint256 _amount) external payable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to pass the amount. You're just sending ETH.
* @notice Adds credit to a user's account. | ||
* | ||
* @param _user The address to add credit to. | ||
* @param _amount The amount of credit to add. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
* @notice Resolves an `OPSuccinctFaultDisputeGame`. | ||
* @dev Anyone can resolve a game. | ||
*/ | ||
function resolveGame(IDisputeGame _game) external { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass (address), not IDisputeGame
* @dev Only whitelisted challengers can call this function. | ||
* @dev Exact amount of ETH for proof reward is required when challenging. | ||
*/ | ||
function challengeGame(IDisputeGame _game) external payable onlyChallenger { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass address, not IDispute
With airgap, it's better to distribute bonds when With no "Bank" feature in the EntryPoint contract, it's better to have a simple "AccessManager" contract with a very simple interface with It's not good to call create, challenge to the EntryPoint and resolve, prove to each games. |
OPSuccinctEntryPoint
OPSuccinctEntryPoint
is a contract that:disputeGameFactory
to create, challenge, prove, and resolveOPSuccinctFaultDisputeGame
s.OPSuccinctFaultDisputeGame
extraData
when creating a game.claimCredit
method is removed since credits are now managed in the entry point contract.Tests