Skip to content

Add zk fault proof contracts#258

Open
redhdx wants to merge 17 commits intobnb-chain:developfrom
redhdx:zkfp
Open

Add zk fault proof contracts#258
redhdx wants to merge 17 commits intobnb-chain:developfrom
redhdx:zkfp

Conversation

@redhdx
Copy link
Copy Markdown
Contributor

@redhdx redhdx commented Jan 13, 2025

Description

add a description of your changes here...

Rationale

tell us why we need these changes...

Example

add an example CLI or API response...

Changes

Notable changes:

  • add each change in a bullet point here
  • ...

Comment on lines +151 to +178
function _validateGameCreation(
GameType _gameType,
Claim[] calldata _claims,
uint64 _parentGameIndex,
IDisputeGame impl
) private view returns (IDisputeGame parentProxy) {
// 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 IncorrectBondAmount();
if (_claims.length == 0) revert NoClaims();
// When the parent game index is the maximum value of a uint64, it means that there is no parent game.
// And use the state from anchor state registry.
if (_parentGameIndex != type(uint64).max && _parentGameIndex >= _disputeGameList.length) {
revert InvalidParentGameIndex();
}
if (_parentGameIndex != type(uint64).max) {
(GameType parentGameType, , address proxy) = _disputeGameList[_parentGameIndex].unpack();
parentProxy = IDisputeGame(proxy);
// The parent game must be of the same type as the child game.
if (parentGameType.raw() != _gameType.raw()) revert InvalidParentGameType();
if (parentProxy.status() == GameStatus.CHALLENGER_WINS) {
revert InvalidParentGameStatus();
}
} else {
parentProxy = IDisputeGame(address(0));
}
}

Check warning

Code scanning / Slither

Unused return

DisputeGameFactory._validateGameCreation(GameType,Claim[],uint64,IDisputeGame) (src/dispute/DisputeGameFactory.sol#151-178) ignores return value by (parentGameType,None,proxy) = _disputeGameList[_parentGameIndex].unpack() (src/dispute/DisputeGameFactory.sol#168)
Comment thread packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol Fixed
Comment thread packages/contracts-bedrock/src/dispute/ZkFaultDisputeGame.sol Fixed
Comment thread packages/contracts-bedrock/src/dispute/ZkFaultDisputeGame.sol Fixed
Comment on lines +382 to +468
function resolve() external returns (GameStatus status_) {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
GameStatus parentStatus = parentGameStatus();
// parent game must be resolved
if (parentStatus == GameStatus.IN_PROGRESS) {
revert ParentGameNotResolved();
}
if (parentStatus == GameStatus.CHALLENGER_WINS) {
status_ = GameStatus.CHALLENGER_WINS;
// re-update fault proof prover
faultProofProver = payable(parentGameProxy().gameWinner());
}
if (isChallengeSuccess) {
status_ = GameStatus.CHALLENGER_WINS;
}

if (status_ != GameStatus.CHALLENGER_WINS) {
// first check if the challenge window is expired
if (block.timestamp - createdAt.raw() <= MAX_CLOCK_DURATION.raw()) {
revert ClockNotExpired();
}
// then check if there is any remaining challenges
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
revert UnresolvedChallenges();
}
}
status_ = GameStatus.DEFENDER_WINS;
}

uint256 currentContractBalance = WETH.balanceOf(address(this));
if (status_ == GameStatus.CHALLENGER_WINS) {
// refund valid challengers if there is any
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
// refund the bond
challengers[challengedClaimIndexes[i]].transfer(CHALLENGER_BOND);
}
}
// TODO reward part of challengers bond to validity provers, current reward is zero
// reward the special challenger who submitted the signal which is proven to be valid
// 1. someone submitted a valid fault proof corresponding to the challenge index; or
// 2. the generate proof window is expired and no one submitted a validity proof
// If isChallengeSuccess is true, then the challenger exists;
// If isChallengeSuccess is false, then it indicates that the parent game is CHALLENGER_WINS and
// there is no successful challenge in the current game.
if (isChallengeSuccess) {
// there is a challenger who submmitted the dispute claim index by `challengeBySignal`
uint256 challengerBond = (currentContractBalance * CHALLENGER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
if (challengedClaims[successfulChallengeIndex]) {
_distributeBond(challengers[successfulChallengeIndex], challengerBond);
} else {
// if there is no challenger, then the challenger is the fault proof prover self
_distributeBond(faultProofProver, challengerBond);
}
currentContractBalance = currentContractBalance - challengerBond;
}
// reward the fault proof prover
uint256 proverBond = (currentContractBalance * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(faultProofProver, proverBond);
currentContractBalance = currentContractBalance - proverBond;
} else if (status_ == GameStatus.DEFENDER_WINS) {
// reward part of challengers bond to validity provers
for (uint256 i = 0; i < invalidChallengeClaimIndexes.length; i++) {
uint256 proverBond = (CHALLENGER_BOND * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i]], proverBond);
currentContractBalance = currentContractBalance - proverBond;
}
// refund the bond to proposer
_distributeBond(gameCreator(), PROPOSER_BOND);
currentContractBalance = currentContractBalance - PROPOSER_BOND;
} else {
// sanity check
revert InvalidGameStatus();
}
// transfer the rest
_distributeBond(FEE_VAULT_ADDRESS, currentContractBalance);

resolvedAt = Timestamp.wrap(uint64(block.timestamp));

// Update the status and emit the resolved event, note that we're performing an assignment here.
emit Resolved(status = status_);

// Try to update the anchor state, this should not revert.
ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
}

Check warning

Code scanning / Slither

Reentrancy vulnerabilities

Reentrancy in ZkFaultDisputeGame.resolve() (src/dispute/ZkFaultDisputeGame.sol#382-468): External calls: - _distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i_scope_1]],proverBond_scope_2) (src/dispute/ZkFaultDisputeGame.sol#448) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#533) - _distributeBond(gameCreator(),PROPOSER_BOND) (src/dispute/ZkFaultDisputeGame.sol#452) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#533) State variables written after the call(s): - _distributeBond(gameCreator(),PROPOSER_BOND) (src/dispute/ZkFaultDisputeGame.sol#452) - credit[_recipient] += _bond (src/dispute/ZkFaultDisputeGame.sol#530) ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64) can be used in cross function reentrancies: - ZkFaultDisputeGame._distributeBond(address,uint256) (src/dispute/ZkFaultDisputeGame.sol#528-534) - ZkFaultDisputeGame.claimCredit(address) (src/dispute/ZkFaultDisputeGame.sol#542-556) - ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64)
Comment on lines +383 to +471
function resolve() external returns (GameStatus status_) {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
GameStatus parentStatus = parentGameStatus();
// parent game must be resolved
if (parentStatus == GameStatus.IN_PROGRESS) {
revert ParentGameNotResolved();
}
if (parentStatus == GameStatus.CHALLENGER_WINS) {
status_ = GameStatus.CHALLENGER_WINS;
// re-update fault proof prover
faultProofProver = payable(parentGameProxy().gameWinner());
}
if (isChallengeSuccess) {
status_ = GameStatus.CHALLENGER_WINS;
}

if (status_ != GameStatus.CHALLENGER_WINS) {
// first check if the challenge window is expired
if (block.timestamp - createdAt.raw() <= MAX_CLOCK_DURATION.raw()) {
revert ClockNotExpired();
}
// then check if there is any remaining challenges
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
revert UnresolvedChallenges();
}
}
status_ = GameStatus.DEFENDER_WINS;
}

uint256 currentContractBalance = WETH.balanceOf(address(this));
if (status_ == GameStatus.CHALLENGER_WINS) {
// refund valid challengers if there is any
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
// refund the bond
_distributeBond(challengers[challengedClaimIndexes[i]], CHALLENGER_BOND);
currentContractBalance = currentContractBalance - CHALLENGER_BOND;
}
}
// TODO reward part of challengers bond to validity provers, current reward is zero
uint256 initialBalance = currentContractBalance;
// reward the special challenger who submitted the signal which is proven to be valid
// 1. someone submitted a valid fault proof corresponding to the challenge index; or
// 2. the generate proof window is expired and no one submitted a validity proof
// If isChallengeSuccess is true, then the challenger exists;
// If isChallengeSuccess is false, then it indicates that the parent game is CHALLENGER_WINS and
// there is no successful challenge in the current game.
if (isChallengeSuccess) {
// there is a challenger who submmitted the dispute claim index by `challengeBySignal`
uint256 challengerBond = (currentContractBalance * CHALLENGER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
if (challengedClaims[successfulChallengeIndex]) {
_distributeBond(challengers[successfulChallengeIndex], challengerBond);
} else {
// if there is no challenger, then the challenger is the fault proof prover self
_distributeBond(faultProofProver, challengerBond);
}
currentContractBalance = currentContractBalance - challengerBond;
}
// reward the fault proof prover
uint256 proverBond = (initialBalance * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(faultProofProver, proverBond);
currentContractBalance = currentContractBalance - proverBond;
} else if (status_ == GameStatus.DEFENDER_WINS) {
// reward part of challengers bond to validity provers
for (uint256 i = 0; i < invalidChallengeClaimIndexes.length; i++) {
uint256 proverBond = (CHALLENGER_BOND * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i]], proverBond);
currentContractBalance = currentContractBalance - proverBond;
}
// refund the bond to proposer
_distributeBond(gameCreator(), PROPOSER_BOND);
currentContractBalance = currentContractBalance - PROPOSER_BOND;
} else {
// sanity check
revert InvalidGameStatus();
}
// transfer the rest
_distributeBond(FEE_VAULT_ADDRESS, currentContractBalance);

