generated from AngleProtocol/boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 14
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: Flashloan rebalancer #112
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
178d2bf
updating rebalancer
sogipec 3a68cf6
simplify tests
sogipec d4acb12
feat: add fuzz back
sogipec aeb67a3
feat: add new test for minAmount
sogipec c4e98c3
feat: add deployment script
sogipec fbfcc10
Empty-Commit
0xtekgrinder 8b7cad3
tests: comment out upgrade tests of the transmuter and remove helpers
0xtekgrinder d19f2a3
chore: add cache to folders to ignore
0xtekgrinder 63a48fb
chore: fix slither ci infinite loop
0xtekgrinder af908aa
chore: setup repo before slither ci
0xtekgrinder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,9 @@ jobs: | |
with: | ||
registry-token: ${{ secrets.GH_REGISTRY_ACCESS_TOKEN }} | ||
|
||
- name: Install dependencies | ||
run: yarn install | ||
|
||
- name: Run solhint | ||
run: yarn lint:check | ||
|
||
|
@@ -51,6 +54,9 @@ jobs: | |
with: | ||
registry-token: ${{ secrets.GH_REGISTRY_ACCESS_TOKEN }} | ||
|
||
- name: Install dependencies | ||
run: yarn install --frozen-lockfile | ||
|
||
- name: Compile foundry | ||
run: yarn compile --sizes | ||
|
||
|
@@ -224,12 +230,13 @@ jobs: | |
run: forge build --build-info --skip */test/** */scripts/** --force | ||
|
||
- name: "Run Slither analysis" | ||
uses: "crytic/[email protected].0" | ||
uses: "crytic/[email protected].2" | ||
id: "slither" | ||
with: | ||
ignore-compile: true | ||
fail-on: "none" | ||
sarif: "results.sarif" | ||
slither-version: "0.10.1" | ||
|
||
- name: "Upload SARIF file to GitHub code scanning" | ||
uses: "github/codeql-action/upload-sarif@v2" | ||
|
@@ -239,4 +246,4 @@ jobs: | |
- name: "Add Slither summary" | ||
run: | | ||
echo "## Slither result" >> $GITHUB_STEP_SUMMARY | ||
echo "✅ Uploaded to GitHub code scanning" >> $GITHUB_STEP_SUMMARY | ||
echo "✅ Uploaded to GitHub code scanning" >> $GITHUB_STEP_SUMMARY |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ export | |
typechain | ||
slither-audit.txt | ||
slither | ||
cache | ||
|
||
# Test output | ||
coverage | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// 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 { | ||
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 | ||
/// @dev This function reverts if the second stablecoin mint gives less than `minAmountOut` of stablecoins | ||
function adjustYieldExposure( | ||
uint256 amountStablecoins, | ||
uint8 increase, | ||
address collateral, | ||
address vault, | ||
uint256 minAmountOut | ||
) external { | ||
if (!TRANSMUTER.isTrustedSeller(msg.sender)) revert NotTrusted(); | ||
FLASHLOAN.flashLoan( | ||
IERC3156FlashBorrower(address(this)), | ||
address(AGTOKEN), | ||
amountStablecoins, | ||
abi.encode(increase, collateral, vault, minAmountOut) | ||
); | ||
} | ||
|
||
/// @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, uint256 minAmountOut) = abi.decode( | ||
data, | ||
(uint256, address, address, uint256) | ||
); | ||
address tokenOut; | ||
address tokenIn; | ||
if (typeAction == 1) { | ||
// Increase yield exposure action: we bring in the ERC4626 token | ||
tokenOut = collateral; | ||
tokenIn = vault; | ||
} else { | ||
// Decrease yield exposure action: we bring in the liquid asset | ||
tokenIn = collateral; | ||
tokenOut = vault; | ||
} | ||
uint256 amountOut = TRANSMUTER.swapExactInput(amount, 0, AGTOKEN, tokenOut, address(this), block.timestamp); | ||
if (typeAction == 1) { | ||
// 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( | ||
amountOut, | ||
minAmountOut, | ||
tokenIn, | ||
AGTOKEN, | ||
address(this), | ||
block.timestamp | ||
); | ||
if (amount > amountStableOut) { | ||
uint256 subsidy = amount - amountStableOut; | ||
orders[tokenIn][tokenOut].subsidyBudget -= subsidy.toUint112(); | ||
budget -= subsidy; | ||
emit SubsidyPaid(tokenIn, tokenOut, subsidy); | ||
} | ||
return CALLBACK_SUCCESS; | ||
} | ||
Comment on lines
+54
to
+99
Check notice Code scanning / Slither Block timestamp Low
RebalancerFlashloan.onFlashLoan(address,address,uint256,uint256,bytes) uses timestamp for comparisons
Dangerous comparisons: - amount > amountStableOut |
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.19; | ||
|
||
import "./utils/Utils.s.sol"; | ||
import { console } from "forge-std/console.sol"; | ||
import { RebalancerFlashloan } from "contracts/helpers/RebalancerFlashloan.sol"; | ||
import { IAccessControlManager } from "contracts/utils/AccessControl.sol"; | ||
import { ITransmuter } from "contracts/interfaces/ITransmuter.sol"; | ||
import { IERC3156FlashLender } from "oz/interfaces/IERC3156FlashLender.sol"; | ||
import "./Constants.s.sol"; | ||
import "oz/interfaces/IERC20.sol"; | ||
import "oz-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; | ||
|
||
contract DeployRebalancerFlashloan is Utils { | ||
function run() external { | ||
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); | ||
vm.startBroadcast(deployerPrivateKey); | ||
|
||
address deployer = vm.addr(deployerPrivateKey); | ||
console.log("Deployer address: ", deployer); | ||
console.log(address(IAccessControlManager(_chainToContract(CHAIN_SOURCE, ContractType.CoreBorrow)))); | ||
console.log(address(ITransmuter(_chainToContract(CHAIN_SOURCE, ContractType.TransmuterAgUSD)))); | ||
RebalancerFlashloan rebalancer = new RebalancerFlashloan( | ||
IAccessControlManager(_chainToContract(CHAIN_SOURCE, ContractType.CoreBorrow)), | ||
ITransmuter(_chainToContract(CHAIN_SOURCE, ContractType.TransmuterAgUSD)), | ||
IERC3156FlashLender(0x4A2FF9bC686A0A23DA13B6194C69939189506F7F) | ||
); | ||
/* | ||
RebalancerFlashloan rebalancer = new RebalancerFlashloan( | ||
IAccessControlManager(0x5bc6BEf80DA563EBf6Df6D6913513fa9A7ec89BE), | ||
ITransmuter(0x222222fD79264BBE280b4986F6FEfBC3524d0137), | ||
IERC3156FlashLender(0x4A2FF9bC686A0A23DA13B6194C69939189506F7F) | ||
); | ||
*/ | ||
console.log("Rebalancer deployed at: ", address(rebalancer)); | ||
|
||
vm.stopBroadcast(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), 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(); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / Slither
Unused return Medium