diff --git a/src/WitchBase.sol b/src/WitchBase.sol index 3aec42c..2e3fd52 100644 --- a/src/WitchBase.sol +++ b/src/WitchBase.sol @@ -1,3 +1,4 @@ +//Note - added to open the PR // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.13; diff --git a/src/oracles/VariableInterestRateOracle.sol b/src/oracles/VariableInterestRateOracle.sol deleted file mode 100644 index cf4f801..0000000 --- a/src/oracles/VariableInterestRateOracle.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.13; - -import "@yield-protocol/utils-v2/src/access/AccessControl.sol"; -import "@yield-protocol/utils-v2/src/utils/Cast.sol"; -import "@yield-protocol/utils-v2/src/utils/Math.sol"; -import "../interfaces/IOracle.sol"; -import "../variable/interfaces/IVRCauldron.sol"; -import "../interfaces/ILadle.sol"; -import "../constants/Constants.sol"; - -contract VariableInterestRateOracle is IOracle, AccessControl, Constants { - using Cast for bytes32; - using Math for uint256; - - struct InterestRateParameter { - /// @dev rate accumulated so far - check `get` for details - uint256 accumulated; - /// @dev time when `accumulated` was last updated - uint256 lastUpdated; - // @dev optimalUsageRate - uint256 optimalUsageRate; - // @dev baseVariableBorrowRate - uint256 baseVariableBorrowRate; - // @dev slope1 - uint256 slope1; - // @dev slope2 - uint256 slope2; - // @dev join - IJoin join; - // @dev ilks - bytes6[] ilks; - } - - /* State Variables - ******************************************************************************************************************/ - - mapping(bytes6 => mapping(bytes6 => InterestRateParameter)) public sources; - - IVRCauldron public cauldron; - ILadle public ladle; - - /* Events - ******************************************************************************************************************/ - event SourceSet( - bytes6 indexed baseId, - bytes6 indexed kind, - uint256 optimalUsageRate, - uint256 baseVariableBorrowRate, - uint256 slope1, - uint256 slope2, - IJoin join - ); - event PerSecondRateUpdated( - bytes6 indexed baseId, - bytes6 indexed kind, - uint256 optimalUsageRate, - uint256 baseVariableBorrowRate, - uint256 slope1, - uint256 slope2, - IJoin join - ); - - constructor(IVRCauldron cauldron_, ILadle ladle_) { - cauldron = cauldron_; - ladle = ladle_; - } - - /** - @notice Set a source - */ - function setSource( - bytes6 baseId, - bytes6 kindId, - uint256 optimalUsageRate, - uint256 accumulated, - uint256 baseVariableBorrowRate, - uint256 slope1, - uint256 slope2, - bytes6[] memory ilks - ) external auth { - InterestRateParameter memory source = sources[baseId][kindId]; - require(source.accumulated == 0, "Source is already set"); - IJoin join = ladle.joins(baseId); - - sources[baseId][kindId] = InterestRateParameter({ - optimalUsageRate: optimalUsageRate, - accumulated: accumulated, - lastUpdated: block.timestamp, - baseVariableBorrowRate: baseVariableBorrowRate, - slope1: slope1, - slope2: slope2, - join: join, - ilks: ilks - }); - - emit SourceSet( - baseId, - kindId, - optimalUsageRate, - baseVariableBorrowRate, - slope1, - slope2, - join - ); - } - - function updateParameters( - bytes6 baseId, - bytes6 kindId, - uint256 optimalUsageRate, - uint256 baseVariableBorrowRate, - uint256 slope1, - uint256 slope2 - ) external auth { - InterestRateParameter memory source = sources[baseId][kindId]; - require(source.accumulated != 0, "Source not found"); - - require( - source.lastUpdated == block.timestamp, - "stale InterestRateParameter" - ); - IJoin join = ladle.joins(baseId); - sources[baseId][kindId].optimalUsageRate = optimalUsageRate; - sources[baseId][kindId].baseVariableBorrowRate = baseVariableBorrowRate; - sources[baseId][kindId].slope1 = slope1; - sources[baseId][kindId].slope2 = slope2; - sources[baseId][kindId].join = join; - - emit PerSecondRateUpdated( - baseId, - kindId, - optimalUsageRate, - baseVariableBorrowRate, - slope1, - slope2, - join - ); - } - - function peek( - bytes32 base, - bytes32 kind, - uint256 - ) - external - view - virtual - override - returns (uint256 accumulated, uint256 updateTime) - { - InterestRateParameter memory source = sources[base.b6()][kind.b6()]; - require(source.accumulated != 0, "Source not found"); - - accumulated = source.accumulated; - require(accumulated > 0, "Accumulated rate is zero"); - - updateTime = source.lastUpdated; - } - - function get( - bytes32 base, - bytes32 kind, - uint256 - ) - external - virtual - override - returns (uint256 accumulated, uint256 updateTime) - { - InterestRateParameter memory rateParameters = sources[base.b6()][ - kind.b6() - ]; - require(rateParameters.accumulated != 0, "Source not found"); - - 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.wmul(rateParameters.slope1).wdiv( - rateParameters.optimalUsageRate - ); - } else { - interestRate = - rateParameters.baseVariableBorrowRate + - rateParameters.slope1 + - (utilizationRate - rateParameters.optimalUsageRate) - .wmul(rateParameters.slope2) - .wdiv(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/VRCauldron.sol b/src/variable/VRCauldron.sol deleted file mode 100644 index fae9a91..0000000 --- a/src/variable/VRCauldron.sol +++ /dev/null @@ -1,486 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.13; -import "../constants/Constants.sol"; -import "../interfaces/IOracle.sol"; -import "../interfaces/DataTypes.sol"; -import "@yield-protocol/utils-v2/src/access/AccessControl.sol"; -import "@yield-protocol/utils-v2/src/utils/Math.sol"; -import "@yield-protocol/utils-v2/src/utils/Cast.sol"; -import { UUPSUpgradeable } from "openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; - -library CauldronMath { - /// @dev Add a number (which might be negative) to a positive, and revert if the result is negative. - function add(uint128 x, int128 y) internal pure returns (uint128 z) { - require (y > 0 || x >= uint128(-y), "Result below zero"); - z = y > 0 ? x + uint128(y) : x - uint128(-y); - } -} - -contract VRCauldron is UUPSUpgradeable, AccessControl, Constants { - using CauldronMath for uint128; - using Math for uint256; - using Cast for uint128; - using Cast for int128; - using Cast for uint256; - - event AssetAdded(bytes6 indexed assetId, address indexed asset); - event BaseAdded(bytes6 indexed baseId); - event IlkAdded(bytes6 indexed baseId, bytes6 indexed ilkId); - event SpotOracleAdded( - bytes6 indexed baseId, - bytes6 indexed ilkId, - address indexed oracle, - uint32 ratio - ); - event RateOracleAdded(bytes6 indexed baseId, address indexed oracle); - event DebtLimitsSet( - bytes6 indexed baseId, - bytes6 indexed ilkId, - uint96 max, - uint24 min, - uint8 dec - ); - - event VaultBuilt( - bytes12 indexed vaultId, - address indexed owner, - bytes6 indexed baseId, - bytes6 ilkId - ); - event VaultTweaked( - bytes12 indexed vaultId, - bytes6 indexed baseId, - bytes6 indexed ilkId - ); - event VaultDestroyed(bytes12 indexed vaultId); - event VaultGiven(bytes12 indexed vaultId, address indexed receiver); - - event VaultPoured( - bytes12 indexed vaultId, - bytes6 indexed baseId, - bytes6 indexed ilkId, - int128 ink, - int128 art - ); - event VaultStirred( - bytes12 indexed from, - bytes12 indexed to, - uint128 ink, - uint128 art - ); - - // ==== Upgradability data ==== - bool public initialized; - - // ==== Configuration data ==== - mapping(bytes6 => address) public assets; // Underlyings and collaterals available in Cauldron. 12 bytes still free. - mapping(bytes6 => bool) public bases; // Assets available in Cauldron for borrowing. - mapping(bytes6 => mapping(bytes6 => bool)) public ilks; // [baseId][assetId] Assets that are approved as collateral for the base - - mapping(bytes6 => IOracle) public rateOracles; // Variable rate oracle for an underlying - mapping(bytes6 => mapping(bytes6 => DataTypes.SpotOracle)) public spotOracles; // [assetId][assetId] Spot price oracles - - // ==== Protocol data ==== - mapping(bytes6 => mapping(bytes6 => DataTypes.Debt)) public debt; // [baseId][ilkId] Max and sum of debt per underlying and collateral. - - // ==== User data ==== - mapping(bytes12 => VRDataTypes.Vault) public vaults; // An user can own one or more Vaults, each one with a bytes12 identifier - mapping(bytes12 => DataTypes.Balances) public balances; // Both debt and assets - - constructor() { - // See https://medium.com/immunefi/wormhole-uninitialized-proxy-bugfix-review-90250c41a43a - initialized = true; // Lock the implementation contract - } - - // ==== Upgradability ==== - - /// @dev Give the ROOT role and create a LOCK role with itself as the admin role and no members. - /// Calling setRoleAdmin(msg.sig, LOCK) means no one can grant that msg.sig role anymore. - function initialize (address root_) public { - require(!initialized, "Already initialized"); - 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 - } - - /// @dev Allow to set a new implementation - function _authorizeUpgrade(address newImplementation) internal override auth {} - - // ==== Administration ==== - - /// @dev Add a new Asset. - function addAsset(bytes6 assetId, address asset) external auth { - require(assetId != bytes6(0), "Asset id is zero"); - require(assets[assetId] == address(0), "Id already used"); - assets[assetId] = asset; - emit AssetAdded(assetId, address(asset)); - } - - /// @dev Set the maximum and minimum debt for an underlying and ilk pair. Can be reset. - function setDebtLimits( - bytes6 baseId, - bytes6 ilkId, - uint96 max, - uint24 min, - uint8 dec - ) external auth { - require(assets[baseId] != address(0), "Base not found"); - require(assets[ilkId] != address(0), "Ilk not found"); - DataTypes.Debt memory debt_ = debt[baseId][ilkId]; - debt_.max = max; - debt_.min = min; - debt_.dec = dec; - debt[baseId][ilkId] = debt_; - emit DebtLimitsSet(baseId, ilkId, max, min, dec); - } - - /// @dev Set a rate oracle. Can be reset. - function setRateOracle(bytes6 baseId, IOracle oracle) external auth { - require(assets[baseId] != address(0), "Base not found"); - rateOracles[baseId] = oracle; - emit RateOracleAdded(baseId, address(oracle)); - } - - /// @dev Set a spot oracle and its collateralization ratio. Can be reset. - function setSpotOracle( - bytes6 baseId, - bytes6 ilkId, - IOracle oracle, - uint32 ratio - ) external auth { - require(assets[baseId] != address(0), "Base not found"); - require(assets[ilkId] != address(0), "Ilk not found"); - spotOracles[baseId][ilkId] = DataTypes.SpotOracle({ - oracle: oracle, - ratio: ratio // With 6 decimals. 1000000 == 100% - }); // Allows to replace an existing oracle. - emit SpotOracleAdded(baseId, ilkId, address(oracle), ratio); - } - - /// @dev Add a new base - function addBase(bytes6 baseId) external auth { - address base = assets[baseId]; - require(base != address(0), "Base not found"); - require( - rateOracles[baseId] != IOracle(address(0)), - "Rate oracle not found" - ); - bases[baseId] = true; - emit BaseAdded(baseId); - } - - /// @dev Add a new Ilk (approve an asset as collateral for a base). - function addIlks(bytes6 baseId, bytes6[] calldata ilkIds) external auth { - require(bases[baseId], "Base not found"); - for (uint256 i; i < ilkIds.length; i++) { - require( - spotOracles[baseId][ilkIds[i]].oracle != IOracle(address(0)), - "Spot oracle not found" - ); - ilks[baseId][ilkIds[i]] = true; - emit IlkAdded(baseId, ilkIds[i]); - } - } - - // ==== Vault management ==== - - /// @dev Create a new vault, linked to a base and a collateral - function build( - address owner, - bytes12 vaultId, - bytes6 baseId, - bytes6 ilkId - ) external auth returns (VRDataTypes.Vault memory vault) { - require(vaultId != bytes12(0), "Vault id is zero"); - require(baseId != bytes6(0), "Base id is zero"); - require(ilkId != bytes6(0), "Ilk id is zero"); - require(vaults[vaultId].baseId == bytes6(0), "Vault already exists"); // Base can't take bytes6(0) as their id - require(ilks[baseId][ilkId] == true, "Ilk not added to base"); - vault = VRDataTypes.Vault({owner: owner, baseId: baseId, ilkId: ilkId}); - vaults[vaultId] = vault; - - emit VaultBuilt(vaultId, owner, baseId, ilkId); - } - - /// @dev Destroy an empty vault. Used to recover gas costs. - function destroy(bytes12 vaultId) external auth { - require(vaults[vaultId].baseId != bytes6(0), "Vault doesn't exist"); // Bases can't take bytes6(0) as their id - DataTypes.Balances memory balances_ = balances[vaultId]; - require(balances_.art == 0 && balances_.ink == 0, "Only empty vaults"); - delete vaults[vaultId]; - emit VaultDestroyed(vaultId); - } - - /// @dev Change a vault base and/or collateral types. - /// We can change the base if there is no debt, or assets if there are no assets - function _tweak( - bytes12 vaultId, - bytes6 baseId, - bytes6 ilkId - ) internal returns (VRDataTypes.Vault memory vault) { - require(baseId != bytes6(0), "Base id is zero"); - require(ilkId != bytes6(0), "Ilk id is zero"); - require(ilks[baseId][ilkId] == true, "Ilk not added to base"); - - vault = vaults[vaultId]; - require(vault.baseId != bytes6(0), "Vault doesn't exist"); // Bases can't take bytes6(0) as their id - - DataTypes.Balances memory balances_ = balances[vaultId]; - if (baseId != vault.baseId) { - require(balances_.art == 0, "Only with no debt"); - vault.baseId = baseId; - } - if (ilkId != vault.ilkId) { - require(balances_.ink == 0, "Only with no collateral"); - vault.ilkId = ilkId; - } - - vaults[vaultId] = vault; - emit VaultTweaked(vaultId, vault.baseId, vault.ilkId); - } - - /// @dev Change a vault base and/or collateral types. - /// We can change the base if there is no debt, or assets if there are no assets - function tweak( - bytes12 vaultId, - bytes6 baseId, - bytes6 ilkId - ) external auth returns (VRDataTypes.Vault memory vault) { - vault = _tweak(vaultId, baseId, ilkId); - } - - /// @dev Transfer a vault to another user. - function _give(bytes12 vaultId, address receiver) - internal - returns (VRDataTypes.Vault memory vault) - { - require(vaultId != bytes12(0), "Vault id is zero"); - require(vaults[vaultId].baseId != bytes6(0), "Vault doesn't exist"); // Base can't take bytes6(0) as their id - vault = vaults[vaultId]; - vault.owner = receiver; - vaults[vaultId] = vault; - emit VaultGiven(vaultId, receiver); - } - - /// @dev Transfer a vault to another user. - function give(bytes12 vaultId, address receiver) - external - auth - returns (VRDataTypes.Vault memory vault) - { - vault = _give(vaultId, receiver); - } - - // ==== Asset and debt management ==== - - function vaultData(bytes12 vaultId) - internal - view - returns ( - VRDataTypes.Vault memory vault_, - DataTypes.Balances memory balances_ - ) - { - vault_ = vaults[vaultId]; - require(vault_.baseId != bytes6(0), "Vault not found"); - balances_ = balances[vaultId]; - } - - /// @dev Convert a base amount to debt terms. - /// @notice Think about rounding up if using, since we are dividing. - function debtFromBase(bytes6 baseId, uint128 base) - external - returns (uint128 art) - { - art = _debtFromBase(baseId, base); - } - - /// @dev Convert a base amount to debt terms. - /// @notice Think about rounding up if using, since we are dividing. - function _debtFromBase(bytes6 baseId, uint128 base) - internal - returns (uint128 art) - { - (uint256 rate, ) = rateOracles[baseId].get(baseId, RATE, 0); // The value returned is an accumulator, it doesn't need an input amount - art = uint256(base).wdivup(rate).u128(); - } - - /// @dev Convert a debt amount for a to base terms - function debtToBase(bytes6 baseId, uint128 art) - external - returns (uint128 base) - { - base = _debtToBase(baseId, art); - } - - /// @dev Convert a debt amount for a to base terms - function _debtToBase(bytes6 baseId, uint128 art) - internal - returns (uint128 base) - { - (uint256 rate, ) = rateOracles[baseId].get(baseId, RATE, 0); // The value returned is an accumulator, it doesn't need an input amount - base = uint256(art).wmul(rate).u128(); - } - - /// @dev Move collateral and debt between vaults. - function stir( - bytes12 from, - bytes12 to, - uint128 ink, - uint128 art - ) - external - auth - returns (DataTypes.Balances memory, DataTypes.Balances memory) - { - require(from != to, "Identical vaults"); - ( - VRDataTypes.Vault memory vaultFrom, - DataTypes.Balances memory balancesFrom - ) = vaultData(from); - ( - VRDataTypes.Vault memory vaultTo, - DataTypes.Balances memory balancesTo - ) = vaultData(to); - - if (ink > 0) { - require(vaultFrom.ilkId == vaultTo.ilkId, "Different collateral"); - balancesFrom.ink -= ink; - balancesTo.ink += ink; - } - if (art > 0) { - require(vaultFrom.baseId == vaultTo.baseId, "Different base"); - balancesFrom.art -= art; - balancesTo.art += art; - } - - balances[from] = balancesFrom; - balances[to] = balancesTo; - - if (ink > 0) - require( - _level(vaultFrom, balancesFrom) >= 0, - "Undercollateralized at origin" - ); - if (art > 0) - require( - _level(vaultTo, balancesTo) >= 0, - "Undercollateralized at destination" - ); - - emit VaultStirred(from, to, ink, art); - return (balancesFrom, balancesTo); - } - - /// @dev Add collateral and rate from vault, pull assets from and push rateed asset to user - /// Or, repay to vault and remove collateral, pull rateed asset from and push assets to user - function _pour( - bytes12 vaultId, - VRDataTypes.Vault memory vault_, - DataTypes.Balances memory balances_, - int128 ink, - int128 art - ) internal returns (DataTypes.Balances memory) { - // For now, the collateralization checks are done outside to allow for underwater operation. That might change. - if (ink != 0) { - balances_.ink = balances_.ink.add(ink); - } - - // Modify vault and global debt records. If debt increases, check global limit. - if (art != 0) { - DataTypes.Debt memory debt_ = debt[vault_.baseId][vault_.ilkId]; - balances_.art = balances_.art.add(art); - debt_.sum = debt_.sum.add(art); - uint128 dust = debt_.min * uint128(10)**debt_.dec; - uint128 line = debt_.max * uint128(10)**debt_.dec; - require( - balances_.art == 0 || balances_.art >= dust, - "Min debt not reached" - ); - if (art > 0) require(debt_.sum <= line, "Max debt exceeded"); - debt[vault_.baseId][vault_.ilkId] = debt_; - } - balances[vaultId] = balances_; - - emit VaultPoured(vaultId, vault_.baseId, vault_.ilkId, ink, art); - return balances_; - } - - /// @dev Manipulate a vault, ensuring it is collateralized afterwards. - /// To be used by debt management contracts. - function pour( - bytes12 vaultId, - int128 ink, - int128 base - ) external virtual auth returns (DataTypes.Balances memory) { - ( - VRDataTypes.Vault memory vault_, - DataTypes.Balances memory balances_ - ) = vaultData(vaultId); - - // Normalize the base amount to debt terms - int128 art = base; - - if (base != 0) - art = base > 0 - ? _debtFromBase(vault_.baseId, base.u128()).i128() - : -_debtFromBase(vault_.baseId, (-base).u128()).i128(); - - balances_ = _pour(vaultId, vault_, balances_, ink, art); - - if (balances_.art > 0 && (ink < 0 || art > 0)) - // If there is debt and we are less safe - require(_level(vault_, balances_) >= 0, "Undercollateralized"); - return balances_; - } - - /// @dev Reduce debt and collateral from a vault, ignoring collateralization checks. - /// To be used by liquidation engines. - function slurp( - bytes12 vaultId, - uint128 ink, - uint128 base - ) external auth returns (DataTypes.Balances memory) { - ( - VRDataTypes.Vault memory vault_, - DataTypes.Balances memory balances_ - ) = vaultData(vaultId); - - // Normalize the base amount to debt terms - int128 art = _debtFromBase(vault_.baseId, base).i128(); - - balances_ = _pour(vaultId, vault_, balances_, -(ink.i128()), -art); - - return balances_; - } - - // ==== Accounting ==== - - /// @dev Return the collateralization level of a vault. It will be negative if undercollateralized. - function level(bytes12 vaultId) external returns (int256) { - ( - VRDataTypes.Vault memory vault_, - DataTypes.Balances memory balances_ - ) = vaultData(vaultId); - - return _level(vault_, balances_); - } - - /// @dev Return the collateralization level of a vault. It will be negative if undercollateralized. - function _level( - VRDataTypes.Vault memory vault_, - DataTypes.Balances memory balances_ - ) internal returns (int256) { - DataTypes.SpotOracle memory spotOracle_ = spotOracles[vault_.baseId][ - vault_.ilkId - ]; - uint256 ratio = uint256(spotOracle_.ratio) * 1e12; // Normalized to 18 decimals - (uint256 inkValue, ) = spotOracle_.oracle.get( - vault_.ilkId, - vault_.baseId, - balances_.ink - ); // ink * spot - uint256 baseValue = _debtToBase(vault_.baseId, balances_.art); // art * rate - return inkValue.i256() - baseValue.wmul(ratio).i256(); - } -} \ No newline at end of file diff --git a/src/variable/VRLadle.sol b/src/variable/VRLadle.sol deleted file mode 100644 index 58f2374..0000000 --- a/src/variable/VRLadle.sol +++ /dev/null @@ -1,389 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.13; -import "../interfaces/IFYToken.sol"; -import "../interfaces/IJoin.sol"; -import "../interfaces/IOracle.sol"; -import "../interfaces/DataTypes.sol"; -import "../interfaces/IRouter.sol"; -import "./interfaces/IVRCauldron.sol"; -import "@yield-protocol/yieldspace-tv/src/interfaces/IPool.sol"; -import "@yield-protocol/utils-v2/src/interfaces/IWETH9.sol"; -import "@yield-protocol/utils-v2/src/token/IERC20.sol"; -import "@yield-protocol/utils-v2/src/token/IERC2612.sol"; -import "@yield-protocol/utils-v2/src/access/AccessControl.sol"; -import "@yield-protocol/utils-v2/src/token/TransferHelper.sol"; -import "@yield-protocol/utils-v2/src/utils/Math.sol"; -import "@yield-protocol/utils-v2/src/utils/Cast.sol"; -import "dss-interfaces/src/dss/DaiAbstract.sol"; -import { UUPSUpgradeable } from "openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; - -/// @dev Ladle orchestrates contract calls throughout the Yield Protocol v2 into useful and efficient user oriented features. -contract VRLadle is UUPSUpgradeable, AccessControl() { - using Math for uint256; - using Cast for uint256; - using Cast for uint128; - using TransferHelper for IERC20; - using TransferHelper for address payable; - - event JoinAdded(bytes6 indexed assetId, address indexed join); - event IntegrationAdded(address indexed integration, bool indexed set); - event TokenAdded(address indexed token, bool indexed set); - event FeeSet(uint256 fee); - - bool public initialized; - IVRCauldron public immutable cauldron; - IRouter public immutable router; - IWETH9 public immutable weth; - uint256 public borrowingFee; - bytes12 cachedVaultId; - - mapping (bytes6 => IJoin) public joins; // Join contracts available to manage assets. The same Join can serve multiple assets (ETH-A, ETH-B, etc...) - mapping (address => bool) public integrations; // Trusted contracts to call anything on. - mapping (address => bool) public tokens; // Trusted contracts to call `transfer` or `permit` on. - - constructor (IVRCauldron cauldron_, IRouter router_, IWETH9 weth_) { - cauldron = cauldron_; - router = router_; - weth = weth_; - - // See https://medium.com/immunefi/wormhole-uninitialized-proxy-bugfix-review-90250c41a43a - initialized = true; // Lock the implementation contract - } - - // ---- Upgradability ---- - - /// @dev Give the ROOT role and create a LOCK role with itself as the admin role and no members. - /// Calling setRoleAdmin(msg.sig, LOCK) means no one can grant that msg.sig role anymore. - function initialize (address root_) public { - require(!initialized, "Already initialized"); - 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 - } - - /// @dev Allow to set a new implementation - function _authorizeUpgrade(address newImplementation) internal override auth {} - - // ---- Data sourcing ---- - /// @dev Obtains a vault by vaultId from the Cauldron, and verifies that msg.sender is the owner - /// If bytes(0) is passed as the vaultId it tries to load a vault from the cache - function getVault( - bytes12 vaultId_ - ) internal view returns (bytes12 vaultId, VRDataTypes.Vault memory vault) { - if (vaultId_ == bytes12(0)) { - // We use the cache - require(cachedVaultId != bytes12(0), "Vault not cached"); - vaultId = cachedVaultId; - } else { - vaultId = vaultId_; - } - vault = cauldron.vaults(vaultId); - require(vault.owner == msg.sender, "Only vault owner"); - } - - /// @dev Obtains a join by assetId, and verifies that it exists - function getJoin(bytes6 assetId) internal view returns (IJoin join) { - join = joins[assetId]; - require(join != IJoin(address(0)), "Join not found"); - } - - // ---- Administration ---- - - /// @dev Add or remove an integration. - function addIntegration(address integration, bool set) external auth { - _addIntegration(integration, set); - } - - /// @dev Add or remove an integration. - function _addIntegration(address integration, bool set) private { - integrations[integration] = set; - emit IntegrationAdded(integration, set); - } - - /// @dev Add or remove a token that the Ladle can call `transfer` or `permit` on. - function addToken(address token, bool set) external auth { - _addToken(token, set); - } - - /// @dev Add or remove a token that the Ladle can call `transfer` or `permit` on. - function _addToken(address token, bool set) private { - tokens[token] = set; - emit TokenAdded(token, set); - } - - /// @dev Add a new Join for an Asset, or replace an existing one for a new one. - /// There can be only one Join per Asset. Until a Join is added, no tokens of that Asset can be posted or withdrawn. - function addJoin(bytes6 assetId, IJoin join) external auth { - address asset = cauldron.assets(assetId); - require(asset != address(0), "Asset not found"); - require(join.asset() == asset, "Mismatched asset and join"); - joins[assetId] = join; - - bool set = (join != IJoin(address(0))) ? true : false; - _addToken(asset, set); // address(0) disables the token - emit JoinAdded(assetId, address(join)); - } - - /// @dev Set the fee parameter - function setFee(uint256 fee) external auth { - borrowingFee = fee; - emit FeeSet(fee); - } - - // ---- Call management ---- - - /// @dev Allows batched call to self (this contract). - /// @param calls An array of inputs for each call. - function batch( - bytes[] calldata calls - ) external payable returns (bytes[] memory results) { - results = new bytes[](calls.length); - for (uint256 i; i < calls.length; i++) { - (bool success, bytes memory result) = address(this).delegatecall( - calls[i] - ); - if (!success) revert(RevertMsgExtractor.getRevertMsg(result)); - results[i] = result; - } - - // build would have populated the cache, this deletes it - cachedVaultId = bytes12(0); - } - - /// @dev Allow users to route calls to a contract, to be used with batch - function route( - address integration, - bytes calldata data - ) external payable returns (bytes memory result) { - require(integrations[integration], "Unknown integration"); - return router.route(integration, data); - } - - // ---- Token management ---- - - /// @dev Execute an ERC2612 permit for the selected token - function forwardPermit( - IERC2612 token, - address spender, - uint256 amount, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external payable { - require(tokens[address(token)], "Unknown token"); - token.permit(msg.sender, spender, amount, deadline, v, r, s); - } - - /// @dev Execute a Dai-style permit for the selected token - function forwardDaiPermit( - DaiAbstract token, - address spender, - uint256 nonce, - uint256 deadline, - bool allowed, - uint8 v, - bytes32 r, - bytes32 s - ) external payable { - require(tokens[address(token)], "Unknown token"); - token.permit(msg.sender, spender, nonce, deadline, allowed, v, r, s); - } - - /// @dev Allow users to trigger a token transfer from themselves to a receiver through the ladle, to be used with batch - function transfer( - IERC20 token, - address receiver, - uint128 wad - ) external payable { - require(tokens[address(token)], "Unknown token"); - token.safeTransferFrom(msg.sender, receiver, wad); - } - - /// @dev Retrieve any token in the Ladle - function retrieve( - IERC20 token, - address to - ) external payable returns (uint256 amount) { - require(tokens[address(token)], "Unknown token"); - amount = token.balanceOf(address(this)); - token.safeTransfer(to, amount); - } - - /// @dev The WETH9 contract will send ether to BorrowProxy on `weth.withdraw` using this function. - receive() external payable { - require(msg.sender == address(weth), "Only receive from WETH"); - } - - /// @dev Accept Ether, wrap it and forward it to the provided address - /// This function should be called first in a batch, and the Join should keep track of stored reserves - /// Passing the id for a join that doesn't link to a contract implemnting IWETH9 will fail - function wrapEther( - address to - ) external payable returns (uint256 ethTransferred) { - ethTransferred = address(this).balance; - weth.deposit{value: ethTransferred}(); - IERC20(address(weth)).safeTransfer(to, ethTransferred); - } - - /// @dev Unwrap Wrapped Ether held by this Ladle, and send the Ether - /// This function should be called last in a batch, and the Ladle should have no reason to keep an WETH balance - function unwrapEther( - address payable to - ) external payable returns (uint256 ethTransferred) { - ethTransferred = weth.balanceOf(address(this)); - weth.withdraw(ethTransferred); - to.safeTransferETH(ethTransferred); - } - - // ---- Vault management ---- - - /// @dev Generate a vaultId. A keccak256 is cheaper than using a counter with a SSTORE, even accounting for eventual collision retries. - function _generateVaultId(uint8 salt) private view returns (bytes12) { - return - bytes12( - keccak256(abi.encodePacked(msg.sender, block.timestamp, salt)) - ); - } - - /// @dev Create a new vault, linked to a base and a collateral - function build( - bytes6 baseId, - bytes6 ilkId, - uint8 salt - ) external payable virtual returns (bytes12, VRDataTypes.Vault memory) { - return _build(baseId, ilkId, salt); - } - - /// @dev Create a new vault, linked to a base and a collateral - function _build( - bytes6 baseId, - bytes6 ilkId, - uint8 salt - ) internal returns (bytes12 vaultId, VRDataTypes.Vault memory vault) { - vaultId = _generateVaultId(salt); - while (cauldron.vaults(vaultId).baseId != bytes6(0)) - vaultId = _generateVaultId(++salt); // If the vault exists, generate other random vaultId - vault = cauldron.build(msg.sender, vaultId, baseId, ilkId); - // Store the vault data in the cache - cachedVaultId = vaultId; - } - - /// @dev Change a vault base or collateral. - function tweak( - bytes12 vaultId_, - bytes6 baseId, - bytes6 ilkId - ) external payable returns (VRDataTypes.Vault memory vault) { - (bytes12 vaultId, ) = getVault(vaultId_); // getVault verifies the ownership as well - // tweak checks that the base and the collateral both exist and that the collateral is approved for the base - vault = cauldron.tweak(vaultId, baseId, ilkId); - } - - /// @dev Give a vault to another user. - function give( - bytes12 vaultId_, - address receiver - ) external payable returns (VRDataTypes.Vault memory vault) { - (bytes12 vaultId, ) = getVault(vaultId_); - vault = cauldron.give(vaultId, receiver); - } - - /// @dev Destroy an empty vault. Used to recover gas costs. - function destroy(bytes12 vaultId_) external payable { - (bytes12 vaultId, ) = getVault(vaultId_); - cauldron.destroy(vaultId); - } - - // ---- 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. - function _pour( - bytes12 vaultId, - VRDataTypes.Vault memory vault, - address to, - int128 ink, - int128 base - ) private { - int128 fee; - if (base > 0 && vault.ilkId != vault.baseId && borrowingFee != 0) - fee = uint256(int256(base)).wmul(borrowingFee).i128(); - - // Update accounting - cauldron.pour(vaultId, ink, base + fee); - - // Manage collateral - if (ink != 0) { - IJoin ilkJoin = getJoin(vault.ilkId); - if (ink > 0) ilkJoin.join(vault.owner, uint128(ink)); - if (ink < 0) ilkJoin.exit(to, uint128(-ink)); - } - - // Manage base - if (base != 0) { - IJoin baseJoin = getJoin(vault.baseId); - if (base < 0) baseJoin.join(vault.owner, uint128(-base)); - if (base > 0) baseJoin.exit(to, uint128(base)); - } - } - - /// @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. - function pour( - bytes12 vaultId_, - address to, - int128 ink, - int128 base - ) external payable { - (bytes12 vaultId, VRDataTypes.Vault memory vault) = getVault(vaultId_); - _pour(vaultId, vault, to, ink, base); - } - - /// @dev Repay all debt in a vault. - /// The base tokens need to be already in the join, unaccounted for. The surplus base will be returned to msg.sender. - function repay( - bytes12 vaultId_, - address inkTo, - address refundTo, - int128 ink - ) external payable returns (uint128 base, uint256 refund) { - (bytes12 vaultId, VRDataTypes.Vault memory vault) = getVault(vaultId_); - - DataTypes.Balances memory balances = cauldron.balances(vaultId); - base = cauldron.debtToBase(vault.baseId, balances.art); - _pour(vaultId, vault, inkTo, ink, -(base.i128())); - - // Given the low rate of change, we probably prefer to send a few extra wei to the join, - // ask for no refund (with refundTo == address(0)), and save gas - if (refundTo != address(0)) { - IJoin baseJoin = getJoin(vault.baseId); - refund = - IERC20(baseJoin.asset()).balanceOf(address(baseJoin)) - - baseJoin.storedBalance(); - baseJoin.exit(refundTo, refund.u128()); - } - } -} diff --git a/src/variable/VRWitch.sol b/src/variable/VRWitch.sol deleted file mode 100644 index a1832e2..0000000 --- a/src/variable/VRWitch.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.13; - -import "@yield-protocol/utils-v2/src/utils/Cast.sol"; -import "./interfaces/IVRCauldron.sol"; -import "../WitchBase.sol"; -import { UUPSUpgradeable } from "openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; - -/// @title The Witch is a DataTypes.Auction/Liquidation Engine for the Yield protocol -/// @notice The Witch grabs under-collateralised vaults, replacing the owner by itself. Then it sells -/// the vault collateral in exchange for underlying to pay its debt. The amount of collateral -/// given increases over time, until it offers to sell all the collateral for underlying to pay -/// all the debt. The auction is held open at the final price indefinitely. -/// @dev After the debt is settled, the Witch returns the vault to its original owner. -contract VRWitch is WitchBase, UUPSUpgradeable { - using Cast for uint256; - - // If we would copy the code from WitchBase here we could make the ladle immutable, - // saving some gas. We don't do that to avoid code duplication and minimize the code to be audited. - - bool public initialized; - - constructor(ICauldron cauldron_, ILadle ladle_) WitchBase(cauldron_, ladle_) { - // See https://medium.com/immunefi/wormhole-uninitialized-proxy-bugfix-review-90250c41a43a - initialized = true; // Lock the implementation contract - } - - // ====================================================================== - // = Upgradability management functions = - // ====================================================================== - - /// @dev Give the ROOT role and create a LOCK role with itself as the admin role and no members. - /// Calling setRoleAdmin(msg.sig, LOCK) means no one can grant that msg.sig role anymore. - /// Set the ladle as well. - function initialize (ILadle ladle_, address root_) public { - require(!initialized, "Already initialized"); - initialized = true; // On an uninitialized contract, no governance functions can be executed, because no one has permission to do so - - ladle = ladle_; - auctioneerReward = ONE_PERCENT; - - _grantRole(ROOT, root_); // Grant ROOT - _setRoleAdmin(LOCK, LOCK); // Create the LOCK role by setting itself as its own admin, creating an independent role tree - } - - /// @dev Allow to set a new implementation - function _authorizeUpgrade(address newImplementation) internal override auth {} - - // ====================================================================== - // = Auction management functions = - // ====================================================================== - - /// @dev Put an under-collateralised vault up for liquidation - /// @param vaultId Id of the vault to liquidate - /// @param to Receiver of the auctioneer reward - /// @return auction_ Info associated to the auction itself - /// @return vault Vault that's being auctioned - function auction(bytes12 vaultId, address to) - external - beforeAshes - returns ( - DataTypes.Auction memory auction_, - VRDataTypes.Vault memory vault - ) - { - vault = IVRCauldron(address(cauldron)).vaults(vaultId); - - DataTypes.Line memory line; - (auction_, line) = _calcAuctionParameters( - vaultId, - vault.baseId, - vault.ilkId, - bytes6(0), - vault.owner, - to - ); - - vault = IVRCauldron(address(cauldron)).give(vaultId, address(this)); - emit Auctioned( - vaultId, - auction_, - line.duration, - line.collateralProportion - ); - } - - // ====================================================================== - // = Bidding functions = - // ====================================================================== - - /// @notice Returns debt that could be paid given the maxBaseIn - function _debtFromBase(DataTypes.Auction memory auction_, uint128 maxBaseIn) - internal - override - returns (uint256 artIn) - { - artIn = cauldron.debtFromBase(auction_.baseId, maxBaseIn); - } - - /// @notice Returns base that could be paid given the artIn - function _debtToBase(DataTypes.Auction memory auction_, uint128 artIn) - internal - override - returns (uint256 baseIn) - { - baseIn = cauldron.debtToBase(auction_.baseId, artIn); - } - - // ====================================================================== - // = Quoting functions = - // ====================================================================== - - function _getVaultDetailsAndDebt(bytes12 vaultId) - internal - view - override - returns (VaultBalanceDebtData memory details) - { - VRDataTypes.Vault memory vault = IVRCauldron(address(cauldron)).vaults( - vaultId - ); - - details.ilkId = vault.ilkId; - details.baseId = vault.baseId; - details.seriesId = bytes6(0); - details.owner = vault.owner; - details.balances = cauldron.balances(vaultId); - details.debt = cauldron.debt(vault.baseId, vault.ilkId); - } -} diff --git a/src/variable/VYToken.sol b/src/variable/VYToken.sol deleted file mode 100644 index d4b0517..0000000 --- a/src/variable/VYToken.sol +++ /dev/null @@ -1,251 +0,0 @@ -// 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"; -import "@yield-protocol/utils-v2/src/utils/Math.sol"; -import "@yield-protocol/utils-v2/src/utils/Cast.sol"; -import "../interfaces/IJoin.sol"; -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 { - using Math for uint256; - using Cast for uint256; - - event Point(bytes32 indexed param, address value); - 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; - bytes6 public immutable underlyingId; // Needed to access the oracle - - constructor( - bytes6 underlyingId_, - IOracle oracle_, // Underlying vs its interest-bearing version - IJoin join_, - string memory name, - string memory symbol - ) ERC20Permit(name, symbol, SafeERC20Namer.tokenDecimals(address(IJoin(join_).asset()))) { - // The join asset is this vyToken's underlying, from which we inherit the decimals - underlyingId = underlyingId_; - join = join_; - underlying = address(IJoin(join_).asset()); - oracle = oracle_; - - // See https://medium.com/immunefi/wormhole-uninitialized-proxy-bugfix-review-90250c41a43a - initialized = true; // Lock the implementation contract - } - - /// @dev Give the ROOT role and create a LOCK role with itself as the admin role and no members. - /// Calling setRoleAdmin(msg.sig, LOCK) means no one can grant that msg.sig role anymore. - function initialize (address root_) public { - require(!initialized, "Already initialized"); - 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 - } - - /// @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); - } - - ///@dev Converts the amount of the principal to the underlying - function _convertToUnderlying(uint256 principalAmount) internal returns (uint256 underlyingAmount) { - (uint256 chi, ) = oracle.get(underlyingId, CHI, 0); // The value returned is an accumulator, it doesn't need an input amount - return principalAmount.wmul(chi); - } - - ///@dev Converts the amount of the underlying to the principal - function convertToPrincipal(uint256 underlyingAmount) external returns (uint256 principalAmount) { - return _convertToPrincipal(underlyingAmount); - } - - ///@dev Converts the amount of the underlying to the principal - function _convertToPrincipal(uint256 underlyingAmount) internal returns (uint256 princpalAmount) { - (uint256 chi, ) = oracle.get(underlyingId, CHI, 0); // The value returned is an accumulator, it doesn't need an input amount - return underlyingAmount.wdivup(chi); - } - - ///@dev returns the maximum redeemable amount for the address holder in terms of the principal - function maxRedeem(address holder) external view returns (uint256 maxPrincipalAmount) { - return _balanceOf[holder]; - } - - ///@dev returns the amount of underlying redeemable in terms of the principal - function previewRedeem(uint256 principalAmount) external returns (uint256 underlyingAmount) { - return _convertToUnderlying(principalAmount); - } - - /// @dev Burn vyToken for an amount of principal that increases according to `chi` - /// If `amount` is 0, the contract will redeem instead the vyToken balance of this contract. Useful for batches. - function redeem(uint256 principalAmount, address receiver, address holder) external returns (uint256 underlyingAmount) { - principalAmount = (principalAmount == 0) ? _balanceOf[address(this)] : principalAmount; - _burn(holder, principalAmount); - underlyingAmount = _convertToUnderlying(principalAmount); - join.exit(receiver, underlyingAmount.u128()); - - emit Redeemed(holder, receiver, principalAmount, underlyingAmount); - } - - /// @dev Burn vyToken for an amount of principal that increases according to `chi` - /// If `amount` is 0, the contract will redeem instead the vyToken balance of this contract. Useful for batches. - function redeem(address receiver, uint256 principalAmount) external returns (uint256 underlyingAmount) { - principalAmount = (principalAmount == 0) ? _balanceOf[address(this)] : principalAmount; - _burn(msg.sender, principalAmount); - underlyingAmount = _convertToUnderlying(principalAmount); - join.exit(receiver, underlyingAmount.u128()); - - emit Redeemed(msg.sender, receiver, principalAmount, underlyingAmount); - } - - ///@dev returns the maximum withdrawable amount for the address holder in terms of the underlying - function maxWithdraw(address holder) external returns (uint256 maxUnderlyingAmount) { - return _convertToUnderlying(_balanceOf[holder]); - } - - ///@dev returns the amount of the principal withdrawable in terms of the underlying - function previewWithdraw(uint256 underlyingAmount) external returns (uint256 principalAmount) { - return _convertToPrincipal(underlyingAmount); - } - - /// @dev Burn vyToken for an amount of underlying that increases according to `chi` - /// If `amount` is 0, the contract will redeem instead the vyToken balance of this contract. Useful for batches. - function withdraw(uint256 underlyingAmount, address receiver, address holder) external returns (uint256 principalAmount) { - principalAmount = (underlyingAmount == 0) ? _balanceOf[address(this)] : _convertToPrincipal(underlyingAmount); - _burn(holder, principalAmount); - underlyingAmount = _convertToUnderlying(principalAmount); - join.exit(receiver, underlyingAmount.u128()); - - emit Redeemed(holder, receiver, principalAmount, underlyingAmount); - } - - /// @dev Mint vyTokens. - function mint(address receiver, uint256 principalAmount) external auth { - join.join(msg.sender, _convertToUnderlying(principalAmount).u128()); - _mint(receiver, principalAmount); - } - - ///@dev returns the maximum mintable amount for the address holder in terms of the principal - function maxMint(address) external view returns (uint256 maxPrincipalAmount) { - return type(uint256).max - _totalSupply; - } - - ///@dev returns the amount of the principal mintable in terms of the underlying - function previewMint(uint256 principalAmount) external returns (uint256 underlyingAmount) { - return _convertToUnderlying(principalAmount.u128()); - } - - /// @dev Mint vyTokens. - function deposit(address receiver, uint256 underlyingAmount) external auth { - join.join(msg.sender, underlyingAmount.u128()); - _mint(receiver, _convertToPrincipal(underlyingAmount)); - } - - ///@dev returns the maximum depositable amount for the address holder in terms of the underlying - function maxDeposit(address) external returns (uint256 maxUnderlyingAmount) { - return _convertToUnderlying(type(uint256).max - _totalSupply); - } - - ///@dev returns the amount of the underlying depositable in terms of the principal - function previewDeposit(uint256 underlyingAmount) external returns (uint256 principalAmount) { - return _convertToPrincipal(underlyingAmount.u128()); - } - - /// @dev Burn vyTokens. - /// Any tokens locked in this contract will be burned first and subtracted from the amount to burn from the user's wallet. - /// This feature allows someone to transfer vyToken to this contract to enable a `burn`, potentially saving the cost of `approve` or `permit`. - function _burn(address holder, uint256 principalAmount) internal override returns (bool) { - // First use any tokens locked in this contract - uint256 available = _balanceOf[address(this)]; - if (available >= principalAmount) { - return super._burn(address(this), principalAmount); - } else { - if (available > 0) super._burn(address(this), available); - unchecked { - _decreaseAllowance(holder, principalAmount - available); - } - unchecked { - return super._burn(holder, principalAmount - available); - } - } - } - - /** - * @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