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

Variable interest rate oracle fix #21

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 39 additions & 43 deletions src/oracles/VariableInterestRateOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 0 additions & 20 deletions src/variable/VRLadle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
71 changes: 1 addition & 70 deletions src/variable/VYToken.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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_;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}
13 changes: 1 addition & 12 deletions test/FixtureStates.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Loading