Skip to content

Commit

Permalink
adds accounting and compliance tests
Browse files Browse the repository at this point in the history
  • Loading branch information
xhad committed Nov 7, 2024
1 parent 8a4d14d commit 08b2b18
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
184 changes: 184 additions & 0 deletions test/unit/accounting.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;

import {Test} from "lib/forge-std/src/Test.sol";
import {Vault} from "src/Vault.sol";
import {TransparentUpgradeableProxy} from "src/Common.sol";
import {MainnetContracts} from "script/Contracts.sol";
import {MainnetActors} from "script/Actors.sol";
import {Etches} from "test/helpers/Etches.sol";
import {WETH9} from "test/mocks/MockWETH.sol";
import {SetupVault} from "test/helpers/SetupVault.sol";

contract VaultAccountingUnitTest is Test, MainnetContracts, MainnetActors, Etches {
Vault public vaultImplementation;
TransparentUpgradeableProxy public vaultProxy;

Vault public vault;
WETH9 public weth;

address public alice = address(0x1);
uint256 public constant INITIAL_BALANCE = 100_000 ether;

function setUp() public {
SetupVault setupVault = new SetupVault();
(vault, weth) = setupVault.setup();

// Give Alice some tokens
deal(alice, INITIAL_BALANCE);
weth.deposit{value: INITIAL_BALANCE}();
weth.transfer(alice, INITIAL_BALANCE);

// Approve vault to spend Alice's tokens
vm.prank(alice);
weth.approve(address(vault), type(uint256).max);
}

function allocateToBuffer(uint256 amount) public {
address[] memory targets = new address[](2);
targets[0] = WETH;
targets[1] = BUFFER_STRATEGY;

uint256[] memory values = new uint256[](2);
values[0] = 0;
values[1] = 0;

bytes[] memory data = new bytes[](2);
data[0] = abi.encodeWithSignature("approve(address,uint256)", vault.bufferStrategy(), amount);
data[1] = abi.encodeWithSignature("deposit(uint256,address)", amount, address(vault));

vm.prank(ADMIN);
vault.processor(targets, values, data);
}

function test_Vault_Accounting_convertToShares() public view {
uint256 assets = 1000 ether;
uint256 shares = vault.convertToShares(assets);
assertEq(shares, vault.previewDeposit(assets), "Shares should match previewDeposit");
}

function test_Vault_Accounting_convertToAssets() public view {
uint256 shares = 1000 ether;
uint256 assets = vault.convertToAssets(shares);
assertEq(assets, vault.previewRedeem(shares), "Assets should match previewRedeem");
}

function test_Vault_Accounting_totalAssets_afterDeposit() public {
uint256 depositAmount = 1000 ether;
vm.prank(alice);
vault.deposit(depositAmount, alice);
uint256 totalAssets = vault.totalAssets();
assertEq(totalAssets, depositAmount, "Total assets should match the deposit amount");
}

function test_Vault_Accounting_totalAssets_afterMultipleDeposits() public {
uint256 depositAmount1 = 1000 ether;
uint256 depositAmount2 = 2000 ether;
vm.prank(alice);
vault.deposit(depositAmount1, alice);
vm.prank(alice);
vault.deposit(depositAmount2, alice);
uint256 totalAssets = vault.totalAssets();
assertEq(totalAssets, depositAmount1 + depositAmount2, "Total assets should match the sum of deposit amounts");
}

function test_Vault_Accounting_totalAssets_afterWithdraw() public {
uint256 depositAmount = 1000 ether;
uint256 withdrawAmount = 500 ether;
vm.prank(alice);
vault.deposit(depositAmount, alice);

allocateToBuffer(depositAmount);

vm.prank(alice);
vault.withdraw(withdrawAmount, alice, alice);
uint256 totalAssets = vault.totalAssets();
assertEq(
totalAssets,
depositAmount - withdrawAmount,
"Total assets should match the remaining amount after withdrawal"
);
}

function test_Vault_Accounting_totalAssets_afterMultipleWithdrawals() public {
uint256 depositAmount = 3000 ether;
uint256 withdrawAmount1 = 1000 ether;
uint256 withdrawAmount2 = 500 ether;
vm.prank(alice);
vault.deposit(depositAmount, alice);

allocateToBuffer(depositAmount);

vm.prank(alice);
vault.withdraw(withdrawAmount1, alice, alice);
vm.prank(alice);
vault.withdraw(withdrawAmount2, alice, alice);
uint256 totalAssets = vault.totalAssets();
assertEq(
totalAssets,
depositAmount - withdrawAmount1 - withdrawAmount2,
"Total assets should match the remaining amount after multiple withdrawals"
);
}

function test_Vault_Accounting_totalSupply_afterDeposit() public {
uint256 depositAmount = 1000 ether;
vm.prank(alice);
vault.deposit(depositAmount, alice);
uint256 totalSupply = vault.totalSupply();
assertEq(totalSupply, depositAmount, "Total supply should match the deposit amount");
}

function test_Vault_Accounting_totalSupply_afterMultipleDeposits() public {
uint256 depositAmount1 = 1000 ether;
uint256 depositAmount2 = 2000 ether;
vm.prank(alice);
vault.deposit(depositAmount1, alice);
vm.prank(alice);
vault.deposit(depositAmount2, alice);
uint256 totalSupply = vault.totalSupply();
assertEq(totalSupply, depositAmount1 + depositAmount2, "Total supply should match the sum of deposit amounts");
}

function test_Vault_Accounting_totalSupply_afterWithdraw() public {
uint256 depositAmount = 1000 ether;
uint256 bufferRatio = 5;

vm.prank(alice);
vault.deposit(depositAmount, alice);

allocateToBuffer(depositAmount / bufferRatio);

vm.prank(alice);
uint256 maxWithdraw = vault.maxWithdraw(alice);
vault.withdraw(maxWithdraw, alice, alice);
uint256 totalSupply = vault.totalSupply();
assertEq(
totalSupply, depositAmount - maxWithdraw, "Total supply should match the remaining amount after withdrawal"
);
}

function test_Vault_Accounting_totalSupply_afterMultipleWithdrawals() public {
uint256 depositAmount = 3000 ether;
uint256 bufferRatio = 5;

uint256 withdrawAmount1 = vault.maxWithdraw(alice) / 3;
uint256 withdrawAmount2 = vault.maxWithdraw(alice) / 7;

vm.prank(alice);
vault.deposit(depositAmount, alice);

allocateToBuffer(depositAmount / bufferRatio);

vm.prank(alice);
vault.withdraw(withdrawAmount1, alice, alice);
vm.prank(alice);
vault.withdraw(withdrawAmount2, alice, alice);
uint256 totalSupply = vault.totalSupply();
assertEq(
totalSupply,
depositAmount - withdrawAmount1 - withdrawAmount2,
"Total supply should match the remaining amount after multiple withdrawals"
);
}
}
94 changes: 94 additions & 0 deletions test/unit/compliance.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;