resolvedAt = Timestamp.wrap(uint64(block.timestamp));

// Update the status and emit the resolved event, note that we're performing an assignment here.
emit Resolved(status = status_);

// Try to update the anchor state, this should not revert.
ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
}

Check warning

Code scanning / Slither

Reentrancy vulnerabilities

Reentrancy in ZkFaultDisputeGame.resolve() (src/dispute/ZkFaultDisputeGame.sol#383-471): External calls: - _distributeBond(challengers[challengedClaimIndexes[i_scope_0]],CHALLENGER_BOND) (src/dispute/ZkFaultDisputeGame.sol#420) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(challengers[successfulChallengeIndex],challengerBond) (src/dispute/ZkFaultDisputeGame.sol#436) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(faultProofProver,challengerBond) (src/dispute/ZkFaultDisputeGame.sol#439) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(faultProofProver,proverBond) (src/dispute/ZkFaultDisputeGame.sol#445) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i_scope_1]],proverBond_scope_2) (src/dispute/ZkFaultDisputeGame.sol#451) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(gameCreator(),PROPOSER_BOND) (src/dispute/ZkFaultDisputeGame.sol#455) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(FEE_VAULT_ADDRESS,currentContractBalance) (src/dispute/ZkFaultDisputeGame.sol#462) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) State variables written after the call(s): - _distributeBond(FEE_VAULT_ADDRESS,currentContractBalance) (src/dispute/ZkFaultDisputeGame.sol#462) - credit[_recipient] += _bond (src/dispute/ZkFaultDisputeGame.sol#533) ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64) can be used in cross function reentrancies: - ZkFaultDisputeGame._distributeBond(address,uint256) (src/dispute/ZkFaultDisputeGame.sol#531-537) - ZkFaultDisputeGame.claimCredit(address) (src/dispute/ZkFaultDisputeGame.sol#545-559) - ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64) - Resolved(status = status_) (src/dispute/ZkFaultDisputeGame.sol#467) ZkFaultDisputeGame.status (src/dispute/ZkFaultDisputeGame.sol#58) can be used in cross function reentrancies: - ZkFaultDisputeGame.challengeByProof(uint256,Claim,Claim[],bytes) (src/dispute/ZkFaultDisputeGame.sol#227-275) - ZkFaultDisputeGame.challengeBySignal(uint256) (src/dispute/ZkFaultDisputeGame.sol#277-292) - ZkFaultDisputeGame.gameWinner() (src/dispute/ZkFaultDisputeGame.sol#597-603) - ZkFaultDisputeGame.resolve() (src/dispute/ZkFaultDisputeGame.sol#383-471) - ZkFaultDisputeGame.resolveClaim() (src/dispute/ZkFaultDisputeGame.sol#344-366) - ZkFaultDisputeGame.status (src/dispute/ZkFaultDisputeGame.sol#58) - ZkFaultDisputeGame.submitProofForSignal(uint256,Claim[],bytes) (src/dispute/ZkFaultDisputeGame.sol#294-342)
Comment on lines +383 to +471
function resolve() external returns (GameStatus status_) {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
GameStatus parentStatus = parentGameStatus();
// parent game must be resolved
if (parentStatus == GameStatus.IN_PROGRESS) {
revert ParentGameNotResolved();
}
if (parentStatus == GameStatus.CHALLENGER_WINS) {
status_ = GameStatus.CHALLENGER_WINS;
// re-update fault proof prover
faultProofProver = payable(parentGameProxy().gameWinner());
}
if (isChallengeSuccess) {
status_ = GameStatus.CHALLENGER_WINS;
}

if (status_ != GameStatus.CHALLENGER_WINS) {
// first check if the challenge window is expired
if (block.timestamp - createdAt.raw() <= MAX_CLOCK_DURATION.raw()) {
revert ClockNotExpired();
}
// then check if there is any remaining challenges
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
revert UnresolvedChallenges();
}
}
status_ = GameStatus.DEFENDER_WINS;
}

