Commit a0bbb398 authored by clabby's avatar clabby

Remove PoPs `BondManager` impl

parent 33608dbe
...@@ -259,11 +259,3 @@ ...@@ -259,11 +259,3 @@
| gameImpls | mapping(GameType => contract IDisputeGame) | 1 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory | | gameImpls | mapping(GameType => contract IDisputeGame) | 1 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGames | mapping(Hash => contract IDisputeGame) | 2 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory | | disputeGames | mapping(Hash => contract IDisputeGame) | 2 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGameList | contract IDisputeGame[] | 3 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory | | disputeGameList | contract IDisputeGame[] | 3 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
=======================
➡ contracts/dispute/BondManager.sol:BondManager
=======================
| Name | Type | Slot | Offset | Bytes | Contract |
|-------|---------------------------------------------|------|--------|-------|-----------------------------------------------|
| bonds | mapping(bytes32 => struct BondManager.Bond) | 0 | 0 | 32 | contracts/dispute/BondManager.sol:BondManager |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IBondManager } from "./interfaces/IBondManager.sol";
/**
* @title BondManager
* @notice The Bond Manager serves as an escrow for permissionless output proposal bonds.
*/
contract BondManager is IBondManager {
/**
* @notice The Bond Type
*/
struct Bond {
address owner;
bytes32 id;
uint128 expiration;
uint128 amount;
}
/**
* @notice The permissioned dispute game factory.
* @dev Used to verify the status of bonds.
*/
IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY;
/**
* @notice Amount of gas used to transfer ether when splitting the bond.
* This is a reasonable amount of gas for a transfer, even to a smart contract.
* The number of participants is bound of by the block gas limit.
*/
uint256 private constant TRANSFER_GAS = 30_000;
/**
* @notice Mapping from bondId to bond.
*/
mapping(bytes32 => Bond) public bonds;
/**
* @notice Instantiates the bond maanger with the registered dispute game factory.
* @param _disputeGameFactory is the dispute game factory.
*/
constructor(IDisputeGameFactory _disputeGameFactory) {
DISPUTE_GAME_FACTORY = _disputeGameFactory;
}
/**
* @inheritdoc IBondManager
*/
function post(
bytes32 _bondId,
address _bondOwner,
uint128 _minClaimHold
) external payable {
require(bonds[_bondId].owner == address(0), "BondManager: BondId already posted.");
require(_bondOwner != address(0), "BondManager: Owner cannot be the zero address.");
require(msg.value > 0, "BondManager: Value must be non-zero.");
uint128 expiration = uint128(_minClaimHold + block.timestamp);
bonds[_bondId] = Bond({
owner: _bondOwner,
id: _bondId,
expiration: expiration,
amount: uint128(msg.value)
});
emit BondPosted(_bondId, _bondOwner, expiration, msg.value);
}
/**
* @inheritdoc IBondManager
*/
function seize(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameTypes.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
/**
* @inheritdoc IBondManager
*/
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameTypes.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
uint256 len = _claimRecipients.length;
uint256 proportionalAmount = b.amount / len;
// Send the proportional amount to each recipient. Do not revert if a send fails as that
// will prevent other recipients from receiving their share.
for (uint256 i; i < len; i++) {
SafeCall.send({
_target: payable(_claimRecipients[i]),
_gas: TRANSFER_GAS,
_value: proportionalAmount
});
}
}
/**
* @inheritdoc IBondManager
*/
function reclaim(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner == msg.sender, "BondManager: Unauthorized claimant.");
require(b.expiration <= block.timestamp, "BondManager: Bond isn't claimable yet.");
delete bonds[_bondId];
emit BondReclaimed(_bondId, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "forge-std/Test.sol";
import "../libraries/DisputeTypes.sol";
import { IDisputeGame } from "../dispute/interfaces/IDisputeGame.sol";
import { IBondManager } from "../dispute/interfaces/IBondManager.sol";
import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol";
import { BondManager } from "../dispute/BondManager.sol";
contract BondManager_Test is Test {
DisputeGameFactory factory;
BondManager bm;
// DisputeGameFactory events
event DisputeGameCreated(
address indexed disputeProxy,
GameType indexed gameType,
Claim indexed rootClaim
);
// BondManager events
event BondPosted(bytes32 bondId, address owner, uint256 expiration, uint256 amount);
event BondSeized(bytes32 bondId, address owner, address seizer, uint256 amount);
event BondReclaimed(bytes32 bondId, address claiment, uint256 amount);
function setUp() public {
factory = new DisputeGameFactory(address(this));
bm = new BondManager(factory);
}
/**
* -------------------------------------------
* Test Bond Posting
* -------------------------------------------
*/
/**
* @notice Tests that posting a bond succeeds.
*/
function testFuzz_post_succeeds(
bytes32 bondId,
address owner,
uint128 minClaimHold,
uint128 amount
) public {
vm.assume(owner != address(0));
vm.assume(owner != address(bm));
vm.assume(owner != address(this));
// Create2Deployer
vm.assume(owner != address(0x4e59b44847b379578588920cA78FbF26c0B4956C));
amount = uint128(bound(amount, 1, type(uint128).max));
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
vm.deal(address(this), amount);
vm.expectEmit(true, true, true, true);
uint128 expiration = uint128(block.timestamp + minClaimHold);
emit BondPosted(bondId, owner, expiration, amount);
bm.post{ value: amount }(bondId, owner, minClaimHold);
// Validate the bond
(
address newFetchedOwner,
bytes32 fetchedBondId,
uint128 fetchedExpiration,
uint128 bondAmount
) = bm.bonds(bondId);
assertEq(newFetchedOwner, owner);
assertEq(fetchedExpiration, block.timestamp + minClaimHold);
assertEq(fetchedBondId, bondId);
assertEq(bondAmount, amount);
}
/**
* @notice Tests that posting a bond with the same id twice reverts.
*/
function testFuzz_post_duplicates_reverts(
bytes32 bondId,
address owner,
uint128 minClaimHold,
uint128 amount
) public {
vm.assume(owner != address(0));
amount = amount / 2;
amount = uint128(bound(amount, 1, type(uint128).max));
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
vm.deal(address(this), amount);
bm.post{ value: amount }(bondId, owner, minClaimHold);
vm.deal(address(this), amount);
vm.expectRevert("BondManager: BondId already posted.");
bm.post{ value: amount }(bondId, owner, minClaimHold);
}
/**
* @notice Posting with the zero address as the owner fails.
*/
function testFuzz_post_zeroAddress_reverts(
bytes32 bondId,
uint128 minClaimHold,
uint128 amount
) public {
address owner = address(0);
vm.deal(address(this), amount);
vm.expectRevert("BondManager: Owner cannot be the zero address.");
bm.post{ value: amount }(bondId, owner, minClaimHold);
}
/**
* @notice Posting zero value bonds should revert.
*/
function testFuzz_post_zeroAddress_reverts(
bytes32 bondId,
address owner,
uint128 minClaimHold
) public {
vm.assume(owner != address(0));
uint128 amount = 0;
vm.deal(address(this), amount);
vm.expectRevert("BondManager: Value must be non-zero.");
bm.post{ value: amount }(bondId, owner, minClaimHold);
}
/**
* -------------------------------------------
* Test Bond Seizing
* -------------------------------------------
*/
/**
* @notice Non-existing bonds shouldn't be seizable.
*/
function testFuzz_seize_missingBond_reverts(bytes32 bondId) public {
vm.expectRevert("BondManager: The bond does not exist.");
bm.seize(bondId);
}
/**
* @notice Bonds that expired cannot be seized.
*/
function testFuzz_seize_expired_reverts(
bytes32 bondId,
address owner,
uint128 minClaimHold,
uint128 amount
) public {
vm.assume(owner != address(0));
vm.assume(owner != address(bm));
vm.assume(owner != address(this));
amount = uint128(bound(amount, 1, type(uint128).max));
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
vm.deal(address(this), amount);
bm.post{ value: amount }(bondId, owner, minClaimHold);
vm.warp(block.timestamp + minClaimHold + 1);
vm.expectRevert("BondManager: Bond expired.");
bm.seize(bondId);
}
/**
* @notice Bonds cannot be seized by unauthorized parties.
*/
function testFuzz_seize_unauthorized_reverts(
bytes32 bondId,
address owner,
uint128 minClaimHold,
uint128 amount
) public {
vm.assume(owner != address(0));
vm.assume(owner != address(bm));
vm.assume(owner != address(this));
amount = uint128(bound(amount, 1, type(uint128).max));
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
vm.deal(address(this), amount);
bm.post{ value: amount }(bondId, owner, minClaimHold);
MockAttestationDisputeGame game = new MockAttestationDisputeGame();
vm.prank(address(game));
vm.expectRevert("BondManager: Unauthorized seizure.");
bm.seize(bondId);
}
/**
* @notice Seizing a bond should succeed if the game resolves.
*/
function testFuzz_seize_succeeds(
bytes32 bondId,
uint128 minClaimHold,
bytes calldata extraData
) public {
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
vm.deal(address(this), 1 ether);
bm.post{ value: 1 ether }(bondId, address(0xba5ed), minClaimHold);
// Create a mock dispute game in the factory
IDisputeGame proxy;
Claim rootClaim;
bytes memory ed = extraData;
{
rootClaim = Claim.wrap(bytes32(""));
MockAttestationDisputeGame implementation = new MockAttestationDisputeGame();
GameType gt = GameTypes.ATTESTATION;
factory.setImplementation(gt, IDisputeGame(address(implementation)));
vm.expectEmit(false, true, true, false);
emit DisputeGameCreated(address(0), gt, rootClaim);
proxy = factory.create(gt, rootClaim, extraData);
assertEq(address(factory.games(gt, rootClaim, extraData)), address(proxy));
}
// Update the game fields
MockAttestationDisputeGame spawned = MockAttestationDisputeGame(payable(address(proxy)));
spawned.setBondManager(bm);
spawned.setRootClaim(rootClaim);
spawned.setGameStatus(GameStatus.CHALLENGER_WINS);
spawned.setBondId(bondId);
spawned.setExtraData(ed);
// Seize the bond by calling resolve
vm.expectEmit(true, true, true, true);
emit BondSeized(bondId, address(0xba5ed), address(spawned), 1 ether);
spawned.resolve();
assertEq(address(spawned).balance, 1 ether);
// Validate that the bond was deleted
(address newFetchedOwner, , , ) = bm.bonds(bondId);
assertEq(newFetchedOwner, address(0));
}
/**
* -------------------------------------------
* Test Bond Split and Seizing
* -------------------------------------------
*/
/**
* @notice Seizing and splitting a bond should succeed if the game resolves.
*/
function testFuzz_seizeAndSplit_succeeds(
bytes32 bondId,
uint128 minClaimHold,
bytes calldata extraData
) public {
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
vm.deal(address(this), 1 ether);
bm.post{ value: 1 ether }(bondId, address(0xba5ed), minClaimHold);
// Create a mock dispute game in the factory
IDisputeGame proxy;
Claim rootClaim;
bytes memory ed = extraData;
{
rootClaim = Claim.wrap(bytes32(""));
MockAttestationDisputeGame implementation = new MockAttestationDisputeGame();
GameType gt = GameTypes.ATTESTATION;
factory.setImplementation(gt, IDisputeGame(address(implementation)));
vm.expectEmit(false, true, true, false);
emit DisputeGameCreated(address(0), gt, rootClaim);
proxy = factory.create(gt, rootClaim, extraData);
assertEq(address(factory.games(gt, rootClaim, extraData)), address(proxy));
}
// Update the game fields
MockAttestationDisputeGame spawned = MockAttestationDisputeGame(payable(address(proxy)));
spawned.setBondManager(bm);
spawned.setRootClaim(rootClaim);
spawned.setGameStatus(GameStatus.CHALLENGER_WINS);
spawned.setBondId(bondId);
spawned.setExtraData(ed);
// Seize the bond by calling resolve
vm.expectEmit(true, true, true, true);
emit BondSeized(bondId, address(0xba5ed), address(spawned), 1 ether);
spawned.splitResolve();
assertEq(address(spawned).balance, 0);
address[] memory challengers = spawned.getChallengers();
uint256 proportionalAmount = 1 ether / challengers.length;
for (uint256 i = 0; i < challengers.length; i++) {
assertEq(address(challengers[i]).balance, proportionalAmount);
}
// Validate that the bond was deleted
(address newFetchedOwner, , , ) = bm.bonds(bondId);
assertEq(newFetchedOwner, address(0));
}
/**
* -------------------------------------------
* Test Bond Reclaiming
* -------------------------------------------
*/
/**
* @notice Bonds can be reclaimed after the specified amount of time.
*/
function testFuzz_reclaim_succeeds(
bytes32 bondId,
address owner,
uint128 minClaimHold,
uint128 amount
) public {
vm.assume(owner != address(factory));
vm.assume(owner != address(bm));
vm.assume(owner != address(this));
vm.assume(owner != address(0));
vm.assume(owner.code.length == 0);
amount = uint128(bound(amount, 1, type(uint128).max));
minClaimHold = uint128(bound(minClaimHold, 0, type(uint128).max - block.timestamp));
assumeNoPrecompiles(owner);
// Post the bond
vm.deal(address(this), amount);
bm.post{ value: amount }(bondId, owner, minClaimHold);
// We can't claim if the block.timestamp is less than the bond expiration.
(, , uint256 expiration, ) = bm.bonds(bondId);
if (expiration > block.timestamp) {
vm.prank(owner);
vm.expectRevert("BondManager: Bond isn't claimable yet.");
bm.reclaim(bondId);
}
// Past expiration, the owner can reclaim
vm.warp(expiration);
vm.prank(owner);
bm.reclaim(bondId);
assertEq(owner.balance, amount);
}
}
/**
* @title MockAttestationDisputeGame
* @dev A mock dispute game for testing bond seizures.
*/
contract MockAttestationDisputeGame {
GameStatus internal gameStatus;
BondManager bm;
Claim internal rc;
bytes internal ed;
bytes32 internal bondId;
address[] internal challengers;
function getChallengers() public view returns (address[] memory) {
return challengers;
}
function setBondId(bytes32 bid) external {
bondId = bid;
}
function setBondManager(BondManager _bm) external {
bm = _bm;
}
function setGameStatus(GameStatus _gs) external {
gameStatus = _gs;
}
function setRootClaim(Claim _rc) external {
rc = _rc;
}
function setExtraData(bytes memory _ed) external {
ed = _ed;
}
receive() external payable {}
fallback() external payable {}
function splitResolve() public {
challengers = [address(1), address(2)];
bm.seizeAndSplit(bondId, challengers);
}
/**
* -------------------------------------------
* Initializable Functions
* -------------------------------------------
*/
function initialize() external {
/* noop */
}
/**
* -------------------------------------------
* IVersioned Functions
* -------------------------------------------
*/
function version() external pure returns (string memory _version) {
return "0.1.0";
}
/**
* -------------------------------------------
* IDisputeGame Functions
* -------------------------------------------
*/
function createdAt() external pure returns (Timestamp _createdAt) {
return Timestamp.wrap(uint64(0));
}
function status() external view returns (GameStatus _status) {
return gameStatus;
}
function gameType() external pure returns (GameType _gameType) {
return GameTypes.ATTESTATION;
}
function rootClaim() external view returns (Claim _rootClaim) {
return rc;
}
function extraData() external view returns (bytes memory _extraData) {
return ed;
}
function gameData()
external
pure
returns (
GameType,
Claim,
bytes memory
)
{
assembly {
revert(0, 0)
}
}
function bondManager() external view returns (IBondManager _bondManager) {
return IBondManager(address(bm));
}
function resolve() external returns (GameStatus _status) {
bm.seize(bondId);
return gameStatus;
}
}
...@@ -32,7 +32,6 @@ contracts=( ...@@ -32,7 +32,6 @@ contracts=(
contracts/universal/OptimismMintableERC20.sol:OptimismMintableERC20 contracts/universal/OptimismMintableERC20.sol:OptimismMintableERC20
contracts/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory contracts/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory
contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory
contracts/dispute/BondManager.sol:BondManager
) )
dir=$(dirname "$0") dir=$(dirname "$0")
......
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