import {Test} from "lib/forge-std/src/Test.sol";
import {Vault} from "src/Vault.sol";
import {TransparentUpgradeableProxy} from "src/Common.sol";
import {MainnetContracts} from "script/Contracts.sol";
import {MainnetActors} from "script/Actors.sol";
import {Etches} from "test/helpers/Etches.sol";
import {WETH9} from "test/mocks/MockWETH.sol";
import {SetupVault} from "test/helpers/SetupVault.sol";

contract Vault4626ComplianceUnitTest is Test, MainnetContracts, MainnetActors, Etches {
Vault public vaultImplementation;
TransparentUpgradeableProxy public vaultProxy;

Vault public vault;
WETH9 public weth;

address public alice = address(0x1);
uint256 public constant INITIAL_BALANCE = 100_000 ether;

function setUp() public {
SetupVault setupVault = new SetupVault();
(vault, weth) = setupVault.setup();

// Give Alice some tokens
deal(alice, INITIAL_BALANCE);
weth.deposit{value: INITIAL_BALANCE}();
weth.transfer(alice, INITIAL_BALANCE);

// Approve vault to spend Alice's tokens
vm.prank(alice);
weth.approve(address(vault), type(uint256).max);
}

/* The maxWithdraw function should return the maximum amount of underlying assets that
can be withdrawn by the owner without affecting the vault's ability to meet its obligations.
Paused State: The function correctly returns 0 if the vault is paused
*/
function test_Vault_Compliance_maxWithdraw_paused() public {
vm.prank(ADMIN);
vault.pause(true);
uint256 maxWithdrawAmount = vault.maxWithdraw(alice);
assertEq(maxWithdrawAmount, 0, "Max withdraw amount should be 0 when paused");
}

/* Asset Conversion: The function calculates the baseConvertedAssets by converting the
owner's balance of vault tokens to the equivalent amount of underlying assets.
This is consistent with the ERC-4626 requirement to determine the maximum withdrawable
amount based on the owner's share balance.
*/
function test_Vault_Compliance_maxWithdraw_assetConversion() public view {
uint256 aliceBalance = vault.balanceOf(alice);
uint256 baseConvertedAssets = vault.convertToAssets(aliceBalance);
uint256 maxWithdrawAmount = vault.maxWithdraw(alice);
assertEq(maxWithdrawAmount, baseConvertedAssets, "Max withdraw amount should be equal to base converted assets");
}

/*
Available Assets Check: The function checks the available assets in the buffer strategy
and ensures that the withdrawal does not exceed this amount. This is a prudent measure
to ensure the vault maintains sufficient liquidity.
*/
function test_Vault_Compliance_maxWithdraw_availableAssetsCheck() public view {
uint256 aliceBalance = vault.balanceOf(alice);
uint256 baseConvertedAssets = vault.convertToAssets(aliceBalance);
uint256 availableAssets = vault.maxWithdraw(address(vault));
uint256 maxWithdrawAmount = vault.maxWithdraw(alice);
assertEq(
maxWithdrawAmount,
availableAssets < baseConvertedAssets ? 0 : baseConvertedAssets,
"Max withdraw amount should be the lesser of available assets or base converted assets"
);
}

/*
5. Return Value: The function returns the lesser of the converted asset value or the available assets,
which aligns with the intent of ensuring that the vault can fulfill the withdrawal request without
compromising its liquidity.
*/
function test_Vault_Compliance_maxWithdraw_returnValue() public view {
uint256 aliceBalance = vault.balanceOf(alice);
uint256 baseConvertedAssets = vault.convertToAssets(aliceBalance);
uint256 availableAssets = vault.maxWithdraw(address(vault));
uint256 maxWithdrawAmount = vault.maxWithdraw(alice);
uint256 expectedValue = availableAssets < baseConvertedAssets ? 0 : baseConvertedAssets;
assertEq(
maxWithdrawAmount,
expectedValue,
"Max withdraw amount should be the lesser of available assets or base converted assets"
);
}
}

0 comments on commit 08b2b18

Please sign in to comment.