uint256 currentContractBalance = WETH.balanceOf(address(this));
if (status_ == GameStatus.CHALLENGER_WINS) {
// refund valid challengers if there is any
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
// refund the bond
_distributeBond(challengers[challengedClaimIndexes[i]], CHALLENGER_BOND);
currentContractBalance = currentContractBalance - CHALLENGER_BOND;
}
}
// TODO reward part of challengers bond to validity provers, current reward is zero
uint256 initialBalance = currentContractBalance;
// reward the special challenger who submitted the signal which is proven to be valid
// 1. someone submitted a valid fault proof corresponding to the challenge index; or
// 2. the generate proof window is expired and no one submitted a validity proof
// If isChallengeSuccess is true, then the challenger exists;
// If isChallengeSuccess is false, then it indicates that the parent game is CHALLENGER_WINS and
// there is no successful challenge in the current game.
if (isChallengeSuccess) {
// there is a challenger who submmitted the dispute claim index by `challengeBySignal`
uint256 challengerBond = (currentContractBalance * CHALLENGER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
if (challengedClaims[successfulChallengeIndex]) {
_distributeBond(challengers[successfulChallengeIndex], challengerBond);
} else {
// if there is no challenger, then the challenger is the fault proof prover self
_distributeBond(faultProofProver, challengerBond);
}
currentContractBalance = currentContractBalance - challengerBond;
}
// reward the fault proof prover
uint256 proverBond = (initialBalance * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(faultProofProver, proverBond);
currentContractBalance = currentContractBalance - proverBond;
} else if (status_ == GameStatus.DEFENDER_WINS) {
// reward part of challengers bond to validity provers
for (uint256 i = 0; i < invalidChallengeClaimIndexes.length; i++) {
uint256 proverBond = (CHALLENGER_BOND * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i]], proverBond);
currentContractBalance = currentContractBalance - proverBond;
}
// refund the bond to proposer
_distributeBond(gameCreator(), PROPOSER_BOND);
currentContractBalance = currentContractBalance - PROPOSER_BOND;
} else {
// sanity check
revert InvalidGameStatus();
}
// transfer the rest
_distributeBond(FEE_VAULT_ADDRESS, currentContractBalance);

resolvedAt = Timestamp.wrap(uint64(block.timestamp));

// Update the status and emit the resolved event, note that we're performing an assignment here.
emit Resolved(status = status_);

// Try to update the anchor state, this should not revert.
ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
}

Check warning

Code scanning / Slither

Reentrancy vulnerabilities

Reentrancy in ZkFaultDisputeGame.resolve() (src/dispute/ZkFaultDisputeGame.sol#383-471): External calls: - _distributeBond(challengers[challengedClaimIndexes[i_scope_0]],CHALLENGER_BOND) (src/dispute/ZkFaultDisputeGame.sol#420) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(challengers[successfulChallengeIndex],challengerBond) (src/dispute/ZkFaultDisputeGame.sol#436) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) State variables written after the call(s): - _distributeBond(challengers[successfulChallengeIndex],challengerBond) (src/dispute/ZkFaultDisputeGame.sol#436) - credit[_recipient] += _bond (src/dispute/ZkFaultDisputeGame.sol#533) ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64) can be used in cross function reentrancies: - ZkFaultDisputeGame._distributeBond(address,uint256) (src/dispute/ZkFaultDisputeGame.sol#531-537) - ZkFaultDisputeGame.claimCredit(address) (src/dispute/ZkFaultDisputeGame.sol#545-559) - ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64)
Comment on lines +383 to +471
function resolve() external returns (GameStatus status_) {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
GameStatus parentStatus = parentGameStatus();
// parent game must be resolved
if (parentStatus == GameStatus.IN_PROGRESS) {
revert ParentGameNotResolved();
}
if (parentStatus == GameStatus.CHALLENGER_WINS) {
status_ = GameStatus.CHALLENGER_WINS;
// re-update fault proof prover
faultProofProver = payable(parentGameProxy().gameWinner());
}
if (isChallengeSuccess) {
status_ = GameStatus.CHALLENGER_WINS;
}

if (status_ != GameStatus.CHALLENGER_WINS) {
// first check if the challenge window is expired
if (block.timestamp - createdAt.raw() <= MAX_CLOCK_DURATION.raw()) {
revert ClockNotExpired();
}
// then check if there is any remaining challenges
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
revert UnresolvedChallenges();
}
}
status_ = GameStatus.DEFENDER_WINS;
}

