2024-03-12 11:38:16 +01:00
// SPDX-License-Identifier: GPL-3.0-or-later
2024-03-28 19:55:01 +01:00
pragma solidity ^ 0 . 8 . 19 ;
2024-03-12 11:38:16 +01:00
import " forge-std/Test.sol " ;
import " forge-std/console.sol " ;
2024-03-12 20:22:10 +01:00
import { TwabController } from " pt-v5-twab-controller/TwabController.sol " ;
2024-03-12 11:38:16 +01:00
import " ../src/Harb.sol " ;
2024-03-14 12:40:57 +01:00
2024-03-12 15:29:59 +01:00
contract HarbTest is Test {
2024-06-19 10:33:28 +02:00
TwabController tc ;
2024-03-14 12:40:57 +01:00
Harb harb ;
2024-06-16 09:26:11 +02:00
address stakingPool ;
2024-06-19 10:33:28 +02:00
address liquidityPool ;
address liquidityManager ;
address taxPool ;
2024-04-16 06:58:41 +02:00
2024-03-12 11:38:16 +01:00
function setUp ( ) public {
2024-06-18 09:49:57 +02:00
tc = new TwabController ( 60 * 60 , uint32 ( block . timestamp ) ) ;
2024-06-19 10:33:28 +02:00
harb = new Harb ( " HARB " , " HARB " , tc ) ;
taxPool = harb . TAX_POOL ( ) ;
stakingPool = makeAddr ( " stakingPool " ) ;
2024-06-16 09:26:11 +02:00
harb . setStakingPool ( stakingPool ) ;
2024-06-19 10:33:28 +02:00
liquidityPool = makeAddr ( " liquidityPool " ) ;
harb . setLiquidityPool ( liquidityPool ) ;
liquidityManager = makeAddr ( " liquidityManager " ) ;
harb . setLiquidityManager ( liquidityManager ) ;
2024-03-12 11:38:16 +01:00
}
2024-06-16 09:26:11 +02:00
// 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 ) ;
}
2024-03-14 12:40:57 +01:00
2024-06-16 09:26:11 +02:00
// 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 ) ;
}
2024-03-12 20:22:10 +01:00
2024-06-19 10:33:28 +02:00
function testHarbConstructor ( ) public view {
2024-06-16 09:26:11 +02:00
// Check if the token details are set as expected
assertEq ( harb . name ( ) , " HARB " ) ;
assertEq ( harb . symbol ( ) , " HARB " ) ;
2024-03-14 17:31:16 +01:00
2024-06-16 09:26:11 +02:00
// Confirm that the TwabController address is correctly set
2024-06-19 10:33:28 +02:00
( address _tc , address _lm , address _sp , address _lp ) = harb . getPeripheryContracts ( ) ;
assertEq ( _tc , address ( tc ) ) ;
assertEq ( _lm , liquidityManager ) ;
assertEq ( _sp , stakingPool ) ;
assertEq ( _lp , liquidityPool ) ;
2024-06-16 09:26:11 +02:00
}
2024-03-14 12:40:57 +01:00
2024-06-16 09:26:11 +02:00
function testMintWithEmptyStakingPool ( ) public {
uint256 initialSupply = harb . totalSupply ( ) ;
uint256 mintAmount = 1000 * 1 e18 ; // 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 * 1 e18 ; // 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 * 1 e18 ; // 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 * 1 e18 ; // 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 * 1 e18 ;
// 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 * 1 e18 ) ;
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 * 1 e18 ;
// 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 * 1 e18 ) ;
uint256 initialTotalSupply = harb . totalSupply ( ) ;
uint256 initialStakingPoolBalance = harb . balanceOf ( stakingPool ) ;
uint256 expectedExcessStake = initialStakingPoolBalance * burnAmount / ( initialTotalSupply - initialStakingPoolBalance ) ;
2024-04-23 06:58:34 +02:00
2024-06-16 09:26:11 +02:00
vm . prank ( address ( liquidityManager ) ) ;
harb . burn ( burnAmount ) ;
2024-04-23 06:58:34 +02:00
2024-06-16 09:26:11 +02:00
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 * 1 e18 ; // 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. " ) ;
2024-06-19 10:33:28 +02:00
// Simulate sending tokens to the taxPool
2024-06-16 09:26:11 +02:00
vm . prank ( address ( liquidityManager ) ) ;
2024-06-19 10:33:28 +02:00
harb . transfer ( taxPool , taxAmount ) ;
2024-06-16 09:26:11 +02:00
// Check that sumTaxCollected has been updated correctly
2024-06-19 10:33:28 +02:00
assertEq ( harb . sumTaxCollected ( ) , taxAmount , " Tax collected not updated correctly after transfer to taxPool. " ) ;
2024-06-16 09:26:11 +02:00
}
function testUBIClaimBySingleAccountOverTime ( ) public {
uint256 initialSupply = 1000 * 1 e18 ; // 1000 HARB tokens
uint256 taxAmount = 200 * 1 e18 ; // 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 ) ) ;
2024-06-19 10:33:28 +02:00
harb . transfer ( taxPool , taxAmount ) ;
2024-06-16 09:26:11 +02:00
// 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 * 1 e18 ; // 1000 HARB tokens
uint256 taxAmount = 300 * 1 e18 ; // 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 * 1 e18 ) ; // Account 1 gets 400 tokens
harb . transfer ( account2 , 300 * 1 e18 ) ; // Account 2 gets 300 tokens
harb . transfer ( account3 , 300 * 1 e18 ) ; // 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 * 1 e18 ) ; // 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 * 1 e18 ) ; // Account 2 transfers 100 tokens to Account 3
// Simulate tax collection after the transactions
vm . startPrank ( address ( liquidityManager ) ) ;
harb . mint ( taxAmount ) ;
2024-06-19 10:33:28 +02:00
harb . transfer ( taxPool , taxAmount ) ;
2024-06-16 09:26:11 +02:00
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
2024-04-23 06:58:34 +02:00
{
2024-06-16 09:26:11 +02:00
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. " ) ;
2024-04-23 06:58:34 +02:00
}
2024-06-16 09:26:11 +02:00
// 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 * 1 e18 * 10 days + 300 * 1 e18 * ( totalPeriod - 10 days ) ) / totalPeriod ;
uint256 account2TWAB = ( 300 * 1 e18 * 10 days + 400 * 1 e18 * 20 days + 300 * 1 e18 * ( totalPeriod - 30 days ) ) / totalPeriod ;
uint256 account3TWAB = ( 300 * 1 e18 * 30 days + 400 * 1 e18 * ( 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 * 1 e18 , expectedBalance1 , 1 * 1 e14 , " Account 1 ' s balance after claiming UBI is incorrect. " ) ;
assertApproxEqRel ( harb . balanceOf ( account2 ) - 300 * 1 e18 , expectedBalance2 , 1 * 1 e14 , " Account 2 ' s balance after claiming UBI is incorrect. " ) ;
assertApproxEqRel ( harb . balanceOf ( account3 ) - 400 * 1 e18 , expectedBalance3 , 1 * 1 e14 , " Account 3 ' s balance after claiming UBI is incorrect. " ) ;
}
function testUBIClaimWithoutAnyTaxCollected ( ) public {
uint256 initialSupply = 1000 * 1 e18 ; // 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. " ) ;
2024-03-12 11:38:16 +01:00
}
2024-06-16 09:26:11 +02:00
2024-06-18 09:49:57 +02:00
function testEdgeCaseWithMaximumTaxCollection ( ) public {
uint256 initialSupply = 1 e24 ; // Large number of HARB tokens to simulate realistic large-scale deployment
uint256 maxTaxAmount = type ( uint96 ) . max - initialSupply ; // Setting max tax just below overflow threshold when added to total supply
address account1 = makeAddr ( " alice " ) ;
// Setup initial supply and allocate to user
vm . startPrank ( address ( liquidityManager ) ) ;
harb . mint ( initialSupply + maxTaxAmount ) ;
harb . transfer ( account1 , initialSupply ) ;
2024-06-19 10:33:28 +02:00
harb . transfer ( taxPool , maxTaxAmount ) ; // Simulate tax collection at the theoretical maximum
2024-06-18 09:49:57 +02:00
vm . stopPrank ( ) ;
// Assert that maximum tax was collected
assertEq ( harb . sumTaxCollected ( ) , maxTaxAmount , " Max tax collected should match the max tax amount transferred. " ) ;
// Simulate time passage and UBI claim
vm . warp ( block . timestamp + 30 days ) ;
// Account 1 claims UBI
vm . prank ( account1 ) ;
harb . claimUbi ( account1 ) ;
// Check if the account's balance increased correctly
uint256 expectedBalance = initialSupply + maxTaxAmount ; // This assumes the entire tax pool goes to one account, simplify as needed
assertEq ( harb . balanceOf ( account1 ) , expectedBalance , " Account 1 ' s balance after claiming UBI with max tax collection is incorrect. " ) ;
// Verify that no taxes are left unclaimed
2024-06-19 10:33:28 +02:00
assertEq ( harb . balanceOf ( taxPool ) , 0 , " All taxes should be claimed after the UBI claim. " ) ;
2024-06-18 09:49:57 +02:00
}
// TODO: why is this test passing even though it exceeds MAX_CARDINALITY?
function testTwabBeyondBuffer ( ) public {
uint256 initialSupply = 1000 * 1 e18 ; // 1000 HARB tokens
uint256 taxAmount = 300 * 1 e18 ; // 300 HARB tokens to be collected as tax
address account1 = makeAddr ( " alice " ) ;
// Setup initial supply and allocate to user
vm . startPrank ( address ( liquidityManager ) ) ;
harb . mint ( initialSupply + taxAmount ) ;
harb . transfer ( account1 , initialSupply / 800 ) ;
2024-06-19 10:33:28 +02:00
harb . transfer ( taxPool , taxAmount ) ; // Simulate tax collection at the theoretical maximum
harb . transfer ( liquidityPool , harb . balanceOf ( address ( liquidityManager ) ) ) ;
2024-06-18 09:49:57 +02:00
vm . stopPrank ( ) ;
vm . warp ( block . timestamp + 1 hours ) ;
// Simulate updates over a longer period, e.g., enough to potentially wrap the buffer.
uint numHours = 399 ; // More than 365 to potentially test buffer wrapping (MAX_CARDINALITY)
for ( uint i = 0 ; i < numHours ; i ++ ) {
2024-06-19 10:33:28 +02:00
vm . prank ( liquidityPool ) ;
2024-06-18 09:49:57 +02:00
harb . transfer ( account1 , initialSupply / 800 ) ;
vm . warp ( block . timestamp + 1 hours ) ; // Fast-forward time by one hour.
}
// Account 1 claims UBI
vm . prank ( account1 ) ;
uint256 ubiCollected = harb . claimUbi ( account1 ) ;
// Check if the account's balance increased correctly
uint256 expectedBalance = ( initialSupply / 2 ) + ubiCollected ; // This assumes the entire tax pool goes to one account, simplify as needed
assertApproxEqRel ( harb . balanceOf ( account1 ) , expectedBalance , 1 * 1 e18 , " Account 1 ' s balance after claiming UBI with max tax collection is incorrect. " ) ;
// Verify that no taxes are left unclaimed
2024-06-19 10:33:28 +02:00
assertEq ( harb . balanceOf ( taxPool ) , 0 , " All taxes should be claimed after the UBI claim. " ) ;
2024-06-18 09:49:57 +02:00
}
2024-03-12 11:38:16 +01:00
}