diff --git a/contracts/interfaces/cpmm/ICPMMGammaPool.sol b/contracts/interfaces/cpmm/ICPMMGammaPool.sol new file mode 100644 index 0000000..3672e33 --- /dev/null +++ b/contracts/interfaces/cpmm/ICPMMGammaPool.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +/// @title Interface for Constant Product Market Maker version of GammaPool +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +interface ICPMMGammaPool { + + /// @dev _maxTotalAPY - new maximum total APY charged to Borrowers + event SetMaxTotalAPY(uint256 _maxTotalAPY); + + /// @dev initialization parameters passed to CPMMGammaPool constructor + struct InitializationParams { + uint16 protocolId; + address factory; + address borrowStrategy; + address repayStrategy; + address rebalanceStrategy; + address shortStrategy; + address liquidationStrategy; + address batchLiquidationStrategy; + address viewer; + address externalRebalanceStrategy; + address externalLiquidationStrategy; + address cfmmFactory; + bytes32 cfmmInitCodeHash; + } + + /// @dev set maximum total APY charged by GammaPool to borrowers + /// @param _maxTotalAPY - new maximum total APY charged to GammaPool borrowers + function setMaxTotalAPY(uint256 _maxTotalAPY) external; +} diff --git a/contracts/interfaces/cpmm/strategies/ICPMMRebalanceStrategy.sol b/contracts/interfaces/cpmm/strategies/ICPMMRebalanceStrategy.sol new file mode 100644 index 0000000..4f3f37e --- /dev/null +++ b/contracts/interfaces/cpmm/strategies/ICPMMRebalanceStrategy.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +/// @title Interface for Rebalance Strategy of Constant Product Market Maker version of GammaPool +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +interface ICPMMRebalanceStrategy { + + /// @dev _maxTotalAPY - new maximum total APY charged to Borrowers + event SetMaxTotalAPY(uint256 _maxTotalAPY); + + /// @dev set maximum total APY charged by GammaPool to borrowers + /// @param _maxTotalAPY - new maximum total APY charged to GammaPool borrowers + function _setMaxTotalAPY(uint256 _maxTotalAPY) external; +} diff --git a/contracts/interfaces/vault/IVaultGammaPool.sol b/contracts/interfaces/vault/IVaultGammaPool.sol new file mode 100644 index 0000000..f98ffed --- /dev/null +++ b/contracts/interfaces/vault/IVaultGammaPool.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +/// @title Interface for Vault GammaPool smart contract +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @dev Used as template for building other Vault GammaPool contract implementations +interface IVaultGammaPool { + + /// @dev enum indices for storage fields saved for Vault GammaPool + enum StorageIndexes { RESERVED_BORROWED_INVARIANT, RESERVED_LP_TOKENS } + + /// @dev reserve LP tokens for future borrowing (prevents others from borrowing) or free reserved LP tokens so others can borrow them. + /// @param tokenId - tokenId of loan used to reserve or free reserved LP tokens. Must be refType 3. + /// @param lpTokens - number of LP tokens that must be reserved or freed. + /// @param isReserve - if true then reserve LP tokens, if false, then free reserved LP tokens. + /// @return reservedLPTokens - LP tokens that have been reserved or freed. + function reserveLPTokens(uint256 tokenId, uint256 lpTokens, bool isReserve) external returns(uint256); + + /// @dev Get borrowed invariant and LP tokens that have been reserved through refType 3 loans + /// @return reservedBorrowedInvariant - borrowed invariant that is reserved for refType 3 loans + /// @return reservedLPTokens - LP tokens reserved for future use by refType 3 loans + function getReservedBalances() external view returns(uint256 reservedBorrowedInvariant, uint256 reservedLPTokens); +} diff --git a/contracts/interfaces/vault/IVaultPoolViewer.sol b/contracts/interfaces/vault/IVaultPoolViewer.sol new file mode 100644 index 0000000..8008ecf --- /dev/null +++ b/contracts/interfaces/vault/IVaultPoolViewer.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import "@gammaswap/v1-core/contracts/interfaces/IGammaPool.sol"; + +interface IVaultPoolViewer { + + struct VaultPoolData { + IGammaPool.PoolData poolData; + uint256 reservedBorrowedInvariant; + uint256 reservedLPTokens; + } + + /// @dev Returns vault pool storage data updated to their latest values + /// @notice Difference with getVaultPoolData() is this struct is what PoolData would return if an update of the GammaPool were to occur at the current block + /// @param pool - address of pool to get pool data for + /// @return data - struct containing all relevant global state variables and descriptive information of GammaPool. Used to avoid making multiple calls + function getLatestVaultPoolData(address pool) external view returns(VaultPoolData memory data); + + /// @dev Return vault pool storage data + /// @param pool - address of pool to get pool data for + /// @return data - struct containing all relevant global state variables and descriptive information of GammaPool. Used to avoid making multiple calls + function getVaultPoolData(address pool) external view returns(VaultPoolData memory data); +} diff --git a/contracts/interfaces/vault/strategies/IVaultReserveStrategy.sol b/contracts/interfaces/vault/strategies/IVaultReserveStrategy.sol new file mode 100644 index 0000000..9c6139e --- /dev/null +++ b/contracts/interfaces/vault/strategies/IVaultReserveStrategy.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +/// @title Interface Vault Strategy +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Used to reserve LP tokens for vault to use in a future call to borrowLiquidity(). +interface IVaultReserveStrategy { + /// @dev reserve LP tokens for future borrowing (prevents others from borrowing) or free reserved LP tokens so others can borrow them. + /// @param tokenId - tokenId of loan used to reserve or free reserved LP tokens. Must be refType 3. + /// @param lpTokens - number of LP tokens that must be reserved or freed. + /// @param isReserve - if true then reserve LP tokens, if false, then free reserved LP tokens. + /// @return reservedLPTokens - LP tokens that have been reserved or freed. + function _reserveLPTokens(uint256 tokenId, uint256 lpTokens, bool isReserve) external returns(uint256); +} diff --git a/contracts/interfaces/vault/strategies/IVaultShortStrategy.sol b/contracts/interfaces/vault/strategies/IVaultShortStrategy.sol new file mode 100644 index 0000000..c950116 --- /dev/null +++ b/contracts/interfaces/vault/strategies/IVaultShortStrategy.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +/// @title Interface Vault Short Strategy +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Used to calculate total supply and assets from Short Strategy accounting for reserved borrowed liquidity +interface IVaultShortStrategy { + /// @dev Parameters used to calculate the GS LP tokens and CFMM LP tokens in the GammaPool after protocol fees and accrued interest + struct VaultReservedBalancesParams { + /// @dev address of factory contract of GammaPool + address factory; + /// @dev address of GammaPool + address pool; + /// @dev address of contract holding rate parameters for pool + address paramsStore; + /// @dev storage number of borrowed liquidity invariant in GammaPool + uint256 BORROWED_INVARIANT; + /// @dev storage number of reserved borrowed liquidity invariant in GammaPool + uint256 RESERVED_BORROWED_INVARIANT; + /// @dev current liquidity invariant in CFMM + uint256 latestCfmmInvariant; + /// @dev current total supply of CFMM LP tokens in existence + uint256 latestCfmmTotalSupply; + /// @dev last block number GammaPool was updated + uint256 LAST_BLOCK_NUMBER; + /// @dev CFMM liquidity invariant at time of last update of GammaPool + uint256 lastCFMMInvariant; + /// @dev CFMM LP Token supply at time of last update of GammaPool + uint256 lastCFMMTotalSupply; + /// @dev CFMM Fee Index at time of last update of GammaPool + uint256 lastCFMMFeeIndex; + /// @dev current total supply of GS LP tokens + uint256 totalSupply; + /// @dev current LP Tokens in GammaPool counted at time of last update + uint256 LP_TOKEN_BALANCE; + /// @dev liquidity invariant of LP tokens in GammaPool at time of last update + uint256 LP_INVARIANT; + } + + /// @dev Calculate current total GS LP tokens after protocol fees and total CFMM LP tokens (real and virtual) in + /// @dev existence in the GammaPool after accrued interest. The total assets and supply numbers returned by this + /// @dev function are used in the ERC4626 implementation of the GammaPool + /// @param vaultReservedBalanceParams - parameters from GammaPool to calculate current total GS LP Tokens and CFMM LP Tokens after fees and interest + /// @return assets - total CFMM LP tokens in existence in the pool (real and virtual) including accrued interest + /// @return supply - total GS LP tokens in the pool including accrued interest + function totalReservedAssetsAndSupply(VaultReservedBalancesParams memory vaultReservedBalanceParams) external view returns(uint256 assets, uint256 supply); +} diff --git a/contracts/pools/CPMMGammaPool.sol b/contracts/pools/CPMMGammaPool.sol index 48ae1f5..bad9ad1 100644 --- a/contracts/pools/CPMMGammaPool.sol +++ b/contracts/pools/CPMMGammaPool.sol @@ -6,11 +6,13 @@ import "@gammaswap/v1-core/contracts/base/GammaPoolExternal.sol"; import "@gammaswap/v1-core/contracts/libraries/AddressCalculator.sol"; import "@gammaswap/v1-core/contracts/libraries/GammaSwapLibrary.sol"; import "@gammaswap/v1-core/contracts/libraries/GSMath.sol"; +import "../interfaces/cpmm/strategies/ICPMMRebalanceStrategy.sol"; +import "../interfaces/cpmm/ICPMMGammaPool.sol"; /// @title GammaPool implementation for Constant Product Market Maker /// @author Daniel D. Alcarraz (https://github.com/0xDanr) /// @dev This implementation is specifically for validating UniswapV2Pair and clone contracts -contract CPMMGammaPool is GammaPool, GammaPoolExternal { +contract CPMMGammaPool is GammaPool, GammaPoolExternal, ICPMMGammaPool { error NotContract(); error BadProtocol(); @@ -24,16 +26,14 @@ contract CPMMGammaPool is GammaPool, GammaPoolExternal { /// @return cfmmInitCodeHash - init code hash of CFMM bytes32 immutable public cfmmInitCodeHash; - /// @dev Initializes the contract by setting `protocolId`, `factory`, `borrowStrategy`, `repayStrategy`, + /// @dev Initializes the contract by setting `protocolId`, `factory`, `borrowStrategy`, `repayStrategy`, `rebalanceStrategy`, /// @dev `shortStrategy`, `liquidationStrategy`, `batchLiquidationStrategy`, `cfmmFactory`, and `cfmmInitCodeHash`. - constructor(uint16 _protocolId, address _factory, address _borrowStrategy, address _repayStrategy, - address _shortStrategy, address _liquidationStrategy, address _batchLiquidationStrategy, address _viewer, - address _externalRebalanceStrategy, address _externalLiquidationStrategy, address _cfmmFactory, bytes32 _cfmmInitCodeHash) - GammaPool(_protocolId, _factory, _borrowStrategy, _repayStrategy, _borrowStrategy, _shortStrategy, - _liquidationStrategy, _batchLiquidationStrategy, _viewer) - GammaPoolExternal(_externalRebalanceStrategy, _externalLiquidationStrategy) { - cfmmFactory = _cfmmFactory; - cfmmInitCodeHash = _cfmmInitCodeHash; + constructor(InitializationParams memory params) GammaPool(params.protocolId, params.factory, params.borrowStrategy, + params.repayStrategy, params.rebalanceStrategy, params.shortStrategy, params.liquidationStrategy, + params.batchLiquidationStrategy, params.viewer) GammaPoolExternal(params.externalRebalanceStrategy, + params.externalLiquidationStrategy) { + cfmmFactory = params.cfmmFactory; + cfmmInitCodeHash = params.cfmmInitCodeHash; } /// @dev See {IGammaPool-createLoan} @@ -57,7 +57,8 @@ contract CPMMGammaPool is GammaPool, GammaPoolExternal { } /// @dev See {IGammaPool-validateCFMM} - function validateCFMM(address[] calldata _tokens, address _cfmm, bytes calldata) external virtual override view returns(address[] memory _tokensOrdered) { + function validateCFMM(address[] calldata _tokens, address _cfmm, bytes calldata) external virtual override view + returns(address[] memory _tokensOrdered) { if(!GammaSwapLibrary.isContract(_cfmm)) revert NotContract(); // Not a smart contract (hence not a CFMM) or not instantiated yet if(_tokens.length != 2) revert InvalidTokensLength(); @@ -66,8 +67,20 @@ contract CPMMGammaPool is GammaPool, GammaPoolExternal { (_tokensOrdered[0], _tokensOrdered[1]) = _tokens[0] < _tokens[1] ? (_tokens[0], _tokens[1]) : (_tokens[1], _tokens[0]); // Verify CFMM was created by CFMM's factory contract - if(_cfmm != AddressCalculator.calcAddress(cfmmFactory,keccak256(abi.encodePacked(_tokensOrdered[0], _tokensOrdered[1])),cfmmInitCodeHash)) { + if(_cfmm != AddressCalculator.calcAddress(cfmmFactory, + keccak256(abi.encodePacked(_tokensOrdered[0], _tokensOrdered[1])),cfmmInitCodeHash)) { revert BadProtocol(); } } + + /// @dev See {ICPMMGammaPool-setMaxTotalAPY} + function setMaxTotalAPY(uint256 _maxTotalAPY) external virtual override { + abi.decode(callStrategy(rebalanceStrategy, abi.encodeCall(ICPMMRebalanceStrategy._setMaxTotalAPY, _maxTotalAPY)), ()); + } + + /// @dev See {IGammaPoolExternal-liquidateExternally} + function liquidateExternally(uint256 tokenId, uint128[] calldata amounts, uint256 lpTokens, address to, bytes calldata data) + external override virtual returns(uint256 loanLiquidity, uint256[] memory refund) { + return (0, new uint256[](0)); + } } diff --git a/contracts/pools/CPMMGammaPoolMain.sol b/contracts/pools/CPMMGammaPoolMain.sol index 9da83d9..5a9f745 100644 --- a/contracts/pools/CPMMGammaPoolMain.sol +++ b/contracts/pools/CPMMGammaPoolMain.sol @@ -11,13 +11,9 @@ contract CPMMGammaPoolMain is CPMMGammaPool { using LibStorage for LibStorage.Storage; - /// @dev Initializes the contract by setting `protocolId`, `factory`, `borrowStrategy`, `repayStrategy`, + /// @dev Initializes the contract by setting `protocolId`, `factory`, `borrowStrategy`, `repayStrategy`, `rebalanceStrategy`, /// @dev `shortStrategy`, `liquidationStrategy`, `batchLiquidationStrategy`, `cfmmFactory`, and `cfmmInitCodeHash`. - constructor(uint16 _protocolId, address _factory, address _borrowStrategy, address _repayStrategy, - address _shortStrategy, address _liquidationStrategy, address _batchLiquidationStrategy, address _viewer, - address _externalRebalanceStrategy, address _externalLiquidationStrategy, address _cfmmFactory, bytes32 _cfmmInitCodeHash) - CPMMGammaPool(_protocolId, _factory, _borrowStrategy, _repayStrategy, _shortStrategy, _liquidationStrategy, - _batchLiquidationStrategy, _viewer, _externalRebalanceStrategy, _externalLiquidationStrategy, _cfmmFactory, _cfmmInitCodeHash) { + constructor(InitializationParams memory params) CPMMGammaPool(params) { } /// @dev See {IGammaPool-initialize} diff --git a/contracts/pools/VaultGammaPool.sol b/contracts/pools/VaultGammaPool.sol new file mode 100644 index 0000000..63f278b --- /dev/null +++ b/contracts/pools/VaultGammaPool.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../interfaces/vault/strategies/IVaultReserveStrategy.sol"; +import "../interfaces/vault/strategies/IVaultShortStrategy.sol"; +import "../interfaces/vault/IVaultGammaPool.sol"; +import "./CPMMGammaPool.sol"; + +/// @title Vault GammaPool implementation for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @dev This implementation is specifically for validating UniswapV2Pair and clone contracts +contract VaultGammaPool is CPMMGammaPool, IVaultGammaPool { + + using LibStorage for LibStorage.Storage; + + /// @dev Initializes the contract by setting `protocolId`, `factory`, `borrowStrategy`, `repayStrategy`, `rebalanceStrategy`, + /// @dev `shortStrategy`, `liquidationStrategy`, `batchLiquidationStrategy`, `cfmmFactory`, and `cfmmInitCodeHash`. + constructor(InitializationParams memory params) CPMMGammaPool(params) { + } + + /// @dev See {IVaultGammaPool-reserveLPTokens} + function reserveLPTokens(uint256 tokenId, uint256 lpTokens, bool isReserve) external virtual override whenNotPaused(26) returns(uint256) { + return abi.decode(callStrategy(externalRebalanceStrategy, abi.encodeCall(IVaultReserveStrategy._reserveLPTokens, (tokenId, lpTokens, isReserve))), (uint256)); + } + + /// @dev See {IVaultGammaPool-getReservedBalances} + function getReservedBalances() external virtual override view returns(uint256 reservedBorrowedInvariant, uint256 reservedLPTokens) { + reservedBorrowedInvariant = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT)); + reservedLPTokens = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + } + + /// @dev See {IGammaPool-liquidateWithLP} + function liquidateWithLP(uint256 tokenId) external virtual override returns(uint256 loanLiquidity, uint256[] memory refund) { + return (0, new uint256[](0)); + } + + /// @dev See {IGammaPool-batchLiquidations} + function batchLiquidations(uint256[] calldata tokenIds) external virtual override returns(uint256 totalLoanLiquidity, uint256[] memory refund) { + return (0, new uint256[](0)); + } + + /// @dev See {GammaPoolERC4626-maxAssets} + function maxAssets(uint256 assets) internal view virtual override returns(uint256) { + uint256 reservedLpTokenBalance = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + uint256 lpTokenBalance = s.LP_TOKEN_BALANCE; // CFMM LP tokens in GammaPool that have not been borrowed + lpTokenBalance = lpTokenBalance - GSMath.min(reservedLpTokenBalance, lpTokenBalance); + if(assets < lpTokenBalance){ // limit assets available to withdraw to what has not been borrowed + return assets; + } + return lpTokenBalance; + } + + /// @dev See {GammaPoolERC4626-_totalAssetsAndSupply} + function _totalAssetsAndSupply() internal view virtual override returns (uint256 assets, uint256 supply) { + address _factory = s.factory; + (assets, supply) = IVaultShortStrategy(vaultImplementation()).totalReservedAssetsAndSupply( + IVaultShortStrategy.VaultReservedBalancesParams({ + factory: _factory, + pool: address(this), + paramsStore: _factory, + BORROWED_INVARIANT: s.BORROWED_INVARIANT, + RESERVED_BORROWED_INVARIANT: s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT)), + latestCfmmInvariant: _getLatestCFMMInvariant(), + latestCfmmTotalSupply: _getLatestCFMMTotalSupply(), + LAST_BLOCK_NUMBER: s.LAST_BLOCK_NUMBER, + lastCFMMInvariant: s.lastCFMMInvariant, + lastCFMMTotalSupply: s.lastCFMMTotalSupply, + lastCFMMFeeIndex: s.lastCFMMFeeIndex, + totalSupply: s.totalSupply, + LP_TOKEN_BALANCE: s.LP_TOKEN_BALANCE, + LP_INVARIANT: s.LP_INVARIANT + }) + ); + } +} diff --git a/contracts/pools/VaultGammaPoolMain.sol b/contracts/pools/VaultGammaPoolMain.sol new file mode 100644 index 0000000..bda9423 --- /dev/null +++ b/contracts/pools/VaultGammaPoolMain.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "./VaultGammaPool.sol"; + +/// @title Vault GammaPool implementation for Constant Product Market Maker in mainnet Ethereum +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @dev This implementation is specifically for validating UniswapV2Pair and clone contracts +/// @dev Overrides the initialize function to set params for higher network costs in mainnet Ethereum +contract VaultGammaPoolMain is VaultGammaPool { + + using LibStorage for LibStorage.Storage; + + /// @dev Initializes the contract by setting `protocolId`, `factory`, `borrowStrategy`, `repayStrategy`, `rebalanceStrategy`, + /// @dev `shortStrategy`, `liquidationStrategy`, `batchLiquidationStrategy`, `cfmmFactory`, and `cfmmInitCodeHash`. + constructor(InitializationParams memory params) VaultGammaPool(params) { + } + + /// @dev See {IGammaPool-initialize} + function initialize(address _cfmm, address[] calldata _tokens, uint8[] calldata _decimals, uint72 _minBorrow, bytes calldata) external virtual override { + if(msg.sender != factory) revert Forbidden(); // only factory is allowed to initialize + s.initialize(factory, _cfmm, protocolId, _tokens, _decimals, _minBorrow); + s.ltvThreshold = 15; // 150 basis points + s.liquidationFee = 125; // 125 basis points + } +} diff --git a/contracts/strategies/cpmm/base/CPMMBaseStrategy.sol b/contracts/strategies/cpmm/base/CPMMBaseStrategy.sol index c009037..fbdf253 100644 --- a/contracts/strategies/cpmm/base/CPMMBaseStrategy.sol +++ b/contracts/strategies/cpmm/base/CPMMBaseStrategy.sol @@ -12,14 +12,19 @@ import "../../../interfaces/external/cpmm/ICPMM.sol"; /// @dev This implementation was specifically designed to work with UniswapV2. Inherits Rate Model abstract contract CPMMBaseStrategy is BaseStrategy, LinearKinkedRateModel { + using LibStorage for LibStorage.Storage; + error MaxTotalApy(); /// @dev Number of blocks network will issue within a ear. Currently expected uint256 immutable public BLOCKS_PER_YEAR; // 2628000 blocks per year in ETH mainnet (12 seconds per block) - /// @dev Max total annual APY the GammaPool will charge liquidity borrowers (e.g. 1,000%). + /// @dev Default max total APY the GammaPool will charge liquidity borrowers (e.g. 1,000%). uint256 immutable public MAX_TOTAL_APY; + /// @dev Key for overriding default max total APY + bytes32 internal constant MAX_TOTAL_APY_KEY = keccak256("MAX_TOTAL_APY"); + /// @dev Initializes the contract by setting `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` constructor(uint256 maxTotalApy_, uint256 blocksPerYear_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) LinearKinkedRateModel(baseRate_, optimalUtilRate_, slope1_, slope2_) { @@ -30,9 +35,14 @@ abstract contract CPMMBaseStrategy is BaseStrategy, LinearKinkedRateModel { BLOCKS_PER_YEAR = blocksPerYear_; } + /// @dev If set to 0 use default max total APY /// @dev See {BaseStrategy-maxTotalApy}. function maxTotalApy() internal virtual override view returns(uint256) { - return MAX_TOTAL_APY; + uint256 _maxTotalApy = s.getUint256(uint256(MAX_TOTAL_APY_KEY)); + if(_maxTotalApy == 0) { + return MAX_TOTAL_APY; + } + return _maxTotalApy; } /// @dev See {BaseStrategy-blocksPerYear}. diff --git a/contracts/strategies/cpmm/lending/CPMMBorrowStrategy.sol b/contracts/strategies/cpmm/lending/CPMMBorrowStrategy.sol index c26f9c5..32a1d95 100644 --- a/contracts/strategies/cpmm/lending/CPMMBorrowStrategy.sol +++ b/contracts/strategies/cpmm/lending/CPMMBorrowStrategy.sol @@ -2,14 +2,13 @@ pragma solidity 0.8.21; import "@gammaswap/v1-core/contracts/strategies/lending/BorrowStrategy.sol"; -import "@gammaswap/v1-core/contracts/strategies/rebalance/RebalanceStrategy.sol"; import "../base/CPMMBaseRebalanceStrategy.sol"; -/// @title Borrow and Rebalance Strategy concrete implementation contract for Constant Product Market Maker +/// @title Borrow Strategy concrete implementation contract for Constant Product Market Maker /// @author Daniel D. Alcarraz (https://github.com/0xDanr) /// @notice Sets up variables used by BorrowStrategy and RebalanceStrategy and defines internal functions specific to CPMM implementation /// @dev This implementation was specifically designed to work with UniswapV2 -contract CPMMBorrowStrategy is CPMMBaseRebalanceStrategy, BorrowStrategy, RebalanceStrategy { +contract CPMMBorrowStrategy is CPMMBaseRebalanceStrategy, BorrowStrategy { /// @dev Initializes the contract by setting `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `tradingFee1`, `tradingFee2`, /// @dev `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` diff --git a/contracts/strategies/cpmm/rebalance/CPMMRebalanceStrategy.sol b/contracts/strategies/cpmm/rebalance/CPMMRebalanceStrategy.sol new file mode 100644 index 0000000..5e4b2b6 --- /dev/null +++ b/contracts/strategies/cpmm/rebalance/CPMMRebalanceStrategy.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/strategies/rebalance/RebalanceStrategy.sol"; +import "../../../interfaces/cpmm/strategies/ICPMMRebalanceStrategy.sol"; +import "../base/CPMMBaseRebalanceStrategy.sol"; + +/// @title Rebalance Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by BorrowStrategy and RebalanceStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract CPMMRebalanceStrategy is CPMMBaseRebalanceStrategy, RebalanceStrategy, ICPMMRebalanceStrategy { + + using LibStorage for LibStorage.Storage; + + /// @dev Initializes the contract by setting `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `tradingFee1`, `tradingFee2`, + /// @dev `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, uint24 tradingFee2_, + address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) CPMMBaseRebalanceStrategy(mathLib_, + maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {ICPMMRebalanceStrategy-_setMaxTotalAPY}. + function _setMaxTotalAPY(uint256 _maxTotalAPY) external virtual override { + if(msg.sender != s.factory) revert Forbidden(); // only factory is allowed to set Max Total APY + if(_maxTotalAPY > 0 && _maxTotalAPY < baseRate + slope1 + slope2) revert MaxTotalApy(); + + s.setUint256(uint256(MAX_TOTAL_APY_KEY), _maxTotalAPY); + + emit SetMaxTotalAPY(_maxTotalAPY); + } +} diff --git a/contracts/strategies/vault/VaultShortStrategy.sol b/contracts/strategies/vault/VaultShortStrategy.sol new file mode 100644 index 0000000..f0bb73a --- /dev/null +++ b/contracts/strategies/vault/VaultShortStrategy.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../cpmm/CPMMShortStrategy.sol"; +import "./base/VaultBaseStrategy.sol"; +import "../../interfaces/vault/strategies/IVaultShortStrategy.sol"; + +/// @title Vault Short Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by ShortStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultShortStrategy is CPMMShortStrategy, VaultBaseStrategy, IVaultShortStrategy { + + using LibStorage for LibStorage.Storage; + + /// @dev Initializes the contract by setting `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(uint256 maxTotalApy_, uint256 blocksPerYear_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) + CPMMShortStrategy(maxTotalApy_, blocksPerYear_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev Excludes reserved LP tokens from available LP tokens to withdraw + /// @dev See {ShortStrategy-withdrawAssetsNoPull}. + function withdrawAssetsNoPull(address to, bool askForReserves) internal virtual override returns(uint256[] memory reserves, uint256 assets) { + // Check is GammaPool has received GS LP tokens + uint256 shares = s.balanceOf[address(this)]; + + // Update interest rate and state variables before conversion + updateIndex(); + + // Convert GS LP tokens (`shares`) to CFMM LP tokens (`assets`) + assets = convertToAssets(shares, false); + // revert if request is for 0 CFMM LP tokens + if(assets == 0) revert ZeroAssets(); + + // Revert if not enough CFMM LP tokens in GammaPool + if(assets > getAdjLPTokenBalance()) revert ExcessiveWithdrawal(); + + // Send CFMM LP tokens or reserve tokens to receiver (`to`) and burn corresponding GS LP tokens from GammaPool address + reserves = withdrawAssets(address(this), to, address(this), assets, shares, askForReserves); + } + + /// @dev See {IShortStrategy-_withdraw}. + function _withdraw(uint256 assets, address to, address from) external virtual override lock returns(uint256 shares) { + // Update interest rate and state variables before conversion + updateIndex(); + + // Revert if not enough CFMM LP tokens to withdraw + if(assets > getAdjLPTokenBalance()) revert ExcessiveWithdrawal(); + + // Convert CFMM LP tokens to GS LP tokens + shares = convertToShares(assets, true); + + // Revert if redeeming 0 GS LP tokens + if(shares == 0) revert ZeroShares(); + + // Send CFMM LP tokens to receiver (`to`) and burn corresponding GS LP tokens from msg.sender + withdrawAssets(msg.sender, to, from, assets, shares, false); + } + + /// @dev See {IShortStrategy-_redeem}. + function _redeem(uint256 shares, address to, address from) external virtual override lock returns(uint256 assets) { + // Update interest rate and state variables before conversion + updateIndex(); + + // Convert GS LP tokens to CFMM LP tokens + assets = convertToAssets(shares, false); + if(assets == 0) revert ZeroAssets(); // revert if withdrawing 0 CFMM LP tokens + + // Revert if not enough CFMM LP tokens to withdraw + if(assets > getAdjLPTokenBalance()) revert ExcessiveWithdrawal(); + + // Send CFMM LP tokens to receiver (`to`) and burn corresponding GS LP tokens from msg.sender + withdrawAssets(msg.sender, to, from, assets, shares, false); + } + + /// @dev See {IVaultShortStrategy-totalReservedAssetsAndSupply}. + function totalReservedAssetsAndSupply(IVaultShortStrategy.VaultReservedBalancesParams memory _params) external virtual override view returns(uint256 assets, uint256 supply) { + // use lastFeeIndex and cfmmFeeIndex to hold maxCFMMFeeLeverage and spread respectively + (uint256 borrowRate, uint256 utilizationRate, uint256 lastFeeIndex, uint256 cfmmFeeIndex) = calcBorrowRate(_params.LP_INVARIANT, + _params.BORROWED_INVARIANT, _params.paramsStore, _params.pool); + + (lastFeeIndex, cfmmFeeIndex) = getLastFees(borrowRate, _params.BORROWED_INVARIANT, _params.latestCfmmInvariant, + _params.latestCfmmTotalSupply, _params.lastCFMMInvariant, _params.lastCFMMTotalSupply, _params.LAST_BLOCK_NUMBER, + _params.lastCFMMFeeIndex, lastFeeIndex, cfmmFeeIndex); + + _params.RESERVED_BORROWED_INVARIANT = GSMath.min(_params.RESERVED_BORROWED_INVARIANT, _params.BORROWED_INVARIANT); + unchecked { + _params.BORROWED_INVARIANT = _params.BORROWED_INVARIANT - _params.RESERVED_BORROWED_INVARIANT; + } + // Total amount of GS LP tokens issued after protocol fees are paid + assets = totalAssets(_params.BORROWED_INVARIANT, _params.LP_TOKEN_BALANCE + + convertInvariantToLP(_params.RESERVED_BORROWED_INVARIANT, _params.lastCFMMTotalSupply, _params.lastCFMMInvariant), + _params.latestCfmmInvariant, _params.latestCfmmTotalSupply, lastFeeIndex); + + // Calculates total CFMM LP tokens, including accrued interest, using state variables + supply = totalSupply(_params.factory, _params.pool, cfmmFeeIndex, lastFeeIndex, utilizationRate, _params.totalSupply); + } + + /// @dev See {IShortStrategy-totalAssetsAndSupply}. + function totalAssetsAndSupply(VaultBalancesParams memory _params) public virtual override view returns(uint256 assets, uint256 supply) { + return (0,0); + } + + /// @inheritdoc IShortStrategy + function getLatestBalances(uint256 lastFeeIndex, uint256 borrowedInvariant, uint256 lpBalance, uint256 lastCFMMInvariant, uint256 lastCFMMTotalSupply) public virtual override view + returns(uint256 lastLPBalance, uint256 lastBorrowedLPBalance, uint256 lastBorrowedInvariant) { + lastBorrowedInvariant = _accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + lastBorrowedLPBalance = convertInvariantToLP(lastBorrowedInvariant, lastCFMMTotalSupply, lastCFMMInvariant); + lastLPBalance = lpBalance + lastBorrowedLPBalance; + } +} diff --git a/contracts/strategies/vault/base/VaultBaseLiquidationStrategy.sol b/contracts/strategies/vault/base/VaultBaseLiquidationStrategy.sol new file mode 100644 index 0000000..78de5b5 --- /dev/null +++ b/contracts/strategies/vault/base/VaultBaseLiquidationStrategy.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../../cpmm/base/CPMMBaseLiquidationStrategy.sol"; +import "./VaultBaseRepayStrategy.sol"; + +/// @title Vault Base Liquidation Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by BaseLiquidationStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +abstract contract VaultBaseLiquidationStrategy is CPMMBaseLiquidationStrategy, VaultBaseRepayStrategy { + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseRepayStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseRepayStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseRepayStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev See {BaseRepayStrategy-payLoanLiquidity}. + function payLoanLiquidity(uint256 liquidity, uint256 loanLiquidity, LibStorage.Loan storage _loan) internal virtual + override(BaseRepayStrategy,VaultBaseRepayStrategy) returns(uint256 lpTokenPrincipal, uint256 remainingLiquidity) { + return super.payLoanLiquidity(liquidity, loanLiquidity, _loan); + } +} diff --git a/contracts/strategies/vault/base/VaultBaseLongStrategy.sol b/contracts/strategies/vault/base/VaultBaseLongStrategy.sol new file mode 100644 index 0000000..7824cbf --- /dev/null +++ b/contracts/strategies/vault/base/VaultBaseLongStrategy.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../../cpmm/base/CPMMBaseLongStrategy.sol"; +import "./VaultBaseStrategy.sol"; + +/// @title Vault Base Long Strategy abstract contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Common functions used by all concrete strategy implementations for CPMM that need access to loans +/// @dev This implementation was specifically designed to work with UniswapV2. +abstract contract VaultBaseLongStrategy is CPMMBaseLongStrategy, VaultBaseStrategy { + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual override returns(uint256 liquidity) { + uint256 rateIndex = _loan.rateIndex; + liquidity = rateIndex == 0 ? 0 : _loan.refType == 3 ? _loan.liquidity : (_loan.liquidity * accFeeIndex) / rateIndex; + _loan.liquidity = uint128(liquidity); + _loan.rateIndex = uint80(accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } +} diff --git a/contracts/strategies/vault/base/VaultBaseRebalanceStrategy.sol b/contracts/strategies/vault/base/VaultBaseRebalanceStrategy.sol new file mode 100644 index 0000000..9296be1 --- /dev/null +++ b/contracts/strategies/vault/base/VaultBaseRebalanceStrategy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../../cpmm/base/CPMMBaseRebalanceStrategy.sol"; +import "./VaultBaseLongStrategy.sol"; + +/// @title Vault Base Rebalance Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by BaseRebalanceStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +abstract contract VaultBaseRebalanceStrategy is CPMMBaseRebalanceStrategy, VaultBaseLongStrategy { + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseLongStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseLongStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseLongStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } +} diff --git a/contracts/strategies/vault/base/VaultBaseRepayStrategy.sol b/contracts/strategies/vault/base/VaultBaseRepayStrategy.sol new file mode 100644 index 0000000..9ad93fb --- /dev/null +++ b/contracts/strategies/vault/base/VaultBaseRepayStrategy.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/strategies/base/BaseRepayStrategy.sol"; +import "./VaultBaseRebalanceStrategy.sol"; + +/// @title Abstract base contract for Vault Repay Strategy implementation +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice All functions here are internal, external functions implemented in BaseLongStrategy as part of ILongStrategy implementation +/// @dev Only defines common functions that would be used by all contracts that repay liquidity +abstract contract VaultBaseRepayStrategy is BaseRepayStrategy, VaultBaseRebalanceStrategy { + + using LibStorage for LibStorage.Storage; + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseRebalanceStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseRebalanceStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseRebalanceStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev Account for paid liquidity debt in loan and account for refType 3 loans + /// @dev See {BaseRepayStrategy-payLoanLiquidity}. + function payLoanLiquidity(uint256 liquidity, uint256 loanLiquidity, LibStorage.Loan storage _loan) internal virtual + override returns(uint256 lpTokenPrincipal, uint256 remainingLiquidity) { + uint256 loanLpTokens = _loan.lpTokens; // Loan's CFMM LP token principal + uint256 loanInitLiquidity = _loan.initLiquidity; // Loan's liquidity invariant principal + + // Calculate loan's CFMM LP token principal repaid + lpTokenPrincipal = GSMath.min(loanLpTokens, convertInvariantToLP(liquidity, loanLpTokens, loanLiquidity)); + + uint256 initLiquidityPaid = GSMath.min(loanInitLiquidity, liquidity * loanInitLiquidity / loanLiquidity); + + uint256 _paidLiquidity = _loan.liquidity; + + unchecked { + // Calculate loan's outstanding liquidity invariant principal after liquidity payment + loanInitLiquidity = loanInitLiquidity - initLiquidityPaid; + + // Update loan's outstanding CFMM LP token principal + loanLpTokens = loanLpTokens - lpTokenPrincipal; + + // Calculate loan's outstanding liquidity invariant after liquidity payment + remainingLiquidity = loanLiquidity - GSMath.min(loanLiquidity, liquidity); + + _paidLiquidity = _paidLiquidity - GSMath.min(_paidLiquidity, remainingLiquidity); + } + + // Can't be less than min liquidity to avoid rounding issues + if (remainingLiquidity > 0 && remainingLiquidity < minBorrow()) revert MinBorrow(); + + // If fully paid, free memory to save gas + if(remainingLiquidity == 0) { // lpTokens should be zero + _loan.rateIndex = 0; + _loan.px = 0; + _loan.lpTokens = 0; + _loan.initLiquidity = 0; + _loan.liquidity = 0; + if(loanLpTokens > 0) lpTokenPrincipal += loanLpTokens; // cover rounding issues + // pay whole liquidity + } else { + _loan.lpTokens = uint128(loanLpTokens); + _loan.initLiquidity = uint128(loanInitLiquidity); + _loan.liquidity = uint128(remainingLiquidity); + // calc paid liquidity + } + + if(_loan.refType == 3 && _paidLiquidity > 0) { + uint256 reservedBorrowedInvariant = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT)); + _paidLiquidity = GSMath.min(reservedBorrowedInvariant, _paidLiquidity); + unchecked { + reservedBorrowedInvariant = reservedBorrowedInvariant - _paidLiquidity; + } + s.setUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT), reservedBorrowedInvariant); + } + } +} diff --git a/contracts/strategies/vault/base/VaultBaseStrategy.sol b/contracts/strategies/vault/base/VaultBaseStrategy.sol new file mode 100644 index 0000000..52e0956 --- /dev/null +++ b/contracts/strategies/vault/base/VaultBaseStrategy.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/strategies/base/BaseStrategy.sol"; +import "../../../interfaces/vault/IVaultGammaPool.sol"; + +/// @title Vault Base Strategy abstract contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Common functions used by all concrete strategy implementations for Constant Product Market Maker +/// @dev This implementation was specifically designed to work with UniswapV2. Inherits Rate Model +abstract contract VaultBaseStrategy is BaseStrategy { + + using LibStorage for LibStorage.Storage; + + /// @dev Accrue interest to borrowed invariant amount excluding reserved borrowed invariant + /// @param borrowedInvariant - liquidity invariant borrowed in the GammaPool + /// @param lastFeeIndex - interest accrued to loans in GammaPool + /// @return newBorrowedInvariant - borrowed invariant with accrued interest + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual override view returns(uint256) { + uint256 reservedBorrowedInvariant = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT)); + reservedBorrowedInvariant = GSMath.min(borrowedInvariant,reservedBorrowedInvariant); + unchecked { + borrowedInvariant = borrowedInvariant - reservedBorrowedInvariant; + } + return _accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex) + reservedBorrowedInvariant; + } + + /// @dev Accrue interest to borrowed invariant amount + /// @param borrowedInvariant - liquidity invariant borrowed in the GammaPool + /// @param lastFeeIndex - interest accrued to loans in GammaPool + /// @return newBorrowedInvariant - borrowed invariant with accrued interest + function _accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Revert if lpTokens withdrawal causes utilization rate to go over 98% + /// @param lpTokens - lpTokens expected to change utilization rate + /// @param isLoan - true if lpTokens are being borrowed + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override view { + uint256 lastCFMMInvariant = s.lastCFMMInvariant; + uint256 lastCFMMTotalSupply = s.lastCFMMTotalSupply; + uint256 reservedLPTokens = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + + uint256 reservedLPInvariant = convertLPToInvariant(reservedLPTokens, lastCFMMInvariant, lastCFMMTotalSupply); + uint256 lpTokenInvariant = convertLPToInvariant(lpTokens, lastCFMMInvariant, lastCFMMTotalSupply); + uint256 lpInvariant = s.LP_INVARIANT; + lpInvariant = lpInvariant >= reservedLPInvariant ? lpInvariant - reservedLPInvariant : 0; + + if(lpInvariant < lpTokenInvariant) revert NotEnoughLPInvariant(); + unchecked { + lpInvariant = lpInvariant - lpTokenInvariant; + } + uint256 borrowedInvariant = s.BORROWED_INVARIANT + (isLoan ? lpTokenInvariant : 0) + reservedLPInvariant; + if(calcUtilizationRate(lpInvariant, borrowedInvariant) > 98e16) { + revert MaxUtilizationRate(); + } + } + + function getAdjLPTokenBalance() internal virtual view returns(uint256 lpTokenBalance) { + uint256 reservedLPTokens = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + lpTokenBalance = s.LP_TOKEN_BALANCE; + reservedLPTokens = GSMath.min(lpTokenBalance, reservedLPTokens); + unchecked { + lpTokenBalance = lpTokenBalance - reservedLPTokens; + } + } +} diff --git a/contracts/strategies/vault/lending/VaultBorrowStrategy.sol b/contracts/strategies/vault/lending/VaultBorrowStrategy.sol new file mode 100644 index 0000000..8cc17a3 --- /dev/null +++ b/contracts/strategies/vault/lending/VaultBorrowStrategy.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/strategies/lending/BorrowStrategy.sol"; +import "../base/VaultBaseRebalanceStrategy.sol"; + +/// @title Rebalance Strategy concrete implementation contract for Vault GammaPool Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by BorrowStrategy and RebalanceStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultBorrowStrategy is VaultBaseRebalanceStrategy, BorrowStrategy { + + using LibStorage for LibStorage.Storage; + + /// @dev Initializes the contract by setting `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `tradingFee1`, `tradingFee2`, + /// @dev `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, uint24 tradingFee2_, + address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) CPMMBaseRebalanceStrategy(mathLib_, + maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseBorrowStrategy-getCurrentCFMMPrice}. + function getCurrentCFMMPrice() internal virtual override view returns(uint256) { + return s.CFMM_RESERVES[1] * (10 ** s.decimals[0]) / s.CFMM_RESERVES[0]; + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseRebalanceStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseRebalanceStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev Revert if lpTokens withdrawal causes utilization rate to go over 98% + /// @param lpTokens - lpTokens expected to change utilization rate + /// @param isRefType3 - true if loan borrowed is of refType 3 + function checkExpectedUtilizationRate(uint256 lpTokens, bool isRefType3) internal virtual + override(BaseStrategy,VaultBaseRebalanceStrategy) view { + uint256 lastCFMMInvariant = s.lastCFMMInvariant; + uint256 lastCFMMTotalSupply = s.lastCFMMTotalSupply; + + uint256 lpInvariant = s.LP_INVARIANT; + uint256 reservedLPInvariant = 0; + if(!isRefType3) { + reservedLPInvariant = convertLPToInvariant(s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)), + lastCFMMInvariant, lastCFMMTotalSupply); + reservedLPInvariant = GSMath.min(lpInvariant, reservedLPInvariant); + unchecked { + lpInvariant = lpInvariant - reservedLPInvariant; + } + } + + uint256 lpTokenInvariant = convertLPToInvariant(lpTokens, lastCFMMInvariant, lastCFMMTotalSupply); + if(lpInvariant < lpTokenInvariant) revert NotEnoughLPInvariant(); + unchecked { + lpInvariant = lpInvariant - lpTokenInvariant; + } + uint256 borrowedInvariant = s.BORROWED_INVARIANT + lpTokenInvariant + reservedLPInvariant; + if(calcUtilizationRate(lpInvariant, borrowedInvariant) > 98e16) { + revert MaxUtilizationRate(); + } + } + + /// @dev See {IBorrowStrategy-_borrowLiquidity}. + function _borrowLiquidity(uint256 tokenId, uint256 lpTokens, uint256[] calldata ratio) external virtual override lock returns(uint256 liquidityBorrowed, uint256[] memory amounts, uint128[] memory tokensHeld) { + // Get loan for tokenId, revert if not loan creator + LibStorage.Loan storage _loan = _getLoan(tokenId); + + bool isRefType3 = _loan.refType == 3; // if refType3 include reserved LP tokens + + uint256 lpTokenBalance = isRefType3 ? s.LP_TOKEN_BALANCE : getAdjLPTokenBalance(); + + // Revert if borrowing all CFMM LP tokens in pool + if(lpTokens >= lpTokenBalance) revert ExcessiveBorrowing(); + + // Update liquidity debt to include accrued interest since last update + uint256 loanLiquidity = updateLoan(_loan); + + checkExpectedUtilizationRate(lpTokens, isRefType3); + + if(isRefType3) { + uint256 reservedLPTokens = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + if(lpTokens >= reservedLPTokens) revert ExcessiveBorrowing(); + unchecked { + reservedLPTokens = reservedLPTokens - lpTokens; + } + s.setUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS), reservedLPTokens); + } + + // Withdraw reserve tokens from CFMM that lpTokens represent + amounts = withdrawFromCFMM(s.cfmm, address(this), lpTokens); + + // Add withdrawn tokens as part of loan collateral + (tokensHeld,) = updateCollateral(_loan); + + // Add liquidity debt to total pool debt and start tracking loan + (liquidityBorrowed, loanLiquidity) = openLoan(_loan, lpTokens); + + if(isRefType3) { + uint256 reservedBorrowedInvariant = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT)); + s.setUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_BORROWED_INVARIANT), reservedBorrowedInvariant + liquidityBorrowed); + } + + if(isRatioValid(ratio)) { + //get current reserves without updating + uint128[] memory _reserves = getReserves(s.cfmm); + int256[] memory deltas = _calcDeltasForRatio(tokensHeld, _reserves, ratio); + if(isDeltasValid(deltas)) { + (tokensHeld,) = rebalanceCollateral(_loan, deltas, _reserves); + } + } + + // Check that loan is not undercollateralized + checkMargin(calcInvariant(s.cfmm, tokensHeld) + onLoanUpdate(_loan, tokenId), loanLiquidity); + + emit LoanUpdated(tokenId, tokensHeld, uint128(loanLiquidity), _loan.initLiquidity, _loan.lpTokens, _loan.rateIndex, TX_TYPE.BORROW_LIQUIDITY); + + emit PoolUpdated(s.LP_TOKEN_BALANCE, s.LP_TOKEN_BORROWED, s.LAST_BLOCK_NUMBER, s.accFeeIndex, + s.LP_TOKEN_BORROWED_PLUS_INTEREST, s.LP_INVARIANT, s.BORROWED_INVARIANT, s.CFMM_RESERVES, TX_TYPE.BORROW_LIQUIDITY); + } +} diff --git a/contracts/strategies/vault/lending/VaultRepayStrategy.sol b/contracts/strategies/vault/lending/VaultRepayStrategy.sol new file mode 100644 index 0000000..beaad9a --- /dev/null +++ b/contracts/strategies/vault/lending/VaultRepayStrategy.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/strategies/lending/RepayStrategy.sol"; +import "../base/VaultBaseRepayStrategy.sol"; + +/// @title Vault Repay Strategy concrete implementation contract for Vault GammaPool Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by RepayStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultRepayStrategy is RepayStrategy, VaultBaseRepayStrategy { + + /// @dev Initializes the contract by setting `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`,`tradingFee1`, `tradingFee2`, + /// @dev `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, uint24 tradingFee2_, + address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) CPMMBaseRebalanceStrategy(mathLib_, + maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseRepayStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseRepayStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseRepayStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev See {BaseRepayStrategy-payLoanLiquidity}. + function payLoanLiquidity(uint256 liquidity, uint256 loanLiquidity, LibStorage.Loan storage _loan) internal virtual + override(BaseRepayStrategy,VaultBaseRepayStrategy) returns(uint256 lpTokenPrincipal, uint256 remainingLiquidity) { + return super.payLoanLiquidity(liquidity, loanLiquidity, _loan); + } +} diff --git a/contracts/strategies/vault/liquidation/VaultBatchLiquidationStrategy.sol b/contracts/strategies/vault/liquidation/VaultBatchLiquidationStrategy.sol new file mode 100644 index 0000000..f8593b1 --- /dev/null +++ b/contracts/strategies/vault/liquidation/VaultBatchLiquidationStrategy.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../../cpmm/liquidation/CPMMBatchLiquidationStrategy.sol"; +import "../base/VaultBaseLiquidationStrategy.sol"; + +/// @title Vault Batch Liquidation Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by LiquidationStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultBatchLiquidationStrategy is CPMMBatchLiquidationStrategy, VaultBaseLiquidationStrategy { + + /// @dev Initializes the contract by setting `liquidator`, `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, + /// @dev `tradingFee1`, `tradingFee2`,`feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address liquidator_, address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, + uint24 tradingFee2_, address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) + CPMMBatchLiquidationStrategy(liquidator_, mathLib_, maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, + feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseLiquidationStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseLiquidationStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseLiquidationStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev See {BaseRepayStrategy-payLoanLiquidity}. + function payLoanLiquidity(uint256 liquidity, uint256 loanLiquidity, LibStorage.Loan storage _loan) internal virtual + override(BaseRepayStrategy,VaultBaseLiquidationStrategy) returns(uint256 lpTokenPrincipal, uint256 remainingLiquidity) { + return super.payLoanLiquidity(liquidity, loanLiquidity, _loan); + } +} diff --git a/contracts/strategies/vault/liquidation/VaultExternalLiquidationStrategy.sol b/contracts/strategies/vault/liquidation/VaultExternalLiquidationStrategy.sol new file mode 100644 index 0000000..6b031bb --- /dev/null +++ b/contracts/strategies/vault/liquidation/VaultExternalLiquidationStrategy.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../../cpmm/liquidation/CPMMExternalLiquidationStrategy.sol"; +import "../base/VaultBaseLiquidationStrategy.sol"; + +/// @title Vault External Liquidation Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice LiquidationStrategy implementation for Constant Product Market Maker that also allows external swaps (flash loans) during liquidations +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultExternalLiquidationStrategy is CPMMExternalLiquidationStrategy, VaultBaseLiquidationStrategy { + + /// @dev Initializes the contract by setting `liquidator`, `mathLib`,`MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, + /// @dev `tradingFee1`, `tradingFee2`, `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address liquidator_, address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, + uint24 tradingFee2_, address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) + CPMMExternalLiquidationStrategy(liquidator_, mathLib_, maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, + feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseLiquidationStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseLiquidationStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseLiquidationStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev See {BaseRepayStrategy-payLoanLiquidity}. + function payLoanLiquidity(uint256 liquidity, uint256 loanLiquidity, LibStorage.Loan storage _loan) internal virtual + override(BaseRepayStrategy,VaultBaseLiquidationStrategy) returns(uint256 lpTokenPrincipal, uint256 remainingLiquidity) { + return super.payLoanLiquidity(liquidity, loanLiquidity, _loan); + } +} diff --git a/contracts/strategies/vault/liquidation/VaultLiquidationStrategy.sol b/contracts/strategies/vault/liquidation/VaultLiquidationStrategy.sol new file mode 100644 index 0000000..da96e2d --- /dev/null +++ b/contracts/strategies/vault/liquidation/VaultLiquidationStrategy.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../../cpmm/liquidation/CPMMLiquidationStrategy.sol"; +import "../base/VaultBaseLiquidationStrategy.sol"; + +/// @title Vault Liquidation Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by LiquidationStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultLiquidationStrategy is CPMMLiquidationStrategy, VaultBaseLiquidationStrategy { + + /// @dev Initializes the contract by setting `liquidator`, `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, + /// @dev `tradingFee1`, `tradingFee2`, `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address liquidator_, address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, + uint24 tradingFee2_, address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) + CPMMLiquidationStrategy(liquidator_, mathLib_, maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, + feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseLiquidationStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseLiquidationStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseLiquidationStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev See {BaseRepayStrategy-payLoanLiquidity}. + function payLoanLiquidity(uint256 liquidity, uint256 loanLiquidity, LibStorage.Loan storage _loan) internal virtual + override(BaseRepayStrategy,VaultBaseLiquidationStrategy) returns(uint256 lpTokenPrincipal, uint256 remainingLiquidity) { + return super.payLoanLiquidity(liquidity, loanLiquidity, _loan); + } +} diff --git a/contracts/strategies/vault/rebalance/VaultExternalRebalanceStrategy.sol b/contracts/strategies/vault/rebalance/VaultExternalRebalanceStrategy.sol new file mode 100644 index 0000000..29edd79 --- /dev/null +++ b/contracts/strategies/vault/rebalance/VaultExternalRebalanceStrategy.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/strategies/rebalance/ExternalRebalanceStrategy.sol"; +import "../../../interfaces/vault/strategies/IVaultReserveStrategy.sol"; +import "../base/VaultBaseLongStrategy.sol"; + +/// @title Vault External Long Strategy concrete implementation contract for Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Constant Product Market Maker Long Strategy implementation that allows external swaps (flash loans) +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultExternalRebalanceStrategy is VaultBaseLongStrategy, ExternalRebalanceStrategy, IVaultReserveStrategy { + + error InvalidRefType(); + error ExcessiveLPTokensReserved(); + + using LibStorage for LibStorage.Storage; + + /// @dev Initializes the contract by setting `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `tradingFee1`, `tradingFee2`, + /// @dev `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, uint24 tradingFee2_, address feeSource_, + uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) CPMMBaseLongStrategy(maxTotalApy_, + blocksPerYear_, tradingFee1_, tradingFee2_, feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseLongStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseLongStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual override(BaseStrategy,VaultBaseLongStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } + + /// @dev See {IVaultStrategy-_reserveLPTokens}. + function _reserveLPTokens(uint256 tokenId, uint256 lpTokens, bool isReserve) external virtual override lock returns(uint256) { + // Get loan for tokenId, revert if not loan creator + LibStorage.Loan storage _loan = _getLoan(tokenId); + + if(_loan.refType == 3) revert InvalidRefType(); + + updateLoan(_loan); + + if(isReserve) { + uint256 lpTokenBalance = getAdjLPTokenBalance(); + + // Revert if reserving all remaining CFMM LP tokens in pool + if(lpTokens >= lpTokenBalance) revert ExcessiveLPTokensReserved(); + + checkExpectedUtilizationRate(lpTokens, true); + + uint256 reservedLPTokens = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + + s.setUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS), reservedLPTokens + lpTokens); + } else { + uint256 reservedLPTokens = s.getUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS)); + + lpTokens = GSMath.min(reservedLPTokens, lpTokens); + unchecked { + reservedLPTokens = reservedLPTokens - lpTokens; + } + + s.setUint256(uint256(IVaultGammaPool.StorageIndexes.RESERVED_LP_TOKENS), reservedLPTokens); + } + + return lpTokens; + } +} diff --git a/contracts/strategies/vault/rebalance/VaultRebalanceStrategy.sol b/contracts/strategies/vault/rebalance/VaultRebalanceStrategy.sol new file mode 100644 index 0000000..ef42e01 --- /dev/null +++ b/contracts/strategies/vault/rebalance/VaultRebalanceStrategy.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "../base/VaultBaseRebalanceStrategy.sol"; +import "../../cpmm/rebalance/CPMMRebalanceStrategy.sol"; + +/// @title Rebalance Strategy concrete implementation contract for Vault GammaPool Constant Product Market Maker +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @notice Sets up variables used by BorrowStrategy and RebalanceStrategy and defines internal functions specific to CPMM implementation +/// @dev This implementation was specifically designed to work with UniswapV2 +contract VaultRebalanceStrategy is CPMMRebalanceStrategy, VaultBaseRebalanceStrategy { + + /// @dev Initializes the contract by setting `mathLib`, `MAX_TOTAL_APY`, `BLOCKS_PER_YEAR`, `tradingFee1`, `tradingFee2`, + /// @dev `feeSource`, `baseRate`, `optimalUtilRate`, `slope1`, and `slope2` + constructor(address mathLib_, uint256 maxTotalApy_, uint256 blocksPerYear_, uint24 tradingFee1_, uint24 tradingFee2_, + address feeSource_, uint64 baseRate_, uint64 optimalUtilRate_, uint64 slope1_, uint64 slope2_) CPMMRebalanceStrategy(mathLib_, + maxTotalApy_, blocksPerYear_, tradingFee1_, tradingFee2_, feeSource_, baseRate_, optimalUtilRate_, slope1_, slope2_) { + } + + /// @dev See {BaseStrategy-accrueBorrowedInvariant}. + function accrueBorrowedInvariant(uint256 borrowedInvariant, uint256 lastFeeIndex) internal virtual + override(BaseStrategy,VaultBaseRebalanceStrategy) view returns(uint256) { + return super.accrueBorrowedInvariant(borrowedInvariant, lastFeeIndex); + } + + /// @dev Update loan's liquidity debt with interest charged except when loan is of refType 3 + /// @dev See {BaseLongStrategy-updateLoanLiquidity}. + function updateLoanLiquidity(LibStorage.Loan storage _loan, uint256 accFeeIndex) internal virtual + override(BaseLongStrategy,VaultBaseRebalanceStrategy) returns(uint256 liquidity) { + return super.updateLoanLiquidity(_loan, accFeeIndex); + } + + /// @dev See {BaseStrategy-checkExpectedUtilizationRate}. + function checkExpectedUtilizationRate(uint256 lpTokens, bool isLoan) internal virtual + override(BaseStrategy,VaultBaseRebalanceStrategy) view { + return super.checkExpectedUtilizationRate(lpTokens, isLoan); + } +} diff --git a/contracts/test/strategies/cpmm/TestCPMMBorrowStrategy.sol b/contracts/test/strategies/cpmm/TestCPMMBorrowStrategy.sol index c4ba1d2..f9667f4 100644 --- a/contracts/test/strategies/cpmm/TestCPMMBorrowStrategy.sol +++ b/contracts/test/strategies/cpmm/TestCPMMBorrowStrategy.sol @@ -82,8 +82,4 @@ contract TestCPMMBorrowStrategy is CPMMBorrowStrategy { function _increaseCollateral(uint256, uint256[] calldata) external virtual override returns(uint128[] memory) { return new uint128[](2); } - - function _rebalanceCollateral(uint256, int256[] memory, uint256[] calldata) external virtual override returns(uint128[] memory) { - return new uint128[](2); - } } diff --git a/contracts/test/strategies/deltaswap/TestDSV2BorrowStrategy.sol b/contracts/test/strategies/deltaswap/TestDSV2BorrowStrategy.sol index 956e57c..5a27f7b 100644 --- a/contracts/test/strategies/deltaswap/TestDSV2BorrowStrategy.sol +++ b/contracts/test/strategies/deltaswap/TestDSV2BorrowStrategy.sol @@ -82,8 +82,4 @@ contract TestDSV2BorrowStrategy is DSV2BorrowStrategy { function _increaseCollateral(uint256, uint256[] calldata) external virtual override returns(uint128[] memory) { return new uint128[](2); } - - function _rebalanceCollateral(uint256, int256[] memory, uint256[] calldata) external virtual override returns(uint128[] memory) { - return new uint128[](2); - } } diff --git a/contracts/viewers/vault/VaultPoolViewer.sol b/contracts/viewers/vault/VaultPoolViewer.sol new file mode 100644 index 0000000..47eb29e --- /dev/null +++ b/contracts/viewers/vault/VaultPoolViewer.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.21; + +import "@gammaswap/v1-core/contracts/base/PoolViewer.sol"; +import "../../interfaces/vault/IVaultGammaPool.sol"; +import "../../interfaces/vault/IVaultPoolViewer.sol"; + +/// @title Implementation of Viewer Contract for Vault GammaPool +/// @author Daniel D. Alcarraz (https://github.com/0xDanr) +/// @dev Used make complex view function calls from GammaPool's storage data (e.g. updated loan and pool debt) +contract VaultPoolViewer is PoolViewer, IVaultPoolViewer { + + /// @inheritdoc PoolViewer + function _getUpdatedLoans(address pool, IGammaPool.LoanData[] memory _loans) internal virtual override view returns(IGammaPool.LoanData[] memory) { + address[] memory _tokens = IGammaPool(pool).tokens(); + (string[] memory _symbols, string[] memory _names, uint8[] memory _decimals) = getTokensMetaData(_tokens); + IGammaPool.RateData memory data = _getLastFeeIndex(pool); + uint256 _size = _loans.length; + IGammaPool.LoanData memory _loan; + for(uint256 i = 0; i < _size;) { + _loan = _loans[i]; + if(_loan.id == 0) { + break; + } + _loan.tokens = _tokens; + _loan.symbols = _symbols; + _loan.names = _names; + _loan.decimals = _decimals; + address refAddr = address(0); + if(_loan.refType == 3) { + refAddr = _loan.refAddr; + } else { + _loan.liquidity = _updateLiquidity(_loan.liquidity, _loan.rateIndex, data.accFeeIndex); + } + _loan.collateral = _collateral(pool, _loan.tokenId, _loan.tokensHeld, refAddr); + _loan.shortStrategy = data.shortStrategy; + _loan.paramsStore = data.paramsStore; + _loan.ltvThreshold = data.ltvThreshold; + _loan.liquidationFee = data.liquidationFee; + _loan.canLiquidate = _canLiquidate(_loan.liquidity, _loan.collateral, _loan.ltvThreshold); + unchecked { + ++i; + } + } + return _loans; + } + + /// @inheritdoc IPoolViewer + function loan(address pool, uint256 tokenId) external virtual override view returns(IGammaPool.LoanData memory _loanData) { + _loanData = IGammaPool(pool).getLoanData(tokenId); + if(_loanData.id == 0) { + return _loanData; + } + _loanData.accFeeIndex = _getLoanLastFeeIndex(_loanData); + address refAddr = address(0); + if(_loanData.refType == 3) { + refAddr = _loanData.refAddr; + } else { + _loanData.liquidity = _updateLiquidity(_loanData.liquidity, _loanData.rateIndex, _loanData.accFeeIndex); + } + _loanData.collateral = _collateral(pool, tokenId, _loanData.tokensHeld, refAddr); + _loanData.canLiquidate = _canLiquidate(_loanData.liquidity, _loanData.collateral, _loanData.ltvThreshold); + (_loanData.symbols, _loanData.names, _loanData.decimals) = getTokensMetaData(_loanData.tokens); + return _loanData; + } + + /// @inheritdoc IPoolViewer + function canLiquidate(address pool, uint256 tokenId) external virtual override view returns(bool) { + IGammaPool.LoanData memory _loanData = IGammaPool(pool).getLoanData(tokenId); + if(_loanData.liquidity == 0) { + return false; + } + address refAddr = address(0); + if(_loanData.refType == 3) { + refAddr = _loanData.refAddr; + } else { + _loanData.liquidity = _updateLiquidity(_loanData.liquidity, _loanData.rateIndex, _loanData.accFeeIndex); + } + uint256 collateral = _collateral(pool, tokenId, _loanData.tokensHeld, refAddr); + return _canLiquidate(_loanData.liquidity, collateral, _loanData.ltvThreshold); + } + + /// @inheritdoc PoolViewer + function _getLastFeeIndex(address pool) internal virtual override view returns(IGammaPool.RateData memory data) { + IGammaPool.PoolData memory params = IGammaPool(pool).getPoolData(); + + uint256 lastCFMMInvariant; + uint256 lastCFMMTotalSupply; + (, lastCFMMInvariant, lastCFMMTotalSupply) = IGammaPool(pool).getLatestCFMMBalances(); + if(lastCFMMTotalSupply > 0) { + uint256 maxCFMMFeeLeverage; + uint256 spread; + (data.borrowRate,data.utilizationRate,maxCFMMFeeLeverage,spread) = AbstractRateModel(params.shortStrategy) + .calcBorrowRate(params.LP_INVARIANT, params.BORROWED_INVARIANT, params.paramsStore, pool); + + (data.lastFeeIndex,data.lastCFMMFeeIndex) = IShortStrategy(params.shortStrategy) + .getLastFees(data.borrowRate, params.BORROWED_INVARIANT, lastCFMMInvariant, lastCFMMTotalSupply, + params.lastCFMMInvariant, params.lastCFMMTotalSupply, params.LAST_BLOCK_NUMBER, params.lastCFMMFeeIndex, + maxCFMMFeeLeverage, spread); + + data.supplyRate = data.borrowRate * data.utilizationRate / 1e18; + + (data.LP_INVARIANT,) = IVaultGammaPool(pool).getReservedBalances(); + data.LP_INVARIANT = GSMath.min(params.BORROWED_INVARIANT, data.LP_INVARIANT); + unchecked { + data.BORROWED_INVARIANT = uint256(params.BORROWED_INVARIANT) - data.LP_INVARIANT; + } + (,,data.BORROWED_INVARIANT) = IShortStrategy(params.shortStrategy).getLatestBalances(data.lastFeeIndex, + data.BORROWED_INVARIANT, params.LP_TOKEN_BALANCE, lastCFMMInvariant, lastCFMMTotalSupply); + + data.BORROWED_INVARIANT = data.BORROWED_INVARIANT + data.LP_INVARIANT; + data.LP_INVARIANT = uint128(params.LP_TOKEN_BALANCE * lastCFMMInvariant / lastCFMMTotalSupply); + + data.utilizationRate = _calcUtilizationRate(data.LP_INVARIANT, data.BORROWED_INVARIANT); + data.emaUtilRate = uint40(IShortStrategy(params.shortStrategy).calcUtilRateEma(data.utilizationRate, + params.emaUtilRate, GSMath.max(block.number - params.LAST_BLOCK_NUMBER, params.emaMultiplier))); + } else { + data.lastFeeIndex = 1e18; + } + + data.origFee = params.origFee; + data.feeDivisor = params.feeDivisor; + data.minUtilRate1 = params.minUtilRate1; + data.minUtilRate2 = params.minUtilRate2; + data.ltvThreshold = params.ltvThreshold; + data.liquidationFee = params.liquidationFee; + data.shortStrategy = params.shortStrategy; + data.paramsStore = params.paramsStore; + + data.accFeeIndex = params.accFeeIndex * data.lastFeeIndex / 1e18; + data.lastBlockNumber = params.LAST_BLOCK_NUMBER; + data.currBlockNumber = block.number; + } + + /// @inheritdoc IPoolViewer + function getLatestPoolData(address pool) public virtual override view returns(IGammaPool.PoolData memory data) { + data = getPoolData(pool); + uint256 lastCFMMInvariant; + uint256 lastCFMMTotalSupply; + (data.CFMM_RESERVES, lastCFMMInvariant, lastCFMMTotalSupply) = IGammaPool(pool).getLatestCFMMBalances(); + if(lastCFMMTotalSupply == 0) { + return data; + } + + uint256 lastCFMMFeeIndex; // holding maxCFMMFeeLeverage temporarily + uint256 borrowedInvariant; // holding spread temporarily + (data.borrowRate, data.utilizationRate, lastCFMMFeeIndex, borrowedInvariant) = + AbstractRateModel(data.shortStrategy).calcBorrowRate(data.LP_INVARIANT, data.BORROWED_INVARIANT, + data.paramsStore, pool); + + (data.lastFeeIndex,lastCFMMFeeIndex) = IShortStrategy(data.shortStrategy) + .getLastFees(data.borrowRate, data.BORROWED_INVARIANT, lastCFMMInvariant, lastCFMMTotalSupply, + data.lastCFMMInvariant, data.lastCFMMTotalSupply, data.LAST_BLOCK_NUMBER, data.lastCFMMFeeIndex, + lastCFMMFeeIndex, borrowedInvariant); + + data.supplyRate = data.borrowRate * data.utilizationRate / 1e18; + + data.lastCFMMFeeIndex = uint64(lastCFMMFeeIndex); + + (lastCFMMFeeIndex,) = IVaultGammaPool(pool).getReservedBalances(); + lastCFMMFeeIndex = GSMath.min(data.BORROWED_INVARIANT, lastCFMMFeeIndex); + unchecked { + borrowedInvariant = data.BORROWED_INVARIANT - lastCFMMFeeIndex; + } + (,data.LP_TOKEN_BORROWED_PLUS_INTEREST, borrowedInvariant) = IShortStrategy(data.shortStrategy) + .getLatestBalances(data.lastFeeIndex, borrowedInvariant, data.LP_TOKEN_BALANCE, lastCFMMInvariant, lastCFMMTotalSupply); + + data.LP_TOKEN_BORROWED_PLUS_INTEREST += lastCFMMFeeIndex * lastCFMMTotalSupply / lastCFMMInvariant; + + data.BORROWED_INVARIANT = uint128(borrowedInvariant + lastCFMMFeeIndex); + data.LP_INVARIANT = uint128(data.LP_TOKEN_BALANCE * lastCFMMInvariant / lastCFMMTotalSupply); + data.accFeeIndex = uint80(data.accFeeIndex * data.lastFeeIndex / 1e18); + + data.utilizationRate = _calcUtilizationRate(data.LP_INVARIANT, data.BORROWED_INVARIANT); + data.emaUtilRate = uint40(IShortStrategy(data.shortStrategy).calcUtilRateEma(data.utilizationRate, data.emaUtilRate, + GSMath.max(block.number - data.LAST_BLOCK_NUMBER, data.emaMultiplier))); + + data.lastPrice = IGammaPool(pool).getLastCFMMPrice(); + data.lastCFMMInvariant = uint128(lastCFMMInvariant); + data.lastCFMMTotalSupply = lastCFMMTotalSupply; + } + + /// @dev Returns vault pool storage data updated to their latest values + /// @notice Difference with getVaultPoolData() is this struct is what PoolData would return if an update of the GammaPool were to occur at the current block + /// @param pool - address of pool to get pool data for + /// @return data - struct containing all relevant global state variables and descriptive information of GammaPool. Used to avoid making multiple calls + function getLatestVaultPoolData(address pool) public virtual override view returns(IVaultPoolViewer.VaultPoolData memory data) { + (data.reservedBorrowedInvariant, data.reservedLPTokens) = IVaultGammaPool(pool).getReservedBalances(); + data.poolData = getLatestPoolData(pool); + } + + /// @dev Return vault pool storage data + /// @param pool - address of pool to get pool data for + /// @return data - struct containing all relevant global state variables and descriptive information of GammaPool. Used to avoid making multiple calls + function getVaultPoolData(address pool) public virtual override view returns(IVaultPoolViewer.VaultPoolData memory data) { + (data.reservedBorrowedInvariant, data.reservedLPTokens) = IVaultGammaPool(pool).getReservedBalances(); + data.poolData = IGammaPool(pool).getPoolData(); + } +} \ No newline at end of file diff --git a/package.json b/package.json index 6d11d56..3bb1b15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gammaswap/v1-implementations", - "version": "1.2.11", + "version": "1.2.12", "description": "Pool and strategies implementation contracts for GammaSwap V1 protocol", "homepage": "https://gammaswap.com", "scripts": { @@ -71,7 +71,7 @@ "typescript": "^4.7.4" }, "dependencies": { - "@gammaswap/v1-core": "^1.2.11", + "@gammaswap/v1-core": "^1.2.12", "@openzeppelin/contracts": "^4.7.0" } } diff --git a/yarn.lock b/yarn.lock index 44c5898..d35b37c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -449,10 +449,10 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@gammaswap/v1-core@^1.2.11": - version "1.2.11" - resolved "https://npm.pkg.github.com/download/@gammaswap/v1-core/1.2.11/354d4cd420d1bab50ae41711da757a7de563911e#354d4cd420d1bab50ae41711da757a7de563911e" - integrity sha512-BqEeNJb80xG0E7lfy/4ZUfeP0Q1bEoEQnmv58rJp98wtQ/FBQIXDEDKsjzUJdDDE6oa7p/ZJzo+zkFhbInuoKg== +"@gammaswap/v1-core@^1.2.12": + version "1.2.12" + resolved "https://npm.pkg.github.com/download/@gammaswap/v1-core/1.2.12/414f66123c7bc2f1b231c06e559f5d509d8abf49#414f66123c7bc2f1b231c06e559f5d509d8abf49" + integrity sha512-ti59W/jM5/VxLcEj9IP3Qhz6NirNMEqt7HIJNme1QAfD2SThPE+KF1pzfLwNlRYJnyZ2iP3Ye0cItwS2bYEsgQ== dependencies: "@openzeppelin/contracts" "^4.7.0"