uint256 currentContractBalance = WETH.balanceOf(address(this));
if (status_ == GameStatus.CHALLENGER_WINS) {
// refund valid challengers if there is any
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
// refund the bond
_distributeBond(challengers[challengedClaimIndexes[i]], CHALLENGER_BOND);
currentContractBalance = currentContractBalance - CHALLENGER_BOND;
}
}
// TODO reward part of challengers bond to validity provers, current reward is zero
uint256 initialBalance = currentContractBalance;
// reward the special challenger who submitted the signal which is proven to be valid
// 1. someone submitted a valid fault proof corresponding to the challenge index; or
// 2. the generate proof window is expired and no one submitted a validity proof
// If isChallengeSuccess is true, then the challenger exists;
// If isChallengeSuccess is false, then it indicates that the parent game is CHALLENGER_WINS and
// there is no successful challenge in the current game.
if (isChallengeSuccess) {
// there is a challenger who submmitted the dispute claim index by `challengeBySignal`
uint256 challengerBond = (currentContractBalance * CHALLENGER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
if (challengedClaims[successfulChallengeIndex]) {
_distributeBond(challengers[successfulChallengeIndex], challengerBond);
} else {
// if there is no challenger, then the challenger is the fault proof prover self
_distributeBond(faultProofProver, challengerBond);
}
currentContractBalance = currentContractBalance - challengerBond;
}
// reward the fault proof prover
uint256 proverBond = (initialBalance * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(faultProofProver, proverBond);
currentContractBalance = currentContractBalance - proverBond;
} else if (status_ == GameStatus.DEFENDER_WINS) {
// reward part of challengers bond to validity provers
for (uint256 i = 0; i < invalidChallengeClaimIndexes.length; i++) {
uint256 proverBond = (CHALLENGER_BOND * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i]], proverBond);
currentContractBalance = currentContractBalance - proverBond;
}
// refund the bond to proposer
_distributeBond(gameCreator(), PROPOSER_BOND);
currentContractBalance = currentContractBalance - PROPOSER_BOND;
} else {
// sanity check
revert InvalidGameStatus();
}
// transfer the rest
_distributeBond(FEE_VAULT_ADDRESS, currentContractBalance);

resolvedAt = Timestamp.wrap(uint64(block.timestamp));

// Update the status and emit the resolved event, note that we're performing an assignment here.
emit Resolved(status = status_);

// Try to update the anchor state, this should not revert.
ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
}

Check warning

Code scanning / Slither

Reentrancy vulnerabilities

Reentrancy in ZkFaultDisputeGame.resolve() (src/dispute/ZkFaultDisputeGame.sol#383-471): External calls: - _distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i_scope_1]],proverBond_scope_2) (src/dispute/ZkFaultDisputeGame.sol#451) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(gameCreator(),PROPOSER_BOND) (src/dispute/ZkFaultDisputeGame.sol#455) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) State variables written after the call(s): - _distributeBond(gameCreator(),PROPOSER_BOND) (src/dispute/ZkFaultDisputeGame.sol#455) - credit[_recipient] += _bond (src/dispute/ZkFaultDisputeGame.sol#533) ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64) can be used in cross function reentrancies: - ZkFaultDisputeGame._distributeBond(address,uint256) (src/dispute/ZkFaultDisputeGame.sol#531-537) - ZkFaultDisputeGame.claimCredit(address) (src/dispute/ZkFaultDisputeGame.sol#545-559) - ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64)
Comment on lines +383 to +471
function resolve() external returns (GameStatus status_) {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
GameStatus parentStatus = parentGameStatus();
// parent game must be resolved
if (parentStatus == GameStatus.IN_PROGRESS) {
revert ParentGameNotResolved();
}
if (parentStatus == GameStatus.CHALLENGER_WINS) {
status_ = GameStatus.CHALLENGER_WINS;
// re-update fault proof prover
faultProofProver = payable(parentGameProxy().gameWinner());
}
if (isChallengeSuccess) {
status_ = GameStatus.CHALLENGER_WINS;
}

if (status_ != GameStatus.CHALLENGER_WINS) {
// first check if the challenge window is expired
if (block.timestamp - createdAt.raw() <= MAX_CLOCK_DURATION.raw()) {
revert ClockNotExpired();
}
// then check if there is any remaining challenges
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
revert UnresolvedChallenges();
}
}
status_ = GameStatus.DEFENDER_WINS;
}

