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: first version of the Rebalancer with flashloan #109

Closed
wants to merge 61 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
255439e
feat: oracles with firewalls on mint and burn
GuillaumeNervoXS Feb 27, 2024
ee265b4
feat: oracles with target value as max
GuillaumeNervoXS Feb 27, 2024
1b4dad0
feat: fixed tests + test on mint and burn firewalls oracles
GuillaumeNervoXS Feb 28, 2024
3a2f014
test updateOracle
GuillaumeNervoXS Feb 29, 2024
085fb65
feat: start testing deployment
GuillaumeNervoXS Mar 1, 2024
9a937c9
feat: start scripts
GuillaumeNervoXS Mar 5, 2024
8df3d42
feat: mint and burn test done
GuillaumeNervoXS Mar 5, 2024
5853221
chore: try to fix linter issues
GuillaumeNervoXS Mar 5, 2024
675df65
feat: testing done
GuillaumeNervoXS Mar 5, 2024
cb25899
format: prettier contracts
0xtekgrinder Mar 7, 2024
c48a34d
fix part of the stack too deep when via-ir
GuillaumeNervoXS Mar 6, 2024
4b0dc05
find which test files has compilation problem
GuillaumeNervoXS Mar 8, 2024
08322e5
fix all compilation tests except RedeemTest
GuillaumeNervoXS Mar 11, 2024
0ae8ac3
add back the RedeemTest
GuillaumeNervoXS Mar 11, 2024
e9723ad
fix redeem test compilation
GuillaumeNervoXS Mar 11, 2024
f4fb8e3
chore: setup repo inside ci
0xtekgrinder Mar 11, 2024
54939d3
chore: move setup repo action to actions subdirectory
0xtekgrinder Mar 11, 2024
8436001
start burn protection for users
GuillaumeNervoXS Mar 13, 2024
f28d063
fix firewall and user protection + tests
GuillaumeNervoXS Mar 13, 2024
2a24778
feat: add back burnRatio deviation user protection
GuillaumeNervoXS Mar 14, 2024
986d691
fix burnRatio protection
GuillaumeNervoXS Mar 14, 2024
f12fb2f
tests fixed
GuillaumeNervoXS Mar 20, 2024
ef604f3
working tests
GuillaumeNervoXS Mar 21, 2024
668a94e
yarn prettier
GuillaumeNervoXS Mar 21, 2024
7123c60
add bERNX + new target exposures
GuillaumeNervoXS Mar 21, 2024
1026b40
testing after adding bERNX
GuillaumeNervoXS Mar 21, 2024
6fa7c57
feat: change updateOracle to only trustedSeller only
GuillaumeNervoXS Mar 21, 2024
04e34cc
change input to secret
GuillaumeNervoXS Mar 22, 2024
f3f37e7
revert prev change
GuillaumeNervoXS Mar 22, 2024
54023f4
revert deadline tests changes
GuillaumeNervoXS Mar 22, 2024
4facab2
feat: USDA setup (#108)
sogipec Mar 25, 2024
2d630b2
Add a test on non arbitrage Mint->Burn
GuillaumeNervoXS Mar 27, 2024
278ede5
compiling version
GuillaumeNervoXS Mar 27, 2024
b5ccc8b
fix tests
GuillaumeNervoXS Mar 27, 2024
cd23713
chore: review minor changes
GuillaumeNervoXS Mar 28, 2024
909a40f
update oracle hyperparameters
GuillaumeNervoXS Mar 28, 2024
bb5d842
remove test on no arbitrage --> impossible with the user protections
GuillaumeNervoXS Mar 28, 2024
1260225
feat: usda setup
sogipec Mar 20, 2024
7d47251
feat: setup script for USDA transmuter
sogipec Mar 20, 2024
e821632
feat: put variables in constants
sogipec Mar 21, 2024
b41a73e
feat: change some stuff
sogipec Mar 21, 2024
55731ab
fix tests
sogipec Mar 21, 2024
9fe5736
feat: add keyring
sogipec Mar 21, 2024
e786b4e
feat: add minter flashloan
sogipec Mar 22, 2024
578cffa
remove flashloan
sogipec Mar 22, 2024
9989685
feat: first version of the Rebalancer with flashloan
sogipec Mar 21, 2024
ec26416
feat: change comments
sogipec Mar 21, 2024
e56a2f7
feat: add rebalancer
sogipec Mar 22, 2024
9753ebd
feat: prepare script
sogipec Mar 22, 2024
1a8e9ed
setup script
sogipec Mar 22, 2024
303aebb
feat: script
sogipec Mar 22, 2024
08701bc
fix: test
sogipec Mar 25, 2024
9bd8d52
fix: test setup
sogipec Mar 25, 2024
c78eb68
fix tests
sogipec Mar 25, 2024
f1ae045
feat: tests for the rebalancer with flashloan
sogipec Mar 25, 2024
0817e2c
finish rebase
sogipec Mar 25, 2024
cd846af
fix: post rebase
sogipec Mar 28, 2024
7dec79f
fix: allowance stuff
sogipec Mar 28, 2024
2a01e43
fix: rebase
sogipec Mar 28, 2024
0267e88
fix: rebase
sogipec Apr 5, 2024
4086d5d
feat: new assets
sogipec Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions contracts/helpers/Rebalancer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ contract Rebalancer is IRebalancer, AccessControl {
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
// First, dealing with the allowance of the rebalancer to the Transmuter: this allowance is made infinite
// by default
uint256 allowance = IERC20(tokenIn).allowance(address(this), address(TRANSMUTER));
if (allowance < amountIn)
IERC20(tokenIn).safeIncreaseAllowance(address(TRANSMUTER), type(uint256).max - allowance);
_adjustAllowance(tokenIn, address(TRANSMUTER), amountIn);
// Mint agToken from `tokenIn`
uint256 amountAgToken = TRANSMUTER.swapExactInput(
amountIn,
Expand Down Expand Up @@ -199,4 +197,13 @@ contract Rebalancer is IRebalancer, AccessControl {
revert InvalidParam();
IERC20(token).safeTransfer(to, amount);
}

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HELPER
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

function _adjustAllowance(address token, address sender, uint256 amountIn) internal {
uint256 allowance = IERC20(token).allowance(address(this), sender);
if (allowance < amountIn) IERC20(token).safeIncreaseAllowance(sender, type(uint256).max - allowance);
}
}
95 changes: 95 additions & 0 deletions contracts/helpers/RebalancerFlashloan.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.19;

import "./Rebalancer.sol";
import { IERC4626 } from "interfaces/external/IERC4626.sol";
import { IERC3156FlashBorrower } from "oz/interfaces/IERC3156FlashBorrower.sol";
import { IERC3156FlashLender } from "oz/interfaces/IERC3156FlashLender.sol";

/// @title RebalancerFlashloan
/// @author Angle Labs, Inc.
/// @dev Rebalancer contract for a Transmuter with as collaterals a liquid stablecoin and an ERC4626 token
/// using this liquid stablecoin as an asset
contract RebalancerFlashloan is Rebalancer, IERC3156FlashBorrower {
sogipec marked this conversation as resolved.
Show resolved Hide resolved
sogipec marked this conversation as resolved.
Show resolved Hide resolved
using SafeERC20 for IERC20;
using SafeCast for uint256;
bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

/// @notice Angle stablecoin flashloan contract
IERC3156FlashLender public immutable FLASHLOAN;

constructor(
IAccessControlManager _accessControlManager,
ITransmuter _transmuter,
IERC3156FlashLender _flashloan
) Rebalancer(_accessControlManager, _transmuter) {
if (address(_flashloan) == address(0)) revert ZeroAddress();
FLASHLOAN = _flashloan;
IERC20(AGTOKEN).safeApprove(address(_flashloan), type(uint256).max);
}

/// @notice Burns `amountStablecoins` for one collateral asset and mints stablecoins from the proceeds of the
/// first burn
/// @dev If `increase` is 1, then the system tries to increase its exposure to the yield bearing asset which means
/// burning stablecoin for the liquid asset, depositing into the ERC4626 vault, then minting the stablecoin
function adjustYieldExposure(
uint256 amountStablecoins,
uint8 increase,
address collateral,
address vault
) external {
if (!TRANSMUTER.isTrustedSeller(msg.sender)) revert NotTrusted();
sogipec marked this conversation as resolved.
Show resolved Hide resolved
FLASHLOAN.flashLoan(

Check warning on line 43 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L43

Added line #L43 was not covered by tests
IERC3156FlashBorrower(address(this)),
address(AGTOKEN),
amountStablecoins,
abi.encode(increase, collateral, vault)
);
}

/// @inheritdoc IERC3156FlashBorrower
function onFlashLoan(
address initiator,
address,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32) {
if (msg.sender != address(FLASHLOAN) || initiator != address(this) || fee != 0) revert NotTrusted();
(uint256 typeAction, address collateral, address vault) = abi.decode(data, (uint256, address, address));
address tokenOut;
address tokenIn;
if (typeAction == 1) {

Check warning on line 63 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L61-L63

Added lines #L61 - L63 were not covered by tests
sogipec marked this conversation as resolved.
Show resolved Hide resolved
// Increase yield exposure action: we bring in the ERC4626 token
tokenOut = collateral;
tokenIn = vault;

Check warning on line 66 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L65-L66

Added lines #L65 - L66 were not covered by tests
} else {
// Decrease yield exposure action: we bring in the liquid asset
tokenIn = collateral;
tokenOut = vault;

Check warning on line 70 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L69-L70

Added lines #L69 - L70 were not covered by tests
}
uint256 amountOut = TRANSMUTER.swapExactInput(amount, 0, AGTOKEN, tokenOut, address(this), block.timestamp);
if (typeAction == 1) {

Check warning on line 73 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L72-L73

Added lines #L72 - L73 were not covered by tests
// Granting allowance with the collateral for the vault asset
_adjustAllowance(collateral, vault, amountOut);
amountOut = IERC4626(vault).deposit(amountOut, address(this));
} else amountOut = IERC4626(vault).redeem(amountOut, address(this), address(this));
_adjustAllowance(tokenIn, address(TRANSMUTER), amountOut);
uint256 amountStableOut = TRANSMUTER.swapExactInput(

Check warning on line 79 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L75-L79

Added lines #L75 - L79 were not covered by tests
amountOut,
0,
tokenIn,
AGTOKEN,
address(this),
block.timestamp
);
if (amount > amountStableOut) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have a min amount out check

uint256 subsidy = amount - amountStableOut;
orders[tokenIn][tokenOut].subsidyBudget -= subsidy.toUint112();
budget -= subsidy;
emit SubsidyPaid(tokenIn, tokenOut, subsidy);

Check warning on line 91 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L87-L91

Added lines #L87 - L91 were not covered by tests
}
return CALLBACK_SUCCESS;

Check warning on line 93 in contracts/helpers/RebalancerFlashloan.sol

View check run for this annotation

Codecov / codecov/patch

contracts/helpers/RebalancerFlashloan.sol#L93

Added line #L93 was not covered by tests
}
}
4 changes: 2 additions & 2 deletions scripts/Helpers.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ contract Helpers is Utils {
address public sweeper;

function setUp() public virtual {
ethereumFork = vm.createFork(vm.envString("ETH_NODE_URI_MAINNET"));
arbitrumFork = vm.createFork(vm.envString("ETH_NODE_URI_ARBITRUM"));
avalancheFork = vm.createFork(vm.envString("ETH_NODE_URI_AVALANCHE"));
ethereumFork = vm.createFork(vm.envString("ETH_NODE_URI_MAINNET"));
optimismFork = vm.createFork(vm.envString("ETH_NODE_URI_OPTIMISM"));
polygonFork = vm.createFork(vm.envString("ETH_NODE_URI_POLYGON"));
gnosisFork = vm.createFork(vm.envString("ETH_NODE_URI_GNOSIS"));
Expand All @@ -39,9 +39,9 @@ contract Helpers is Utils {
baseFork = vm.createFork(vm.envString("ETH_NODE_URI_BASE"));
lineaFork = vm.createFork(vm.envString("ETH_NODE_URI_LINEA"));

forkIdentifier[CHAIN_ETHEREUM] = ethereumFork;
forkIdentifier[CHAIN_ARBITRUM] = arbitrumFork;
forkIdentifier[CHAIN_AVALANCHE] = avalancheFork;
forkIdentifier[CHAIN_ETHEREUM] = ethereumFork;
forkIdentifier[CHAIN_OPTIMISM] = optimismFork;
forkIdentifier[CHAIN_POLYGON] = polygonFork;
forkIdentifier[CHAIN_GNOSIS] = gnosisFork;
Expand Down
2 changes: 1 addition & 1 deletion scripts/UpdateTransmuterFacets.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract UpdateTransmuterFacets is Helpers {
function run() external {
// TODO: make sure that selectors are well generated `yarn generate` before running this script
// Here the `selectors.json` file is normally up to date
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
uint256 deployerPrivateKey = vm.envUint("KEEPER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

Storage.FacetCut[] memory replaceCut;
Expand Down
89 changes: 89 additions & 0 deletions test/fuzz/RebalancerFlashloanTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import { SafeERC20 } from "oz/token/ERC20/utils/SafeERC20.sol";

import { stdError } from "forge-std/Test.sol";

import "contracts/utils/Errors.sol" as Errors;

import "../Fixture.sol";
import { IERC20Metadata } from "../mock/MockTokenPermit.sol";
import "../utils/FunctionUtils.sol";

import "contracts/savings/Savings.sol";
import "../mock/MockTokenPermit.sol";
import "contracts/helpers/RebalancerFlashloan.sol";

contract RebalancerFlashloanTest is Fixture, FunctionUtils {
using SafeERC20 for IERC20;

RebalancerFlashloan public rebalancer;
Savings internal _saving;
string internal _name;
string internal _symbol;
address public collat;

function setUp() public override {
super.setUp();

MockTokenPermit token = new MockTokenPermit("EURC", "EURC", 6);
collat = address(token);

address _savingImplementation = address(new Savings());
bytes memory data;
_saving = Savings(_deployUpgradeable(address(proxyAdmin), address(_savingImplementation), data));
_name = "savingAgEUR";
_symbol = "SAGEUR";

vm.startPrank(governor);
token.mint(governor, 1e12);
token.approve(address(_saving), 1e12);
_saving.initialize(accessControlManager, IERC20MetadataUpgradeable(address(token)), _name, _symbol, BASE_6);
vm.stopPrank();

rebalancer = new RebalancerFlashloan(accessControlManager, transmuter, IERC3156FlashLender(governor));
}

function test_RebalancerInitialization() public {
assertEq(address(rebalancer.accessControlManager()), address(accessControlManager));
assertEq(address(rebalancer.AGTOKEN()), address(agToken));
assertEq(address(rebalancer.TRANSMUTER()), address(transmuter));
assertEq(address(rebalancer.FLASHLOAN()), governor);
assertEq(IERC20Metadata(address(agToken)).allowance(address(rebalancer), address(governor)), type(uint256).max);
assertEq(IERC20Metadata(address(collat)).allowance(address(rebalancer), address(_saving)), 0);
}

function test_Constructor_RevertWhen_ZeroAddress() public {
vm.expectRevert(Errors.ZeroAddress.selector);
new RebalancerFlashloan(accessControlManager, transmuter, IERC3156FlashLender(address(0)));
}

function test_adjustYieldExposure_RevertWhen_NotTrusted() public {
vm.expectRevert(Errors.NotTrusted.selector);
rebalancer.adjustYieldExposure(1, 1, address(0), address(0));
}

function test_onFlashLoan_RevertWhen_NotTrusted() public {
vm.expectRevert(Errors.NotTrusted.selector);
rebalancer.onFlashLoan(address(rebalancer), address(0), 1, 0, abi.encode(1));

vm.expectRevert(Errors.NotTrusted.selector);
rebalancer.onFlashLoan(address(rebalancer), address(0), 1, 1, abi.encode(1));

vm.expectRevert(Errors.NotTrusted.selector);
vm.startPrank(governor);
rebalancer.onFlashLoan(address(0), address(0), 1, 0, abi.encode(1));
vm.stopPrank();

vm.expectRevert(Errors.NotTrusted.selector);
vm.startPrank(governor);
rebalancer.onFlashLoan(address(rebalancer), address(0), 1, 1, abi.encode(1));
vm.stopPrank();

vm.expectRevert();
vm.startPrank(governor);
rebalancer.onFlashLoan(address(rebalancer), address(0), 1, 0, abi.encode(1, 2));
vm.stopPrank();
}
sogipec marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading