pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import {Reserve} from "../../src/mocks/Reserve.sol"; contract ReserveTest is Test { address constant INITIALIZER = 0x0000000000000000000000000000000000000001; address constant FAUCET_ADDRESS = 0x0000000000000000000000000000000000000002; address constant ALICE_ADDRESS = 0x0000000000000000000000000000000000000003; address constant BOB_ADDRESS = 0x0000000000000000000000000000000000000004; uint256 constant CONVERSION_RATE = 69 * 10e5; uint256 constant SEND_AMOUNT = 1e16; string constant NAME = "Test DAI"; string constant SYMBOL = "tDAI"; Reserve reserve; event Transfer(address indexed from, address indexed to, uint256 value); event LogStakingContractUpdated(address stakingContract); function setUp() public { vm.prank(INITIALIZER); reserve = new Reserve( "Test DAI", "tDAI", CONVERSION_RATE ); } function test_isConstructedCorrectly() public view { assertEq(reserve.name(), NAME); assertEq(reserve.symbol(), SYMBOL); assertEq(reserve.decimals(), 18); assertEq(reserve.totalSupply(), 0); assertEq(reserve.donationRate(), 0); assertEq(reserve.conversionRate(), CONVERSION_RATE); } function test_mint_couldBeDoneWithValue() public { assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); assertEq(reserve.balanceOf(ALICE_ADDRESS), SEND_AMOUNT * CONVERSION_RATE); } function test_mint_couldNotBeDoneWithoutEmptyValue() public { assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); vm.prank(ALICE_ADDRESS); reserve.mint(ALICE_ADDRESS); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); } function test_mint_couldNotMintIfNotEnoughValue() public { assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); bool didRevert = false; vm.prank(ALICE_ADDRESS); try reserve.mint{ value: type(uint256).max }(ALICE_ADDRESS) { } catch { didRevert = true; } assertEq(didRevert, true); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); } function test_mint_superMintCouldBeDoneFromDeployer() public { assertEq(reserve.balanceOf(INITIALIZER), 0); vm.prank(INITIALIZER); reserve.superMint(INITIALIZER, 420 * 1e18); assertEq(reserve.balanceOf(INITIALIZER), 420 * 1e18); } function test_mint_superMintCouldNotBeDoneFromArbitraryAddress() public { assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); vm.expectRevert(); vm.prank(ALICE_ADDRESS); reserve.superMint(ALICE_ADDRESS, 420 * 1e18); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); } function test_mint_donationNotTakenIfRateNotSet() public { assertEq(reserve.totalSupply(), 0); assertEq(reserve.donationRate(), 0); assertEq(reserve.accumulatedDonation(), 0); deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); assertEq(reserve.totalSupply(), SEND_AMOUNT * CONVERSION_RATE); assertEq(reserve.donationRate(), 0); assertEq(reserve.accumulatedDonation(), 0); } function test_mint_donationIsTakenIfRateExists() public { assertEq(reserve.totalSupply(), 0); assertEq(reserve.donationRate(), 0); assertEq(reserve.accumulatedDonation(), 0); vm.prank(INITIALIZER); reserve.changeDonationRate(1e4); // 10% deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); assertEq(reserve.totalSupply(), SEND_AMOUNT * CONVERSION_RATE); assertEq(reserve.donationRate(), 1e4); assertEq(reserve.accumulatedDonation(), SEND_AMOUNT * 1e4 / 1e5); } function test_rate_couldBeChangedByDeployer() public { assertEq(reserve.conversionRate(), CONVERSION_RATE); vm.prank(INITIALIZER); reserve.changeRate(1337); assertEq(reserve.conversionRate(), 1337); } function test_rate_couldNotBeChangedByArbitraryAddress() public { assertEq(reserve.conversionRate(), CONVERSION_RATE); vm.expectRevert(); vm.prank(ALICE_ADDRESS); reserve.changeRate(1337); assertEq(reserve.conversionRate(), CONVERSION_RATE); } function test_withdraw_couldBeDoneByDeployer() public { assertEq(address(reserve).balance, 0); vm.prank(INITIALIZER); reserve.changeDonationRate(1e5); deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); assertEq(address(reserve).balance, SEND_AMOUNT); vm.prank(INITIALIZER); reserve.withdraw(payable(FAUCET_ADDRESS)); assertEq(address(reserve).balance, 0); assertEq(FAUCET_ADDRESS.balance, SEND_AMOUNT); } function test_withdraw_onlyAccumulatedDonationsCouldBeWithdrawn() public { assertEq(address(reserve).balance, 0); vm.prank(INITIALIZER); reserve.changeDonationRate(5 * 1e4); // 50% deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); assertEq(address(reserve).balance, SEND_AMOUNT); vm.prank(INITIALIZER); reserve.withdraw(payable(FAUCET_ADDRESS)); assertEq(address(reserve).balance, SEND_AMOUNT / 2); assertEq(FAUCET_ADDRESS.balance, SEND_AMOUNT / 2); vm.prank(INITIALIZER); reserve.withdraw(payable(FAUCET_ADDRESS)); assertEq(address(reserve).balance, SEND_AMOUNT / 2); assertEq(FAUCET_ADDRESS.balance, SEND_AMOUNT / 2); } function test_withdraw_couldNotBeDoneByArbitraryAddress() public { assertEq(address(reserve).balance, 0); deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); assertEq(address(reserve).balance, SEND_AMOUNT); vm.expectRevert(); vm.prank(ALICE_ADDRESS); reserve.withdraw(payable(ALICE_ADDRESS)); assertEq(address(reserve).balance, SEND_AMOUNT); assertEq(ALICE_ADDRESS.balance, 0); } function test_burn_shouldReturnFullAmountIfRateNotSet() public { deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); uint256 balance = reserve.balanceOf(ALICE_ADDRESS); assertEq(reserve.totalSupply(), SEND_AMOUNT * CONVERSION_RATE); assertEq(reserve.totalSupply(), balance); assertEq(reserve.accumulatedDonation(), 0); assertEq(ALICE_ADDRESS.balance, 0); vm.prank(ALICE_ADDRESS); reserve.burn(balance); assertEq(reserve.totalSupply(), 0); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); assertEq(reserve.accumulatedDonation(), 0); assertEq(ALICE_ADDRESS.balance, SEND_AMOUNT); } function test_burn_shouldTakeDonationInAccordanceToRate() public { vm.prank(INITIALIZER); reserve.changeDonationRate(5 * 1e4); // 50% deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); uint256 balance = reserve.balanceOf(ALICE_ADDRESS); assertEq(reserve.totalSupply(), SEND_AMOUNT * CONVERSION_RATE); assertEq(reserve.totalSupply(), balance); assertEq(reserve.accumulatedDonation(), SEND_AMOUNT / 2); assertEq(ALICE_ADDRESS.balance, 0); vm.prank(ALICE_ADDRESS); reserve.burn(balance); assertEq(reserve.totalSupply(), 0); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); assertEq(reserve.accumulatedDonation(), SEND_AMOUNT / 2); assertEq(ALICE_ADDRESS.balance, SEND_AMOUNT / 2); } function test_burn_multipleUsersAccumulateInTotal() public { vm.prank(INITIALIZER); reserve.changeDonationRate(5 * 1e4); // 50% deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(ALICE_ADDRESS); vm.prank(INITIALIZER); reserve.changeRate(CONVERSION_RATE * 9); deal(BOB_ADDRESS, SEND_AMOUNT); vm.prank(BOB_ADDRESS); reserve.mint{ value: SEND_AMOUNT }(BOB_ADDRESS); assertEq(ALICE_ADDRESS.balance, 0); assertEq(BOB_ADDRESS.balance, 0); uint256 aliceBalance = reserve.balanceOf(ALICE_ADDRESS); uint256 bobBalance = reserve.balanceOf(BOB_ADDRESS); uint256 bobEstimation = reserve.estimateAmount(bobBalance); uint256 aliceEstimation = reserve.estimateAmount(aliceBalance); vm.prank(BOB_ADDRESS); reserve.burn(bobBalance); vm.prank(ALICE_ADDRESS); reserve.burn(aliceBalance); assertEq(BOB_ADDRESS.balance, SEND_AMOUNT * 9 / 10); assertEq(BOB_ADDRESS.balance, bobEstimation); assertEq(ALICE_ADDRESS.balance, SEND_AMOUNT / 10); assertEq(ALICE_ADDRESS.balance, aliceEstimation); assertEq(reserve.totalSupply(), 0); assertEq(reserve.accumulatedDonation(), SEND_AMOUNT); assertEq(address(reserve).balance, SEND_AMOUNT); } function test_reserveFallback() public { assertEq(address(reserve).balance, 0); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); vm.prank(ALICE_ADDRESS); (bool success, ) = address(reserve).call(abi.encodeWithSignature("nonExistentFunction()")); require(success, "Fallback call failed"); assertEq(address(reserve).balance, 0); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); } function test_reserveReceive() public { assertEq(address(reserve).balance, 0); assertEq(reserve.balanceOf(ALICE_ADDRESS), 0); deal(ALICE_ADDRESS, SEND_AMOUNT); vm.prank(ALICE_ADDRESS); (bool success, ) = address(reserve).call{value: SEND_AMOUNT}(""); require(success, "Transfer of native failed"); uint256 estimatedReceiveAmount = SEND_AMOUNT * reserve.conversionRate(); assertEq(address(reserve).balance, SEND_AMOUNT); assertEq(reserve.balanceOf(ALICE_ADDRESS), estimatedReceiveAmount); } }