uint256 currentContractBalance = WETH.balanceOf(address(this));
if (status_ == GameStatus.CHALLENGER_WINS) {
// refund valid challengers if there is any
for (uint256 i = 0; i < challengedClaimIndexes.length; i++) {
if (!invalidChallengeClaims[challengedClaimIndexes[i]]) {
// refund the bond
_distributeBond(challengers[challengedClaimIndexes[i]], CHALLENGER_BOND);
currentContractBalance = currentContractBalance - CHALLENGER_BOND;
}
}
// TODO reward part of challengers bond to validity provers, current reward is zero
uint256 initialBalance = currentContractBalance;
// reward the special challenger who submitted the signal which is proven to be valid
// 1. someone submitted a valid fault proof corresponding to the challenge index; or
// 2. the generate proof window is expired and no one submitted a validity proof
// If isChallengeSuccess is true, then the challenger exists;
// If isChallengeSuccess is false, then it indicates that the parent game is CHALLENGER_WINS and
// there is no successful challenge in the current game.
if (isChallengeSuccess) {
// there is a challenger who submmitted the dispute claim index by `challengeBySignal`
uint256 challengerBond = (currentContractBalance * CHALLENGER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
if (challengedClaims[successfulChallengeIndex]) {
_distributeBond(challengers[successfulChallengeIndex], challengerBond);
} else {
// if there is no challenger, then the challenger is the fault proof prover self
_distributeBond(faultProofProver, challengerBond);
}
currentContractBalance = currentContractBalance - challengerBond;
}
// reward the fault proof prover
uint256 proverBond = (initialBalance * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(faultProofProver, proverBond);
currentContractBalance = currentContractBalance - proverBond;
} else if (status_ == GameStatus.DEFENDER_WINS) {
// reward part of challengers bond to validity provers
for (uint256 i = 0; i < invalidChallengeClaimIndexes.length; i++) {
uint256 proverBond = (CHALLENGER_BOND * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR;
_distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i]], proverBond);
currentContractBalance = currentContractBalance - proverBond;
}
// refund the bond to proposer
_distributeBond(gameCreator(), PROPOSER_BOND);
currentContractBalance = currentContractBalance - PROPOSER_BOND;
} else {
// sanity check
revert InvalidGameStatus();
}
// transfer the rest
_distributeBond(FEE_VAULT_ADDRESS, currentContractBalance);

resolvedAt = Timestamp.wrap(uint64(block.timestamp));

// Update the status and emit the resolved event, note that we're performing an assignment here.
emit Resolved(status = status_);

// Try to update the anchor state, this should not revert.
ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
}

Check warning

Code scanning / Slither

Reentrancy vulnerabilities

Reentrancy in ZkFaultDisputeGame.resolve() (src/dispute/ZkFaultDisputeGame.sol#383-471): External calls: - _distributeBond(challengers[challengedClaimIndexes[i_scope_0]],CHALLENGER_BOND) (src/dispute/ZkFaultDisputeGame.sol#420) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(challengers[successfulChallengeIndex],challengerBond) (src/dispute/ZkFaultDisputeGame.sol#436) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(faultProofProver,challengerBond) (src/dispute/ZkFaultDisputeGame.sol#439) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) - _distributeBond(faultProofProver,proverBond) (src/dispute/ZkFaultDisputeGame.sol#445) - WETH.unlock(_recipient,_bond) (src/dispute/ZkFaultDisputeGame.sol#536) State variables written after the call(s): - _distributeBond(faultProofProver,proverBond) (src/dispute/ZkFaultDisputeGame.sol#445) - credit[_recipient] += _bond (src/dispute/ZkFaultDisputeGame.sol#533) ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64) can be used in cross function reentrancies: - ZkFaultDisputeGame._distributeBond(address,uint256) (src/dispute/ZkFaultDisputeGame.sol#531-537) - ZkFaultDisputeGame.claimCredit(address) (src/dispute/ZkFaultDisputeGame.sol#545-559) - ZkFaultDisputeGame.credit (src/dispute/ZkFaultDisputeGame.sol#64)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants