Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions script/ClaimEscrow.s.sol

This file was deleted.

3 changes: 0 additions & 3 deletions script/ReadEscrow.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ contract ReadEscrowScript is Script {
console.log("Counterparty: ", e.counterparty);
console.log("Token: ", e.token);
console.log("Principal: ", e.principal);
console.log("Beneficiary claim:", escrow.claimableAmount(e.beneficiary, e.token));
console.log("Counterparty claim:", escrow.claimableAmount(e.counterparty, e.token));
console.log("Treasury claim:", escrow.treasuryClaimableAmount(e.token));
console.log("Deposited at: ", e.depositedAt);
console.log("Status: ", uint256(uint8(e.status)));
console.log("Scope: ", vm.toString(e.scope));
Expand Down
35 changes: 3 additions & 32 deletions src/GitBondEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ library GitBondEscrowStorage {
mapping(bytes32 => Escrow) escrows;
mapping(address => bool) tokenWhitelist;
mapping(address => uint256) totalEscrowedByToken;
mapping(address => mapping(address => uint256)) claimAmounts;
address treasury;
uint256 platformCutBps;
uint256 deprecatedFlatRefundFee;
uint256 minEscrowAmount;
uint256 totalEscrowed;
mapping(address => mapping(address => bool)) refundDelegates;
Expand Down Expand Up @@ -98,8 +96,6 @@ contract GitBondEscrow is
uint256 platformCut
);

event EscrowClaimed(address indexed claimant, address indexed token, uint256 amount);

event EscrowCounterpartyUpdated(
bytes32 indexed key, address indexed previousCounterparty, address indexed newCounterparty
);
Expand All @@ -116,7 +112,6 @@ contract GitBondEscrow is
error GitBondEscrow__InvalidAmount();
error GitBondEscrow__InvalidPlatformCut(uint256 requestedBps);
error GitBondEscrow__MinimumEscrowAmountTooLow(uint256 requestedAmount, uint256 minimumAllowedAmount);
error GitBondEscrow__NoClaimableAmount();
error GitBondEscrow__NotAuthorized();
error GitBondEscrow__PermitCallerMustBePayer(address caller, address payer);
error GitBondEscrow__TokenNotWhitelisted(address token);
Expand Down Expand Up @@ -194,8 +189,7 @@ contract GitBondEscrow is
e.status = GitBondEscrowStorage.EscrowStatus.Refunded;
s.totalEscrowed -= e.principal;
s.totalEscrowedByToken[e.token] -= e.principal;

s.claimAmounts[e.beneficiary][e.token] += amountToReturn;
_transferWithAmountCheck(e.token, e.beneficiary, amountToReturn);

emit EscrowRefunded(key, e.payer, e.beneficiary, e.token, amountToReturn);
}
Expand All @@ -217,27 +211,13 @@ contract GitBondEscrow is
uint256 amountToCounterparty = e.principal - platformCut;

if (platformCut > 0) {
s.claimAmounts[s.treasury][e.token] += platformCut;
_transferWithAmountCheck(e.token, s.treasury, platformCut);
}
s.claimAmounts[e.counterparty][e.token] += amountToCounterparty;
_transferWithAmountCheck(e.token, e.counterparty, amountToCounterparty);

emit EscrowSlashed(key, e.beneficiary, e.counterparty, e.token, amountToCounterparty, platformCut);
}

function claim(address token) external virtual whenNotPaused nonReentrant {
GitBondEscrowStorage.Layout storage s = _layout();
uint256 amount = s.claimAmounts[msg.sender][token];

if (amount == 0) {
revert GitBondEscrow__NoClaimableAmount();
}

s.claimAmounts[msg.sender][token] = 0;
_transferWithAmountCheck(token, msg.sender, amount);

emit EscrowClaimed(msg.sender, token, amount);
}

function addRefundDelegate(address delegate) external virtual {
if (delegate == address(0)) {
revert GitBondEscrow__InvalidAddress();
Expand Down Expand Up @@ -282,15 +262,6 @@ contract GitBondEscrow is
return _layout().tokenWhitelist[token];
}

function claimableAmount(address claimant, address token) external view virtual returns (uint256) {
return _layout().claimAmounts[claimant][token];
}

function treasuryClaimableAmount(address token) external view virtual returns (uint256) {
GitBondEscrowStorage.Layout storage s = _layout();
return s.claimAmounts[s.treasury][token];
}

function isRefundDelegate(address counterparty, address delegate) external view virtual returns (bool) {
return _layout().refundDelegates[counterparty][delegate];
}
Expand Down
72 changes: 46 additions & 26 deletions test/GitBondEscrow.invariant.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,32 @@ import {GitBondEscrow, GitBondEscrowStorage} from "src/GitBondEscrow.sol";
import {MockUSDToken} from "test/utils/GitBondEscrowTestUtils.sol";

contract GitBondEscrowHandler {
uint256 public constant INITIAL_TOKEN_SUPPLY = 500_000_000;

GitBondEscrow public escrow;
MockUSDToken public immutable token;
address public immutable beneficiary;
address public immutable counterparty;
address public immutable treasury;

bytes32[3] internal keys;
mapping(bytes32 => uint256) public activePrincipal;
mapping(address => uint256) public claimablePrincipal;
uint256 public activePrincipalTotal;
uint256 public claimablePrincipalTotal;
uint256 public beneficiaryPaidTotal;
uint256 public counterpartyPaidTotal;
uint256 public treasuryPaidTotal;

constructor(MockUSDToken token_, address beneficiary_, address counterparty_) {
constructor(MockUSDToken token_, address beneficiary_, address counterparty_, address treasury_) {
token = token_;
beneficiary = beneficiary_;
counterparty = counterparty_;
treasury = treasury_;

keys[0] = keccak256("handler-key-0");
keys[1] = keccak256("handler-key-1");
keys[2] = keccak256("handler-key-2");

token.mint(address(this), 500_000_000);
token.mint(address(this), INITIAL_TOKEN_SUPPLY);
}

function setEscrow(GitBondEscrow escrow_) external {
Expand Down Expand Up @@ -64,8 +69,7 @@ contract GitBondEscrowHandler {

escrow.refundEscrow(key);
activePrincipalTotal -= activePrincipal[key];
claimablePrincipal[beneficiary] += tracked.principal;
claimablePrincipalTotal += tracked.principal;
beneficiaryPaidTotal += tracked.principal;
activePrincipal[key] = 0;
}

Expand All @@ -77,14 +81,13 @@ contract GitBondEscrowHandler {
}

uint256 principal = activePrincipal[key];
uint256 claimable = principal - ((principal * escrow.platformCutBps()) / 10_000);
uint256 platformCut = (principal * escrow.platformCutBps()) / 10_000;
uint256 counterpartyPayout = principal - platformCut;

escrow.slashEscrow(key);
activePrincipalTotal -= principal;
claimablePrincipal[counterparty] += claimable;
claimablePrincipal[address(0xD00D)] += principal - claimable;
claimablePrincipalTotal += claimable;
claimablePrincipalTotal += principal - claimable;
counterpartyPaidTotal += counterpartyPayout;
treasuryPaidTotal += platformCut;
activePrincipal[key] = 0;
}
}
Expand All @@ -93,10 +96,11 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test {
GitBondEscrow internal escrow;
GitBondEscrowHandler internal handler;
MockUSDToken internal token;
address internal constant TREASURY = address(0xD00D);

function setUp() public {
token = new MockUSDToken("pathUSD", "pathUSD");
handler = new GitBondEscrowHandler(token, address(0xBEEF), address(0xCAFE));
handler = new GitBondEscrowHandler(token, address(0xBEEF), address(0xCAFE), TREASURY);

address[] memory initialWhitelistedTokens = new address[](1);
initialWhitelistedTokens[0] = address(token);
Expand All @@ -109,42 +113,58 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test {
implementation,
abi.encodeCall(
GitBondEscrow.initialize,
(address(0xD00D), address(handler), initialWhitelistedTokens, initialResolutionOperators)
(TREASURY, address(handler), initialWhitelistedTokens, initialResolutionOperators)
)
)
)
);
handler.setEscrow(escrow);

targetContract(address(handler));
bytes4[] memory selectors = new bytes4[](3);
selectors[0] = GitBondEscrowHandler.create.selector;
selectors[1] = GitBondEscrowHandler.refund.selector;
selectors[2] = GitBondEscrowHandler.slash.selector;
targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
}

function invariant_totalEscrowedMatchesHandlerAccounting() public view {
assertEq(escrow.getTotalEscrowed(), handler.activePrincipalTotal());
assertEq(escrow.getTotalEscrowed(address(token)), handler.activePrincipalTotal());
}

function invariant_contractBalanceMatchesActiveAndClaimablePrincipal() public view {
assertEq(token.balanceOf(address(escrow)), handler.activePrincipalTotal() + handler.claimablePrincipalTotal());
function invariant_contractBalanceMatchesActivePrincipal() public view {
assertEq(token.balanceOf(address(escrow)), handler.activePrincipalTotal());
}

function invariant_payoutRecipientsMatchTrackedBalances() public view {
assertEq(token.balanceOf(handler.beneficiary()), handler.beneficiaryPaidTotal());
assertEq(token.balanceOf(handler.counterparty()), handler.counterpartyPaidTotal());
assertEq(token.balanceOf(handler.treasury()), handler.treasuryPaidTotal());
}

function invariant_tokenBalanceConservationHolds() public view {
assertEq(
token.balanceOf(address(handler)) + token.balanceOf(address(escrow))
+ token.balanceOf(handler.beneficiary()) + token.balanceOf(handler.counterparty())
+ token.balanceOf(handler.treasury()),
handler.INITIAL_TOKEN_SUPPLY()
);
}

function invariant_handlerAndContractAgreeOnActiveKeys() public view {
for (uint256 i; i < 3; ++i) {
bytes32 key = handler.keyAt(i);
GitBondEscrowStorage.Escrow memory tracked = escrow.getEscrow(key);
bool contractActive = tracked.status == GitBondEscrowStorage.EscrowStatus.Active;
bool handlerActive = handler.activePrincipal(key) > 0;
uint256 handlerPrincipal = handler.activePrincipal(key);
bool handlerActive = handlerPrincipal > 0;
assertEq(contractActive, handlerActive);
if (contractActive) {
assertEq(tracked.principal, handlerPrincipal);
} else {
assertEq(handlerPrincipal, 0);
}
}

assertEq(
escrow.claimableAmount(handler.beneficiary(), address(token)),
handler.claimablePrincipal(handler.beneficiary())
);
assertEq(
escrow.claimableAmount(handler.counterparty(), address(token)),
handler.claimablePrincipal(handler.counterparty())
);
assertEq(escrow.treasuryClaimableAmount(address(token)), handler.claimablePrincipal(address(0xD00D)));
}
}
Loading
Loading