Skip to content

Instantly share code, notes, and snippets.

@Namaskar-1F64F
Created February 19, 2024 10:13
Show Gist options
  • Save Namaskar-1F64F/fb3787e89120acacee1ffb6837386620 to your computer and use it in GitHub Desktop.
Save Namaskar-1F64F/fb3787e89120acacee1ffb6837386620 to your computer and use it in GitHub Desktop.
Solmate tests
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.20;
import "forge-std/Vm.sol";
import "forge-std/console.sol";
import "forge-std/Test.sol";
import {GGPVault} from "../contracts/GGPVault.sol";
import {MockTokenGGP} from "./mocks/MockTokenGGP.sol";
import {MockStaking} from "./mocks/MockStaking.sol";
import {MockStorage} from "./mocks/MockStorage.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
contract GGPVaultTest is Test {
GGPVault vault;
MockTokenGGP ggpToken;
MockStaking mockStaking;
MockStorage mockStorage;
address owner;
address nodeOp1 = address(0x9);
event GGPCapUpdated(uint256 newCap);
event DepositedFromStaking(address indexed caller, uint256 amount);
error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);
error OwnableUnauthorizedAccount(address account);
function setUp() public {
owner = address(this);
ggpToken = new MockTokenGGP(address(this));
mockStaking = new MockStaking(ggpToken);
mockStorage = new MockStorage();
mockStorage.setAddress(keccak256(abi.encodePacked("contract.address", "staking")), address(mockStaking));
address proxy = Upgrades.deployUUPSProxy(
"GGPVault.sol",
abi.encodeCall(GGPVault.initialize, (address(ggpToken), address(mockStorage), address(this)))
);
vault = GGPVault(proxy);
}
function invariant_check() public {}
function testFuzzSingleMintRedeem(uint256 amount) public {
amount = bound(amount, uint256(0), vault.GGPCap());
if (amount == 0) amount = 1;
uint256 aliceShareAmount = amount;
address alice = address(0xABCD);
ggpToken.transfer(alice, aliceShareAmount);
vm.prank(alice);
ggpToken.approve(address(vault), aliceShareAmount);
assertEq(ggpToken.allowance(alice, address(vault)), aliceShareAmount);
uint256 alicePreDepositBal = ggpToken.balanceOf(alice);
vm.prank(alice);
uint256 aliceUnderlyingAmount = vault.mint(aliceShareAmount, alice);
// Expect exchange rate to be 1:1 on initial mint.
assertEq(aliceShareAmount, aliceUnderlyingAmount);
assertEq(vault.previewWithdraw(aliceShareAmount), aliceUnderlyingAmount);
assertEq(vault.previewDeposit(aliceUnderlyingAmount), aliceShareAmount);
assertEq(vault.totalSupply(), aliceShareAmount);
assertEq(vault.totalAssets(), aliceUnderlyingAmount);
assertEq(vault.balanceOf(alice), aliceUnderlyingAmount);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount);
assertEq(ggpToken.balanceOf(alice), alicePreDepositBal - aliceUnderlyingAmount);
vm.prank(alice);
vault.redeem(aliceShareAmount, alice, alice);
assertEq(vault.totalAssets(), 0);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0);
assertEq(ggpToken.balanceOf(alice), alicePreDepositBal);
}
function testFuzzSingleDepositWithdraw(uint256 amount) public {
amount = bound(amount, uint256(0), vault.GGPCap());
if (amount == 0) amount = 1;
uint256 aliceUnderlyingAmount = amount;
address alice = address(0xABCD);
ggpToken.transfer(alice, aliceUnderlyingAmount);
vm.prank(alice);
ggpToken.approve(address(vault), aliceUnderlyingAmount);
assertEq(ggpToken.allowance(alice, address(vault)), aliceUnderlyingAmount);
uint256 alicePreDepositBal = ggpToken.balanceOf(alice);
vm.prank(alice);
uint256 aliceShareAmount = vault.deposit(aliceUnderlyingAmount, alice);
// Expect exchange rate to be 1:1 on initial deposit.
assertEq(aliceUnderlyingAmount, aliceShareAmount);
assertEq(vault.previewWithdraw(aliceShareAmount), aliceUnderlyingAmount);
assertEq(vault.previewDeposit(aliceUnderlyingAmount), aliceShareAmount);
assertEq(vault.totalSupply(), aliceShareAmount);
assertEq(vault.totalAssets(), aliceUnderlyingAmount);
assertEq(vault.balanceOf(alice), aliceShareAmount);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount);
assertEq(ggpToken.balanceOf(alice), alicePreDepositBal - aliceUnderlyingAmount);
vm.prank(alice);
vault.withdraw(aliceUnderlyingAmount, alice, alice);
assertEq(vault.totalAssets(), 0);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0);
assertEq(ggpToken.balanceOf(alice), alicePreDepositBal);
}
function testMultipleMintDepositRedeemWithdraw() public {
// Scenario:
// A = Alice, B = Bob
// ________________________________________________________
// | Vault shares | A share | A assets | B share | B assets |
// |========================================================|
// | 1. Alice mints 2000 shares (costs 2000 tokens) |
// |--------------|---------|----------|---------|----------|
// | 2000 | 2000 | 2000 | 0 | 0 |
// |--------------|---------|----------|---------|----------|
// | 2. Bob deposits 4000 tokens (mints 4000 shares) |
// |--------------|---------|----------|---------|----------|
// | 6000 | 2000 | 2000 | 4000 | 4000 |
// |--------------|---------|----------|---------|----------|
// | 3. Vault mutates by +3000 tokens... |
// | (simulated yield returned from strategy)... |
// |--------------|---------|----------|---------|----------|
// | 6000 | 2000 | 3000 | 4000 | 6000 |
// |--------------|---------|----------|---------|----------|
// | 4. Alice deposits 2000 tokens (mints 1333 shares) |
// |--------------|---------|----------|---------|----------|
// | 7333 | 3333 | 4999 | 4000 | 6000 |
// |--------------|---------|----------|---------|----------|
// | 5. Bob mints 2000 shares (costs 3001 assets) |
// | NOTE: Bob's assets spent got rounded up |
// | NOTE: Alice's vault assets got rounded up |
// |--------------|---------|----------|---------|----------|
// | 9333 | 3333 | 5000 | 6000 | 9000 |
// |--------------|---------|----------|---------|----------|
// | 6. Vault mutates by +3000 tokens... |
// | (simulated yield returned from strategy) |
// | NOTE: Vault holds 17001 tokens, but sum of |
// | assetsOf() is 17000. |
// |--------------|---------|----------|---------|----------|
// | 9333 | 3333 | 6071 | 6000 | 10929 |
// |--------------|---------|----------|---------|----------|
// | 7. Alice redeem 1333 shares (2428 assets) |
// |--------------|---------|----------|---------|----------|
// | 8000 | 2000 | 3643 | 6000 | 10929 |
// |--------------|---------|----------|---------|----------|
// | 8. Bob withdraws 2928 assets (1608 shares) |
// |--------------|---------|----------|---------|----------|
// | 6392 | 2000 | 3643 | 4392 | 8000 |
// |--------------|---------|----------|---------|----------|
// | 9. Alice withdraws 3643 assets (2000 shares) |
// | NOTE: Bob's assets have been rounded back up |
// |--------------|---------|----------|---------|----------|
// | 4392 | 0 | 0 | 4392 | 8001 |
// |--------------|---------|----------|---------|----------|
// | 10. Bob redeem 4392 shares (8001 tokens) |
// |--------------|---------|----------|---------|----------|
// | 0 | 0 | 0 | 0 | 0 |
// |______________|_________|__________|_________|__________|
address alice = address(0xABCD);
address bob = address(0xDCBA);
uint256 mutationUnderlyingAmount = 3000;
ggpToken.transfer(alice, 4000);
vm.prank(alice);
ggpToken.approve(address(vault), 4000);
assertEq(ggpToken.allowance(alice, address(vault)), 4000);
ggpToken.transfer(bob, 7001);
vm.prank(bob);
ggpToken.approve(address(vault), 7001);
assertEq(ggpToken.allowance(bob, address(vault)), 7001);
// 1. Alice mints 2000 shares (costs 2000 tokens)
vm.prank(alice);
uint256 aliceUnderlyingAmount = vault.mint(2000, alice);
uint256 aliceShareAmount = vault.previewDeposit(aliceUnderlyingAmount);
// Expect to have received the requested mint amount.
assertEq(aliceShareAmount, 2000);
assertEq(vault.balanceOf(alice), aliceShareAmount);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount);
assertEq(vault.convertToShares(aliceUnderlyingAmount), vault.balanceOf(alice));
// Expect a 1:1 ratio before mutation.
assertEq(aliceUnderlyingAmount, 2000);
// Sanity check.
assertEq(vault.totalSupply(), aliceShareAmount);
assertEq(vault.totalAssets(), aliceUnderlyingAmount);
// 2. Bob deposits 4000 tokens (mints 4000 shares)
vm.prank(bob);
uint256 bobShareAmount = vault.deposit(4000, bob);
uint256 bobUnderlyingAmount = vault.previewWithdraw(bobShareAmount);
// Expect to have received the requested underlying amount.
assertEq(bobUnderlyingAmount, 4000);
assertEq(vault.balanceOf(bob), bobShareAmount);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), bobUnderlyingAmount);
assertEq(vault.convertToShares(bobUnderlyingAmount), vault.balanceOf(bob));
// Expect a 1:1 ratio before mutation.
assertEq(bobShareAmount, bobUnderlyingAmount);
// Sanity check.
uint256 preMutationShareBal = aliceShareAmount + bobShareAmount;
uint256 preMutationBal = aliceUnderlyingAmount + bobUnderlyingAmount;
assertEq(vault.totalSupply(), preMutationShareBal);
assertEq(vault.totalAssets(), preMutationBal);
assertEq(vault.totalSupply(), 6000);
assertEq(vault.totalAssets(), 6000);
// 3. Vault mutates by +3000 tokens... |
// (simulated yield returned from strategy)...
// The Vault now contains more tokens than deposited which causes the exchange rate to change.
// Alice share is 33.33% of the Vault, Bob 66.66% of the Vault.
// Alice's share count stays the same but the underlying amount changes from 2000 to 3000.
// Bob's share count stays the same but the underlying amount changes from 4000 to 6000.
ggpToken.transfer(address(vault), mutationUnderlyingAmount);
assertEq(vault.totalSupply(), preMutationShareBal);
assertEq(vault.totalAssets(), preMutationBal + mutationUnderlyingAmount);
assertEq(vault.balanceOf(alice), aliceShareAmount);
assertEq(
vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount + (mutationUnderlyingAmount / 3) * 1
);
assertEq(vault.balanceOf(bob), bobShareAmount);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), bobUnderlyingAmount + (mutationUnderlyingAmount / 3) * 2);
// 4. Alice deposits 2000 tokens (mints 1333 shares)
vm.prank(alice);
vault.deposit(2000, alice);
assertEq(vault.totalSupply(), 7333);
assertEq(vault.balanceOf(alice), 3333);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 4999);
assertEq(vault.balanceOf(bob), 4000);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 6000);
// 5. Bob mints 2000 shares (costs 3001 assets)
// NOTE: Bob's assets spent got rounded up
// NOTE: Alices's vault assets got rounded up
vm.prank(bob);
vault.mint(2000, bob);
assertEq(vault.totalSupply(), 9333);
assertEq(vault.balanceOf(alice), 3333);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 5000);
assertEq(vault.balanceOf(bob), 6000);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 9000);
// Sanity checks:
// Alice and bob should have spent all their tokens now
assertEq(ggpToken.balanceOf(alice), 0);
assertEq(ggpToken.balanceOf(bob), 0);
// Assets in vault: 4k (alice) + 7k (bob) + 3k (yield) + 1 (round up)
assertEq(vault.totalAssets(), 14001);
// 6. Vault mutates by +3000 tokens
// NOTE: Vault holds 17001 tokens, but sum of assetsOf() is 17000.
ggpToken.transfer(address(vault), mutationUnderlyingAmount);
assertEq(vault.totalAssets(), 17001);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 6071);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 10929);
// 7. Alice redeem 1333 shares (2428 assets)
vm.prank(alice);
vault.redeem(1333, alice, alice);
assertEq(ggpToken.balanceOf(alice), 2428);
assertEq(vault.totalSupply(), 8000);
assertEq(vault.totalAssets(), 14573);
assertEq(vault.balanceOf(alice), 2000);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 3643);
assertEq(vault.balanceOf(bob), 6000);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 10929);
// 8. Bob withdraws 2929 assets (1608 shares)
vm.prank(bob);
vault.withdraw(2929, bob, bob);
assertEq(ggpToken.balanceOf(bob), 2929);
assertEq(vault.totalSupply(), 6392);
assertEq(vault.totalAssets(), 11644);
assertEq(vault.balanceOf(alice), 2000);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 3643);
assertEq(vault.balanceOf(bob), 4392);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 8000);
// 9. Alice withdraws 3643 assets (2000 shares)
// NOTE: Bob's assets have been rounded back up
vm.prank(alice);
vault.withdraw(3643, alice, alice);
assertEq(ggpToken.balanceOf(alice), 6071);
assertEq(vault.totalSupply(), 4392);
assertEq(vault.totalAssets(), 8001);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0);
assertEq(vault.balanceOf(bob), 4392);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 8001);
// 10. Bob redeem 4392 shares (8001 tokens)
vm.prank(bob);
vault.redeem(4392, bob, bob);
assertEq(ggpToken.balanceOf(bob), 10930);
assertEq(vault.totalSupply(), 0);
assertEq(vault.totalAssets(), 0);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0);
assertEq(vault.balanceOf(bob), 0);
assertEq(vault.convertToAssets(vault.balanceOf(bob)), 0);
// Sanity check
assertEq(ggpToken.balanceOf(address(vault)), 0);
}
function testFailDepositWithNotEnoughApproval() public {
ggpToken.transfer(address(this), 0.5e18);
ggpToken.approve(address(vault), 0.5e18);
assertEq(ggpToken.allowance(address(this), address(vault)), 0.5e18);
vault.deposit(1e18, address(this));
}
function testFailWithdrawWithNotEnoughUnderlyingAmount() public {
ggpToken.transfer(address(this), 0.5e18);
ggpToken.approve(address(vault), 0.5e18);
vault.deposit(0.5e18, address(this));
vault.withdraw(1e18, address(this), address(this));
}
function testFailRedeemWithNotEnoughShareAmount() public {
ggpToken.transfer(address(this), 0.5e18);
ggpToken.approve(address(vault), 0.5e18);
vault.deposit(0.5e18, address(this));
vault.redeem(1e18, address(this), address(this));
}
function testFailWithdrawWithNoUnderlyingAmount() public {
vault.withdraw(1e18, address(this), address(this));
}
function testFailRedeemWithNoShareAmount() public {
vault.redeem(1e18, address(this), address(this));
}
function testFailDepositWithNoApproval() public {
vault.deposit(1e18, address(this));
}
function testFailMintWithNoApproval() public {
vault.mint(1e18, address(this));
}
function testMintZero() public {
vault.mint(0, address(this));
assertEq(vault.balanceOf(address(this)), 0);
assertEq(vault.convertToAssets(vault.balanceOf(address(this))), 0);
assertEq(vault.totalSupply(), 0);
assertEq(vault.totalAssets(), 0);
}
function testWithdrawZero() public {
vault.withdraw(0, address(this), address(this));
assertEq(vault.balanceOf(address(this)), 0);
assertEq(vault.convertToAssets(vault.balanceOf(address(this))), 0);
assertEq(vault.totalSupply(), 0);
assertEq(vault.totalAssets(), 0);
}
function testVaultInteractionsForSomeoneElse() public {
// init 2 users with a 1e18 balance
address alice = address(0xABCD);
address bob = address(0xDCBA);
ggpToken.transfer(alice, 1e18);
ggpToken.transfer(bob, 1e18);
vm.prank(alice);
ggpToken.approve(address(vault), 1e18);
vm.prank(bob);
ggpToken.approve(address(vault), 1e18);
// alice deposits 1e18 for bob
vm.prank(alice);
vault.deposit(1e18, bob);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.balanceOf(bob), 1e18);
assertEq(ggpToken.balanceOf(alice), 0);
// bob mint 1e18 for alice
vm.prank(bob);
vault.mint(1e18, alice);
assertEq(vault.balanceOf(alice), 1e18);
assertEq(vault.balanceOf(bob), 1e18);
assertEq(ggpToken.balanceOf(bob), 0);
// alice redeem 1e18 for bob
vm.prank(alice);
vault.redeem(1e18, bob, alice);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.balanceOf(bob), 1e18);
assertEq(ggpToken.balanceOf(bob), 1e18);
// bob withdraw 1e18 for alice
vm.prank(bob);
vault.withdraw(1e18, alice, bob);
assertEq(vault.balanceOf(alice), 0);
assertEq(vault.balanceOf(bob), 0);
assertEq(ggpToken.balanceOf(alice), 1e18);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment