From 65fac13cf81e3e9822692f57050649e91841f851 Mon Sep 17 00:00:00 2001 From: iamsahu Date: Mon, 8 May 2023 12:59:05 +0200 Subject: [PATCH] fix: variable interest rate oracle - Removed stir from VRLadle - Removed flash loan from VYToken --- src/oracles/VariableInterestRateOracle.sol | 82 ++++++------ src/variable/VRLadle.sol | 20 --- src/variable/VYToken.sol | 71 +---------- test/FixtureStates.sol | 13 +- test/VRLadle.t.sol | 137 --------------------- test/VYToken.t.sol | 79 ------------ 6 files changed, 41 insertions(+), 361 deletions(-) diff --git a/src/oracles/VariableInterestRateOracle.sol b/src/oracles/VariableInterestRateOracle.sol index 7ad05f0..5186805 100644 --- a/src/oracles/VariableInterestRateOracle.sol +++ b/src/oracles/VariableInterestRateOracle.sol @@ -176,53 +176,49 @@ contract VariableInterestRateOracle is IOracle, AccessControl, Constants { uint256 secondsSinceLastUpdate = (block.timestamp - rateParameters.lastUpdated); - if (secondsSinceLastUpdate > 0) { - // Calculate the total debt - uint128 totalDebt; - DataTypes.Debt memory debt_; - debt_ = cauldron.debt(base.b6(), base.b6()); - // Adding the debt borrowing base against itself - totalDebt = totalDebt + debt_.sum; - - // Adding the debt borrowing base against other ilks - for (uint256 i = 0; i < rateParameters.ilks.length; i++) { - if (cauldron.ilks(base.b6(), rateParameters.ilks[i])) { - debt_ = cauldron.debt(base.b6(), rateParameters.ilks[i]); - totalDebt = totalDebt + debt_.sum; - } - } - // Calculate utilization rate - // Total debt / Total Liquidity - uint256 utilizationRate = uint256(totalDebt).wdiv( - rateParameters.join.storedBalance() - ); - - uint256 interestRate; - if (utilizationRate <= rateParameters.optimalUsageRate) { - interestRate = - rateParameters.baseVariableBorrowRate + - (utilizationRate * rateParameters.slope1) / - rateParameters.optimalUsageRate; - } else { - interestRate = - rateParameters.baseVariableBorrowRate + - rateParameters.slope1 + - ((utilizationRate - rateParameters.optimalUsageRate) * - rateParameters.slope2) / - (1e18 - rateParameters.optimalUsageRate); + // Calculate the total debt + uint128 totalDebt; + DataTypes.Debt memory debt_; + + // Adding the debt borrowing base against other ilks + for (uint256 i = 0; i < rateParameters.ilks.length; i++) { + if (cauldron.ilks(base.b6(), rateParameters.ilks[i])) { + debt_ = cauldron.debt(base.b6(), rateParameters.ilks[i]); + totalDebt = totalDebt + debt_.sum; } - // Calculate per second rate - interestRate = interestRate / 365 days; - rateParameters.accumulated *= (1e18 + interestRate).wpow( - secondsSinceLastUpdate - ); - rateParameters.accumulated /= 1e18; - rateParameters.lastUpdated = block.timestamp; - - sources[base.b6()][kind.b6()] = rateParameters; } + // Calculate utilization rate + // Total debt / Total Liquidity + uint256 utilizationRate = uint256(totalDebt).wdiv( + rateParameters.join.storedBalance() + ); + + uint256 interestRate; + if (utilizationRate <= rateParameters.optimalUsageRate) { + interestRate = + rateParameters.baseVariableBorrowRate + + (utilizationRate * rateParameters.slope1) / + rateParameters.optimalUsageRate; + } else { + interestRate = + rateParameters.baseVariableBorrowRate + + rateParameters.slope1 + + ((utilizationRate - rateParameters.optimalUsageRate) * + rateParameters.slope2) / + (1e18 - rateParameters.optimalUsageRate); + } + // Calculate per second rate + interestRate = interestRate / 365 days; + rateParameters.accumulated *= (1e18 + interestRate).wpow( + secondsSinceLastUpdate + ); + rateParameters.accumulated /= 1e18; + rateParameters.lastUpdated = block.timestamp; + + sources[base.b6()][kind.b6()] = rateParameters; + accumulated = rateParameters.accumulated; require(accumulated > 0, "Accumulated rate is zero"); updateTime = block.timestamp; diff --git a/src/variable/VRLadle.sol b/src/variable/VRLadle.sol index e142cd7..f0268f7 100644 --- a/src/variable/VRLadle.sol +++ b/src/variable/VRLadle.sol @@ -297,26 +297,6 @@ contract VRLadle is UUPSUpgradeable, AccessControl() { // ---- Asset and debt management ---- - /// @dev Move collateral and debt between vaults. - function stir( - bytes12 from, - bytes12 to, - uint128 ink, - uint128 art - ) external payable { - if (ink > 0) - require( - cauldron.vaults(from).owner == msg.sender, - "Only origin vault owner" - ); - if (art > 0) - require( - cauldron.vaults(to).owner == msg.sender, - "Only destination vault owner" - ); - cauldron.stir(from, to, ink, art); - } - /// @dev Add collateral and borrow from vault, pull assets from and push borrowed asset to user /// Or, repay to vault and remove collateral, pull borrowed asset from and push assets to user /// Borrow only before maturity. diff --git a/src/variable/VYToken.sol b/src/variable/VYToken.sol index ec46be8..fe14557 100644 --- a/src/variable/VYToken.sol +++ b/src/variable/VYToken.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.13; -import "erc3156/contracts/interfaces/IERC3156FlashBorrower.sol"; -import "erc3156/contracts/interfaces/IERC3156FlashLender.sol"; import "@yield-protocol/utils-v2/src/token/ERC20Permit.sol"; import "@yield-protocol/utils-v2/src/token/SafeERC20Namer.sol"; import "@yield-protocol/utils-v2/src/access/AccessControl.sol"; @@ -13,19 +11,14 @@ import "../interfaces/IOracle.sol"; import "../constants/Constants.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -contract VYToken is IERC3156FlashLender, UUPSUpgradeable, AccessControl, ERC20Permit, Constants { +contract VYToken is UUPSUpgradeable, AccessControl, ERC20Permit, Constants { using Math for uint256; using Cast for uint256; - event FlashFeeFactorSet(uint256 indexed fee); event Redeemed(address indexed holder, address indexed receiver, uint256 principalAmount, uint256 underlyingAmount); bool public initialized; - bytes32 internal constant FLASH_LOAN_RETURN = keccak256("ERC3156FlashBorrower.onFlashLoan"); - uint256 constant FLASH_LOANS_DISABLED = type(uint256).max; - uint256 public flashFeeFactor = FLASH_LOANS_DISABLED; // Fee on flash loans, as a percentage in fixed point with 18 decimals. Flash loans disabled by default by overflow from `flashFee`. - IOracle public immutable oracle; // Oracle for the savings rate. IJoin public immutable join; // Source of redemption funds. address public immutable underlying; @@ -56,7 +49,6 @@ contract VYToken is IERC3156FlashLender, UUPSUpgradeable, AccessControl, ERC20Pe initialized = true; // On an uninitialized contract, no governance functions can be executed, because no one has permission to do so _grantRole(ROOT, root_); // Grant ROOT _setRoleAdmin(LOCK, LOCK); // Create the LOCK role by setting itself as its own admin, creating an independent role tree - flashFeeFactor = FLASH_LOANS_DISABLED; // Flash loans disabled by default name = name_; symbol = symbol_; decimals = decimals_; @@ -65,12 +57,6 @@ contract VYToken is IERC3156FlashLender, UUPSUpgradeable, AccessControl, ERC20Pe /// @dev Allow to set a new implementation function _authorizeUpgrade(address newImplementation) internal override auth {} - /// @dev Set the flash loan fee factor - function setFlashFeeFactor(uint256 flashFeeFactor_) external auth { - flashFeeFactor = flashFeeFactor_; - emit FlashFeeFactorSet(flashFeeFactor_); - } - ///@dev Converts the amount of the principal to the underlying function convertToUnderlying(uint256 principalAmount) external returns (uint256 underlyingAmount) { return _convertToUnderlying(principalAmount); @@ -196,59 +182,4 @@ contract VYToken is IERC3156FlashLender, UUPSUpgradeable, AccessControl, ERC20Pe } } } - - /** - * @dev From ERC-3156. The amount of currency available to be lended. - * @param token The loan currency. It must be a VYToken contract. - * @return The amount of `token` that can be borrowed. - */ - function maxFlashLoan(address token) external view returns (uint256) { - return token == address(this) ? type(uint256).max - _totalSupply : 0; - } - - /** - * @dev From ERC-3156. The fee to be charged for a given loan. - * @param token The loan currency. It must be the asset. - * @param principalAmount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function flashFee(address token, uint256 principalAmount) external view returns (uint256) { - require(token == address(this), "Unsupported currency"); - return _flashFee(principalAmount); - } - - /** - * @dev The fee to be charged for a given loan. - * @param principalAmount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function _flashFee(uint256 principalAmount) internal view returns (uint256) { - return principalAmount.wmul(flashFeeFactor); - } - - /** - * @dev From ERC-3156. Loan `amount` vyDai to `receiver`, which needs to return them plus fee to this contract within the same transaction. - * Note that if the initiator and the borrower are the same address, no approval is needed for this contract to take the principal + fee from the borrower. - * If the borrower transfers the principal + fee to this contract, they will be burnt here instead of pulled from the borrower. - * @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface. - * @param token The loan currency. Must be a vyDai contract. - * @param principalAmount The amount of tokens lent. - * @param data A data parameter to be passed on to the `receiver` for any custom use. - */ - function flashLoan( - IERC3156FlashBorrower receiver, - address token, - uint256 principalAmount, - bytes memory data - ) external returns (bool) { - require(token == address(this), "Unsupported currency"); - _mint(address(receiver), principalAmount); - uint128 fee = _flashFee(principalAmount).u128(); - require( - receiver.onFlashLoan(msg.sender, token, principalAmount, fee, data) == FLASH_LOAN_RETURN, - "Non-compliant borrower" - ); - _burn(address(receiver), principalAmount + fee); - return true; - } } \ No newline at end of file diff --git a/test/FixtureStates.sol b/test/FixtureStates.sol index 4531e18..99eb3e4 100644 --- a/test/FixtureStates.sol +++ b/test/FixtureStates.sol @@ -159,20 +159,9 @@ abstract contract VYTokenZeroState is ZeroState { timelock = address(1); vyToken.grantRole(VYToken.mint.selector, address(this)); vyToken.grantRole(VYToken.deposit.selector, address(this)); - vyToken.grantRole(VYToken.setFlashFeeFactor.selector, address(this)); - borrower = new FlashBorrower(vyToken); unit = uint128(10 ** ERC20Mock(address(vyToken)).decimals()); deal(address(vyToken), address(this), unit); deal(address(vyToken.underlying()), address(this), unit); } -} - -abstract contract FlashLoanEnabledState is VYTokenZeroState { - event Transfer(address indexed src, address indexed dst, uint256 wad); - - function setUp() public override { - super.setUp(); - vyToken.setFlashFeeFactor(0); - } -} +} \ No newline at end of file diff --git a/test/VRLadle.t.sol b/test/VRLadle.t.sol index cc397aa..a868b00 100644 --- a/test/VRLadle.t.sol +++ b/test/VRLadle.t.sol @@ -153,19 +153,6 @@ contract VaultTests is VaultBuiltState { vm.prank(admin); ladle.give(vaultId, admin); } - - function testOnlyOwnerCouldMove() public { - console.log("cannot move a vault with a different owner"); - vm.prank(admin); - vm.expectRevert("Only origin vault owner"); - ladle.stir(vaultId, otherVaultId, 1, 1); - } - - function testOnlyDestinationVaultOwner() public { - vm.prank(admin); - vm.expectRevert("Only destination vault owner"); - ladle.stir(vaultId, otherVaultId, 0, 1); - } } contract PourTests is VaultBuiltState { @@ -274,130 +261,6 @@ contract PouredStateTests is CauldronPouredState { assertEq(ink, INK); assertEq(art, ART + FEE); } - - function testMoveDebt() public { - console.log("can move debt from one vault to another"); - (bytes12 otherVaultId, ) = ladle.build(baseId, usdcId, 123); - (address owner, , bytes6 ilkId) = cauldron.vaults(vaultId); - deal(cauldron.assets(ilkId), owner, INK); - IERC20(cauldron.assets(ilkId)).approve( - address(ladle.joins(ilkId)), - INK - ); - ladle.pour(otherVaultId, msg.sender, (INK).i128(), 0); - ladle.pour(vaultId, address(this), 0, (ART).i128()); - - (uint128 art, uint128 ink) = cauldron.balances(vaultId); - vm.expectEmit(true, true, true, true); - emit VaultStirred(vaultId, otherVaultId, 0, art); - ladle.stir(vaultId, otherVaultId, 0, art); - - (art, ink) = cauldron.balances(vaultId); - assertEq(ink, INK); - assertEq(art, 0); - - (art, ink) = cauldron.balances(otherVaultId); - assertEq(ink, INK); - assertEq(art, ART); - } - - function testMoveCollateral() public { - console.log("can move collateral from one vault to another"); - (bytes12 otherVaultId, ) = ladle.build(baseId, usdcId, 123); - (uint128 art, uint128 ink) = cauldron.balances(vaultId); - - vm.expectEmit(true, true, true, true); - emit VaultStirred(vaultId, otherVaultId, ink, 0); - ladle.stir(vaultId, otherVaultId, ink, 0); - - (art, ink) = cauldron.balances(vaultId); - assertEq(ink, 0); - assertEq(art, 0); - - (art, ink) = cauldron.balances(otherVaultId); - assertEq(ink, INK); - assertEq(art, 0); - } - - function testMoveDebtAndCollateral() public { - console.log("can move debt and collateral from one vault to another"); - (bytes12 otherVaultId, ) = ladle.build(baseId, usdcId, 123); - ladle.pour(vaultId, address(this), 0, (ART).i128()); - (uint128 art, uint128 ink) = cauldron.balances(vaultId); - - vm.expectEmit(true, true, true, true); - emit VaultStirred(vaultId, otherVaultId, ink, art); - ladle.stir(vaultId, otherVaultId, ink, art); - - (art, ink) = cauldron.balances(vaultId); - assertEq(ink, 0); - assertEq(art, 0); - - (art, ink) = cauldron.balances(otherVaultId); - assertEq(ink, INK); - assertEq(art, ART); - } - - function testMoveCollateralInABatch() public { - console.log("can move collateral from one vault to another in a batch"); - (bytes12 otherVaultId, ) = ladle.build(baseId, usdcId, 123); - (uint128 art, uint128 ink) = cauldron.balances(vaultId); - - bytes[] memory calls = new bytes[](1); - calls[0] = abi.encodeWithSelector( - VRLadle.stir.selector, - vaultId, - otherVaultId, - ink, - 0 - ); - - vm.expectEmit(true, true, true, true); - emit VaultStirred(vaultId, otherVaultId, ink, 0); - ladle.batch(calls); - - (art, ink) = cauldron.balances(vaultId); - assertEq(ink, 0); - assertEq(art, 0); - - (art, ink) = cauldron.balances(otherVaultId); - assertEq(ink, INK); - assertEq(art, 0); - } - - function testMoveDebtInABatch() public { - console.log("can move debt from one vault to another in a batch"); - (bytes12 otherVaultId, ) = ladle.build(baseId, usdcId, 123); - (address owner, , bytes6 ilkId) = cauldron.vaults(vaultId); - deal(cauldron.assets(ilkId), owner, INK); - IERC20(cauldron.assets(ilkId)).approve( - address(ladle.joins(ilkId)), - INK - ); - ladle.pour(otherVaultId, msg.sender, (INK).i128(), 0); - ladle.pour(vaultId, address(this), 0, (ART).i128()); - - bytes[] memory calls = new bytes[](1); - calls[0] = abi.encodeWithSelector( - VRLadle.stir.selector, - vaultId, - otherVaultId, - 0, - ART - ); - - vm.expectEmit(true, true, true, true); - emit VaultStirred(vaultId, otherVaultId, 0, uint128(ART)); - ladle.batch(calls); - - (uint128 art, uint128 ink) = cauldron.balances(vaultId); - assertEq(ink, INK); - assertEq(art, 0); - - (art, ink) = cauldron.balances(otherVaultId); - assertEq(ink, INK); - assertEq(art, ART); - } } contract BorrowedStateTests is BorrowedState { diff --git a/test/VYToken.t.sol b/test/VYToken.t.sol index 449101d..8e7900d 100644 --- a/test/VYToken.t.sol +++ b/test/VYToken.t.sol @@ -177,13 +177,6 @@ contract VYTokenTest is VYTokenZeroState { assertEq(unit, IERC20(vyToken.underlying()).balanceOf(user)); } - function testFlashFeeFactor() public { - console.log("can set the flash fee factor"); - assertEq(vyToken.flashFeeFactor(), type(uint256).max); - vyToken.setFlashFeeFactor(1); - assertEq(vyToken.flashFeeFactor(), 1); - } - function testFuzz_convertToUnderlyingWithIncreasingRates(uint128 newRate) public { @@ -262,75 +255,3 @@ contract VYTokenTest is VYTokenZeroState { assertLe(principalAmount, vyToken.convertToPrincipal(INK)); } } - -contract FlashLoanEnabledStateTests is FlashLoanEnabledState { - function testReturnsCorrectMaxFlashLoan() public { - console.log("can return the correct max flash loan"); - assertEq(vyToken.maxFlashLoan(address(vyToken)), type(uint256).max); - } - - function testFlashBorrow() public { - console.log("can do a simple flash borrow"); - - borrower.flashBorrow( - address(vyToken), - unit, - FlashBorrower.Action.NORMAL - ); - - assertEq(vyToken.balanceOf(address(this)), unit); - assertEq(borrower.flashBalance(), unit); - assertEq(borrower.flashToken(), address(vyToken)); - assertEq(borrower.flashAmount(), unit); - assertEq(borrower.flashInitiator(), address(borrower)); - } - - function testRepayWithTransfer() public { - vm.expectEmit(true, true, false, true); - emit Transfer(address(vyToken), address(0), unit); - - borrower.flashBorrow( - address(vyToken), - unit, - FlashBorrower.Action.TRANSFER - ); - - assertEq(vyToken.balanceOf(address(this)), unit); - assertEq(borrower.flashBalance(), unit); - assertEq(borrower.flashToken(), address(vyToken)); - assertEq(borrower.flashAmount(), unit); - assertEq(borrower.flashFee(), 0); - assertEq(borrower.flashInitiator(), address(borrower)); - } - - function testApproveNonInitiator() public { - vm.expectRevert("ERC20: Insufficient approval"); - vm.prank(address(this)); - vyToken.flashLoan( - borrower, - address(vyToken), - unit, - bytes(abi.encode(0)) - ); - } - - function testEnoughFundsForLoanRepay() public { - vm.expectRevert("ERC20: Insufficient balance"); - vm.prank(address(this)); - borrower.flashBorrow( - address(vyToken), - unit, - FlashBorrower.Action.STEAL - ); - } - - function testNestedFlashLoans() public { - borrower.flashBorrow( - address(vyToken), - unit, - FlashBorrower.Action.REENTER - ); - vm.prank(address(this)); - assertEq(borrower.flashBalance(), unit * 3); - } -}