Commit a320015e authored by clabby's avatar clabby Committed by GitHub

fix(ctb): Bond payouts (#9259)

* Add latest game search to DGF

* Bond payout fix
parent 0c0b907b
This diff is collapsed.
......@@ -92,8 +92,8 @@
"sourceCodeHash": "0x78a030cd808a997f4b82c4fa284d6c394dae9d1fafeccb8783059a3b60bfb0ce"
},
"src/dispute/FaultDisputeGame.sol": {
"initCodeHash": "0xe410bdcc3f0e8e71ddb609d1a0c21a40e1b06debeb55de6aff1452c8dda4af0b",
"sourceCodeHash": "0x315508f5b100dcb6b81077fb8d6999de425b47f0dfd0330ac1973ee4df166f4a"
"initCodeHash": "0xccfcffe9a293c76f8e395677501b85bbc45854ea9235ec1b54ef8c78c672a87f",
"sourceCodeHash": "0x7c582f30c639bdc687bec06a8faf517a2b8338edefc02f8703b8c41e7f09c8af"
},
"src/legacy/DeployerWhitelist.sol": {
"initCodeHash": "0x8de80fb23b26dd9d849f6328e56ea7c173cd9e9ce1f05c9beea559d1720deb3d",
......
......@@ -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#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",
"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#668-727)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#641-659)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#516-518)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#460-513)\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-434)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"type": "function",
"name": "step",
"start": 4981,
......@@ -988,7 +988,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#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",
"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#668-727)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#641-659)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#516-518)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#460-513)\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-434)\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,
......@@ -1000,7 +1000,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#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",
"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#668-727)\n\t- FaultDisputeGame._findTraceAncestor(Position,uint256,bool) (src/dispute/FaultDisputeGame.sol#641-659)\n\t- FaultDisputeGame.claimData (src/dispute/FaultDisputeGame.sol#66)\n\t- FaultDisputeGame.claimDataLen() (src/dispute/FaultDisputeGame.sol#516-518)\n\t- FaultDisputeGame.initialize() (src/dispute/FaultDisputeGame.sol#460-513)\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-434)\n\t- FaultDisputeGame.step(uint256,bool,bytes,bytes) (src/dispute/FaultDisputeGame.sol#124-191)\n",
"type": "node",
"name": "parent.counteredBy = msg.sender",
"start": 9171,
......@@ -1132,10 +1132,10 @@
"impact": "Medium",
"confidence": "Medium",
"check": "uninitialized-local",
"description": "FaultDisputeGame._findStartingAndDisputedOutputs(uint256).currentDepth (src/dispute/FaultDisputeGame.sol#680) is a local variable never initialized\n",
"description": "FaultDisputeGame._findStartingAndDisputedOutputs(uint256).currentDepth (src/dispute/FaultDisputeGame.sol#686) is a local variable never initialized\n",
"type": "variable",
"name": "currentDepth",
"start": 32115,
"start": 32709,
"length": 20,
"filename_relative": "src/dispute/FaultDisputeGame.sol"
},
......
......@@ -81,8 +81,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
bool internal initialized;
/// @notice Semantic version.
/// @custom:semver 0.0.24
string public constant version = "0.0.24";
/// @custom:semver 0.0.25
string public constant version = "0.0.25";
/// @param _gameType The type ID of the game.
/// @param _absolutePrestate The absolute prestate of the instruction trace.
......@@ -389,7 +389,13 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
// Uncontested claims are resolved implicitly unless they are the root claim. Pay out the bond to the claimant
// and return early.
if (challengeIndicesLen == 0 && _claimIndex != 0) {
_distributeBond(parent.claimant, parent);
// In the event that the parent claim is at the max depth, there will always be 0 subgames. If the
// `counteredBy` field is set and there are no subgames, this implies that the parent claim was successfully
// stepped against. In this case, we pay out the bond to the party that stepped against the parent claim.
// Otherwise, the parent claim is uncontested, and the bond is returned to the claimant.
address counteredBy = parent.counteredBy;
address recipient = counteredBy == address(0) ? parent.claimant : counteredBy;
_distributeBond(recipient, parent);
return;
}
......@@ -405,7 +411,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
// Ignore false claims
if (claim.counteredBy == address(0)) {
countered = msg.sender;
countered = claim.claimant;
break;
}
}
......
......@@ -627,6 +627,71 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
// Ensure that bonds were paid out correctly.
assertEq(address(this).balance, 100 ether);
assertEq(address(gameProxy).balance, 0);
// Ensure that the init bond for the game is 0, in case we change it in the test suite in the future.
assertEq(factory.initBonds(GAME_TYPE), 0);
}
/// @dev Static unit test asserting that resolve pays out bonds on step, output bisection, and execution trace
/// moves with 2 actors and a dishonest root claim.
function test_resolve_bondPayoutsSeveralActors_succeeds() public {
// Give the test contract and bob some ether
address bob = address(0xb0b);
vm.deal(address(this), 100 ether);
vm.deal(bob, 100 ether);
// Make claims all the way down the tree, trading off between bob and the test contract.
gameProxy.attack{ value: 1 ether }(0, _dummyClaim());
vm.prank(bob);
gameProxy.attack{ value: 1 ether }(1, _dummyClaim());
gameProxy.attack{ value: 1 ether }(2, _dummyClaim());
vm.prank(bob);
gameProxy.attack{ value: 1 ether }(3, _dummyClaim());
gameProxy.attack{ value: 1 ether }(4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC));
vm.prank(bob);
gameProxy.attack{ value: 1 ether }(5, _dummyClaim());
gameProxy.attack{ value: 1 ether }(6, _dummyClaim());
vm.prank(bob);
gameProxy.attack{ value: 1 ether }(7, _dummyClaim());
gameProxy.addLocalData(LocalPreimageKey.STARTING_L2_BLOCK_NUMBER, 8, 0);
gameProxy.step(8, true, absolutePrestateData, hex"");
// Ensure that the step successfully countered the leaf claim.
(, address counteredBy,,,,,) = gameProxy.claimData(8);
assertEq(counteredBy, address(this));
// Ensure we bonded the correct amounts
uint256 bonded = ((gameProxy.claimDataLen() - 1) / 2) * 1 ether;
assertEq(address(this).balance, 100 ether - bonded);
assertEq(bob.balance, 100 ether - bonded);
assertEq(address(gameProxy).balance, bonded * 2);
// Resolve all claims
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) {
(bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1)));
success;
}
gameProxy.resolve();
gameProxy.claimCredit(address(this));
gameProxy.claimCredit(bob);
// Ensure that bonds were paid out correctly.
assertEq(address(this).balance, 100 ether + bonded);
assertEq(bob.balance, 100 ether - bonded);
assertEq(address(gameProxy).balance, 0);
// Ensure that the init bond for the game is 0, in case we change it in the test suite in the future.
assertEq(factory.initBonds(GAME_TYPE), 0);
}
/// @dev Static unit test asserting that credit may not be drained past allowance through reentrancy.
......
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