more harb.sol tests

This commit is contained in:
JulesCrown 2024-06-16 09:26:11 +02:00
parent b94f5559dd
commit 2b6d1ff261
2 changed files with 315 additions and 108 deletions

View file

@ -203,19 +203,17 @@ contract Harb is ERC20, ERC20Permit {
function ubiDue(address _account, uint256 lastTaxClaimed, uint256 _sumTaxCollected) internal view returns (uint256 amountDue, uint256 lastPeriodEndAt) {
lastPeriodEndAt = ((block.timestamp - PERIOD_OFFSET) / uint256(PERIOD_LENGTH)) * PERIOD_LENGTH + PERIOD_OFFSET - 1;
if (lastTaxClaimed == 0 || lastTaxClaimed > lastPeriodEndAt || lastPeriodEndAt - lastTaxClaimed < PERIOD_LENGTH) {
return (0, lastPeriodEndAt);
}
uint256 accountTwab = twabController.getTwabBetween(address(this), _account, lastTaxClaimed, lastPeriodEndAt);
uint256 stakeTwab = twabController.getTwabBetween(address(this), stakingPool, lastTaxClaimed, lastPeriodEndAt);
uint256 poolTwab = twabController.getTwabBetween(address(this), address(pool), lastTaxClaimed, lastPeriodEndAt);
uint256 taxTwab = twabController.getTwabBetween(address(this), TAX_POOL, lastTaxClaimed, lastPeriodEndAt);
uint256 totalSupplyTwab = twabController.getTotalSupplyTwabBetween(address(this), lastTaxClaimed, lastPeriodEndAt);
uint256 taxCollectedSinceLastClaim = sumTaxCollected - _sumTaxCollected;
amountDue = taxCollectedSinceLastClaim.mulDiv(accountTwab, (totalSupplyTwab - stakeTwab - poolTwab), Math.Rounding.Down);
amountDue = taxCollectedSinceLastClaim.mulDiv(accountTwab, (totalSupplyTwab - stakeTwab - poolTwab - taxTwab), Math.Rounding.Down);
}
function claimUbi(address _account) external {
@ -229,6 +227,8 @@ contract Harb is ERC20, ERC20Permit {
ubiTitles[_account].time = lastPeriodEndAt;
twabController.transfer(TAX_POOL, _account, SafeCast.toUint96(ubiAmountDue));
emit UbiClaimed(_account, ubiAmountDue);
} else {
revert("No UBI to claim.");
}
}

View file

@ -11,7 +11,6 @@ import "../src/interfaces/IWETH9.sol";
import {WETH} from "solmate/tokens/WETH.sol";
import "../src/Harb.sol";
import {BaseLineLP} from "../src/BaseLineLP.sol";
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
address constant TAX_POOL = address(2);
// default fee of 1%
@ -22,8 +21,9 @@ contract HarbTest is Test {
IWETH9 weth;
Harb harb;
IUniswapV3Factory factory;
Stake stake;
address stakingPool;
BaseLineLP liquidityManager;
TwabController tc;
function deployContract(bytes memory bytecode, bytes memory constructorArgs) internal returns (address addr) {
bytes memory deploymentData = abi.encodePacked(bytecode, constructorArgs);
@ -39,116 +39,323 @@ contract HarbTest is Test {
factory = IUniswapV3Factory(factoryAddress);
weth = IWETH9(address(new WETH()));
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
harb = new Harb("HARB", "HARB", factoryAddress, address(weth), tc);
factory = IUniswapV3Factory(factoryAddress);
factory.createPool(address(weth), address(harb), FEE);
stake = new Stake(address(harb));
harb.setStakingPool(address(stake));
stakingPool = makeAddr("stakingPool"); // This represents the staking pool
harb.setStakingPool(stakingPool);
liquidityManager = new BaseLineLP(factoryAddress, address(weth), address(harb));
harb.setLiquidityManager(address(liquidityManager));
}
function test_MintStakeUnstake(address account, uint256 amount) public {
vm.assume(amount > 10000);
vm.assume(amount < 2 ** 93); // TWAB limit = 2**96
vm.assume(account != address(0));
vm.assume(account != address(1)); // TWAB sponsorship address
vm.assume(account != address(2)); // tax pool address
vm.assume(account != address(harb));
vm.assume(account != address(stake));
address alice = makeAddr("alice");
// test mint
uint256 totalSupplyBefore = harb.totalSupply();
uint256 balanceBefore = harb.balanceOf(account);
harb.setLiquidityManager(account);
vm.prank(account);
harb.mint(amount);
uint256 totalAfter = harb.totalSupply();
assertEq(totalAfter, totalSupplyBefore + amount, "total supply should match");
assertEq(harb.balanceOf(account), balanceBefore + amount, "balance should match");
// test UBI title
{
// prepare UBI title
vm.prank(account);
harb.mint(amount * 4);
vm.prank(account);
harb.transfer(alice, amount);
vm.prank(alice);
harb.transfer(account, amount);
// check ubi title
(uint256 titleSumTax, uint256 titleTime) = harb.ubiTitles(account);
assertEq(titleSumTax, 0, "no taxes paid yet");
assertEq(block.timestamp, titleTime, "title start time should match");
}
// test stake
{
// get some stake
assertEq(stake.outstandingStake(), 0, "init failure");
vm.prank(account);
harb.approve(address(stake), amount);
uint256[] memory empty;
vm.prank(account);
stake.snatch(amount, account, 1, empty);
assertEq(harb.totalSupply(), totalAfter * 5, "total supply should match after stake");
assertEq(harb.balanceOf(account), amount * 4, "balance should match after stake");
assertEq(harb.balanceOf(address(stake)), amount, "balance should match after stake");
// check stake position
(uint256 share, address owner, uint32 creationTime, uint32 lastTaxTime, uint32 taxRate) = stake.positions(654321);
assertEq(share, stake.totalSupply() / 5, "share should match");
assertEq(owner, account, "owners should match");
assertEq(creationTime, block.timestamp, "time should match");
assertEq(lastTaxTime, block.timestamp, "tax time should match");
assertEq(taxRate, 1, "tax rate should match");
}
// test stake when stake full
{
uint256[] memory empty;
vm.expectRevert();
//vm.expectRevert(abi.encodeWithSelector(ExceededAvailableStake.selector, account, amount, 0));
vm.prank(account);
stake.snatch(amount, account, 2, empty);
}
// test unstake
{
// advance the time
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 24 * 4));
uint256 taxDue = stake.taxDue(654321, 60 * 60 * 24 * 3);
uint256 sumTaxCollectedBefore = harb.sumTaxCollected();
vm.prank(account);
stake.exitPosition(654321);
assertApproxEqRel(harb.balanceOf(account), amount * 5 - taxDue, 1e14, "account balance should match");
assertEq(harb.balanceOf(TAX_POOL), taxDue, "tax pool balance should match");
assertEq(sumTaxCollectedBefore + taxDue, harb.sumTaxCollected(), "collected tax should have increased");
}
// claim tax
{
balanceBefore = harb.balanceOf(account);
(uint256 ubiDue, ) = harb.getUbiDue(account);
vm.prank(account);
harb.claimUbi(account);
assertFalse(ubiDue == 0, "No UBI paid");
assertEq(balanceBefore + ubiDue, harb.balanceOf(account), "ubi should match");
}
// test UBIdue
{
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 24 * 7));
harb.getUbiDue(account);
harb.getUbiDue(alice);
}
// Simulates staking by transferring tokens to the stakingPool address.
function simulateStake(uint256 amount) internal {
// the amount of token has to be available on the balance
// of the test contract
harb.transfer(stakingPool, amount);
}
// Simulates unstaking by transferring tokens from the stakingPool back to a given address.
function simulateUnstake(uint256 amount) internal {
// Direct transfer from the stakingPool to 'to' address to simulate unstaking
vm.prank(stakingPool); // Assuming 'stake' contract would allow this in an actual scenario
harb.transfer(address(this), amount);
}
function testHarbConstructor() view public {
// Check if the token details are set as expected
assertEq(harb.name(), "HARB");
assertEq(harb.symbol(), "HARB");
// Confirm that the TwabController address is correctly set
assertEq(address(harb.twabController()), address(tc));
// Check that the initial staking pool and liquidity manager are correctly set
assertEq(address(harb.stakingPool()), stakingPool);
assertEq(address(harb.liquidityManager()), address(liquidityManager));
}
function testMintWithEmptyStakingPool() public {
uint256 initialSupply = harb.totalSupply();
uint256 mintAmount = 1000 * 1e18; // 1000 HARB tokens
vm.prank(address(liquidityManager));
harb.mint(mintAmount);
// Check if the total supply has increased correctly
assertEq(harb.totalSupply(), initialSupply + mintAmount);
// Check if the staking pool balance is still 0, as before
assertEq(harb.balanceOf(stakingPool), 0);
}
function testBurnWithEmptyStakingPool() public {
uint256 initialSupply = harb.totalSupply();
uint256 burnAmount = 500 * 1e18; // 500 HARB tokens
// First, mint some tokens to burn
vm.prank(address(liquidityManager));
harb.mint(burnAmount);
vm.prank(address(liquidityManager));
harb.burn(burnAmount);
// Check if the total supply has decreased correctly
assertEq(harb.totalSupply(), initialSupply);
// Check if the staking pool balance has decreased correctly
assertEq(harb.balanceOf(stakingPool), 0);
}
function testMintImpactOnSimulatedStaking() public {
uint256 initialStakingPoolBalance = harb.balanceOf(stakingPool);
uint256 mintAmount = 1000 * 1e18; // 1000 HARB tokens
// Ensure the test contract has enough tokens to simulate staking
vm.prank(address(liquidityManager));
harb.mint(mintAmount);
vm.prank(address(liquidityManager));
harb.transfer(address(this), mintAmount);
// Simulate staking of the minted amount
simulateStake(mintAmount);
// Check balances after simulated staking
assertEq(harb.balanceOf(stakingPool), initialStakingPoolBalance + mintAmount);
}
function testUnstakeImpactOnTotalSupply() public {
uint256 stakeAmount = 500 * 1e18; // 500 HARB tokens
// Ensure the test contract has enough tokens to simulate staking
vm.prank(address(liquidityManager));
harb.mint(stakeAmount);
vm.prank(address(liquidityManager));
harb.transfer(address(this), stakeAmount);
uint256 initialTotalSupply = harb.totalSupply();
// Simulate staking and then unstaking
simulateStake(stakeAmount);
simulateUnstake(stakeAmount);
// Check total supply remains unchanged after unstake
assertEq(harb.totalSupply(), initialTotalSupply);
}
// Fuzz test for mint function with varying stake amounts
function testMintWithStake(uint8 _stakePercentage, uint256 mintAmount) public {
uint256 initialAmount = 500 * 1e18;
// Ensure the test contract has enough tokens to simulate staking
vm.prank(address(liquidityManager));
harb.mint(initialAmount);
vm.prank(address(liquidityManager));
harb.transfer(address(this), initialAmount);
// Limit fuzzing input to 0% - 20%
uint8 effectiveStakePercentage = _stakePercentage % 21;
uint256 stakeAmount = (initialAmount * effectiveStakePercentage) / 100;
simulateStake(stakeAmount);
uint256 initialTotalSupply = harb.totalSupply();
uint256 initialStakingPoolBalance = harb.balanceOf(stakingPool);
mintAmount = bound(mintAmount, 0, 500 * 1e18);
uint256 expectedNewStake = initialStakingPoolBalance * mintAmount / (initialTotalSupply - initialStakingPoolBalance);
vm.prank(address(liquidityManager));
harb.mint(mintAmount);
uint256 expectedStakingPoolBalance = initialStakingPoolBalance + expectedNewStake;
uint256 expectedTotalSupply = initialTotalSupply + mintAmount + expectedNewStake;
assertEq(harb.totalSupply(), expectedTotalSupply, "Total supply did not match expected after mint.");
assertEq(harb.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after mint.");
}
// Fuzz test for burn function with varying stake amounts
function testBurnWithStake(uint8 _stakePercentage, uint256 burnAmount) public {
uint256 mintAmount = 500 * 1e18;
// Ensure the test contract has enough tokens to simulate staking
vm.prank(address(liquidityManager));
harb.mint(mintAmount);
// Limit fuzzing input to 0% - 20%
uint8 effectiveStakePercentage = _stakePercentage % 21;
uint256 stakeAmount = (mintAmount * effectiveStakePercentage) / 100;
vm.prank(address(liquidityManager));
harb.transfer(address(this), stakeAmount);
simulateStake(stakeAmount);
burnAmount = bound(burnAmount, 0, 200 * 1e18);
uint256 initialTotalSupply = harb.totalSupply();
uint256 initialStakingPoolBalance = harb.balanceOf(stakingPool);
uint256 expectedExcessStake = initialStakingPoolBalance * burnAmount / (initialTotalSupply - initialStakingPoolBalance);
vm.prank(address(liquidityManager));
harb.burn(burnAmount);
uint256 expectedStakingPoolBalance = initialStakingPoolBalance - expectedExcessStake;
uint256 expectedTotalSupply = initialTotalSupply - burnAmount - expectedExcessStake;
assertEq(harb.totalSupply(), expectedTotalSupply, "Total supply did not match expected after burn.");
assertEq(harb.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after burn.");
}
function testTaxAccumulation() public {
uint256 taxAmount = 100 * 1e18; // 100 HARB tokens
vm.prank(address(liquidityManager));
harb.mint(taxAmount);
// Initial tax collected should be zero
assertEq(harb.sumTaxCollected(), 0, "Initial tax collected should be zero.");
// Simulate sending tokens to the TAX_POOL
vm.prank(address(liquidityManager));
harb.transfer(TAX_POOL, taxAmount);
// Check that sumTaxCollected has been updated correctly
assertEq(harb.sumTaxCollected(), taxAmount, "Tax collected not updated correctly after transfer to TAX_POOL.");
}
function testUBIClaimBySingleAccountOverTime() public {
uint256 initialSupply = 1000 * 1e18; // 1000 HARB tokens
uint256 taxAmount = 200 * 1e18; // 200 HARB tokens to be collected as tax
address user = makeAddr("alice");
// Setup initial supply and distribute to user
vm.prank(address(liquidityManager));
harb.mint(initialSupply + taxAmount);
vm.prank(address(liquidityManager));
harb.transfer(user, initialSupply);
// Simulate tax collection
vm.prank(address(liquidityManager));
harb.transfer(TAX_POOL, taxAmount);
// Simulate time passage to ensure TWAB is recorded over time
vm.warp(block.timestamp + 30 days);
// Assert initial user balance and sumTaxCollected before claiming UBI
assertEq(harb.balanceOf(user), initialSupply, "User should hold the entire initial supply.");
assertEq(harb.sumTaxCollected(), taxAmount, "Tax collected should match the tax amount transferred.");
// User claims UBI
vm.prank(user);
harb.claimUbi(user);
// Compute expected UBI
// Assume the user is the only one holding tokens, they get all the collected taxes.
uint256 expectedUbiAmount = taxAmount;
// Verify UBI claim
uint256 postClaimBalance = harb.balanceOf(user);
assertEq(postClaimBalance, initialSupply + expectedUbiAmount, "User's balance after claiming UBI is incorrect.");
// Ensure that claiming doesn't affect the total supply
uint256 expectedTotalSupply = initialSupply + taxAmount; // Include minted tax
assertEq(harb.totalSupply(), expectedTotalSupply, "Total supply should be unchanged after UBI claim.");
}
function testUBIClaimByMultipleAccountsWithDifferentHoldingPeriods() public {
uint256 initialSupply = 1000 * 1e18; // 1000 HARB tokens
uint256 taxAmount = 300 * 1e18; // 300 HARB tokens to be collected as tax
address account1 = makeAddr("alice");
address account2 = makeAddr("bob");
address account3 = makeAddr("charly");
// Setup initial supply and distribute to users
vm.startPrank(address(liquidityManager));
harb.mint(initialSupply);
harb.transfer(account1, 400 * 1e18); // Account 1 gets 400 tokens
harb.transfer(account2, 300 * 1e18); // Account 2 gets 300 tokens
harb.transfer(account3, 300 * 1e18); // Account 3 gets 300 tokens
vm.stopPrank();
uint256 startTime = block.timestamp;
// Simulate different holding periods
vm.warp(block.timestamp + 10 days); // Fast forward 10 days
vm.prank(account1);
harb.transfer(account2, 100 * 1e18); // Account 1 transfers 100 tokens to Account 2
vm.warp(block.timestamp + 20 days); // Fast forward another 20 days
vm.prank(account2);
harb.transfer(account3, 100 * 1e18); // Account 2 transfers 100 tokens to Account 3
// Simulate tax collection after the transactions
vm.startPrank(address(liquidityManager));
harb.mint(taxAmount);
harb.transfer(TAX_POOL, taxAmount);
vm.stopPrank();
// Assert sumTaxCollected before claiming UBI
assertEq(harb.sumTaxCollected(), taxAmount, "Tax collected should match the tax amount transferred.");
// Each account claims UBI
vm.prank(account1);
harb.claimUbi(account1);
vm.prank(account2);
harb.claimUbi(account2);
vm.prank(account3);
harb.claimUbi(account3);
// Assert the post-claim balances reflect the TWAB calculations
{
uint256 totalDistributed = harb.balanceOf(account1) + harb.balanceOf(account2) + harb.balanceOf(account3) - initialSupply;
// Tolerance setup: 0.01% of the total tax amount
uint256 lowerBound = taxAmount - (taxAmount / 10000);
assertTrue(totalDistributed >= lowerBound && totalDistributed <= totalDistributed, "Total distributed UBI does not match the total tax collected within an acceptable tolerance range.");
}
// Calculate expected UBI amounts based on simplified TWAB assumptions
// These should be replaced with actual TWAB calculations from your contract
uint256 totalPeriod = block.timestamp - startTime;
uint256 account1TWAB = (400 * 1e18 * 10 days + 300 * 1e18 * (totalPeriod - 10 days)) / totalPeriod;
uint256 account2TWAB = (300 * 1e18 * 10 days + 400 * 1e18 * 20 days + 300 * 1e18 * (totalPeriod - 30 days)) / totalPeriod;
uint256 account3TWAB = (300 * 1e18 * 30 days + 400 * 1e18 * (totalPeriod - 30 days)) / totalPeriod;
uint256 totalTWAB = account1TWAB + account2TWAB + account3TWAB;
// Calculate exact expected UBI payouts
uint256 expectedBalance1 = (taxAmount * account1TWAB) / totalTWAB;
uint256 expectedBalance2 = (taxAmount * account2TWAB) / totalTWAB;
uint256 expectedBalance3 = (taxAmount * account3TWAB) / totalTWAB;
// Assert the post-claim balances reflect the TWAB calculations with a smaller rounding tolerance
// 1 * 1e14; // 0.0001 HARB token tolerance for rounding errors
assertApproxEqRel(harb.balanceOf(account1) - 300 * 1e18, expectedBalance1, 1 * 1e14, "Account 1's balance after claiming UBI is incorrect.");
assertApproxEqRel(harb.balanceOf(account2) - 300 * 1e18, expectedBalance2, 1 * 1e14, "Account 2's balance after claiming UBI is incorrect.");
assertApproxEqRel(harb.balanceOf(account3) - 400 * 1e18, expectedBalance3, 1 * 1e14, "Account 3's balance after claiming UBI is incorrect.");
}
function testUBIClaimWithoutAnyTaxCollected() public {
uint256 initialSupply = 1000 * 1e18; // 1000 HARB tokens
address user = makeAddr("alice");
// Setup initial supply and allocate to user
vm.startPrank(address(liquidityManager));
harb.mint(initialSupply);
harb.transfer(user, initialSupply);
vm.stopPrank();
// Ensure no tax has been collected yet
assertEq(harb.sumTaxCollected(), 0, "Initial tax collected should be zero.");
// Simulate time passage to ensure TWAB is recorded
vm.warp(block.timestamp + 30 days);
// User attempts to claim UBI
vm.prank(user);
vm.expectRevert("No UBI to claim."); // Assuming your contract reverts with a message when there's no UBI to claim
harb.claimUbi(user);
// Ensure the user's balance remains unchanged as no UBI should be distributed
assertEq(harb.balanceOf(user), initialSupply, "User's balance should not change after attempting to claim UBI without any taxes collected.");
// Check if sumTaxCollected remains zero after the claim attempt
assertEq(harb.sumTaxCollected(), 0, "No tax should be collected, and sumTaxCollected should remain zero after the claim attempt.");
}
}