Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/deposit-max-balance-savings #26

Merged
merged 11 commits into from
Aug 9, 2024
7 changes: 7 additions & 0 deletions contracts/BaseRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import "./interfaces/ISwapper.sol";
import "./interfaces/IVaultManager.sol";

import "forge-std/console.sol";

Check failure on line 51 in contracts/BaseRouter.sol

View workflow job for this annotation

GitHub Actions / lint

Unexpected import of console file

// ============================== STRUCTS AND ENUM =============================

/// @notice Action types
Expand Down Expand Up @@ -251,13 +253,15 @@
data[i],
(IERC20, IERC4626, uint256, address, uint256)
);
if (shares == type(uint256).max) shares = savingsRate.previewDeposit(token.balanceOf(address(this)));
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
_changeAllowance(token, address(savingsRate), type(uint256).max);
_mint4626(savingsRate, shares, to, maxAmountIn);
} else if (actions[i] == ActionType.deposit4626) {
(IERC20 token, IERC4626 savingsRate, uint256 amount, address to, uint256 minSharesOut) = abi.decode(
data[i],
(IERC20, IERC4626, uint256, address, uint256)
);
if (amount == type(uint256).max) amount = token.balanceOf(address(this));
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
_changeAllowance(token, address(savingsRate), type(uint256).max);
_deposit4626(savingsRate, amount, to, minSharesOut);
} else if (actions[i] == ActionType.deposit4626Referral) {
Expand All @@ -269,13 +273,15 @@
uint256 minSharesOut,
address referrer
) = abi.decode(data[i], (IERC20, IERC4626, uint256, address, uint256, address));
if (amount == type(uint256).max) amount = token.balanceOf(address(this));
_changeAllowance(token, address(savingsRate), type(uint256).max);
_deposit4626Referral(savingsRate, amount, to, minSharesOut, referrer);
} else if (actions[i] == ActionType.redeem4626) {
(IERC4626 savingsRate, uint256 shares, address to, uint256 minAmountOut) = abi.decode(
data[i],
(IERC4626, uint256, address, uint256)
);
if (shares == type(uint256).max) shares = savingsRate.balanceOf(msg.sender);
_redeem4626(savingsRate, shares, to, minAmountOut);
} else if (actions[i] == ActionType.withdraw4626) {
(IERC4626 savingsRate, uint256 amount, address to, uint256 maxSharesOut) = abi.decode(
Expand Down Expand Up @@ -481,6 +487,7 @@
address to,
uint256 minSharesOut
) internal returns (uint256 sharesOut) {
if (amount == type(uint256).max) amount = IERC20(savingsRate.asset()).balanceOf(address(this));
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
_slippageCheck(sharesOut = savingsRate.deposit(amount, to), minSharesOut);
}

Expand Down
169 changes: 168 additions & 1 deletion test/foundry/AngeRouterMainnet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ contract AngleRouterMainnetTest is BaseTest {
assertEq(token.balanceOf(address(to)), 0);
}

function testMint4626ForgotFunds(uint256 initShares, uint256 shares, uint256 maxAmount, uint256 gainOrLoss) public {
function testMint4626ForgotFunds(
uint256 initShares,
uint256 shares,
uint256 maxAmount,
uint256 gainOrLoss
) public {
address to = address(router);
uint256 balanceUsers = BASE_TOKENS * 1 ether;
deal(address(token), address(_alice), balanceUsers);
Expand Down Expand Up @@ -142,6 +147,50 @@ contract AngleRouterMainnetTest is BaseTest {
assertEq(token.balanceOf(address(to)), 0);
}

function testMint4626MaxBalance(
uint256 initShares,
uint256 maxAmount,
uint256 gainOrLoss
) public {
address to = address(router);
uint256 balanceUsers = BASE_TOKENS * 1 ether;
deal(address(token), address(_alice), balanceUsers);

_randomizeSavingsRate(gainOrLoss, initShares);

uint256 shares = savingsRate.previewDeposit(balanceUsers);
uint256 previewMint = savingsRate.previewMint(shares);

// this can be done with foundry though
// https://book.getfoundry.sh/tutorials/testing-eip712?highlight=permit#diving-in
PermitType[] memory paramsPermit = new PermitType[](0);
ActionType[] memory actionType = new ActionType[](2);
bytes[] memory data = new bytes[](2);

actionType[0] = ActionType.transfer;
data[0] = abi.encode(token, router, previewMint);
actionType[1] = ActionType.mint4626;
data[1] = abi.encode(token, savingsRate, type(uint256).max, to, maxAmount);

vm.startPrank(_alice);
token.approve(address(router), type(uint256).max);
// as this is a mock vault, previewMint is exactly what is needed to mint
if (maxAmount < previewMint) {
vm.expectRevert(BaseRouter.TooSmallAmountOut.selector);
router.mixer(paramsPermit, actionType, data);
return;
} else {
router.mixer(paramsPermit, actionType, data);
}
vm.stopPrank();

assertEq(savingsRate.balanceOf(address(to)), shares);

assertEq(token.balanceOf(address(router)), 0);
assertEq(token.balanceOf(address(_alice)), balanceUsers - previewMint);
assertEq(token.balanceOf(address(to)), 0);
}

function testDeposit4626GoodPractice(
uint256 initShares,
uint256 amount,
Expand Down Expand Up @@ -189,6 +238,52 @@ contract AngleRouterMainnetTest is BaseTest {
assertEq(token.balanceOf(address(to)), 0);
}

0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
function testDeposit4626MaxBalance(
uint256 initShares,
uint256 amount,
uint256 minSharesOut,
uint256 gainOrLoss
) public {
address to = address(router);

uint256 balanceUsers = BASE_TOKENS * 1 ether;
deal(address(token), address(_alice), balanceUsers);

_randomizeSavingsRate(gainOrLoss, initShares);

amount = bound(amount, 0, balanceUsers);
uint256 previewDeposit = savingsRate.previewDeposit(amount);

PermitType[] memory paramsPermit = new PermitType[](0);
ActionType[] memory actionType = new ActionType[](2);
bytes[] memory data = new bytes[](2);

actionType[0] = ActionType.transfer;
data[0] = abi.encode(token, router, amount);
actionType[1] = ActionType.deposit4626;
data[1] = abi.encode(token, savingsRate, type(uint256).max, to, minSharesOut);

uint256 mintedShares = savingsRate.convertToShares(amount);

vm.startPrank(_alice);
token.approve(address(router), type(uint256).max);
// as this is a mock vault, previewMint is exactly what is needed to mint
if (previewDeposit < minSharesOut) {
vm.expectRevert(BaseRouter.TooSmallAmountOut.selector);
router.mixer(paramsPermit, actionType, data);
return;
} else {
router.mixer(paramsPermit, actionType, data);
}
vm.stopPrank();

assertEq(savingsRate.balanceOf(address(to)), previewDeposit);
assertEq(savingsRate.balanceOf(address(to)), mintedShares);

assertEq(token.balanceOf(address(router)), 0);
assertEq(token.balanceOf(address(_alice)), balanceUsers - amount);
}

function testDeposit4626ForgotFunds(
uint256 initShares,
uint256 amount,
Expand Down Expand Up @@ -387,6 +482,78 @@ contract AngleRouterMainnetTest is BaseTest {
assertEq(token.balanceOf(address(_alice)), balanceUsers - aliceAmount);
}

function testRedeem4626MaxBalance(
uint256 initShares,
uint256 aliceAmount,
uint256 minAmount,
uint256 gainOrLoss,
uint256 gainOrLoss2
) public {
uint256 balanceUsers = BASE_TOKENS * 1 ether;
deal(address(token), address(_alice), balanceUsers);

_randomizeSavingsRate(gainOrLoss, initShares);

aliceAmount = bound(aliceAmount, 0, balanceUsers);
uint256 previewDeposit = savingsRate.previewDeposit(aliceAmount);
// otherwise there could be overflows
vm.assume(previewDeposit < type(uint256).max / BASE_PARAMS);

uint256 previewRedeem;
{
// do a first deposit
PermitType[] memory paramsPermit = new PermitType[](0);
ActionType[] memory actionType = new ActionType[](2);
bytes[] memory data = new bytes[](2);

actionType[0] = ActionType.transfer;
data[0] = abi.encode(token, router, aliceAmount);
actionType[1] = ActionType.deposit4626;
data[1] = abi.encode(token, savingsRate, aliceAmount, _alice, previewDeposit);

vm.startPrank(_alice);
token.approve(address(router), type(uint256).max);
router.mixer(paramsPermit, actionType, data);
vm.stopPrank();

assertEq(savingsRate.balanceOf(address(router)), 0);
assertEq(savingsRate.balanceOf(address(_alice)), previewDeposit);
assertEq(token.balanceOf(address(router)), 0);
assertEq(token.balanceOf(address(_alice)), balanceUsers - aliceAmount);

// make the savings rate have a loss / gain
gainOrLoss2 = bound(gainOrLoss2, 1, 1 ether * 1 ether);
deal(address(token), address(savingsRate), gainOrLoss2);

// then redeem
uint256 sharesToBurn = savingsRate.balanceOf(_alice);

actionType = new ActionType[](1);
data = new bytes[](1);

actionType[0] = ActionType.redeem4626;
data[0] = abi.encode(savingsRate, type(uint256).max, address(router), minAmount);

previewRedeem = savingsRate.previewRedeem(sharesToBurn);
vm.startPrank(_alice);
savingsRate.approve(address(router), type(uint256).max);
// as this is a mock vault, previewRedeem is exactly what should be received
if (previewRedeem < minAmount) {
vm.expectRevert(BaseRouter.TooSmallAmountOut.selector);
router.mixer(paramsPermit, actionType, data);
return;
} else {
router.mixer(paramsPermit, actionType, data);
}
vm.stopPrank();
assertEq(savingsRate.balanceOf(address(_alice)), previewDeposit - sharesToBurn);
}

assertEq(savingsRate.balanceOf(address(router)), 0);
assertEq(token.balanceOf(address(router)), previewRedeem);
assertEq(token.balanceOf(address(_alice)), balanceUsers - aliceAmount);
}

function testWithdraw4626GoodPractice(
uint256 initShares,
uint256 aliceAmount,
Expand Down
Loading