test: prove V1 accounting invariants
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.35;
|
||||||
|
|
||||||
|
import {BankTestBase} from "./helpers/BankTestBase.sol";
|
||||||
|
import {BankHandler} from "./helpers/BankHandler.sol";
|
||||||
|
|
||||||
|
contract BankInvariantTest is BankTestBase {
|
||||||
|
BankHandler internal handler;
|
||||||
|
|
||||||
|
function setUp() public override {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
handler = new BankHandler(token, bank);
|
||||||
|
vm.prank(owner);
|
||||||
|
token.transferOwnership(address(handler));
|
||||||
|
|
||||||
|
targetContract(address(handler));
|
||||||
|
bytes4[] memory selectors = new bytes4[](3);
|
||||||
|
selectors[0] = handler.deposit.selector;
|
||||||
|
selectors[1] = handler.withdraw.selector;
|
||||||
|
selectors[2] = handler.donate.selector;
|
||||||
|
targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function invariant_liabilitiesEqualTrackedBalances() public view {
|
||||||
|
uint256 sum;
|
||||||
|
for (uint256 i; i < handler.actorCount(); ++i) {
|
||||||
|
sum += bank.balanceOf(handler.actorAt(i));
|
||||||
|
}
|
||||||
|
assertEq(sum, bank.totalLiabilities());
|
||||||
|
}
|
||||||
|
|
||||||
|
function invariant_reservesCoverLiabilities() public view {
|
||||||
|
assertGe(token.balanceOf(address(bank)), bank.totalLiabilities());
|
||||||
|
}
|
||||||
|
|
||||||
|
function invariant_ghostAccountingMatchesChain() public view {
|
||||||
|
assertEq(handler.ghostDeposited() - handler.ghostWithdrawn(), bank.totalLiabilities());
|
||||||
|
assertEq(
|
||||||
|
handler.ghostDeposited() + handler.ghostDonated() - handler.ghostWithdrawn(), token.balanceOf(address(bank))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.35;
|
||||||
|
|
||||||
|
import {Test} from "forge-std/Test.sol";
|
||||||
|
|
||||||
|
import {BankV1} from "../../src/BankV1.sol";
|
||||||
|
import {MockUSDC} from "../../src/MockUSDC.sol";
|
||||||
|
|
||||||
|
contract BankHandler is Test {
|
||||||
|
MockUSDC internal immutable token;
|
||||||
|
BankV1 internal immutable bank;
|
||||||
|
|
||||||
|
address internal immutable actor0;
|
||||||
|
address internal immutable actor1;
|
||||||
|
address internal immutable actor2;
|
||||||
|
address internal immutable actor3;
|
||||||
|
|
||||||
|
uint256 public ghostDeposited;
|
||||||
|
uint256 public ghostWithdrawn;
|
||||||
|
uint256 public ghostDonated;
|
||||||
|
|
||||||
|
constructor(MockUSDC token_, BankV1 bank_) {
|
||||||
|
token = token_;
|
||||||
|
bank = bank_;
|
||||||
|
actor0 = address(0x1001);
|
||||||
|
actor1 = address(0x1002);
|
||||||
|
actor2 = address(0x1003);
|
||||||
|
actor3 = address(0x1004);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deposit(uint256 actorSeed, uint256 amount) external {
|
||||||
|
address actor = actorAt(actorSeed % actorCount());
|
||||||
|
amount = bound(amount, 1, 10_000e6);
|
||||||
|
token.mint(actor, amount);
|
||||||
|
|
||||||
|
vm.startPrank(actor);
|
||||||
|
token.approve(address(bank), amount);
|
||||||
|
bank.deposit(amount);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
ghostDeposited += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withdraw(uint256 actorSeed, uint256 amount) external {
|
||||||
|
address actor = actorAt(actorSeed % actorCount());
|
||||||
|
uint256 balance = bank.balanceOf(actor);
|
||||||
|
if (balance == 0) return;
|
||||||
|
|
||||||
|
amount = bound(amount, 1, balance);
|
||||||
|
vm.startPrank(actor);
|
||||||
|
bank.withdraw(amount);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
ghostWithdrawn += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function donate(uint256 actorSeed, uint256 amount) external {
|
||||||
|
address actor = actorAt(actorSeed % actorCount());
|
||||||
|
amount = bound(amount, 1, 1_000e6);
|
||||||
|
token.mint(actor, amount);
|
||||||
|
|
||||||
|
vm.startPrank(actor);
|
||||||
|
token.transfer(address(bank), amount);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
ghostDonated += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actorCount() public pure returns (uint256) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actorAt(uint256 index) public view returns (address) {
|
||||||
|
if (index == 0) return actor0;
|
||||||
|
if (index == 1) return actor1;
|
||||||
|
if (index == 2) return actor2;
|
||||||
|
return actor3;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user