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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/utils"]
path = lib/utils
url = https://github.com/AngleProtocol/utils
21 changes: 18 additions & 3 deletions contracts/BaseRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ abstract contract BaseRouter is Initializable, IDepositWithReferral {
);

/// @notice Deploys the router contract on a chain
function initializeRouter(address _core, address _uniswapRouter, address _oneInch) public initializer {
function initializeRouter(
address _core,
address _uniswapRouter,
address _oneInch
) public initializer {
if (_core == address(0)) revert ZeroAddress();
core = ICoreBorrow(_core);
uniswapV3Router = IUniswapV3Router(_uniswapRouter);
Expand Down Expand Up @@ -258,6 +262,7 @@ abstract contract BaseRouter is Initializable, IDepositWithReferral {
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 +274,15 @@ abstract contract BaseRouter is Initializable, IDepositWithReferral {
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 @@ -406,7 +413,11 @@ abstract contract BaseRouter is Initializable, IDepositWithReferral {
/// @param tokenOut Token to sweep
/// @param minAmountOut Minimum amount of tokens to recover
/// @param to Address to which tokens should be sent
function _sweep(address tokenOut, uint256 minAmountOut, address to) internal virtual {
function _sweep(
address tokenOut,
uint256 minAmountOut,
address to
) internal virtual {
uint256 balanceToken = IERC20(tokenOut).balanceOf(address(this));
_slippageCheck(balanceToken, minAmountOut);
if (balanceToken != 0) {
Expand Down Expand Up @@ -577,7 +588,11 @@ abstract contract BaseRouter is Initializable, IDepositWithReferral {
/// @param token Address of the token to change allowance
/// @param spender Address to change the allowance of
/// @param amount Amount allowed
function _changeAllowance(IERC20 token, address spender, uint256 amount) internal {
function _changeAllowance(
IERC20 token,
address spender,
uint256 amount
) internal {
uint256 currentAllowance = token.allowance(address(this), spender);
// In case `currentAllowance < type(uint256).max / 2` and we want to increase it:
// Do nothing (to handle tokens that need reapprovals to 0 and save gas)
Expand Down
16 changes: 15 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,25 @@ runs = 500
runs = 500

[rpc_endpoints]
arbitrum = "${ETH_NODE_URI_ARBITRUM}"
mainnet = "${ETH_NODE_URI_MAINNET}"
polygon = "${ETH_NODE_URI_POLYGON}"
goerli = "${ETH_NODE_URI_GOERLI}"
optimism = "${ETH_NODE_URI_OPTIMISM}"
avalanche = "${ETH_NODE_URI_AVALANCHE}"
base = "${ETH_NODE_URI_BASE}"
linea = "${ETH_NODE_URI_LINEA}"
celo = "${ETH_NODE_URI_CELO}"
gnosis = "${ETH_NODE_URI_GNOSIS}"

[etherscan]
arbitrum = { key = "${ARBITRUM_ETHERSCAN_API_KEY}" }
mainnet = { key = "${MAINNET_ETHERSCAN_API_KEY}" }
polygon = { key = "${POLYGON_ETHERSCAN_API_KEY}" }
goerli = { key = "${GOERLI_ETHERSCAN_API_KEY}" }
goerli = { key = "${GOERLI_ETHERSCAN_API_KEY}" }
optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" }
avalanche = { key = "${AVALANCHE_ETHERSCAN_API_KEY}" }
base = { key = "${BASE_ETHERSCAN_API_KEY}", url = "https://api.basescan.org/api" }
linea = { key = "${LINEA_ETHERSCAN_API_KEY}"}
celo = { key = "${CELO_ETHERSCAN_API_KEY}", url = "https://api.celoscan.io/api" }
gnosis = { key = "${GNOSIS_ETHERSCAN_API_KEY}" , url = "https://api.gnosisscan.io/api"}
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 64 files
+1 −0 .gitattributes
+128 −0 .github/workflows/ci.yml
+31 −0 .github/workflows/sync.yml
+0 −27 .github/workflows/tests.yml
+1 −1 .gitignore
+0 −3 .gitmodules
+1 −1 LICENSE-APACHE
+1 −1 LICENSE-MIT
+9 −5 README.md
+19 −0 foundry.toml
+0 −1 lib/ds-test
+16 −0 package.json
+635 −0 scripts/vm.py
+35 −0 src/Base.sol
+24 −41 src/Script.sol
+669 −0 src/StdAssertions.sol
+259 −0 src/StdChains.sol
+817 −0 src/StdCheats.sol
+15 −0 src/StdError.sol
+122 −0 src/StdInvariant.sol
+122 −61 src/StdJson.sol
+43 −0 src/StdMath.sol
+473 −0 src/StdStorage.sol
+333 −0 src/StdStyle.sol
+179 −0 src/StdToml.sol
+226 −0 src/StdUtils.sol
+29 −1,134 src/Test.sol
+1,865 −218 src/Vm.sol
+401 −382 src/console.sol
+1 −1,535 src/console2.sol
+105 −0 src/interfaces/IERC1155.sol
+12 −0 src/interfaces/IERC165.sol
+43 −0 src/interfaces/IERC20.sol
+190 −0 src/interfaces/IERC4626.sol
+164 −0 src/interfaces/IERC721.sol
+73 −0 src/interfaces/IMulticall3.sol
+234 −0 src/mocks/MockERC20.sol
+231 −0 src/mocks/MockERC721.sol
+13,937 −0 src/safeconsole.sol
+0 −20 src/test/Script.t.sol
+0 −602 src/test/StdAssertions.t.sol
+0 −282 src/test/StdCheats.t.sol
+0 −200 src/test/StdMath.t.sol
+0 −321 src/test/StdStorage.t.sol
+145 −0 test/StdAssertions.t.sol
+226 −0 test/StdChains.t.sol
+618 −0 test/StdCheats.t.sol
+14 −18 test/StdError.t.sol
+49 −0 test/StdJson.t.sol
+212 −0 test/StdMath.t.sol
+471 −0 test/StdStorage.t.sol
+110 −0 test/StdStyle.t.sol
+49 −0 test/StdToml.t.sol
+342 −0 test/StdUtils.t.sol
+15 −0 test/Vm.t.sol
+10 −0 test/compilation/CompilationScript.sol
+10 −0 test/compilation/CompilationScriptBase.sol
+10 −0 test/compilation/CompilationTest.sol
+10 −0 test/compilation/CompilationTestBase.sol
+0 −0 test/fixtures/broadcast.log.json
+8 −0 test/fixtures/test.json
+6 −0 test/fixtures/test.toml
+441 −0 test/mocks/MockERC20.t.sol
+721 −0 test/mocks/MockERC721.t.sol
1 change: 1 addition & 0 deletions lib/utils
Submodule utils added at e64751
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
@uniswap/=node_modules/@uniswap/
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
utils/=lib/utils/
50 changes: 50 additions & 0 deletions scripts/foundry/UpgradeRouter.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Script.sol";
import "utils/src/CommonUtils.sol";
import { AngleRouterMainnet } from "contracts/implementations/mainnet/AngleRouterMainnet.sol";
import { AngleRouterArbitrum } from "contracts/implementations/arbitrum/AngleRouterArbitrum.sol";
import { AngleRouterOptimism } from "contracts/implementations/optimism/AngleRouterOptimism.sol";
import { AngleRouterAvalanche } from "contracts/implementations/avalanche/AngleRouterAvalanche.sol";
import { AngleRouterBase } from "contracts/implementations/base/AngleRouterBase.sol";
import { AngleRouterCelo } from "contracts/implementations/celo/AngleRouterCelo.sol";
import { AngleRouterGnosis } from "contracts/implementations/gnosis/AngleRouterGnosis.sol";
import { AngleRouterLinea } from "contracts/implementations/linea/AngleRouterLinea.sol";
import { AngleRouterPolygon } from "contracts/implementations/polygon/AngleRouterPolygon.sol";

contract UpgradeRouterScript is Script, CommonUtils {
function run() public {
uint256 chainId = vm.envUint("CHAIN_ID");

uint256 deployerPrivateKey = vm.deriveKey(vm.envString("MNEMONIC_MAINNET"), 0);
address deployer = vm.addr(deployerPrivateKey);
console.log("Address: %s", deployer);
vm.startBroadcast(deployerPrivateKey);

address routerImpl;
if (chainId == CHAIN_ETHEREUM) {
routerImpl = address(new AngleRouterMainnet());
} else if (chainId == CHAIN_ARBITRUM) {
routerImpl = address(new AngleRouterArbitrum());
} else if (chainId == CHAIN_OPTIMISM) {
routerImpl = address(new AngleRouterOptimism());
} else if (chainId == CHAIN_AVALANCHE) {
routerImpl = address(new AngleRouterAvalanche());
} else if (chainId == CHAIN_BASE) {
routerImpl = address(new AngleRouterBase());
} else if (chainId == CHAIN_CELO) {
routerImpl = address(new AngleRouterCelo());
} else if (chainId == CHAIN_GNOSIS) {
routerImpl = address(new AngleRouterGnosis());
} else if (chainId == CHAIN_LINEA) {
routerImpl = address(new AngleRouterLinea());
} else if (chainId == CHAIN_POLYGON) {
routerImpl = address(new AngleRouterPolygon());
}

console.log("Deployed router implementation at address: %s", routerImpl);

vm.stopBroadcast();
}
}
172 changes: 171 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 @@ -189,6 +194,52 @@ contract AngleRouterMainnetTest is BaseTest {
assertEq(token.balanceOf(address(to)), 0);
}

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);
}

0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
function testDeposit4626ForgotFunds(
uint256 initShares,
uint256 amount,
Expand Down Expand Up @@ -232,6 +283,53 @@ contract AngleRouterMainnetTest is BaseTest {
assertEq(token.balanceOf(address(_alice)), balanceUsers - amount);
}

function testDepositReferral4626MaxBalance(
uint256 initShares,
uint256 amount,
uint256 minSharesOut,
uint256 gainOrLoss,
address referrer
) 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.deposit4626Referral;
data[1] = abi.encode(token, savingsRate, type(uint256).max, to, minSharesOut, referrer);

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 testRedeem4626GoodPractice(
uint256 initShares,
uint256 aliceAmount,
Expand Down Expand Up @@ -387,6 +485,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