Skip to content

Commit 00a493b

Browse files
authored
Merge pull request #23 from LoadPipe/HAMT-63-unit-tests-ragequit
Added new rage quit tests
2 parents 09d1e49 + d267363 commit 00a493b

1 file changed

Lines changed: 339 additions & 0 deletions

File tree

test/RagequitTest.t.sol

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,38 @@ import "./DeploymentSetup.t.sol";
66
import "../src/CustomBaal.sol";
77
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
88
import { console2 } from "forge-std/console2.sol";
9+
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
910

1011
/**
1112
* @dev RagequitTest contract to test the ragequit functionality of the Baal DAO
1213
*/
1314
contract RagequitTest is DeploymentSetup {
1415

16+
// For config-agnostic tests
17+
CustomBaal public customBaalDirect;
18+
address public mockCommunityVault;
19+
address public mockLootToken;
20+
address public mockSharesToken;
21+
address public testUser1;
22+
address public testUser2;
23+
address public testUser3;
24+
ERC20PresetMinterPauser public testToken1;
25+
ERC20PresetMinterPauser public testToken2;
26+
27+
// Override setup to initialize config-agnostic test variables
28+
function setUp() public override {
29+
super.setUp();
30+
31+
// Create test users
32+
testUser1 = makeAddr("testUser1");
33+
testUser2 = makeAddr("testUser2");
34+
testUser3 = makeAddr("testUser3");
35+
36+
// Deploy test tokens for ragequit
37+
testToken1 = new ERC20PresetMinterPauser("Test Token 1", "TT1");
38+
testToken2 = new ERC20PresetMinterPauser("Test Token 2", "TT2");
39+
}
40+
1541
// Test ragequit with ETH
1642
function testRagequitWithEth() public {
1743
// Get the Baal contract instance
@@ -324,4 +350,317 @@ contract RagequitTest is DeploymentSetup {
324350
console2.log("Baal avatar new ETH balance:", newAvatarEthBalance);
325351
assertEq(newAvatarEthBalance, avatarEthBalance - expectedEthReturn, "Avatar ETH balance not reduced correctly");
326352
}
353+
354+
// Direct test of ragequit with multiple tokens
355+
function testRagequitWithMultipleTokens() public {
356+
CustomBaal baalContract = CustomBaal(baal);
357+
358+
// Get user's initial loot balance
359+
uint256 initialLootBalance = IERC20(lootToken).balanceOf(user);
360+
uint256 lootToBurn = initialLootBalance / 3; // Burn a third of the loot
361+
362+
// Setup test tokens in treasury
363+
uint256 treasuryToken1Amount = 100 ether;
364+
uint256 treasuryToken2Amount = 200 ether;
365+
366+
// Mint tokens and send to avatar (treasury)
367+
testToken1.mint(address(this), treasuryToken1Amount);
368+
testToken2.mint(address(this), treasuryToken2Amount);
369+
testToken1.transfer(baalContract.avatar(), treasuryToken1Amount);
370+
testToken2.transfer(baalContract.avatar(), treasuryToken2Amount);
371+
372+
// Send ETH to avatar
373+
vm.deal(address(this), 5 ether);
374+
(bool success, ) = payable(baalContract.avatar()).call{value: 5 ether}("");
375+
require(success, "ETH transfer failed");
376+
377+
// Get initial balances
378+
uint256 initialTotalSupply = baalContract.totalSupply();
379+
uint256 vaultLootBalance = IERC20(lootToken).balanceOf(communityVault);
380+
uint256 vaultSharesBalance = IERC20(address(baalContract.sharesToken())).balanceOf(communityVault);
381+
382+
// Calculate adjusted total supply
383+
uint256 adjustedTotalSupply = initialTotalSupply - vaultLootBalance - vaultSharesBalance;
384+
385+
// Calculate expected returns for each token
386+
uint256 expectedEthReturn = (lootToBurn * 5 ether) / adjustedTotalSupply;
387+
uint256 expectedToken1Return = (lootToBurn * treasuryToken1Amount) / adjustedTotalSupply;
388+
uint256 expectedToken2Return = (lootToBurn * treasuryToken2Amount) / adjustedTotalSupply;
389+
390+
// Record initial balances
391+
uint256 initialUserEthBalance = user.balance;
392+
uint256 initialUserToken1Balance = testToken1.balanceOf(user);
393+
uint256 initialUserToken2Balance = testToken2.balanceOf(user);
394+
395+
// Sort token addresses in ascending order for ragequit
396+
address ethAddress = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
397+
address token1Address = address(testToken1);
398+
address token2Address = address(testToken2);
399+
400+
// Create the tokens array with sorted addresses
401+
address[] memory tokens = new address[](3);
402+
403+
// Simple sorting based on address values
404+
if (token1Address < token2Address && token1Address < ethAddress) {
405+
tokens[0] = token1Address;
406+
if (token2Address < ethAddress) {
407+
tokens[1] = token2Address;
408+
tokens[2] = ethAddress;
409+
} else {
410+
tokens[1] = ethAddress;
411+
tokens[2] = token2Address;
412+
}
413+
} else if (token2Address < token1Address && token2Address < ethAddress) {
414+
tokens[0] = token2Address;
415+
if (token1Address < ethAddress) {
416+
tokens[1] = token1Address;
417+
tokens[2] = ethAddress;
418+
} else {
419+
tokens[1] = ethAddress;
420+
tokens[2] = token1Address;
421+
}
422+
} else {
423+
tokens[0] = ethAddress;
424+
if (token1Address < token2Address) {
425+
tokens[1] = token1Address;
426+
tokens[2] = token2Address;
427+
} else {
428+
tokens[1] = token2Address;
429+
tokens[2] = token1Address;
430+
}
431+
}
432+
433+
// Execute ragequit
434+
vm.startPrank(user);
435+
baalContract.ragequit(user, 0, lootToBurn, tokens);
436+
vm.stopPrank();
437+
438+
// Check loot was burned
439+
assertEq(IERC20(lootToken).balanceOf(user), initialLootBalance - lootToBurn);
440+
441+
// Check user received tokens and treasury balances were reduced
442+
// We need to verify each token based on its position in the sorted array
443+
for (uint i = 0; i < tokens.length; i++) {
444+
if (tokens[i] == ethAddress) {
445+
assertEq(user.balance, initialUserEthBalance + expectedEthReturn, "ETH balance incorrect");
446+
assertEq(address(baalContract.avatar()).balance, 5 ether - expectedEthReturn, "Avatar ETH balance incorrect");
447+
} else if (tokens[i] == token1Address) {
448+
assertEq(testToken1.balanceOf(user), initialUserToken1Balance + expectedToken1Return, "Token1 balance incorrect");
449+
assertEq(testToken1.balanceOf(baalContract.avatar()), treasuryToken1Amount - expectedToken1Return, "Avatar Token1 balance incorrect");
450+
} else if (tokens[i] == token2Address) {
451+
assertEq(testToken2.balanceOf(user), initialUserToken2Balance + expectedToken2Return, "Token2 balance incorrect");
452+
assertEq(testToken2.balanceOf(baalContract.avatar()), treasuryToken2Amount - expectedToken2Return, "Avatar Token2 balance incorrect");
453+
}
454+
}
455+
}
456+
457+
// Test multiple users doing ragequit sequentially
458+
function testSequentialRagequit() public {
459+
CustomBaal baalContract = CustomBaal(baal);
460+
461+
// Setup initial state - share loot among test users
462+
vm.startPrank(user);
463+
uint256 initialLootBalance = IERC20(lootToken).balanceOf(user);
464+
465+
// Keep half, give 1/4 to each test user
466+
uint256 transferAmount = initialLootBalance / 4;
467+
IERC20(lootToken).transfer(testUser1, transferAmount);
468+
IERC20(lootToken).transfer(testUser2, transferAmount);
469+
vm.stopPrank();
470+
471+
// Send ETH to avatar
472+
vm.deal(address(this), 12 ether);
473+
(bool success, ) = payable(baalContract.avatar()).call{value: 12 ether}("");
474+
require(success, "ETH transfer failed");
475+
476+
// Get adjusted total supply
477+
uint256 totalSupply = baalContract.totalSupply();
478+
uint256 vaultLootBalance = IERC20(lootToken).balanceOf(communityVault);
479+
uint256 vaultSharesBalance = IERC20(address(baalContract.sharesToken())).balanceOf(communityVault);
480+
uint256 adjustedTotalSupply = totalSupply - vaultLootBalance - vaultSharesBalance;
481+
482+
// User 1 ragequits with all their loot
483+
address[] memory tokens = new address[](1);
484+
tokens[0] = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); // ETH
485+
486+
uint256 user1InitialBalance = testUser1.balance;
487+
uint256 avatarInitialBalance = address(baalContract.avatar()).balance;
488+
uint256 expectedReturn1 = (transferAmount * avatarInitialBalance) / adjustedTotalSupply;
489+
490+
vm.startPrank(testUser1);
491+
baalContract.ragequit(testUser1, 0, transferAmount, tokens);
492+
vm.stopPrank();
493+
494+
// Check user1 received ETH
495+
assertEq(testUser1.balance, user1InitialBalance + expectedReturn1);
496+
497+
// User 2 ragequits with all their loot
498+
uint256 user2InitialBalance = testUser2.balance;
499+
uint256 avatarUpdatedBalance = address(baalContract.avatar()).balance;
500+
501+
// Recalculate adjusted total supply after user1's ragequit
502+
adjustedTotalSupply -= transferAmount;
503+
uint256 expectedReturn2 = (transferAmount * avatarUpdatedBalance) / adjustedTotalSupply;
504+
505+
vm.startPrank(testUser2);
506+
baalContract.ragequit(testUser2, 0, transferAmount, tokens);
507+
vm.stopPrank();
508+
509+
// Check user2 received ETH
510+
assertEq(testUser2.balance, user2InitialBalance + expectedReturn2);
511+
512+
// Verify original user's loot remains intact
513+
assertEq(IERC20(lootToken).balanceOf(user), initialLootBalance / 2);
514+
515+
// Verify both test users have 0 loot
516+
assertEq(IERC20(lootToken).balanceOf(testUser1), 0);
517+
assertEq(IERC20(lootToken).balanceOf(testUser2), 0);
518+
}
519+
520+
// Test edge case: total supply equals vault balance
521+
function testRagequitWithTotalSupplyEqualsVaultBalance() public {
522+
CustomBaal baalContract = CustomBaal(baal);
523+
524+
// Get user's current loot
525+
uint256 userLootBalance = IERC20(lootToken).balanceOf(user);
526+
527+
// Mint more loot to the community vault to make adjustedTotalSupply very small
528+
vm.startPrank(baalContract.avatar());
529+
baalContract.mintLoot(
530+
_singleAddressArray(communityVault),
531+
_singleUint256Array(baalContract.totalSupply() * 100) // Make vault balance far exceed total supply
532+
);
533+
vm.stopPrank();
534+
535+
// Send ETH to avatar
536+
vm.deal(address(this), 5 ether);
537+
(bool success, ) = payable(baalContract.avatar()).call{value: 5 ether}("");
538+
require(success, "ETH transfer failed");
539+
540+
// Calculate expected return
541+
uint256 lootToBurn = userLootBalance / 2;
542+
address[] memory tokens = new address[](1);
543+
tokens[0] = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); // ETH
544+
545+
uint256 initialUserEthBalance = user.balance;
546+
uint256 avatarEthBalance = address(baalContract.avatar()).balance;
547+
548+
vm.startPrank(user);
549+
baalContract.ragequit(user, 0, lootToBurn, tokens);
550+
vm.stopPrank();
551+
552+
// Even with extreme vault balance, user should still get proportional ETH
553+
assertGt(user.balance, initialUserEthBalance, "User should have received some ETH");
554+
assertLt(address(baalContract.avatar()).balance, avatarEthBalance, "Avatar balance should have decreased");
555+
}
556+
557+
// Test ragequit with zero community vault
558+
function testRagequitWithZeroCommunityVault() public {
559+
CustomBaal baalContract = CustomBaal(baal);
560+
561+
// Get initial balances and information
562+
uint256 initialUserLootBalance = IERC20(lootToken).balanceOf(user);
563+
uint256 vaultLootBalance = IERC20(lootToken).balanceOf(communityVault);
564+
console2.log("Initial community vault LOOT balance:", vaultLootBalance);
565+
566+
vm.startPrank(baalContract.avatar());
567+
568+
// Burn all loot tokens from the community vault
569+
baalContract.burnLoot(
570+
_singleAddressArray(communityVault),
571+
_singleUint256Array(vaultLootBalance)
572+
);
573+
vm.stopPrank();
574+
575+
// Verify the community vault now has zero loot tokens
576+
uint256 newVaultBalance = IERC20(lootToken).balanceOf(communityVault);
577+
console2.log("Community vault LOOT balance after burning:", newVaultBalance);
578+
assertEq(newVaultBalance, 0, "Vault should have 0 loot");
579+
580+
// Send ETH to the avatar for ragequit
581+
vm.deal(address(this), 5 ether);
582+
(bool success, ) = payable(baalContract.avatar()).call{value: 5 ether}("");
583+
require(success, "ETH transfer failed");
584+
585+
// Record initial balances before ragequit
586+
uint256 initialTotalSupply = baalContract.totalSupply();
587+
uint256 initialUserEthBalance = user.balance;
588+
uint256 avatarEthBalance = address(baalContract.avatar()).balance;
589+
590+
// Calculate loot to burn (half of user's balance)
591+
uint256 lootToBurn = initialUserLootBalance / 2;
592+
593+
// Prepare token array for ragequit
594+
address[] memory tokens = new address[](1);
595+
tokens[0] = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); // ETH
596+
597+
// Calculate expected return with zero vault balance
598+
uint256 adjustedTotalSupply = initialTotalSupply;
599+
uint256 expectedEthReturn = (lootToBurn * avatarEthBalance) / adjustedTotalSupply;
600+
601+
// Execute ragequit with zero vault balance
602+
vm.startPrank(user);
603+
baalContract.ragequit(user, 0, lootToBurn, tokens);
604+
vm.stopPrank();
605+
606+
// Verify user received the expected ETH amount
607+
assertEq(user.balance, initialUserEthBalance + expectedEthReturn, "User didn't receive correct ETH amount");
608+
609+
// Verify user's loot was burned
610+
assertEq(IERC20(lootToken).balanceOf(user), initialUserLootBalance - lootToBurn, "Incorrect loot balance after ragequit");
611+
612+
// Verify avatar ETH balance decreased correctly
613+
assertEq(address(baalContract.avatar()).balance, avatarEthBalance - expectedEthReturn, "Avatar ETH balance didn't decrease correctly");
614+
}
615+
616+
// Test ragequit with precise amounts and custom loot distribution
617+
function testRagequitWithPreciseAmounts() public {
618+
CustomBaal baalContract = CustomBaal(baal);
619+
620+
// User wants to burn exactly 35% of their loot
621+
uint256 initialLootBalance = IERC20(lootToken).balanceOf(user);
622+
uint256 lootToBurn = (initialLootBalance * 35) / 100; // 35%
623+
624+
// Send a precise amount of ETH to avatar
625+
uint256 ethAmount = 7.77777 ether;
626+
vm.deal(address(this), ethAmount);
627+
(bool success, ) = payable(baalContract.avatar()).call{value: ethAmount}("");
628+
require(success, "ETH transfer failed");
629+
630+
// Calculate expected return
631+
uint256 totalSupply = baalContract.totalSupply();
632+
uint256 vaultLootBalance = IERC20(lootToken).balanceOf(communityVault);
633+
uint256 vaultSharesBalance = IERC20(address(baalContract.sharesToken())).balanceOf(communityVault);
634+
uint256 adjustedTotalSupply = totalSupply - vaultLootBalance - vaultSharesBalance;
635+
636+
uint256 expectedEthReturn = (lootToBurn * ethAmount) / adjustedTotalSupply;
637+
638+
// Record initial balances
639+
uint256 initialUserEthBalance = user.balance;
640+
641+
// Ragequit with ETH
642+
address[] memory tokens = new address[](1);
643+
tokens[0] = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
644+
645+
vm.startPrank(user);
646+
baalContract.ragequit(user, 0, lootToBurn, tokens);
647+
vm.stopPrank();
648+
649+
// Verify results
650+
assertEq(IERC20(lootToken).balanceOf(user), initialLootBalance - lootToBurn);
651+
assertEq(user.balance, initialUserEthBalance + expectedEthReturn);
652+
}
653+
654+
// Helper functions
655+
function _singleAddressArray(address _addr) private pure returns (address[] memory) {
656+
address[] memory arr = new address[](1);
657+
arr[0] = _addr;
658+
return arr;
659+
}
660+
661+
function _singleUint256Array(uint256 _val) private pure returns (uint256[] memory) {
662+
uint256[] memory arr = new uint256[](1);
663+
arr[0] = _val;
664+
return arr;
665+
}
327666
}

0 commit comments

Comments
 (0)