Skip to content

Commit 1b09681

Browse files
authored
Merge pull request #23 from openfort-xyz/test/vault_manager
test: vaultManager
2 parents 08f354b + bb4b9a5 commit 1b09681

File tree

2 files changed

+368
-0
lines changed

2 files changed

+368
-0
lines changed

contracts/interfaces/IVaultManager.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ interface IVaultManager {
8080
*/
8181
function deposit(IERC20 token, IVault vault, uint256 amount, bool isYield) external returns (uint256);
8282

83+
/**
84+
* @notice Deposit the specified amount of tokens into the Vault for the specified account.
85+
* @param account The account to deposit for.
86+
* @param token The token to deposit.
87+
* @param vault The Vault to deposit into.
88+
* @param amount The amount of tokens to deposit.
89+
* @param isYield A flag to indicate if the deposit is in yield mode.
90+
* @return shares The amount of shares issued at the current exchange rate.
91+
*/
92+
function depositFor(address account, IERC20 token, IVault vault, uint256 amount, bool isYield) external returns (uint256);
93+
8394
/**
8495
* @notice Withdraw the specified amount of tokens from the Vault.
8596
* @param vaults The Vaults to withdraw from.

test/VaultManager.t.sol

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {IInvoiceManager} from "../contracts/interfaces/IInvoiceManager.sol";
5+
import {IVault} from "../contracts/interfaces/IVault.sol";
6+
import {IVaultManager} from "../contracts/interfaces/IVaultManager.sol";
7+
import {IYieldVault} from "../contracts/interfaces/IYieldVault.sol";
8+
9+
import {MockERC20} from "../contracts/mocks/MockERC20.sol";
10+
import {MockInvoiceManager} from "../contracts/mocks/MockInvoiceManager.sol";
11+
import {UpgradeableOpenfortProxy} from "../contracts/proxy/UpgradeableOpenfortProxy.sol";
12+
import {BaseVault} from "../contracts/vaults/BaseVault.sol";
13+
import {VaultManager} from "../contracts/vaults/VaultManager.sol";
14+
15+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
16+
import {Test} from "forge-std/Test.sol";
17+
18+
contract VaultManagerTest is Test {
19+
VaultManager public vaultManager;
20+
21+
address public owner;
22+
address public user1;
23+
address public user2;
24+
MockERC20 public mockERC20;
25+
BaseVault public vault;
26+
MockInvoiceManager public invoiceManager;
27+
uint256 public constant WITHDRAW_LOCK_BLOCK = 10;
28+
29+
function setUp() public {
30+
owner = makeAddr("OWNER");
31+
user1 = makeAddr("USER1");
32+
user2 = makeAddr("USER2");
33+
34+
// Deploy mock tokens and contracts
35+
mockERC20 = new MockERC20();
36+
invoiceManager = new MockInvoiceManager();
37+
38+
// Deploy VaultManager through proxy
39+
vaultManager = VaultManager(
40+
payable(
41+
new UpgradeableOpenfortProxy(
42+
address(new VaultManager()),
43+
abi.encodeWithSelector(
44+
VaultManager.initialize.selector, owner, IInvoiceManager(address(invoiceManager)), WITHDRAW_LOCK_BLOCK
45+
)
46+
)
47+
)
48+
);
49+
50+
// Deploy BaseVault through proxy
51+
vault = BaseVault(
52+
payable(
53+
new UpgradeableOpenfortProxy(
54+
address(new BaseVault()),
55+
abi.encodeWithSelector(BaseVault.initialize.selector, IVaultManager(address(vaultManager)), mockERC20)
56+
)
57+
)
58+
);
59+
60+
// Register vault in VaultManager
61+
vm.startPrank(owner);
62+
vaultManager.addVault(IVault(address(vault)));
63+
vm.stopPrank();
64+
65+
// Mint tokens to users for testing
66+
mockERC20.mint(user1, 1000);
67+
mockERC20.mint(user2, 1000);
68+
}
69+
70+
function testInitialization() public {
71+
assertEq(address(vaultManager.invoiceManager()), address(invoiceManager));
72+
assertEq(vaultManager.withdrawLockBlock(), WITHDRAW_LOCK_BLOCK);
73+
assertEq(vaultManager.owner(), owner);
74+
assertTrue(vaultManager.registeredVaults(IVault(address(vault))));
75+
}
76+
77+
function testAddVault() public {
78+
// Deploy a new vault
79+
BaseVault newVault = BaseVault(
80+
payable(
81+
new UpgradeableOpenfortProxy(
82+
address(new BaseVault()),
83+
abi.encodeWithSelector(BaseVault.initialize.selector, IVaultManager(address(vaultManager)), mockERC20)
84+
)
85+
)
86+
);
87+
88+
// Add the new vault
89+
vm.startPrank(owner);
90+
vaultManager.addVault(IVault(address(newVault)));
91+
vm.stopPrank();
92+
93+
// Verify the vault was added
94+
assertTrue(vaultManager.registeredVaults(IVault(address(newVault))));
95+
96+
// Check that the vault is in the underlyingToVaultList
97+
IVault[] memory vaults = vaultManager.getUnderlyingToVaultList(mockERC20);
98+
assertEq(vaults.length, 2);
99+
assertEq(address(vaults[0]), address(vault));
100+
assertEq(address(vaults[1]), address(newVault));
101+
}
102+
103+
function testDeposit() public {
104+
uint256 depositAmount = 100;
105+
106+
// Approve tokens for VaultManager
107+
vm.startPrank(user1);
108+
mockERC20.approve(address(vaultManager), depositAmount);
109+
110+
// Deposit tokens
111+
uint256 newShares = vaultManager.deposit(mockERC20, IVault(address(vault)), depositAmount, false);
112+
vm.stopPrank();
113+
114+
// Verify shares were added to the user
115+
assertEq(vaultManager.accountShares(user1, IVault(address(vault))), newShares);
116+
117+
// Verify tokens were transferred to the vault
118+
assertEq(mockERC20.balanceOf(address(vault)), depositAmount);
119+
120+
// Verify user's vault list contains the vault
121+
IVault userVaults = vaultManager.accountVaultList(user1, 0);
122+
assertEq(address(userVaults), address(vault));
123+
}
124+
125+
function testDepositFor() public {
126+
uint256 depositAmount = 100;
127+
128+
// Approve tokens for VaultManager
129+
vm.startPrank(user1);
130+
mockERC20.approve(address(vaultManager), depositAmount);
131+
132+
// Deposit tokens for user2
133+
uint256 newShares = vaultManager.depositFor(user2, mockERC20, IVault(address(vault)), depositAmount, false);
134+
vm.stopPrank();
135+
136+
// Verify shares were added to user2
137+
assertEq(vaultManager.accountShares(user2, IVault(address(vault))), newShares);
138+
139+
// Verify tokens were transferred to the vault
140+
assertEq(mockERC20.balanceOf(address(vault)), depositAmount);
141+
142+
// Verify user2's vault list contains the vault
143+
IVault userVaults = vaultManager.accountVaultList(user2, 0);
144+
assertEq(address(userVaults), address(vault));
145+
}
146+
147+
function testQueueWithdrawals() public {
148+
uint256 depositAmount = 100;
149+
150+
// Deposit tokens first
151+
vm.startPrank(user1);
152+
mockERC20.approve(address(vaultManager), depositAmount);
153+
vaultManager.deposit(mockERC20, IVault(address(vault)), depositAmount, false);
154+
155+
// Queue withdrawal
156+
IVault[] memory vaults = new IVault[](1);
157+
vaults[0] = IVault(address(vault));
158+
159+
uint256[] memory shares = new uint256[](1);
160+
shares[0] = vaultManager.accountShares(user1, IVault(address(vault)));
161+
162+
bytes32 withdrawalId = vaultManager.queueWithdrawals(vaults, shares, user1);
163+
vm.stopPrank();
164+
165+
// Verify withdrawal was queued
166+
IVaultManager.Withdrawal memory withdrawal = vaultManager.getWithdrawal(withdrawalId);
167+
assertEq(withdrawal.account, user1);
168+
assertEq(address(withdrawal.vaults[0]), address(vault));
169+
assertEq(withdrawal.amounts[0], shares[0]);
170+
assertEq(withdrawal.startBlock, block.number);
171+
assertEq(withdrawal.nonce, 0);
172+
assertFalse(withdrawal.completed);
173+
174+
// Verify nonce was incremented
175+
assertEq(vaultManager.getWithdrawalNonce(user1), 1);
176+
}
177+
178+
function testCompleteWithdrawals() public {
179+
uint256 depositAmount = 100;
180+
181+
// Deposit tokens first
182+
vm.startPrank(user1);
183+
mockERC20.approve(address(vaultManager), depositAmount);
184+
vaultManager.deposit(mockERC20, IVault(address(vault)), depositAmount, false);
185+
186+
// Queue withdrawal
187+
IVault[] memory vaults = new IVault[](1);
188+
vaults[0] = IVault(address(vault));
189+
190+
uint256[] memory shares = new uint256[](1);
191+
shares[0] = vaultManager.accountShares(user1, IVault(address(vault)));
192+
193+
bytes32 withdrawalId = vaultManager.queueWithdrawals(vaults, shares, user1);
194+
195+
// Try to complete withdrawal before lock period
196+
vm.expectRevert("VaultManager: withdrawal not ready");
197+
bytes32[] memory withdrawalIds = new bytes32[](1);
198+
withdrawalIds[0] = withdrawalId;
199+
vaultManager.completeWithdrawals(withdrawalIds);
200+
201+
// Advance blocks to pass lock period
202+
vm.roll(block.number + WITHDRAW_LOCK_BLOCK + 1);
203+
204+
// Complete withdrawal
205+
vaultManager.completeWithdrawals(withdrawalIds);
206+
vm.stopPrank();
207+
208+
// Verify withdrawal was completed
209+
IVaultManager.Withdrawal memory withdrawal = vaultManager.getWithdrawal(withdrawalId);
210+
assertTrue(withdrawal.completed);
211+
212+
// Verify shares were removed
213+
assertEq(vaultManager.accountShares(user1, IVault(address(vault))), 0);
214+
215+
// Verify tokens were transferred back to user
216+
assertEq(mockERC20.balanceOf(user1), 1000);
217+
}
218+
219+
function testWithdrawSponsorToken() public {
220+
uint256 depositAmount = 100;
221+
222+
// Deposit tokens first
223+
vm.startPrank(user1);
224+
mockERC20.approve(address(vaultManager), depositAmount);
225+
vaultManager.deposit(mockERC20, IVault(address(vault)), depositAmount, false);
226+
vm.stopPrank();
227+
228+
// Prepare withdrawal data
229+
IVault[] memory vaults = new IVault[](1);
230+
vaults[0] = IVault(address(vault));
231+
232+
uint256[] memory amounts = new uint256[](1);
233+
amounts[0] = 50; // Withdraw half of the deposit
234+
235+
// Only InvoiceManager can call withdrawSponsorToken
236+
vm.startPrank(address(invoiceManager));
237+
vaultManager.withdrawSponsorToken(user1, vaults, amounts, user2);
238+
vm.stopPrank();
239+
240+
// Verify shares were reduced
241+
uint256 remainingShares = vaultManager.accountShares(user1, IVault(address(vault)));
242+
assertTrue(remainingShares > 0 && remainingShares < vault.underlyingToShares(depositAmount));
243+
244+
// Verify tokens were transferred to user2
245+
assertEq(mockERC20.balanceOf(user2), 1050);
246+
}
247+
248+
function testGetAccountTokenBalance() public {
249+
uint256 depositAmount = 100;
250+
251+
// Deposit tokens
252+
vm.startPrank(user1);
253+
mockERC20.approve(address(vaultManager), depositAmount);
254+
vaultManager.deposit(mockERC20, IVault(address(vault)), depositAmount, false);
255+
vm.stopPrank();
256+
257+
// Get account token balance
258+
uint256 balance = vaultManager.getAccountTokenBalance(user1, mockERC20);
259+
260+
// Should be approximately equal to deposit amount (might be slightly less due to share calculation)
261+
assertApproxEqAbs(balance, depositAmount, 1);
262+
}
263+
264+
function testMultipleDepositsAndWithdrawals() public {
265+
// User1 deposits
266+
vm.startPrank(user1);
267+
mockERC20.approve(address(vaultManager), 500);
268+
vaultManager.deposit(mockERC20, IVault(address(vault)), 200, false);
269+
vaultManager.deposit(mockERC20, IVault(address(vault)), 300, false);
270+
vm.stopPrank();
271+
272+
// User2 deposits
273+
vm.startPrank(user2);
274+
mockERC20.approve(address(vaultManager), 400);
275+
vaultManager.deposit(mockERC20, IVault(address(vault)), 400, false);
276+
vm.stopPrank();
277+
278+
// Verify total assets in vault
279+
assertEq(vault.totalAssets(), 900);
280+
281+
// User1 queues partial withdrawal
282+
vm.startPrank(user1);
283+
IVault[] memory vaults = new IVault[](1);
284+
vaults[0] = IVault(address(vault));
285+
286+
uint256[] memory shares = new uint256[](1);
287+
shares[0] = vault.underlyingToShares(150);
288+
289+
bytes32 withdrawalId = vaultManager.queueWithdrawals(vaults, shares, user1);
290+
vm.stopPrank();
291+
292+
// Advance blocks
293+
vm.roll(block.number + WITHDRAW_LOCK_BLOCK + 1);
294+
295+
// Complete withdrawal
296+
vm.startPrank(user1);
297+
bytes32[] memory withdrawalIds = new bytes32[](1);
298+
withdrawalIds[0] = withdrawalId;
299+
vaultManager.completeWithdrawals(withdrawalIds);
300+
vm.stopPrank();
301+
302+
// Verify balances
303+
uint256 user1Balance = vaultManager.getAccountTokenBalance(user1, mockERC20);
304+
uint256 user2Balance = vaultManager.getAccountTokenBalance(user2, mockERC20);
305+
306+
assertApproxEqAbs(user1Balance, 350, 1);
307+
assertApproxEqAbs(user2Balance, 400, 1);
308+
assertApproxEqAbs(vault.totalAssets(), 750, 1);
309+
}
310+
311+
function testOnlyOwnerCanAddVault() public {
312+
BaseVault newVault = BaseVault(
313+
payable(
314+
new UpgradeableOpenfortProxy(
315+
address(new BaseVault()),
316+
abi.encodeWithSelector(BaseVault.initialize.selector, IVaultManager(address(vaultManager)), mockERC20)
317+
)
318+
)
319+
);
320+
321+
// Non-owner tries to add vault
322+
vm.startPrank(user1);
323+
vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", user1));
324+
vaultManager.addVault(IVault(address(newVault)));
325+
vm.stopPrank();
326+
327+
// Owner adds vault
328+
vm.startPrank(owner);
329+
vaultManager.addVault(IVault(address(newVault)));
330+
vm.stopPrank();
331+
332+
assertTrue(vaultManager.registeredVaults(IVault(address(newVault))));
333+
}
334+
335+
function testOnlyInvoiceManagerCanWithdrawSponsorToken() public {
336+
uint256 depositAmount = 100;
337+
338+
// Deposit tokens first
339+
vm.startPrank(user1);
340+
mockERC20.approve(address(vaultManager), depositAmount);
341+
vaultManager.deposit(mockERC20, IVault(address(vault)), depositAmount, false);
342+
vm.stopPrank();
343+
344+
// Prepare withdrawal data
345+
IVault[] memory vaults = new IVault[](1);
346+
vaults[0] = IVault(address(vault));
347+
348+
uint256[] memory amounts = new uint256[](1);
349+
amounts[0] = 50;
350+
351+
// Non-InvoiceManager tries to call withdrawSponsorToken
352+
vm.startPrank(user2);
353+
vm.expectRevert("VaultManager: caller is not the InvoiceManager");
354+
vaultManager.withdrawSponsorToken(user1, vaults, amounts, user2);
355+
vm.stopPrank();
356+
}
357+
}

0 commit comments

Comments
 (0)