Skip to content
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

Closed
wants to merge 14 commits into from

Conversation

fakedev9999
Copy link
Member

@fakedev9999 fakedev9999 commented Feb 5, 2025

OPSuccinctEntryPoint

OPSuccinctEntryPoint is a contract that:

  1. Stores whitelisted addresses for proposers and challengers.
  2. Calls the disputeGameFactory to create, challenge, prove, and resolve OPSuccinctFaultDisputeGames.
  3. Holds credits for users when games are resolved.

OPSuccinctFaultDisputeGame

  • Feature for fast finality mode is added. Proposers can now create a game with a proof so that games can be resolved right after creation. This is done by passing additional proof bytes to extraData when creating a game.
  • claimCredit method is removed since credits are now managed in the entry point contract.

Tests

  • Tests are updated to use OPSuccinctEntryPoint.
  • New tests for permissioned proposer and challenger is added.
  • New test to ensure all actions should be done through entry point contract is added.

Copy link

github-actions bot commented Feb 5, 2025

Metric Value
Batch Start 23,716,530
Batch End 23,716,535
Witness Generation (seconds) 0
Execution Duration (seconds) 35
Total Instruction Count 1,445,699,898
Oracle Verify Cycles 248,638,831
Derivation Cycles 855,313,921
Block Execution Cycles 258,907,486
Blob Verification Cycles 109,478,689
Total SP1 Gas 0
Number of Blocks 5
Number of Transactions 39
Ethereum Gas Used 22,331,138
Cycles per Block 289,139,979
Cycles per Transaction 37,069,228
Transactions per Block 7
Gas Used per Block 4,466,227
Gas Used per Transaction 572,593
BN Pair Cycles 0
BN Add Cycles 0
BN Mul Cycles 0
KZG Eval Cycles 0
EC Recover Cycles 216,277
P256 Verify Cycles 0

contracts/src/fp/OPSuccinctEntryPoint.sol Outdated Show resolved Hide resolved
contracts/src/fp/OPSuccinctEntryPoint.sol Outdated Show resolved Hide resolved
@fakedev9999 fakedev9999 force-pushed the taehoon/implement-entrypoint-contract branch 4 times, most recently from 4a2474c to 82fa049 Compare February 10, 2025 22:16
@fakedev9999 fakedev9999 marked this pull request as ready for review February 11, 2025 00:44
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.
@fakedev9999 fakedev9999 force-pushed the taehoon/implement-entrypoint-contract branch from e2db1c4 to b5bb6ca Compare February 11, 2025 00:46
* 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.
Copy link
Member

@ratankaliani ratankaliani Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @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");
Copy link
Member

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.
Copy link
Member

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.
Copy link
Member

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;
Copy link
Member

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);

Copy link
Member

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.

Comment on lines 438 to 442
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) {
Copy link
Member

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();
Copy link
Member

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();
Copy link
Member

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();
Copy link
Member

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;
Copy link
Member

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();
Copy link
Member

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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.
Copy link
Member

@ratankaliani ratankaliani Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Copy link
Member

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Return the proposer's bond to the entry point contract.
// Send proposer's bond to the entrypoint contract so the proposer can claim it.

Comment on lines +154 to +161
// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add periods.

Copy link
Member

@ratankaliani ratankaliani left a 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;
Copy link
Member Author

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 {
Copy link
Member Author

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.
Copy link
Member Author

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 {
Copy link
Member Author

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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass address, not IDispute

@fakedev9999
Copy link
Member Author

With airgap, it's better to distribute bonds when claimCredit is called after resolve.

With no "Bank" feature in the EntryPoint contract, it's better to have a simple "AccessManager" contract with a very simple interface with isProposerWhitelisted, isChallengerWhitelisted. proposers, challengers.

It's not good to call create, challenge to the EntryPoint and resolve, prove to each games.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants