diff --git a/abis/ICxTokenFactory.json b/abis/ICxTokenFactory.json index 4f0e1d13..29cb7d6d 100644 --- a/abis/ICxTokenFactory.json +++ b/abis/ICxTokenFactory.json @@ -19,6 +19,18 @@ "internalType": "uint256", "name": "expiryDate", "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "symbol", + "type": "string" } ], "name": "CxTokenDeployed", diff --git a/abis/IVault.json b/abis/IVault.json index d51e88c4..67c6be67 100644 --- a/abis/IVault.json +++ b/abis/IVault.json @@ -24,6 +24,44 @@ "name": "Approval", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Entered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Exited", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -93,6 +131,44 @@ "name": "InterestAccrued", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "NPMStaken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "NPMUnstaken", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -435,19 +511,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "lqt", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -504,6 +567,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sc", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "totalSupply", diff --git a/abis/IVaultDelegate.json b/abis/IVaultDelegate.json new file mode 100644 index 00000000..265e1624 --- /dev/null +++ b/abis/IVaultDelegate.json @@ -0,0 +1,641 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + } + ], + "name": "accrueInterestImplementation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "podsToBurn", + "type": "uint256" + } + ], + "name": "calculateLiquidityImplementation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "forStablecoinUnits", + "type": "uint256" + } + ], + "name": "calculatePodsImplementation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "getFlashFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "forAccount", + "type": "address" + } + ], + "name": "getInfoImplementation", + "outputs": [ + { + "internalType": "uint256[]", + "name": "result", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getMaxFlashLoan", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + } + ], + "name": "getStablecoinBalanceOfImplementation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "npmStake", + "type": "uint256" + } + ], + "name": "postAddLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "contract IERC3156FlashBorrower", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "postFlashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "strategyName", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "postReceiveFromStrategy", + "outputs": [ + { + "internalType": "uint256", + "name": "income", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "loss", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "npmStake", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "exit", + "type": "bool" + } + ], + "name": "postRemoveLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "postTransferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "strategyName", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "postTransferToStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "npmStake", + "type": "uint256" + } + ], + "name": "preAddLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "podsToMint", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "previousNPMStake", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "contract IERC3156FlashBorrower", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "preFlashLoan", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "stablecoin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolFee", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "strategyName", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "preReceiveFromStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "npmStake", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "exit", + "type": "bool" + } + ], + "name": "preRemoveLiquidity", + "outputs": [ + { + "internalType": "address", + "name": "stablecoin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "stableCoinToRelease", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "preTransferGovernance", + "outputs": [ + { + "internalType": "address", + "name": "stablecoin", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "coverKey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "strategyName", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "preTransferToStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/core/claims/Processor.sol b/contracts/core/claims/Processor.sol index b4ddc577..6b009a07 100644 --- a/contracts/core/claims/Processor.sol +++ b/contracts/core/claims/Processor.sol @@ -130,11 +130,13 @@ contract Processor is IClaimsProcessor, Recoverable { if (key > 0) { previous = s.getUintByKeys(ProtoUtilV1.NS_CLAIM_PERIOD, key); s.setUintByKeys(ProtoUtilV1.NS_CLAIM_PERIOD, key, value); - } else { - previous = s.getUintByKey(ProtoUtilV1.NS_CLAIM_PERIOD); - s.setUintByKey(ProtoUtilV1.NS_CLAIM_PERIOD, value); + emit ClaimPeriodSet(key, previous, value); + return; } + previous = s.getUintByKey(ProtoUtilV1.NS_CLAIM_PERIOD); + s.setUintByKey(ProtoUtilV1.NS_CLAIM_PERIOD, value); + emit ClaimPeriodSet(key, previous, value); } diff --git a/contracts/core/cxToken/cxToken.sol b/contracts/core/cxToken/cxToken.sol index 212ab21f..50cacea6 100644 --- a/contracts/core/cxToken/cxToken.sol +++ b/contracts/core/cxToken/cxToken.sol @@ -23,7 +23,7 @@ contract cxToken is ICxToken, Recoverable, ERC20 { using ValidationLibV1 for IStore; bytes32 public immutable override coverKey; - uint256 public immutable override createdOn; + uint256 public immutable override createdOn = block.timestamp; // solhint-disable-line uint256 public immutable override expiresOn; /** @@ -40,8 +40,6 @@ contract cxToken is ICxToken, Recoverable, ERC20 { string memory symbol ) ERC20(name, symbol) Recoverable(store) { coverKey = key; - - createdOn = block.timestamp; // solhint-disable-line expiresOn = expiry; } @@ -62,7 +60,7 @@ contract cxToken is ICxToken, Recoverable, ERC20 { require(amount > 0, "Please specify amount"); require(key == coverKey, "Invalid cover"); - s.callerMustBePolicyContract(); + s.senderMustBePolicyContract(); super._mint(to, amount); } diff --git a/contracts/core/cxToken/cxTokenFactory.sol b/contracts/core/cxToken/cxTokenFactory.sol index 05f62309..0e354fbf 100644 --- a/contracts/core/cxToken/cxTokenFactory.sol +++ b/contracts/core/cxToken/cxTokenFactory.sol @@ -50,11 +50,14 @@ contract cxTokenFactory is ICxTokenFactory, Recoverable { // @suppress-acl Can only be called by the latest policy contract s.mustNotBePaused(); s.mustBeValidCoverKey(key); - s.callerMustBePolicyContract(); + s.senderMustBePolicyContract(); require(expiryDate > 0, "Please specify expiry date"); - (bytes memory bytecode, bytes32 salt) = cxTokenFactoryLibV1.getByteCode(s, key, expiryDate, _getTokenName(key), _getTokenSymbol()); + string memory name = _getTokenName(key); + string memory symbol = _getTokenSymbol(); + + (bytes memory bytecode, bytes32 salt) = cxTokenFactoryLibV1.getByteCode(s, key, expiryDate, name, symbol); require(s.getAddress(salt) == address(0), "Already deployed"); @@ -79,7 +82,7 @@ contract cxTokenFactory is ICxTokenFactory, Recoverable { s.setBoolByKeys(ProtoUtilV1.NS_COVER_CXTOKEN, deployed, true); s.setAddressArrayByKeys(ProtoUtilV1.NS_COVER_CXTOKEN, key, deployed); - emit CxTokenDeployed(key, deployed, expiryDate); + emit CxTokenDeployed(key, deployed, expiryDate, name, symbol); } /** diff --git a/contracts/core/delegates/VaultDelegate.sol b/contracts/core/delegates/VaultDelegate.sol new file mode 100644 index 00000000..20fd65cc --- /dev/null +++ b/contracts/core/delegates/VaultDelegate.sol @@ -0,0 +1,26 @@ +// Neptune Mutual Protocol (https://neptunemutual.com) +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.0; + +import "./VaultDelegateWithFlashLoan.sol"; + +/** + * @title Cover Vault for Liquidity + * @dev Liquidity providers can earn fees by adding stablecoin liquidity + * to any cover contract. The cover pool is collectively owned by liquidity providers + * where fees automatically get accumulated and compounded.

+ * **Fees**
+ * + * - Cover fees paid in stablecoin get added to the liquidity pool. + * - The protocol supplies a small portion of idle assets to lending protocols (v2). + * - Flash loan interest also gets added back to the pool. + * - To protect liquidity providers from cover incidents, they can redeem up to 25% of the cover payouts through NPM provision. + * - To protect liquidity providers from cover incidents, they can redeem up to 25% of the cover payouts through `reassurance token` allocation. + */ +contract VaultDelegate is VaultDelegateWithFlashLoan { + using ProtoUtilV1 for IStore; + using ValidationLibV1 for IStore; + using VaultLibV1 for IStore; + + constructor(IStore store) VaultDelegateBase(store) {} // solhint-disable-line +} diff --git a/contracts/core/delegates/VaultDelegateBase.sol b/contracts/core/delegates/VaultDelegateBase.sol new file mode 100644 index 00000000..bf45b4fc --- /dev/null +++ b/contracts/core/delegates/VaultDelegateBase.sol @@ -0,0 +1,256 @@ +// Neptune Mutual Protocol (https://neptunemutual.com) +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.0; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "../Recoverable.sol"; +import "../../interfaces/IVaultDelegate.sol"; +import "../../libraries/ProtoUtilV1.sol"; +import "../../libraries/CoverUtilV1.sol"; +import "../../libraries/VaultLibV1.sol"; +import "../../libraries/ValidationLibV1.sol"; +import "../../libraries/StrategyLibV1.sol"; + +/** + * @title Vault POD (Proof of Deposit) + * @dev The VaultPod has `_mintPods` and `_redeemPods` features which enables + * POD minting and burning on demand.

+ * + * **How Does This Work?** + * + * When you add liquidity to the Vault, + * PODs are minted representing your proportional share of the pool. + * Similarly, when you redeem your PODs, you get your proportional + * share of the Vault liquidity back, burning the PODs. + */ +abstract contract VaultDelegateBase is IVaultDelegate, Recoverable { + using ProtoUtilV1 for bytes; + using ProtoUtilV1 for IStore; + using VaultLibV1 for IStore; + using ValidationLibV1 for IStore; + using RoutineInvokerLibV1 for IStore; + using StoreKeyUtil for IStore; + using StrategyLibV1 for IStore; + using CoverUtilV1 for IStore; + using NTransferUtilV2 for IERC20; + + /** + * @dev Constructs this contract + * @param store Provide the store contract instance + */ + constructor(IStore store) Recoverable(store) {} // solhint-disable-line + + function preTransferGovernance( + address caller, + bytes32 coverKey, + address, /*to*/ + uint256 amount + ) external override nonReentrant returns (address stablecoin) { + require(amount > 0, "Please specify amount"); + + s.mustNotBePaused(); + s.senderMustBeVaultContract(coverKey); + s.callerMustBeClaimsProcessorContract(caller); + + stablecoin = s.getStablecoin(); + } + + function postTransferGovernance( + address, /*caller*/ + bytes32 coverKey, + address, /*to*/ + uint256 /*amount*/ + ) external view override { + s.senderMustBeVaultContract(coverKey); + // @suppress-reentrancy The `postTransferGovernance` hook is executed under the same context of `preTransferGovernance`. + // @note: do not update state and liquidity since `transferGovernance` is an internal contract-only function + } + + function preTransferToStrategy( + address caller, + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external override nonReentrant { + require(amount > 0, "Please specify amount"); + + s.mustNotBePaused(); + s.senderMustBeVaultContract(coverKey); + s.callerMustBeSpecificStrategyContract(caller, strategyName); + + s.preTransferToStrategyInternal(token, coverKey, strategyName, amount); + } + + function postTransferToStrategy( + address, /*caller*/ + IERC20, /*token*/ + bytes32 coverKey, + bytes32, /*strategyName*/ + uint256 /*amount*/ + ) external view override { + s.senderMustBeVaultContract(coverKey); + // @suppress-reentrancy The `postTransferToStrategy` hook is executed under the same context of `preTransferToStrategy`. + // @note: do not update state and liquidity since `transferToStrategy` itself is a part of the state update + } + + function preReceiveFromStrategy( + address caller, + IERC20, /*token*/ + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external override nonReentrant { + require(amount > 0, "Please specify amount"); + + s.mustNotBePaused(); + s.senderMustBeVaultContract(coverKey); + s.callerMustBeSpecificStrategyContract(caller, strategyName); + } + + function postReceiveFromStrategy( + address caller, + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external override returns (uint256 income, uint256 loss) { + s.mustNotBePaused(); + s.senderMustBeVaultContract(coverKey); + s.callerMustBeStrategyContract(caller); + + (income, loss) = s.postReceiveFromStrategyInternal(token, coverKey, strategyName, amount); + // @suppress-reentrancy The `postReceiveFromStrategy` hook is executed under the same context of `preReceiveFromStrategy`. + // @note: do not update state and liquidity since `receiveFromStrategy` itself is a part of the state update + } + + /** + * @dev Adds liquidity to the specified cover contract + * @param coverKey Enter the cover key + * @param amount Enter the amount of liquidity token to supply. + * @param npmStakeToAdd Enter the amount of NPM token to stake. + */ + function preAddLiquidity( + address caller, + bytes32 coverKey, + uint256 amount, + uint256 npmStakeToAdd + ) external override nonReentrant returns (uint256 podsToMint, uint256 previousNpmStake) { + require(amount > 0, "Please specify amount"); + s.mustNotBePaused(); + s.senderMustBeVaultContract(coverKey); + + (podsToMint, previousNpmStake) = s.preAddLiquidityInternal(coverKey, msg.sender, caller, amount, npmStakeToAdd); + } + + function postAddLiquidity( + address, /*caller*/ + bytes32 coverKey, + uint256, /*amount*/ + uint256 /*npmStakeToAdd*/ + ) external override { + s.senderMustBeVaultContract(coverKey); + s.updateStateAndLiquidity(coverKey); + + // @suppress-reentrancy The `postAddLiquidity` hook is executed under the same context of `preAddLiquidity`. + } + + function accrueInterestImplementation(address caller, bytes32 coverKey) external override { + s.senderMustBeVaultContract(coverKey); + AccessControlLibV1.callerMustBeLiquidityManager(s, caller); + + s.accrueInterestInternal(coverKey); + } + + /** + * @dev Removes liquidity from the specified cover contract + * @param coverKey Enter the cover key + * @param podsToRedeem Enter the amount of pods to redeem + * @param npmStakeToRemove Enter the amount of NPM stake to remove. + */ + function preRemoveLiquidity( + address, /*caller*/ + bytes32 coverKey, + uint256 podsToRedeem, + uint256 npmStakeToRemove, + bool exit + ) external override nonReentrant returns (address stablecoin, uint256 stablecoinToRelease) { + require(podsToRedeem > 0, "Please specify amount"); + + s.mustNotBePaused(); + s.senderMustBeVaultContract(coverKey); + + return s.preRemoveLiquidityInternal(coverKey, msg.sender, podsToRedeem, npmStakeToRemove, exit); + } + + function postRemoveLiquidity( + address, /*caller*/ + bytes32 coverKey, + uint256, /*podsToRedeem*/ + uint256, /*npmStakeToRemove*/ + bool /*exit*/ + ) external override { + s.senderMustBeVaultContract(coverKey); + s.updateStateAndLiquidity(coverKey); + + // @suppress-reentrancy The `postRemoveLiquidity` hook is executed under the same context of `preRemoveLiquidity`. + } + + /** + * @dev Calculates the amount of PODS to mint for the given amount of liquidity to transfer + */ + function calculatePodsImplementation(bytes32 coverKey, uint256 forStablecoinUnits) external view override returns (uint256) { + s.senderMustBeVaultContract(coverKey); + return s.calculatePodsInternal(coverKey, msg.sender, forStablecoinUnits); + } + + /** + * @dev Calculates the amount of stablecoins to withdraw for the given amount of PODs to redeem + */ + function calculateLiquidityImplementation(bytes32 coverKey, uint256 podsToBurn) external view override returns (uint256) { + s.senderMustBeVaultContract(coverKey); + return s.calculateLiquidityInternal(coverKey, msg.sender, podsToBurn); + } + + /** + * @dev Returns the stablecoin balance of this vault + * This also includes amounts lent out in lending strategies + */ + function getStablecoinBalanceOfImplementation(bytes32 coverKey) external view override returns (uint256) { + s.senderMustBeVaultContract(coverKey); + return s.getStablecoinBalanceOfInternal(coverKey); + } + + /** + * @dev Gets information of a given vault by the cover key + * @param you The address for which the info will be customized + * @param values[0] totalPods --> Total PODs in existence + * @param values[1] balance --> Stablecoins held in the vault + * @param values[2] extendedBalance --> Stablecoins lent outside of the protocol + * @param values[3] totalReassurance -- > Total reassurance for this cover + * @param values[4] myPodBalance --> Your POD Balance + * @param values[5] myDeposits --> Sum of your deposits (in stablecoin) + * @param values[6] myWithdrawals --> Sum of your withdrawals (in stablecoin) + * @param values[7] myShare --> My share of the liquidity pool (in stablecoin) + * @param values[8] withdrawalOpen --> The timestamp when withdrawals are opened + * @param values[9] withdrawalClose --> The timestamp when withdrawals are closed again + */ + function getInfoImplementation(bytes32 coverKey, address you) external view override returns (uint256[] memory values) { + s.senderMustBeVaultContract(coverKey); + return s.getInfoInternal(coverKey, msg.sender, you); + } + + /** + * @dev Version number of this contract + */ + function version() external pure override returns (bytes32) { + return "v0.1"; + } + + /** + * @dev Name of this contract + */ + function getName() external pure override returns (bytes32) { + return ProtoUtilV1.CNAME_VAULT_DELEGATE; + } +} diff --git a/contracts/core/delegates/VaultDelegateWithFlashLoan.sol b/contracts/core/delegates/VaultDelegateWithFlashLoan.sol new file mode 100644 index 00000000..57a90c3d --- /dev/null +++ b/contracts/core/delegates/VaultDelegateWithFlashLoan.sol @@ -0,0 +1,98 @@ +// Neptune Mutual Protocol (https://neptunemutual.com) +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.0; + +import "./VaultDelegateBase.sol"; + +/** + * @title With Flash Loan Contract + * @dev WithFlashLoan contract implements `EIP-3156 Flash Loan`. + * Using flash loans, you can borrow up to the total available amount of + * the stablecoin liquidity available in this cover liquidity pool. + * You need to return back the borrowed amount + fee in the same transaction. + * The function `flashFee` enables you to check, in advance, fee that + * you need to pay to take out the loan. + */ +abstract contract VaultDelegateWithFlashLoan is VaultDelegateBase { + using ProtoUtilV1 for IStore; + using StoreKeyUtil for IStore; + using ValidationLibV1 for IStore; + using VaultLibV1 for IStore; + using NTransferUtilV2 for IERC20; + using RoutineInvokerLibV1 for IStore; + + /** + * @dev The fee to be charged for a given loan. + * @param token The loan currency. + * @param amount The amount of tokens lent. + * @return The amount of `token` to be charged for the loan, on top of the returned principal. + */ + function getFlashFee( + address, /*caller*/ + bytes32 coverKey, + address token, + uint256 amount + ) external view override returns (uint256) { + s.senderMustBeVaultContract(coverKey); + return s.getFlashFeeInternal(coverKey, token, amount); + } + + /** + * @dev The amount of currency available to be lent. + * @param token The loan currency. + * @return The amount of `token` that can be borrowed. + */ + function getMaxFlashLoan( + address, /*caller*/ + bytes32 coverKey, + address token + ) external view override returns (uint256) { + s.senderMustBeVaultContract(coverKey); + return s.getMaxFlashLoanInternal(coverKey, token); + } + + function preFlashLoan( + address, /*caller*/ + bytes32 coverKey, + IERC3156FlashBorrower, /*receiver*/ + address token, + uint256 amount, + bytes calldata /*data*/ + ) + external + override + returns ( + IERC20 stablecoin, + uint256 fee, + uint256 protocolFee + ) + { + s.mustNotBePaused(); + s.mustHaveNormalCoverStatus(coverKey); + s.senderMustBeVaultContract(coverKey); + + stablecoin = IERC20(s.getStablecoin()); + + require(address(stablecoin) == token, "Unknown token"); + require(amount > 0, "Loan too small"); + require(fee > 0, "Fee too little"); + + s.setBoolByKeys(ProtoUtilV1.NS_COVER_HAS_FLASH_LOAN, coverKey, true); + + (fee, protocolFee) = s.getFlashFeesInternal(coverKey, token, amount); + } + + function postFlashLoan( + address, /*caller*/ + bytes32 coverKey, + IERC3156FlashBorrower, /*receiver*/ + address, /*token*/ + uint256, /*amount*/ + bytes calldata /*data*/ + ) external override { + s.senderMustBeVaultContract(coverKey); + + s.setBoolByKeys(ProtoUtilV1.NS_COVER_HAS_FLASH_LOAN, coverKey, false); + s.updateStateAndLiquidity(coverKey); + } +} diff --git a/contracts/core/governance/resolution/Resolvable.sol b/contracts/core/governance/resolution/Resolvable.sol index 53f43046..a95ca0ea 100644 --- a/contracts/core/governance/resolution/Resolvable.sol +++ b/contracts/core/governance/resolution/Resolvable.sol @@ -88,15 +88,6 @@ abstract contract Resolvable is Finalization, IResolvable { // back the original stake. No rewards. CoverUtilV1.CoverStatus status = decision ? CoverUtilV1.CoverStatus.Claimable : CoverUtilV1.CoverStatus.FalseReporting; - // Claim begins from the cooldown period added to the first resolution attempt date - uint256 claimBeginsFrom = decision ? block.timestamp + cooldownPeriod : 0; // solhint-disable-line - - // Claim expires after the period specified by the cover creator. - uint256 claimExpiresAt = decision ? claimBeginsFrom + s.getClaimPeriod(key) : 0; - - s.setUintByKeys(ProtoUtilV1.NS_CLAIM_BEGIN_TS, key, claimBeginsFrom); - s.setUintByKeys(ProtoUtilV1.NS_CLAIM_EXPIRY_TS, key, claimExpiresAt); - // Status can change during `Emergency Resolution` attempt(s) s.setStatusInternal(key, incidentDate, status); @@ -108,6 +99,15 @@ abstract contract Resolvable is Finalization, IResolvable { s.setUintByKeys(ProtoUtilV1.NS_RESOLUTION_DEADLINE, key, deadline); } + // Claim begins when deadline timestamp is passed + uint256 claimBeginsFrom = decision ? deadline + 1 : 0; + + // Claim expires after the period specified by the cover creator. + uint256 claimExpiresAt = decision ? claimBeginsFrom + s.getClaimPeriod(key) : 0; + + s.setUintByKeys(ProtoUtilV1.NS_CLAIM_BEGIN_TS, key, claimBeginsFrom); + s.setUintByKeys(ProtoUtilV1.NS_CLAIM_EXPIRY_TS, key, claimExpiresAt); + s.updateStateAndLiquidity(key); emit Resolved(key, incidentDate, deadline, decision, emergency, claimBeginsFrom, claimExpiresAt); diff --git a/contracts/core/lifecycle/Cover.sol b/contracts/core/lifecycle/Cover.sol index 24d2e9b3..56beaa5e 100644 --- a/contracts/core/lifecycle/Cover.sol +++ b/contracts/core/lifecycle/Cover.sol @@ -34,7 +34,7 @@ contract Cover is CoverBase { function updateCover(bytes32 key, bytes32 info) external override nonReentrant { s.mustNotBePaused(); s.mustHaveNormalCoverStatus(key); - s.callerMustBeCoverOwnerOrAdmin(key); + s.senderMustBeCoverOwnerOrAdmin(key); require(s.getBytes32ByKeys(ProtoUtilV1.NS_COVER_INFO, key) != info, "Duplicate content"); @@ -102,7 +102,7 @@ contract Cover is CoverBase { s.mustNotBePaused(); s.mustHaveStoppedCoverStatus(key); - s.callerMustBeCoverOwnerOrAdmin(key); + s.senderMustBeCoverOwnerOrAdmin(key); address vault = s.deployVaultInternal(key); emit VaultDeployed(key, vault); @@ -152,7 +152,7 @@ contract Cover is CoverBase { ) external override nonReentrant { s.mustNotBePaused(); AccessControlLibV1.mustBeCoverManager(s); - s.callerMustBeCoverOwnerOrAdmin(key); + s.senderMustBeCoverOwnerOrAdmin(key); s.updateCoverUsersWhitelistInternal(key, accounts, statuses); } diff --git a/contracts/core/lifecycle/CoverStake.sol b/contracts/core/lifecycle/CoverStake.sol index cb5e9794..ea88744c 100644 --- a/contracts/core/lifecycle/CoverStake.sol +++ b/contracts/core/lifecycle/CoverStake.sol @@ -52,7 +52,7 @@ contract CoverStake is ICoverStake, Recoverable { // @suppress-acl Can only be accessed by the latest cover contract s.mustNotBePaused(); s.mustBeValidCoverKey(key); - s.callerMustBeCoverContract(); + s.senderMustBeCoverContract(); require(amount >= fee, "Invalid fee"); @@ -86,7 +86,7 @@ contract CoverStake is ICoverStake, Recoverable { // @suppress-acl Can only be accessed by the latest cover contract s.mustNotBePaused(); s.mustBeValidCoverKey(key); - s.callerMustBeCoverContract(); + s.senderMustBeCoverContract(); uint256 drawingPower = _getDrawingPower(key, account); require(amount > 0, "Please specify amount"); diff --git a/contracts/core/liquidity/Vault.sol b/contracts/core/liquidity/Vault.sol index 37c10a71..bb64e1b0 100644 --- a/contracts/core/liquidity/Vault.sol +++ b/contracts/core/liquidity/Vault.sol @@ -1,30 +1,34 @@ // Neptune Mutual Protocol (https://neptunemutual.com) // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.0; - +import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import "openzeppelin-solidity/contracts/interfaces/IERC3156FlashLender.sol"; import "./WithFlashLoan.sol"; -/** - * @title Cover Vault for Liquidity - * @dev Liquidity providers can earn fees by adding stablecoin liquidity - * to any cover contract. The cover pool is collectively owned by liquidity providers - * where fees automatically get accumulated and compounded.

- * **Fees**
- * - * - Cover fees paid in stablecoin get added to the liquidity pool. - * - The protocol supplies a small portion of idle assets to lending protocols (v2). - * - Flash loan interest also gets added back to the pool. - * - To protect liquidity providers from cover incidents, they can redeem up to 25% of the cover payouts through NPM provision. - * - To protect liquidity providers from cover incidents, they can redeem up to 25% of the cover payouts through `reassurance token` allocation. - */ +pragma solidity 0.8.0; + contract Vault is WithFlashLoan { using ProtoUtilV1 for IStore; - using ValidationLibV1 for IStore; - using VaultLibV1 for IStore; + using RegistryLibV1 for IStore; + using NTransferUtilV2 for IERC20; constructor( IStore store, bytes32 coverKey, IERC20 liquidityToken - ) VaultBase(store, coverKey, liquidityToken) {} // solhint-disable-line + ) VaultBase(store, coverKey, liquidityToken) {} + + /** + * @dev For further details, check delegate contract's documentation. + */ + function getInfo(address you) external view override returns (uint256[] memory values) { + return delgate().getInfoImplementation(key, you); + } + + function version() external pure override returns (bytes32) { + return "v0.1"; + } + + function getName() external pure override returns (bytes32) { + return ProtoUtilV1.CNAME_LIQUIDITY_VAULT; + } } diff --git a/contracts/core/liquidity/VaultBase.sol b/contracts/core/liquidity/VaultBase.sol index c798b59e..cb433bef 100644 --- a/contracts/core/liquidity/VaultBase.sol +++ b/contracts/core/liquidity/VaultBase.sol @@ -1,229 +1,35 @@ // Neptune Mutual Protocol (https://neptunemutual.com) // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.0; - import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; import "../Recoverable.sol"; +import "../../interfaces/IVaultDelegate.sol"; import "../../interfaces/IVault.sol"; -import "../../libraries/ProtoUtilV1.sol"; -import "../../libraries/CoverUtilV1.sol"; -import "../../libraries/VaultLibV1.sol"; -import "../../libraries/ValidationLibV1.sol"; -import "../../libraries/StrategyLibV1.sol"; -/** - * @title Vault POD (Proof of Deposit) - * @dev The VaultPod has `_mintPods` and `_redeemPods` features which enables - * POD minting and burning on demand.

- * - * **How Does This Work?** - * - * When you add liquidity to the Vault, - * PODs are minted representing your proportional share of the pool. - * Similarly, when you redeem your PODs, you get your proportional - * share of the Vault liquidity back, burning the PODs. - */ -abstract contract VaultBase is IVault, Recoverable, ERC20 { - using ProtoUtilV1 for bytes; +pragma solidity 0.8.0; + +abstract contract VaultBase is ERC20, Recoverable, IVault { using ProtoUtilV1 for IStore; - using VaultLibV1 for IStore; - using ValidationLibV1 for IStore; - using StoreKeyUtil for IStore; - using StrategyLibV1 for IStore; - using CoverUtilV1 for IStore; + using RegistryLibV1 for IStore; using NTransferUtilV2 for IERC20; - using RoutineInvokerLibV1 for IStore; bytes32 public override key; - address public override lqt; - uint256 private _transferToStrategyEntry = 0; - uint256 private _receiveFromStrategyEntry = 0; + address public override sc; - /** - * @dev Constructs this contract - * @param store Provide the store contract instance - * @param coverKey Enter the cover key or cover this contract is related to - * @param liquidityToken Provide the liquidity token instance for this Vault - */ constructor( IStore store, bytes32 coverKey, - IERC20 liquidityToken - ) ERC20(VaultLibV1.getPodTokenNameInternal(coverKey), "POD") Recoverable(store) { + IERC20 stablecoin + ) ERC20(_getTokenName(coverKey), "POD") Recoverable(store) { key = coverKey; - lqt = address(liquidityToken); - } - - function transferGovernance( - bytes32 coverKey, - address to, - uint256 amount - ) external override nonReentrant { - s.mustNotBePaused(); - s.callerMustBeClaimsProcessorContract(); - - require(coverKey == key, "Forbidden"); - require(amount > 0, "Please specify amount"); - - // @suppress-malicious-erc20 `lqt` can't be manipulated via user input. - IERC20(lqt).ensureTransfer(to, amount); - emit GovernanceTransfer(to, amount); - } - - function transferToStrategy( - IERC20 token, - bytes32 coverKey, - bytes32 strategyName, - uint256 amount - ) external override { - // @suppress-reentrancy Custom reentrancy guard implemented - require(_transferToStrategyEntry == 0, "Access is denied"); - require(amount > 0, "Please specify amount"); - - _transferToStrategyEntry = 1; - - s.mustNotBePaused(); - s.callerMustBeStrategyContract(); - require(coverKey == key, "Forbidden"); - - // @suppress-malicious-erc20 `token` can only be specified by strategy contract. - s.transferToStrategyInternal(token, coverKey, strategyName, amount); - emit StrategyTransfer(address(token), msg.sender, strategyName, amount); - _transferToStrategyEntry = 0; - } - - function receiveFromStrategy( - IERC20 token, - bytes32 coverKey, - bytes32 strategyName, - uint256 amount - ) external override { - // @suppress-reentrancy Custom reentrancy guard implemented - require(_receiveFromStrategyEntry == 0, "Access is denied"); - require(amount > 0, "Please specify amount"); - - _receiveFromStrategyEntry = 1; - - s.mustNotBePaused(); - s.callerMustBeStrategyContract(); - require(coverKey == key, "Forbidden"); - - // @suppress-malicious-erc20 `token` can only be specified by strategy contract. - (uint256 income, uint256 loss) = s.receiveFromStrategyInternal(token, coverKey, strategyName, amount); - - emit StrategyReceipt(address(token), msg.sender, strategyName, amount, income, loss); - _receiveFromStrategyEntry = 0; - } - - /** - * @dev Returns the stablecoin balance of this vault - * This also includes amounts lent out in lending strategies - */ - function getStablecoinBalanceOf() external view override returns (uint256) { - return s.getStablecoinBalanceOfInternal(key); - } - - /** - * @dev Adds liquidity to the specified cover contract - * @param coverKey Enter the cover key - * @param amount Enter the amount of liquidity token to supply. - * @param npmStakeToAdd Enter the amount of NPM token to stake. - */ - function addLiquidity( - bytes32 coverKey, - uint256 amount, - uint256 npmStakeToAdd - ) external override nonReentrant { - // @suppress-acl Marking this as publicly accessible - s.mustNotBePaused(); - s.mustHaveNormalCoverStatus(key); - - require(coverKey == key, "Forbidden"); - require(amount > 0, "Please specify amount"); - - uint256 podsToMint = s.addLiquidityInternal(coverKey, address(this), lqt, msg.sender, amount, npmStakeToAdd); - super._mint(msg.sender, podsToMint); - - s.updateStateAndLiquidity(key); - - emit PodsIssued(msg.sender, podsToMint, amount); - } - - function accrueInterest() external override nonReentrant { - s.mustNotBePaused(); - AccessControlLibV1.mustBeLiquidityManager(s); - s.accrueInterestInternal(key); - - emit InterestAccrued(key); - } - - /** - * @dev Removes liquidity from the specified cover contract - * @param coverKey Enter the cover key - * @param podsToRedeem Enter the amount of pods to redeem - * @param npmStakeToRemove Enter the amount of NPM stake to remove. - */ - function removeLiquidity( - bytes32 coverKey, - uint256 podsToRedeem, - uint256 npmStakeToRemove, - bool exit - ) external override nonReentrant { - // @suppress-acl Marking this as publicly accessible - s.mustNotBePaused(); - s.mustBeAccrued(coverKey); - - require(podsToRedeem > 0, "Please specify amount"); - - require(coverKey == key, "Forbidden"); - uint256 released = s.removeLiquidityInternal(coverKey, address(this), podsToRedeem, npmStakeToRemove, exit); - - emit PodsRedeemed(msg.sender, podsToRedeem, released); - } - - /** - * @dev Calculates the amount of PODS to mint for the given amount of liquidity to transfer - */ - function calculatePods(uint256 forStablecoinUnits) external view override returns (uint256) { - return s.calculatePodsInternal(key, address(this), forStablecoinUnits); - } - - /** - * @dev Calculates the amount of stablecoins to withdraw for the given amount of PODs to redeem - */ - function calculateLiquidity(uint256 podsToBurn) external view override returns (uint256) { - return s.calculateLiquidityInternal(key, address(this), lqt, podsToBurn); - } - - /** - * @dev Gets information of a given vault by the cover key - * @param you The address for which the info will be customized - * @param values[0] totalPods --> Total PODs in existence - * @param values[1] balance --> Stablecoins held in the vault - * @param values[2] extendedBalance --> Stablecoins lent outside of the protocol - * @param values[3] totalReassurance -- > Total reassurance for this cover - * @param values[4] myPodBalance --> Your POD Balance - * @param values[5] myDeposits --> Sum of your deposits (in stablecoin) - * @param values[6] myWithdrawals --> Sum of your withdrawals (in stablecoin) - * @param values[7] myShare --> My share of the liquidity pool (in stablecoin) - * @param values[8] withdrawalOpen --> The timestamp when withdrawals are opened - * @param values[9] withdrawalClose --> The timestamp when withdrawals are closed again - */ - function getInfo(address you) external view override returns (uint256[] memory values) { - return s.getInfoInternal(key, address(this), lqt, you); + sc = address(stablecoin); } - /** - * @dev Version number of this contract - */ - function version() external pure override returns (bytes32) { - return "v0.1"; + function _getTokenName(bytes32 coverKey) private pure returns (string memory) { + return string(abi.encodePacked(string(abi.encodePacked(coverKey)), "-pod")); } - /** - * @dev Name of this contract - */ - function getName() external pure override returns (bytes32) { - return ProtoUtilV1.CNAME_LIQUIDITY_VAULT; + function delgate() public view returns (IVaultDelegate) { + address delegate = s.getVaultDelegate(); + return IVaultDelegate(delegate); } } diff --git a/contracts/core/liquidity/VaultFactory.sol b/contracts/core/liquidity/VaultFactory.sol index 8d9bdb43..7d4445df 100644 --- a/contracts/core/liquidity/VaultFactory.sol +++ b/contracts/core/liquidity/VaultFactory.sol @@ -34,7 +34,7 @@ contract VaultFactory is IVaultFactory, Recoverable { function deploy(IStore s, bytes32 key) external override nonReentrant returns (address addr) { s.mustNotBePaused(); s.mustHaveNormalCoverStatus(key); - s.callerMustBeCoverContract(); + s.senderMustBeCoverContract(); (bytes memory bytecode, bytes32 salt) = VaultFactoryLibV1.getByteCode(s, key, s.getStablecoin()); diff --git a/contracts/core/liquidity/VaultLiquidity.sol b/contracts/core/liquidity/VaultLiquidity.sol new file mode 100644 index 00000000..b99ec4c0 --- /dev/null +++ b/contracts/core/liquidity/VaultLiquidity.sol @@ -0,0 +1,158 @@ +// Neptune Mutual Protocol (https://neptunemutual.com) +// SPDX-License-Identifier: BUSL-1.1 +import "./VaultBase.sol"; +import "hardhat/console.sol"; + +pragma solidity 0.8.0; + +abstract contract VaultLiquidity is VaultBase { + using ProtoUtilV1 for IStore; + using RegistryLibV1 for IStore; + using NTransferUtilV2 for IERC20; + + function transferGovernance( + bytes32 coverKey, + address to, + uint256 amount + ) external override nonReentrant { + require(coverKey == key, "Forbidden"); + + /****************************************************************************************** + PRE + ******************************************************************************************/ + address stablecoin = delgate().preTransferGovernance(msg.sender, coverKey, to, amount); + + /****************************************************************************************** + BODY + ******************************************************************************************/ + + IERC20(stablecoin).ensureTransfer(to, amount); + + /****************************************************************************************** + POST + ******************************************************************************************/ + delgate().postTransferGovernance(msg.sender, coverKey, to, amount); + emit GovernanceTransfer(to, amount); + } + + /** + * @dev Adds liquidity to the specified cover contract + * @param coverKey Enter the cover key + * @param amount Enter the amount of liquidity token to supply. + * @param npmStakeToAdd Enter the amount of NPM token to stake. + */ + function addLiquidity( + bytes32 coverKey, + uint256 amount, + uint256 npmStakeToAdd + ) external override nonReentrant { + // @suppress-acl Marking this as publicly accessible + require(coverKey == key, "Forbidden"); + + /****************************************************************************************** + PRE + ******************************************************************************************/ + + (uint256 podsToMint, uint256 previousNpmStake) = delgate().preAddLiquidity(msg.sender, coverKey, amount, npmStakeToAdd); + + require(podsToMint > 0, "Can't determine PODs"); + + /****************************************************************************************** + BODY + ******************************************************************************************/ + + IERC20(sc).ensureTransferFrom(msg.sender, address(this), amount); + + if (npmStakeToAdd > 0) { + IERC20(s.getNpmTokenAddress()).ensureTransferFrom(msg.sender, address(this), npmStakeToAdd); + } + + super._mint(msg.sender, podsToMint); + + /****************************************************************************************** + POST + ******************************************************************************************/ + + delgate().postAddLiquidity(msg.sender, coverKey, amount, npmStakeToAdd); + + emit PodsIssued(msg.sender, podsToMint, amount); + + if (previousNpmStake == 0) { + emit Entered(coverKey, msg.sender); + } + + emit NPMStaken(msg.sender, npmStakeToAdd); + } + + /** + * @dev Removes liquidity from the specified cover contract + * @param coverKey Enter the cover key + * @param podsToRedeem Enter the amount of pods to redeem + * @param npmStakeToRemove Enter the amount of NPM stake to remove. + */ + function removeLiquidity( + bytes32 coverKey, + uint256 podsToRedeem, + uint256 npmStakeToRemove, + bool exit + ) external override nonReentrant { + // @suppress-acl Marking this as publicly accessible + require(coverKey == key, "Forbidden"); + + /****************************************************************************************** + PRE + ******************************************************************************************/ + (address stablecoin, uint256 stablecoinToRelease) = delgate().preRemoveLiquidity(msg.sender, coverKey, podsToRedeem, npmStakeToRemove, exit); + + /****************************************************************************************** + BODY + ******************************************************************************************/ + IERC20(address(this)).ensureTransferFrom(msg.sender, address(this), podsToRedeem); + IERC20(stablecoin).ensureTransfer(msg.sender, stablecoinToRelease); + + // Unstake NPM tokens + if (npmStakeToRemove > 0) { + IERC20(s.getNpmTokenAddress()).ensureTransfer(msg.sender, npmStakeToRemove); + } + + /****************************************************************************************** + POST + ******************************************************************************************/ + delgate().postRemoveLiquidity(msg.sender, coverKey, podsToRedeem, npmStakeToRemove, exit); + + emit PodsRedeemed(msg.sender, podsToRedeem, stablecoinToRelease); + + if (exit) { + emit Exited(coverKey, msg.sender); + } + + emit NPMUnstaken(msg.sender, npmStakeToRemove); + } + + /** + * @dev Calculates the amount of PODS to mint for the given amount of liquidity to transfer + */ + function calculatePods(uint256 forStablecoinUnits) external view override returns (uint256) { + return delgate().calculatePodsImplementation(key, forStablecoinUnits); + } + + /** + * @dev Calculates the amount of stablecoins to withdraw for the given amount of PODs to redeem + */ + function calculateLiquidity(uint256 podsToBurn) external view override returns (uint256) { + return delgate().calculateLiquidityImplementation(key, podsToBurn); + } + + /** + * @dev Returns the stablecoin balance of this vault + * This also includes amounts lent out in lending strategies + */ + function getStablecoinBalanceOf() external view override returns (uint256) { + return delgate().getStablecoinBalanceOfImplementation(key); + } + + function accrueInterest() external override nonReentrant { + delgate().accrueInterestImplementation(msg.sender, key); + emit InterestAccrued(key); + } +} diff --git a/contracts/core/liquidity/VaultStrategy.sol b/contracts/core/liquidity/VaultStrategy.sol new file mode 100644 index 00000000..3cdd8974 --- /dev/null +++ b/contracts/core/liquidity/VaultStrategy.sol @@ -0,0 +1,80 @@ +// Neptune Mutual Protocol (https://neptunemutual.com) +// SPDX-License-Identifier: BUSL-1.1 +import "./VaultLiquidity.sol"; + +pragma solidity 0.8.0; + +abstract contract VaultStrategy is VaultLiquidity { + using ProtoUtilV1 for IStore; + using RegistryLibV1 for IStore; + using NTransferUtilV2 for IERC20; + + uint256 private _transferToStrategyEntry = 0; + uint256 private _receiveFromStrategyEntry = 0; + + function transferToStrategy( + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external override { + // @suppress-reentrancy Custom reentrancy guard implemented + require(coverKey == key, "Forbidden"); + require(_transferToStrategyEntry == 0, "Access is denied"); + require(amount > 0, "Please specify amount"); + + _transferToStrategyEntry = 1; + + /****************************************************************************************** + PRE + ******************************************************************************************/ + delgate().preTransferToStrategy(msg.sender, token, coverKey, strategyName, amount); + + /****************************************************************************************** + BODY + ******************************************************************************************/ + + token.ensureTransfer(msg.sender, amount); + + /****************************************************************************************** + POST + ******************************************************************************************/ + delgate().postTransferToStrategy(msg.sender, token, coverKey, strategyName, amount); + + emit StrategyTransfer(address(token), msg.sender, strategyName, amount); + _transferToStrategyEntry = 0; + } + + function receiveFromStrategy( + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external override { + // @suppress-reentrancy Custom reentrancy guard implemented + require(coverKey == key, "Forbidden"); + require(_receiveFromStrategyEntry == 0, "Access is denied"); + require(amount > 0, "Please specify amount"); + + _receiveFromStrategyEntry = 1; + + /****************************************************************************************** + PRE + ******************************************************************************************/ + delgate().preReceiveFromStrategy(msg.sender, token, coverKey, strategyName, amount); + + /****************************************************************************************** + BODY + ******************************************************************************************/ + + token.ensureTransferFrom(msg.sender, address(this), amount); + + /****************************************************************************************** + POST + ******************************************************************************************/ + (uint256 income, uint256 loss) = delgate().postReceiveFromStrategy(msg.sender, token, coverKey, strategyName, amount); + + emit StrategyReceipt(address(token), msg.sender, strategyName, amount, income, loss); + _receiveFromStrategyEntry = 0; + } +} diff --git a/contracts/core/liquidity/WithFlashLoan.sol b/contracts/core/liquidity/WithFlashLoan.sol index 34478540..305d43a3 100644 --- a/contracts/core/liquidity/WithFlashLoan.sol +++ b/contracts/core/liquidity/WithFlashLoan.sol @@ -2,66 +2,59 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.0; -import "./VaultBase.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; import "openzeppelin-solidity/contracts/interfaces/IERC3156FlashLender.sol"; +import "./VaultStrategy.sol"; -/** - * @title With Flash Loan Contract - * @dev WithFlashLoan contract implements `EIP-3156 Flash Loan`. - * Using flash loans, you can borrow up to the total available amount of - * the stablecoin liquidity available in this cover liquidity pool. - * You need to return back the borrowed amount + fee in the same transaction. - * The function `flashFee` enables you to check, in advance, fee that - * you need to pay to take out the loan. - */ -abstract contract WithFlashLoan is VaultBase, IERC3156FlashLender { +abstract contract WithFlashLoan is VaultStrategy, IERC3156FlashLender { using ProtoUtilV1 for IStore; - using StoreKeyUtil for IStore; - using ValidationLibV1 for IStore; - using VaultLibV1 for IStore; + using RegistryLibV1 for IStore; using NTransferUtilV2 for IERC20; - using RoutineInvokerLibV1 for IStore; - /** - * @dev The fee to be charged for a given loan. - * @param token The loan currency. - * @param amount 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 amount) external view override returns (uint256) { - return s.getFlashFeeInternal(token, amount); - } - - /** - * @dev The amount of currency available to be lent. - * @param token The loan currency. - * @return The amount of `token` that can be borrowed. - */ - function maxFlashLoan(address token) external view override returns (uint256) { - return s.getMaxFlashLoanInternal(token); - } - - /** - * @dev Initiate a flash loan. - * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ function flashLoan( IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data ) external override nonReentrant returns (bool) { - // @suppress-acl Marking this as publicly accessible - // @suppress-address-trust-issue The instance of `token` can be trusted because we're ensuring it matches with the protocol stablecoin address. - s.mustNotBePaused(); - require(amount > 0, "Please specify amount"); - uint256 fee = s.flashLoanInternal(receiver, key, token, amount, data); + /****************************************************************************************** + PRE + ******************************************************************************************/ + (IERC20 stablecoin, uint256 fee, uint256 protocolFee) = delgate().preFlashLoan(msg.sender, key, receiver, token, amount, data); + + /****************************************************************************************** + BODY + ******************************************************************************************/ + // @suppress-address-trust-issue, @suppress-malicious-erc20 `stablecoin` can't be manipulated via user input. + uint256 previousBalance = stablecoin.balanceOf(address(this)); + require(previousBalance >= amount, "Balance insufficient"); + + stablecoin.ensureTransfer(address(receiver), amount); + require(receiver.onFlashLoan(msg.sender, token, amount, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"), "IERC3156: Callback failed"); + stablecoin.ensureTransferFrom(address(receiver), address(this), amount + fee); + stablecoin.ensureTransfer(s.getTreasury(), protocolFee); + + uint256 finalBalance = stablecoin.balanceOf(address(this)); + require(finalBalance >= previousBalance + fee, "Access is denied"); + + /****************************************************************************************** + POST + ******************************************************************************************/ + + delgate().postFlashLoan(msg.sender, key, receiver, token, amount, data); + emit FlashLoanBorrowed(address(this), address(receiver), token, amount, fee); + return true; } + + function flashFee(address token, uint256 amount) external view override returns (uint256) { + return delgate().getFlashFee(msg.sender, key, token, amount); + } + + function maxFlashLoan(address token) external view override returns (uint256) { + return delgate().getMaxFlashLoan(msg.sender, key, token); + } } diff --git a/contracts/core/liquidity/strategies/AaveStrategy.sol b/contracts/core/liquidity/strategies/AaveStrategy.sol index 68d1c262..8345e27c 100644 --- a/contracts/core/liquidity/strategies/AaveStrategy.sol +++ b/contracts/core/liquidity/strategies/AaveStrategy.sol @@ -75,7 +75,7 @@ contract AaveStrategy is ILendingStrategy, Recoverable { */ function deposit(bytes32 coverKey, uint256 amount) external override nonReentrant returns (uint256 aTokenReceived) { s.mustNotBePaused(); - s.callerMustBeProtocolMember(); + s.senderMustBeProtocolMember(); IVault vault = s.getVault(coverKey); @@ -121,7 +121,7 @@ contract AaveStrategy is ILendingStrategy, Recoverable { */ function withdraw(bytes32 coverKey) external virtual override nonReentrant returns (uint256 stablecoinWithdrawn) { s.mustNotBePaused(); - s.callerMustBeProtocolMember(); + s.senderMustBeProtocolMember(); IVault vault = s.getVault(coverKey); // @suppress-malicious-erc20 `stablecoin`, `aToken` can't be manipulated via user input. diff --git a/contracts/core/liquidity/strategies/CompoundStrategy.sol b/contracts/core/liquidity/strategies/CompoundStrategy.sol index 9994391f..fb3d668b 100644 --- a/contracts/core/liquidity/strategies/CompoundStrategy.sol +++ b/contracts/core/liquidity/strategies/CompoundStrategy.sol @@ -75,7 +75,7 @@ contract CompoundStrategy is ILendingStrategy, Recoverable { */ function deposit(bytes32 coverKey, uint256 amount) external override nonReentrant returns (uint256 cDaiMinted) { s.mustNotBePaused(); - s.callerMustBeProtocolMember(); + s.senderMustBeProtocolMember(); IVault vault = s.getVault(coverKey); @@ -123,7 +123,7 @@ contract CompoundStrategy is ILendingStrategy, Recoverable { */ function withdraw(bytes32 coverKey) external virtual override nonReentrant returns (uint256 stablecoinWithdrawn) { s.mustNotBePaused(); - s.callerMustBeProtocolMember(); + s.senderMustBeProtocolMember(); IVault vault = s.getVault(coverKey); // @suppress-malicious-erc20 `stablecoin`, `cDai` can't be manipulated via user input. diff --git a/contracts/interfaces/ICxTokenFactory.sol b/contracts/interfaces/ICxTokenFactory.sol index 5bb4f27c..c2cf2fbe 100644 --- a/contracts/interfaces/ICxTokenFactory.sol +++ b/contracts/interfaces/ICxTokenFactory.sol @@ -5,7 +5,7 @@ import "./IStore.sol"; import "./IMember.sol"; interface ICxTokenFactory is IMember { - event CxTokenDeployed(bytes32 indexed key, address cxToken, uint256 expiryDate); + event CxTokenDeployed(bytes32 indexed key, address cxToken, uint256 expiryDate, string name, string symbol); function deploy( IStore s, diff --git a/contracts/interfaces/IVault.sol b/contracts/interfaces/IVault.sol index 9cd0c092..a607a09c 100644 --- a/contracts/interfaces/IVault.sol +++ b/contracts/interfaces/IVault.sol @@ -11,11 +11,15 @@ interface IVault is IMember, IERC20 { event PodsIssued(address indexed account, uint256 issued, uint256 liquidityAdded); event PodsRedeemed(address indexed account, uint256 redeemed, uint256 liquidityReleased); event FlashLoanBorrowed(address indexed lender, address indexed borrower, address indexed stablecoin, uint256 amount, uint256 fee); + event NPMStaken(address indexed account, uint256 amount); + event NPMUnstaken(address indexed account, uint256 amount); event InterestAccrued(bytes32 indexed key); + event Entered(bytes32 indexed key, address indexed account); + event Exited(bytes32 indexed key, address indexed account); function key() external view returns (bytes32); - function lqt() external view returns (address); + function sc() external view returns (address); /** * @dev Adds liquidity to the specified cover contract diff --git a/contracts/interfaces/IVaultDelegate.sol b/contracts/interfaces/IVaultDelegate.sol new file mode 100644 index 00000000..a2c6f992 --- /dev/null +++ b/contracts/interfaces/IVaultDelegate.sol @@ -0,0 +1,131 @@ +// Neptune Mutual Protocol (https://neptunemutual.com) +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.0; +import "./IMember.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-solidity/contracts/interfaces/IERC3156FlashLender.sol"; + +interface IVaultDelegate is IMember { + function preAddLiquidity( + address caller, + bytes32 coverKey, + uint256 amount, + uint256 npmStake + ) external returns (uint256 podsToMint, uint256 previousNPMStake); + + function postAddLiquidity( + address caller, + bytes32 coverKey, + uint256 amount, + uint256 npmStake + ) external; + + function accrueInterestImplementation(address caller, bytes32 coverKey) external; + + function preRemoveLiquidity( + address caller, + bytes32 coverKey, + uint256 amount, + uint256 npmStake, + bool exit + ) external returns (address stablecoin, uint256 stableCoinToRelease); + + function postRemoveLiquidity( + address caller, + bytes32 coverKey, + uint256 amount, + uint256 npmStake, + bool exit + ) external; + + function preTransferGovernance( + address caller, + bytes32 coverKey, + address to, + uint256 amount + ) external returns (address stablecoin); + + function postTransferGovernance( + address caller, + bytes32 coverKey, + address to, + uint256 amount + ) external; + + function preTransferToStrategy( + address caller, + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external; + + function postTransferToStrategy( + address caller, + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external; + + function preReceiveFromStrategy( + address caller, + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external; + + function postReceiveFromStrategy( + address caller, + IERC20 token, + bytes32 coverKey, + bytes32 strategyName, + uint256 amount + ) external returns (uint256 income, uint256 loss); + + function preFlashLoan( + address caller, + bytes32 coverKey, + IERC3156FlashBorrower receiver, + address token, + uint256 amount, + bytes calldata data + ) + external + returns ( + IERC20 stablecoin, + uint256 fee, + uint256 protocolFee + ); + + function postFlashLoan( + address caller, + bytes32 coverKey, + IERC3156FlashBorrower receiver, + address token, + uint256 amount, + bytes calldata data + ) external; + + function calculatePodsImplementation(bytes32 coverKey, uint256 forStablecoinUnits) external view returns (uint256); + + function calculateLiquidityImplementation(bytes32 coverKey, uint256 podsToBurn) external view returns (uint256); + + function getInfoImplementation(bytes32 coverKey, address forAccount) external view returns (uint256[] memory result); + + function getStablecoinBalanceOfImplementation(bytes32 coverKey) external view returns (uint256); + + function getFlashFee( + address caller, + bytes32 coverKey, + address token, + uint256 amount + ) external view returns (uint256); + + function getMaxFlashLoan( + address caller, + bytes32 coverKey, + address token + ) external view returns (uint256); +} diff --git a/contracts/libraries/AccessControlLibV1.sol b/contracts/libraries/AccessControlLibV1.sol index 58f10ff6..ad121e66 100644 --- a/contracts/libraries/AccessControlLibV1.sol +++ b/contracts/libraries/AccessControlLibV1.sol @@ -23,70 +23,137 @@ library AccessControlLibV1 { * @dev Reverts if the sender is not the protocol admin. */ function mustBeAdmin(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_ADMIN); + _mustHaveAccess(s, NS_ROLES_ADMIN, msg.sender); } /** * @dev Reverts if the sender is not the cover manager. */ function mustBeCoverManager(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_COVER_MANAGER); + _mustHaveAccess(s, NS_ROLES_COVER_MANAGER, msg.sender); } /** * @dev Reverts if the sender is not the liquidity manager. */ function mustBeLiquidityManager(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_LIQUIDITY_MANAGER); + _mustHaveAccess(s, NS_ROLES_LIQUIDITY_MANAGER, msg.sender); } /** * @dev Reverts if the sender is not a governance agent. */ function mustBeGovernanceAgent(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_GOVERNANCE_AGENT); + _mustHaveAccess(s, NS_ROLES_GOVERNANCE_AGENT, msg.sender); } /** * @dev Reverts if the sender is not a governance admin. */ function mustBeGovernanceAdmin(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_GOVERNANCE_ADMIN); + _mustHaveAccess(s, NS_ROLES_GOVERNANCE_ADMIN, msg.sender); } /** * @dev Reverts if the sender is not an upgrade agent. */ function mustBeUpgradeAgent(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_UPGRADE_AGENT); + _mustHaveAccess(s, NS_ROLES_UPGRADE_AGENT, msg.sender); } /** * @dev Reverts if the sender is not a recovery agent. */ function mustBeRecoveryAgent(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_RECOVERY_AGENT); + _mustHaveAccess(s, NS_ROLES_RECOVERY_AGENT, msg.sender); } /** * @dev Reverts if the sender is not the pause agent. */ function mustBePauseAgent(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_PAUSE_AGENT); + _mustHaveAccess(s, NS_ROLES_PAUSE_AGENT, msg.sender); } /** * @dev Reverts if the sender is not the unpause agent. */ function mustBeUnpauseAgent(IStore s) external view { - _mustHaveAccess(s, NS_ROLES_UNPAUSE_AGENT); + _mustHaveAccess(s, NS_ROLES_UNPAUSE_AGENT, msg.sender); + } + + /** + * @dev Reverts if the sender is not the protocol admin. + */ + function callerMustBeAdmin(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_ADMIN, caller); + } + + /** + * @dev Reverts if the sender is not the cover manager. + */ + function callerMustBeCoverManager(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_COVER_MANAGER, caller); + } + + /** + * @dev Reverts if the sender is not the liquidity manager. + */ + function callerMustBeLiquidityManager(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_LIQUIDITY_MANAGER, caller); + } + + /** + * @dev Reverts if the sender is not a governance agent. + */ + function callerMustBeGovernanceAgent(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_GOVERNANCE_AGENT, caller); + } + + /** + * @dev Reverts if the sender is not a governance admin. + */ + function callerMustBeGovernanceAdmin(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_GOVERNANCE_ADMIN, caller); + } + + /** + * @dev Reverts if the sender is not an upgrade agent. + */ + function callerMustBeUpgradeAgent(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_UPGRADE_AGENT, caller); + } + + /** + * @dev Reverts if the sender is not a recovery agent. + */ + function callerMustBeRecoveryAgent(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_RECOVERY_AGENT, caller); + } + + /** + * @dev Reverts if the sender is not the pause agent. + */ + function callerMustBePauseAgent(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_PAUSE_AGENT, caller); + } + + /** + * @dev Reverts if the sender is not the unpause agent. + */ + function callerMustBeUnpauseAgent(IStore s, address caller) external view { + _mustHaveAccess(s, NS_ROLES_UNPAUSE_AGENT, caller); } /** * @dev Reverts if the sender does not have access to the given role. */ - function _mustHaveAccess(IStore s, bytes32 role) private view { - require(hasAccess(s, role, msg.sender), "Forbidden"); + function _mustHaveAccess( + IStore s, + bytes32 role, + address caller + ) private view { + require(hasAccess(s, role, caller), "Forbidden"); } /** diff --git a/contracts/libraries/ProtoUtilV1.sol b/contracts/libraries/ProtoUtilV1.sol index a673c6b3..3b5c2959 100644 --- a/contracts/libraries/ProtoUtilV1.sol +++ b/contracts/libraries/ProtoUtilV1.sol @@ -35,6 +35,7 @@ library ProtoUtilV1 { bytes32 public constant CNS_COVER_POLICY_ADMIN = "cns:cover:policy:admin"; bytes32 public constant CNS_COVER_STAKE = "cns:cover:stake"; bytes32 public constant CNS_COVER_VAULT = "cns:cover:vault"; + bytes32 public constant CNS_COVER_VAULT_DELEGATE = "cns:cover:vault:delegate"; bytes32 public constant CNS_COVER_STABLECOIN = "cns:cover:sc"; bytes32 public constant CNS_COVER_CXTOKEN_FACTORY = "cns:cover:cxtoken:factory"; bytes32 public constant CNS_COVER_VAULT_FACTORY = "cns:cover:vault:factory"; @@ -216,6 +217,7 @@ library ProtoUtilV1 { bytes32 public constant CNAME_COVER_STAKE = "CoverStake"; bytes32 public constant CNAME_COVER_REASSURANCE = "CoverReassurance"; bytes32 public constant CNAME_LIQUIDITY_VAULT = "Vault"; + bytes32 public constant CNAME_VAULT_DELEGATE = "VaultDelegate"; bytes32 public constant CNAME_LIQUIDITY_ENGINE = "LiquidityEngine"; bytes32 public constant CNAME_STRATEGY_AAVE = "AaveStrategy"; bytes32 public constant CNAME_STRATEGY_COMPOUND = "CompoundStrategy"; @@ -262,8 +264,20 @@ library ProtoUtilV1 { * @dev Ensures that the sender matches with the exact contract having the specified name. * @param name Enter the name of the contract */ - function callerMustBeExactContract(IStore s, bytes32 name) external view { - return mustBeExactContract(s, name, msg.sender); + function senderMustBeExactContract(IStore s, bytes32 name) external view { + return callerMustBeExactContract(s, name, msg.sender); + } + + /** + * @dev Ensures that the sender matches with the exact contract having the specified name. + * @param name Enter the name of the contract + */ + function callerMustBeExactContract( + IStore s, + bytes32 name, + address caller + ) public view { + return mustBeExactContract(s, name, caller); } function npmToken(IStore s) external view returns (IERC20) { diff --git a/contracts/libraries/RegistryLibV1.sol b/contracts/libraries/RegistryLibV1.sol index 03f04c49..31b8e8d2 100644 --- a/contracts/libraries/RegistryLibV1.sol +++ b/contracts/libraries/RegistryLibV1.sol @@ -77,6 +77,11 @@ library RegistryLibV1 { return vault; } + function getVaultDelegate(IStore s) external view returns (address) { + address vaultImplementation = getProtocolContract(s, ProtoUtilV1.CNS_COVER_VAULT_DELEGATE); + return vaultImplementation; + } + function getStakingPoolAddress(IStore s) external view returns (address) { address vault = getProtocolContract(s, ProtoUtilV1.CNS_STAKING_POOL); return vault; diff --git a/contracts/libraries/RoutineInvokerLibV1.sol b/contracts/libraries/RoutineInvokerLibV1.sol index 5daaf355..ece99c5d 100644 --- a/contracts/libraries/RoutineInvokerLibV1.sol +++ b/contracts/libraries/RoutineInvokerLibV1.sol @@ -19,6 +19,7 @@ library RoutineInvokerLibV1 { using StrategyLibV1 for IStore; using CoverUtilV1 for IStore; using StoreKeyUtil for IStore; + using ValidationLibV1 for IStore; enum Action { Deposit, @@ -41,21 +42,8 @@ library RoutineInvokerLibV1 { } } - function mustBeAccrued(IStore s, bytes32 coverKey) external view { - require(isAccrualComplete(s, coverKey) == true, "Wait for accrual"); - } - - function accrueInterestInternal(IStore s, bytes32 coverKey) external { - (bool isWithdrawalPeriod, , , , ) = _getWithdrawalInfo(s, coverKey); - require(isWithdrawalPeriod == true, "Withdrawal hasn't yet begun"); - - _invokeAssetManagement(s, coverKey); - - setAccrualComplete(s, coverKey, true); - } - - function _getWithdrawalInfo(IStore s, bytes32 coverKey) - private + function getWithdrawalInfoInternal(IStore s, bytes32 coverKey) + public view returns ( bool isWithdrawalPeriod, @@ -78,7 +66,7 @@ library RoutineInvokerLibV1 { } function _executeIsWithdrawalPeriod(IStore s, bytes32 coverKey) private returns (bool) { - (bool isWithdrawalPeriod, uint256 lendingPeriod, uint256 withdrawalWindow, uint256 start, uint256 end) = _getWithdrawalInfo(s, coverKey); + (bool isWithdrawalPeriod, uint256 lendingPeriod, uint256 withdrawalWindow, uint256 start, uint256 end) = getWithdrawalInfoInternal(s, coverKey); // Without a lending period and withdrawal window, deposit is not possible if (lendingPeriod == 0 || withdrawalWindow == 0) { @@ -104,17 +92,17 @@ library RoutineInvokerLibV1 { s.setUintByKey(getNextWithdrawalStartKey(coverKey), start); s.setUintByKey(getNextWithdrawalEndKey(coverKey), end); - setAccrualComplete(s, coverKey, false); + setAccrualCompleteInternal(s, coverKey, false); } return false; } - function isAccrualComplete(IStore s, bytes32 coverKey) public view returns (bool) { + function isAccrualCompleteInternal(IStore s, bytes32 coverKey) external view returns (bool) { return s.getBoolByKey(getAccrualInvocationKey(coverKey)); } - function setAccrualComplete( + function setAccrualCompleteInternal( IStore s, bytes32 coverKey, bool flag diff --git a/contracts/libraries/StrategyLibV1.sol b/contracts/libraries/StrategyLibV1.sol index 7c8b90e5..34a181f0 100644 --- a/contracts/libraries/StrategyLibV1.sol +++ b/contracts/libraries/StrategyLibV1.sol @@ -10,9 +10,9 @@ import "./ProtoUtilV1.sol"; import "./NTransferUtilV2.sol"; library StrategyLibV1 { + using NTransferUtilV2 for IERC20; using ProtoUtilV1 for IStore; using StoreKeyUtil for IStore; - using NTransferUtilV2 for IERC20; event StrategyAdded(address indexed strategy); @@ -136,15 +136,13 @@ library StrategyLibV1 { return s.getUintByKey(k); } - function transferToStrategyInternal( + function preTransferToStrategyInternal( IStore s, IERC20 token, bytes32 coverKey, bytes32 strategyName, uint256 amount ) external { - // @suppress-malicious-erc20 @note: token should be checked on the calling contract - token.ensureTransfer(msg.sender, amount); bool isStablecoin = s.getStablecoin() == address(token) ? true : false; if (isStablecoin == false) { @@ -155,15 +153,13 @@ library StrategyLibV1 { _addToSpecificStrategyOut(s, coverKey, strategyName, address(token), amount); } - function receiveFromStrategyInternal( + function postReceiveFromStrategyInternal( IStore s, IERC20 token, bytes32 coverKey, bytes32 strategyName, uint256 toReceive ) external returns (uint256 income, uint256 loss) { - // @suppress-malicious-erc20 token should be checked on the calling contract - token.ensureTransferFrom(msg.sender, address(this), toReceive); bool isStablecoin = s.getStablecoin() == address(token) ? true : false; if (isStablecoin == false) { diff --git a/contracts/libraries/ValidationLibV1.sol b/contracts/libraries/ValidationLibV1.sol index 7b86c3d0..992e5c9f 100644 --- a/contracts/libraries/ValidationLibV1.sol +++ b/contracts/libraries/ValidationLibV1.sol @@ -93,39 +93,58 @@ library ValidationLibV1 { require(isCoverOwner || isCoverContract, "Forbidden"); } - function callerMustBeCoverOwnerOrAdmin(IStore s, bytes32 key) external view { + function senderMustBeCoverOwnerOrAdmin(IStore s, bytes32 key) external view { if (AccessControlLibV1.hasAccess(s, AccessControlLibV1.NS_ROLES_ADMIN, msg.sender) == false) { mustBeCoverOwner(s, key, msg.sender); } } - function callerMustBePolicyContract(IStore s) external view { - s.callerMustBeExactContract(ProtoUtilV1.CNS_COVER_POLICY); + function senderMustBePolicyContract(IStore s) external view { + s.senderMustBeExactContract(ProtoUtilV1.CNS_COVER_POLICY); } - function callerMustBePolicyManagerContract(IStore s) external view { - s.callerMustBeExactContract(ProtoUtilV1.CNS_COVER_POLICY_MANAGER); + function senderMustBePolicyManagerContract(IStore s) external view { + s.senderMustBeExactContract(ProtoUtilV1.CNS_COVER_POLICY_MANAGER); } - function callerMustBeCoverContract(IStore s) external view { - s.callerMustBeExactContract(ProtoUtilV1.CNS_COVER); + function senderMustBeCoverContract(IStore s) external view { + s.senderMustBeExactContract(ProtoUtilV1.CNS_COVER); } - function callerMustBeVaultContract(IStore s, bytes32 key) external view { + function senderMustBeVaultContract(IStore s, bytes32 key) external view { address vault = s.getVaultAddress(key); require(msg.sender == vault, "Forbidden"); } - function callerMustBeGovernanceContract(IStore s) external view { - s.callerMustBeExactContract(ProtoUtilV1.CNS_GOVERNANCE); + function senderMustBeGovernanceContract(IStore s) external view { + s.senderMustBeExactContract(ProtoUtilV1.CNS_GOVERNANCE); } - function callerMustBeClaimsProcessorContract(IStore s) external view { - s.callerMustBeExactContract(ProtoUtilV1.CNS_CLAIM_PROCESSOR); + function senderMustBeClaimsProcessorContract(IStore s) external view { + s.senderMustBeExactContract(ProtoUtilV1.CNS_CLAIM_PROCESSOR); } - function callerMustBeStrategyContract(IStore s) external view { - bool callerIsStrategyContract = s.getBoolByKey(_getIsActiveStrategyKey(msg.sender)); + function callerMustBeClaimsProcessorContract(IStore s, address caller) external view { + s.callerMustBeExactContract(ProtoUtilV1.CNS_CLAIM_PROCESSOR, caller); + } + + function senderMustBeStrategyContract(IStore s) external view { + bool senderIsStrategyContract = s.getBoolByKey(_getIsActiveStrategyKey(msg.sender)); + require(senderIsStrategyContract == true, "Not a strategy contract"); + } + + function callerMustBeStrategyContract(IStore s, address caller) external view { + bool callerIsStrategyContract = s.getBoolByKey(_getIsActiveStrategyKey(caller)); + require(callerIsStrategyContract == true, "Not a strategy contract"); + } + + function callerMustBeSpecificStrategyContract( + IStore s, + address caller, + bytes32 /*strategyName*/ + ) external view { + // @todo + bool callerIsStrategyContract = s.getBoolByKey(_getIsActiveStrategyKey(caller)); require(callerIsStrategyContract == true, "Not a strategy contract"); } @@ -133,7 +152,7 @@ library ValidationLibV1 { return keccak256(abi.encodePacked(ProtoUtilV1.NS_LENDING_STRATEGY_ACTIVE, strategyAddress)); } - function callerMustBeProtocolMember(IStore s) external view { + function senderMustBeProtocolMember(IStore s) external view { require(s.isProtocolMember(msg.sender), "Forbidden"); } diff --git a/contracts/libraries/VaultLibV1.sol b/contracts/libraries/VaultLibV1.sol index 367df54b..355cfab6 100644 --- a/contracts/libraries/VaultLibV1.sol +++ b/contracts/libraries/VaultLibV1.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.0; import "../interfaces/IStore.sol"; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "./NTransferUtilV2.sol"; import "./ProtoUtilV1.sol"; import "./PolicyHelperV1.sol"; import "./StoreKeyUtil.sol"; @@ -16,7 +15,6 @@ import "./StrategyLibV1.sol"; import "openzeppelin-solidity/contracts/interfaces/IERC3156FlashLender.sol"; library VaultLibV1 { - using NTransferUtilV2 for IERC20; using ProtoUtilV1 for IStore; using PolicyHelperV1 for IStore; using StoreKeyUtil for IStore; @@ -38,7 +36,9 @@ library VaultLibV1 { uint256 balance = getStablecoinBalanceOfInternal(s, coverKey); uint256 podSupply = IERC20(pod).totalSupply(); - // This smart contract contains stablecoins without liquidity provider contribution + // This smart contract contains stablecoins without liquidity provider contribution. + // This can happen if someone wants to create a nuisance by sending stablecoin + // to this contract immediately after deployment. if (podSupply == 0 && balance > 0) { revert("Liquidity/POD mismatch"); } @@ -64,12 +64,11 @@ library VaultLibV1 { IStore s, bytes32 coverKey, address pod, - address stablecoin, uint256 podsToBurn ) public view returns (uint256) { require(s.getBoolByKeys(ProtoUtilV1.NS_COVER_HAS_FLASH_LOAN, coverKey) == false, "On flash loan, please try again"); - uint256 contractStablecoinBalance = IERC20(stablecoin).balanceOf(address(this)); + uint256 contractStablecoinBalance = IERC20(s.getStablecoin()).balanceOf(s.getVaultAddress(coverKey)); return (contractStablecoinBalance * podsToBurn) / IERC20(pod).totalSupply(); } @@ -78,7 +77,6 @@ library VaultLibV1 { * @param s Provide a store instance * @param coverKey Specify cover key to obtain the info of. * @param pod Provide the address of the POD - * @param stablecoin Provide the address of the Vault stablecoin * @param you The address for which the info will be customized * @param values[0] totalPods --> Total PODs in existence * @param values[1] balance --> Stablecoins held in the vault @@ -95,19 +93,18 @@ library VaultLibV1 { IStore s, bytes32 coverKey, address pod, - address stablecoin, address you ) external view returns (uint256[] memory values) { values = new uint256[](11); values[0] = IERC20(pod).totalSupply(); // Total PODs in existence values[1] = getStablecoinBalanceOfInternal(s, coverKey); - values[2] = s.getAmountInStrategies(coverKey, stablecoin); // Stablecoins lent outside of the protocol + values[2] = s.getAmountInStrategies(coverKey, s.getStablecoin()); // Stablecoins lent outside of the protocol values[3] = s.getReassuranceAmountInternal(coverKey); // Total reassurance for this cover values[4] = IERC20(pod).balanceOf(you); // Your POD Balance values[5] = _getCoverLiquidityAddedInternal(s, coverKey, you); // Sum of your deposits (in stablecoin) values[6] = _getCoverLiquidityRemovedInternal(s, coverKey, you); // Sum of your withdrawals (in stablecoin) - values[7] = calculateLiquidityInternal(s, coverKey, pod, stablecoin, values[5]); // My share of the liquidity pool (in stablecoin) + values[7] = calculateLiquidityInternal(s, coverKey, pod, values[5]); // My share of the liquidity pool (in stablecoin) values[8] = s.getUintByKey(RoutineInvokerLibV1.getNextWithdrawalStartKey(coverKey)); values[9] = s.getUintByKey(RoutineInvokerLibV1.getNextWithdrawalEndKey(coverKey)); } @@ -129,36 +126,32 @@ library VaultLibV1 { } /** - * @dev Adds liquidity to the specified cover contract + * @dev Called before adding liquidity to the specified cover contract * @param coverKey Enter the cover key * @param account Specify the account on behalf of which the liquidity is being added. * @param amount Enter the amount of liquidity token to supply. * @param npmStakeToAdd Enter the amount of NPM token to stake. */ - function addLiquidityInternal( + function preAddLiquidityInternal( IStore s, bytes32 coverKey, address pod, - address stablecoin, address account, uint256 amount, uint256 npmStakeToAdd - ) external returns (uint256) { + ) external returns (uint256 podsToMint, uint256 myPreviousStake) { + s.mustNotBePaused(); + s.mustHaveNormalCoverStatus(coverKey); + // @suppress-address-trust-issue, @suppress-malicious-erc20 The address `stablecoin` can be trusted here because we are ensuring it matches with the protocol stablecoin address. // @suppress-address-trust-issue The address `account` can be trusted here because we are not treating it as a contract (even it were). require(account != address(0), "Invalid account"); - require(stablecoin == s.getStablecoin(), "Vault migration required"); // Update values - _updateNpmStake(s, coverKey, account, npmStakeToAdd); + myPreviousStake = _updateNpmStake(s, coverKey, account, npmStakeToAdd); _updateCoverLiquidity(s, coverKey, account, amount); - uint256 podsToMint = calculatePodsInternal(s, coverKey, pod, amount); - - IERC20(stablecoin).ensureTransferFrom(account, address(this), amount); - IERC20(s.getNpmTokenAddress()).ensureTransferFrom(account, address(this), npmStakeToAdd); - - return podsToMint; + podsToMint = calculatePodsInternal(s, coverKey, pod, amount); } function _updateNpmStake( @@ -166,8 +159,8 @@ library VaultLibV1 { bytes32 coverKey, address account, uint256 amount - ) private { - uint256 myPreviousStake = _getMyNpmStake(s, coverKey, account); + ) private returns (uint256 myPreviousStake) { + myPreviousStake = _getMyNpmStake(s, coverKey, account); require(amount + myPreviousStake >= s.getMinStakeToAddLiquidity(), "Insufficient stake"); if (amount > 0) { @@ -203,36 +196,48 @@ library VaultLibV1 { myStake = s.getUintByKey(CoverUtilV1.getCoverLiquidityStakeIndividualKey(coverKey, account)); } + function mustHaveNoBalanceInStrategies( + IStore s, + bytes32 coverKey, + address stablecoin + ) public view { + require(s.getAmountInStrategies(coverKey, stablecoin) == 0, "Strategy balance is not zero"); + } + /** * @dev Removes liquidity from the specified cover contract * @param coverKey Enter the cover key * @param podsToRedeem Enter the amount of liquidity token to remove. */ - function removeLiquidityInternal( + function preRemoveLiquidityInternal( IStore s, bytes32 coverKey, address pod, uint256 podsToRedeem, uint256 npmStakeToRemove, bool exit - ) external returns (uint256) { + ) external returns (address stablecoin, uint256 releaseAmount) { + s.mustNotBePaused(); + mustBeAccrued(s, coverKey); + + require(podsToRedeem > 0, "Please specify amount"); + + stablecoin = s.getStablecoin(); + // @suppress-address-trust-issue, @suppress-malicious-erc20 The address `pod` although can only // come from VaultBase, we still need to ensure if it is a protocol member. // Check `_redeemPods` for more info. s.mustHaveNormalCoverStatus(coverKey); s.mustBeDuringWithdrawalPeriod(coverKey); + mustHaveNoBalanceInStrategies(s, coverKey, stablecoin); // Redeem the PODs and receive DAI - uint256 releaseAmount = _redeemPods(s, coverKey, pod, podsToRedeem); + releaseAmount = _redeemPods(s, coverKey, pod, podsToRedeem); // Unstake NPM tokens if (npmStakeToRemove > 0) { _unStakeNpm(s, coverKey, npmStakeToRemove, exit); - IERC20(s.getNpmTokenAddress()).ensureTransfer(msg.sender, npmStakeToRemove); } - - s.updateStateAndLiquidity(coverKey); - return releaseAmount; } function _unStakeNpm( @@ -261,10 +266,9 @@ library VaultLibV1 { } s.mustBeProtocolMember(pod); - address stablecoin = s.getStablecoin(); uint256 available = s.getStablecoinBalanceOfCoverPoolInternal(coverKey); - uint256 releaseAmount = calculateLiquidityInternal(s, coverKey, pod, stablecoin, podsToRedeem); + uint256 releaseAmount = calculateLiquidityInternal(s, coverKey, pod, podsToRedeem); // You may need to wait till active policies expire require(available >= releaseAmount, "Insufficient balance, wait till policy expiry."); // solhint-disable-line @@ -273,9 +277,6 @@ library VaultLibV1 { s.subtractUintByKey(CoverUtilV1.getCoverLiquidityKey(coverKey), releaseAmount); s.addUintByKeys(ProtoUtilV1.NS_COVER_LIQUIDITY_REMOVED, coverKey, msg.sender, releaseAmount); - IERC20(pod).ensureTransferFrom(msg.sender, address(this), podsToRedeem); - IERC20(stablecoin).ensureTransfer(msg.sender, releaseAmount); - return releaseAmount; } @@ -283,10 +284,25 @@ library VaultLibV1 { return string(abi.encodePacked(string(abi.encodePacked(coverKey)), "-pod")); } + function accrueInterestInternal(IStore s, bytes32 coverKey) external { + s.mustNotBePaused(); + + (bool isWithdrawalPeriod, , , , ) = s.getWithdrawalInfoInternal(coverKey); + require(isWithdrawalPeriod == true, "Withdrawal hasn't yet begun"); + + s.updateStateAndLiquidity(coverKey); + + s.setAccrualCompleteInternal(coverKey, true); + } + + function mustBeAccrued(IStore s, bytes32 coverKey) public view { + require(s.isAccrualCompleteInternal(coverKey) == true, "Wait for accrual"); + } + function getStablecoinBalanceOfInternal(IStore s, bytes32 coverKey) public view returns (uint256) { address stablecoin = s.getStablecoin(); - uint256 balance = IERC20(stablecoin).balanceOf(address(this)); + uint256 balance = IERC20(stablecoin).balanceOf(s.getVaultAddress(coverKey)); uint256 inStrategies = s.getAmountInStrategies(coverKey, stablecoin); return balance + inStrategies; @@ -302,6 +318,7 @@ library VaultLibV1 { */ function getFlashFeesInternal( IStore s, + bytes32 coverKey, address token, uint256 amount ) public view returns (uint256 fee, uint256 protocolFee) { @@ -315,6 +332,7 @@ library VaultLibV1 { If the token is not supported flashFee MUST revert. */ require(stablecoin == token, "Unsupported token"); + require(IERC20(stablecoin).balanceOf(s.getVaultAddress(coverKey)) > amount, "Amount insufficient"); uint256 rate = _getFlashLoanFeeRateInternal(s); uint256 protocolRate = _getProtocolFlashLoanFeeRateInternal(s); @@ -325,10 +343,11 @@ library VaultLibV1 { function getFlashFeeInternal( IStore s, + bytes32 coverKey, address token, uint256 amount ) external view returns (uint256) { - (uint256 fee, ) = getFlashFeesInternal(s, token, amount); + (uint256 fee, ) = getFlashFeesInternal(s, coverKey, token, amount); return fee; } @@ -345,12 +364,16 @@ library VaultLibV1 { * @param token The loan currency. * @return The amount of `token` that can be borrowed. */ - function getMaxFlashLoanInternal(IStore s, address token) external view returns (uint256) { + function getMaxFlashLoanInternal( + IStore s, + bytes32 coverKey, + address token + ) external view returns (uint256) { address stablecoin = s.getStablecoin(); require(stablecoin != address(0), "Cover liquidity uninitialized"); if (stablecoin == token) { - return IERC20(stablecoin).balanceOf(address(this)); + return IERC20(stablecoin).balanceOf(s.getVaultAddress(coverKey)); } /* @@ -376,31 +399,5 @@ library VaultLibV1 { address token, uint256 amount, bytes calldata data - ) external returns (uint256) { - // @suppress-address-trust-issue, @suppress-malicious-erc20 `stablecoin` can't be manipulated via user input. - IERC20 stablecoin = IERC20(s.getStablecoin()); - (uint256 fee, uint256 protocolFee) = getFlashFeesInternal(s, token, amount); - uint256 previousBalance = stablecoin.balanceOf(address(this)); - - require(address(stablecoin) == token, "Unknown token"); - require(amount > 0, "Loan too small"); - require(fee > 0, "Fee too little"); - require(previousBalance >= amount, "Balance insufficient"); - - s.setBoolByKeys(ProtoUtilV1.NS_COVER_HAS_FLASH_LOAN, key, true); - - stablecoin.ensureTransfer(address(receiver), amount); - require(receiver.onFlashLoan(msg.sender, token, amount, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"), "IERC3156: Callback failed"); - stablecoin.ensureTransferFrom(address(receiver), address(this), amount + fee); - stablecoin.ensureTransfer(s.getTreasury(), protocolFee); - - uint256 finalBalance = stablecoin.balanceOf(address(this)); - require(finalBalance >= previousBalance + fee, "Access is denied"); - - s.setBoolByKeys(ProtoUtilV1.NS_COVER_HAS_FLASH_LOAN, key, false); - - s.updateStateAndLiquidity(key); - - return fee; - } + ) external returns (uint256) {} } diff --git a/examples/covers.js b/examples/covers.js index 7586293d..240f17bb 100644 --- a/examples/covers.js +++ b/examples/covers.js @@ -71,7 +71,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'BEC' }, stakingTarget: ether(400_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.000000093), + rewardPerBlock: 4_566_764_500, lockupPeriodInBlocks: minutesToBlocks(3, 5), rewardTokenDeposit: ether(15_000_000) }, @@ -80,7 +80,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'BEC' }, stakingTarget: ether(400_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000003), + rewardPerBlock: 4_566_764_500, lockupPeriodInBlocks: minutesToBlocks(31337, 5), rewardTokenDeposit: ether(30_000) }, @@ -89,8 +89,8 @@ const covers = [ uniRewardTokenDollarPair: { token: 'BEC' }, stakingTarget: ether(400_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000034), - lockupPeriodInBlocks: minutesToBlocks(42, 5), + rewardPerBlock: 4_566_764_500, + lockupPeriodInBlocks: minutesToBlocks(80001, 60), rewardTokenDeposit: ether(18_000_000) } } @@ -139,7 +139,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'CRPOOL' }, stakingTarget: ether(10_000_000), maxStake: ether(90_000), - rewardPerBlock: ether(0.0000080), + rewardPerBlock: 3_300_000_000, lockupPeriodInBlocks: minutesToBlocks(3, 5), rewardTokenDeposit: ether(27_500_000) }, @@ -148,7 +148,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'CRPOOL' }, stakingTarget: ether(10_000_000), maxStake: ether(90_000), - rewardPerBlock: ether(0.0000080), + rewardPerBlock: 3_300_000_000, lockupPeriodInBlocks: minutesToBlocks(31337, 5), rewardTokenDeposit: ether(200_000) }, @@ -157,8 +157,8 @@ const covers = [ uniRewardTokenDollarPair: { token: 'CRPOOL' }, stakingTarget: ether(10_000_000), maxStake: ether(90_000), - rewardPerBlock: ether(0.0000080), - lockupPeriodInBlocks: minutesToBlocks(42, 5), + rewardPerBlock: 3_300_000_000, + lockupPeriodInBlocks: minutesToBlocks(80001, 150), rewardTokenDeposit: ether(27_500_000) } } @@ -238,7 +238,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'HWT' }, stakingTarget: ether(1_000_000), maxStake: ether(25_000), - rewardPerBlock: ether(0.0000025), + rewardPerBlock: 2_534_320_000, lockupPeriodInBlocks: minutesToBlocks(3, 5), rewardTokenDeposit: ether(15_000_000) }, @@ -247,7 +247,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'HWT' }, stakingTarget: ether(1_000_000), maxStake: ether(25_000), - rewardPerBlock: ether(0.0000025), + rewardPerBlock: 2_534_320_000, lockupPeriodInBlocks: minutesToBlocks(31337, 5), rewardTokenDeposit: ether(25_000) }, @@ -256,8 +256,8 @@ const covers = [ uniRewardTokenDollarPair: { token: 'HWT' }, stakingTarget: ether(1_000_000), maxStake: ether(25_000), - rewardPerBlock: ether(0.0000025), - lockupPeriodInBlocks: minutesToBlocks(42, 5), + rewardPerBlock: 2_534_320_000, + lockupPeriodInBlocks: minutesToBlocks(80001, 360), rewardTokenDeposit: ether(15_000_000) } } @@ -307,7 +307,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'OBK' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.000000093), + rewardPerBlock: 4_400_000_000, lockupPeriodInBlocks: minutesToBlocks(3, 5), rewardTokenDeposit: ether(35_000_000) }, @@ -316,7 +316,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'OBK' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000003), + rewardPerBlock: 4_400_000_000, lockupPeriodInBlocks: minutesToBlocks(31337, 5), rewardTokenDeposit: ether(30_000) }, @@ -325,8 +325,8 @@ const covers = [ uniRewardTokenDollarPair: { token: 'OBK' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000034), - lockupPeriodInBlocks: minutesToBlocks(42, 5), + rewardPerBlock: 4_400_000_000, + lockupPeriodInBlocks: minutesToBlocks(80001, 120), rewardTokenDeposit: ether(35_000_000) } } @@ -398,7 +398,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'SABRE' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.000000093), + rewardPerBlock: 2_948_440_000, lockupPeriodInBlocks: minutesToBlocks(3, 5), rewardTokenDeposit: ether(35_000_000) }, @@ -407,7 +407,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'SABRE' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000003), + rewardPerBlock: 2_948_440_000, lockupPeriodInBlocks: minutesToBlocks(31337, 5), rewardTokenDeposit: ether(30_000) }, @@ -416,8 +416,8 @@ const covers = [ uniRewardTokenDollarPair: { token: 'SABRE' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000034), - lockupPeriodInBlocks: minutesToBlocks(42, 5), + rewardPerBlock: 2_948_440_000, + lockupPeriodInBlocks: minutesToBlocks(80001, 180), rewardTokenDeposit: ether(35_000_000) } } @@ -462,7 +462,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'XD' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.000000093), + rewardPerBlock: 6_400_000_000, lockupPeriodInBlocks: minutesToBlocks(3, 5), rewardTokenDeposit: ether(35_000_000) }, @@ -471,7 +471,7 @@ const covers = [ uniRewardTokenDollarPair: { token: 'XD' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000003), + rewardPerBlock: 6_400_000_000, lockupPeriodInBlocks: minutesToBlocks(31337, 5), rewardTokenDeposit: ether(30_000) }, @@ -480,8 +480,8 @@ const covers = [ uniRewardTokenDollarPair: { token: 'XD' }, stakingTarget: ether(800_000), maxStake: ether(20_000), - rewardPerBlock: ether(0.00000034), - lockupPeriodInBlocks: minutesToBlocks(42, 5), + rewardPerBlock: 6_400_000_000, + lockupPeriodInBlocks: minutesToBlocks(80001, 60), rewardTokenDeposit: ether(35_000_000) } } diff --git a/hardhat.config.js b/hardhat.config.js index c7757a82..ef7ea9bb 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -24,13 +24,6 @@ const config = { gasPrice: 15000000000, gas: 'auto' }, - kovan: { - url: `${process.env.KOVAN_RPC_URL}`, - chainId: 42, - accounts: [process.env.PRIVATE_KEY], - gasPrice: 7000000000, - gas: 'auto' - }, bscTestnet: { url: 'https://data-seed-prebsc-1-s1.binance.org:8545/', chainId: 97, @@ -79,7 +72,7 @@ const config = { disambiguatePaths: false }, etherscan: { - apiKey: process.env.ETHERSCAN_API_KEY, + apiKey: process.env.POLYGONSCAN_API_KEY, apiKeyAll: { mainnet: process.env.ETHERSCAN_API_KEY, ropsten: process.env.ETHERSCAN_API_KEY, diff --git a/scripts/config/network/index.js b/scripts/config/network/index.js index 25f9760b..4b6d0a52 100644 --- a/scripts/config/network/index.js +++ b/scripts/config/network/index.js @@ -1,13 +1,11 @@ const hardhat = require('./hardhat') const mainnet = require('./mainnet') const ropsten = require('./ropsten') -const kovan = require('./kovan') const mumbai = require('./mumbai') module.exports = { ...hardhat, ...ropsten, - ...kovan, ...mumbai, ...mainnet } diff --git a/scripts/config/network/kovan.js b/scripts/config/network/kovan.js deleted file mode 100644 index 5d022717..00000000 --- a/scripts/config/network/kovan.js +++ /dev/null @@ -1,89 +0,0 @@ -require('dotenv').config() -const wallet = require('../../../util/wallet') -const { ACCESS_CONTROL } = require('../../../util/key') -const MINUTES = 60 - -const config = { - 42: { - network: 'Kovan Test Network', - chainId: 3, - pool: { - bond: { - period: 10 * MINUTES - } - }, - cover: { - lendingPeriod: 60 * MINUTES, - withdrawalWindow: 60 * MINUTES, - claimPeriod: 120 * MINUTES, - cooldownPeriod: 5 * MINUTES - }, - knownAccounts: [ - { - account: wallet.toWalletAddress(process.env.PRIVATE_KEY), - roles: [ - ACCESS_CONTROL.ADMIN, - ACCESS_CONTROL.COVER_MANAGER, - ACCESS_CONTROL.LIQUIDITY_MANAGER, - ACCESS_CONTROL.GOVERNANCE_ADMIN, - ACCESS_CONTROL.RECOVERY_AGENT, - ACCESS_CONTROL.UNPAUSE_AGENT, - ACCESS_CONTROL.UPGRADE_AGENT - ] - }, { - account: '0xA96813969437F3bad7B59335934aa75933670615', - roles: [ - ACCESS_CONTROL.ADMIN, - ACCESS_CONTROL.COVER_MANAGER, - ACCESS_CONTROL.LIQUIDITY_MANAGER, - ACCESS_CONTROL.GOVERNANCE_ADMIN, - ACCESS_CONTROL.RECOVERY_AGENT, - ACCESS_CONTROL.UNPAUSE_AGENT, - ACCESS_CONTROL.UPGRADE_AGENT - ] - } - ], - deployedTokens: { - DAI: '0xff795577d9ac8bd7d90ee22b6c1703490b6512fd', // Aave Kovan DAI - NPM: '0x481B55f34Ef7839c408f35f6B57a68cd54B84eFC', - CRPOOL: '0xbe29A48292218ca80041F03a946a153207EFC868', - HWT: '0x087BeA6C46A8F6efe94809cC8074fb69445c684e', - OBK: '0x211C1261d25BE74126728642D2DebD37A611B26E', - SABRE: '0x52471FFA579f8278fA23eF153c7ac48498Fe772b', - BEC: '0x8C155c9D52C2Fd2DFCCa2C26e833C1a6eabDbb6d', - XD: '0xE7559d514EC2d6E1883Dd53DFF4bC108d457f1b6', - aToken: '0xdcf0af9e59c002fa3aa091a46196b37530fd48a8', - cDai: '0xF49eBE5A0d62cc8f0318AD14620D17dcc2D53935' - }, - stablecoinPairs: { - NPM_DAI: '0x058A0445B488Cba8cab4963181eE7ec3da71e7A0', - CRPOOL_DAI: '0x4A79e880088687b9b014a15e1a0eec1148bB66C7', - HWT_DAI: '0xe6fEb3F713B5a40903FF2aad66383059faaa51D8', - OBK_DAI: '0xaC1e24504432004ea2a8c92Ab4221A002bd2AF1c', - SABRE_DAI: '0xf9EbDA687bFaCa47c2Ba84417324E27c4ce23662', - BEC_DAI: '0xAd11864feCf8a2b8b2260c89771d0419a527B532', - XD_DAI: '0x3A37460ec2675c6A90Dcd00c38Da04a764F684f9' - }, - uniswapV2Like: { - description: 'Sushiswap on Kovan', - addresses: { - factory: '0xc35DADB65012eC5796536bD9864eD8773aBc74C4', - router: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', - masterChef: '0x80C7DD17B01855a6D2347444a0FCC36136a314de' - } - }, - aave: { - description: 'Aave V2 on Kovan', - addresses: { - lendingPool: '0xE0fBa4Fc209b4948668006B2bE61711b7f465bAe' - } - }, - compound: { - dai: { - delegator: '0xF49eBE5A0d62cc8f0318AD14620D17dcc2D53935' - } - } - } -} - -module.exports = config diff --git a/scripts/config/network/mumbai.js b/scripts/config/network/mumbai.js index d3b290d4..6562610b 100644 --- a/scripts/config/network/mumbai.js +++ b/scripts/config/network/mumbai.js @@ -73,7 +73,7 @@ const config = { } }, aave: { - description: 'Aave V2 on Kovan', + description: 'Aave V2 on Mumbai', addresses: { lendingPool: null } diff --git a/stories/0. setup.story.js b/stories/0. setup.story.js index e1d4d600..a465a4f9 100644 --- a/stories/0. setup.story.js +++ b/stories/0. setup.story.js @@ -121,6 +121,14 @@ describe('Protocol Initialization Stories', () => { previous.daiBalance = expected }) + it('correctness rule: pods should match the number of tokens deposited', async () => { + const pod = await composer.vault.getVault(contracts, coverKey) + const [owner] = await ethers.getSigners() + + const pods = await pod.balanceOf(owner.address) + pods.toString().should.equal(previous.daiBalance.toString()) + }) + it('corretness rule: reassurance token should\'ve been correctly transferred to the reassurance vault', async () => { const vault = await storeUtil.getReassuranceVaultAddress(contracts.store) @@ -152,12 +160,12 @@ describe('Protocol Initialization Stories', () => { previous.daiBalance = expected }) - it('correctness rule: pods should match the number of tokens deposited', async () => { + it('correctness rule: pods should be less than the number of tokens deposited', async () => { const pod = await composer.vault.getVault(contracts, coverKey) const [owner] = await ethers.getSigners() const pods = await pod.balanceOf(owner.address) - pods.toString().should.equal(previous.daiBalance.toString()) + pods.should.be.lte(previous.daiBalance.toString()) }) it('reassurance token allocation was increased', async () => { diff --git a/stories/4.liquidity.story.js b/stories/4.liquidity.story.js index 4ea92064..2124e673 100644 --- a/stories/4.liquidity.story.js +++ b/stories/4.liquidity.story.js @@ -59,13 +59,28 @@ describe('Liquidity Stories', () => { await vault.addLiquidity(coverKey, initialLiquidity, minReportingStake) }) - it('deployer removed $3M from Bitmart cover pool', async () => { + it('interest could not be accrued before withdrawal period', async () => { + const vault = await composer.vault.getVault(contracts, coverKey) + + await vault.accrueInterest().should.be.rejectedWith('Withdrawal hasn\'t yet begun') + }) + + it('deployer can not remove liquidity without interest accrual', async () => { const [owner] = await ethers.getSigners() const toRedeem = helper.ether(3_000_000) const vault = await composer.vault.getVault(contracts, coverKey) await network.provider.send('evm_increaseTime', [181 * DAYS]) + await approve(vault.address, vault.address, owner) + await vault.removeLiquidity(coverKey, toRedeem, '0', false).should.be.rejectedWith('Wait for accrual') + }) + + it('deployer removed $3M from Bitmart cover pool', async () => { + const [owner] = await ethers.getSigners() + const toRedeem = helper.ether(3_000_000) + const vault = await composer.vault.getVault(contracts, coverKey) + await vault.accrueInterest() await approve(vault.address, vault.address, owner) diff --git a/test/claims-processor/deps.js b/test/claims-processor/deps.js index 0c5037e0..35a0b88d 100644 --- a/test/claims-processor/deps.js +++ b/test/claims-processor/deps.js @@ -13,7 +13,7 @@ const deployDependencies = async () => { AccessControlLibV1: all.accessControlLibV1.address, BaseLibV1: all.baseLibV1.address, GovernanceUtilV1: all.governanceLib.address, - RoutineInvokerLibV1: all.RoutineInvokerLibV1.address, + RoutineInvokerLibV1: all.routineInvokerLibV1.address, NTransferUtilV2: all.transferLib.address, ProtoUtilV1: all.protoUtilV1.address, RegistryLibV1: all.registryLibV1.address, diff --git a/test/protocol.spec.js b/test/protocol.spec.js index f9637de8..1d53d269 100644 --- a/test/protocol.spec.js +++ b/test/protocol.spec.js @@ -36,15 +36,12 @@ const deployDependencies = async () => { StoreKeyUtil: storeKeyUtil.address }) - const transferLib = await deployer.deploy(cache, 'NTransferUtilV2') - const strategyLibV1 = await deployer.deployWithLibraries(cache, 'StrategyLibV1', { - NTransferUtilV2: transferLib.address, ProtoUtilV1: protoUtilV1.address, StoreKeyUtil: storeKeyUtil.address }) - const RoutineInvokerLibV1 = await deployer.deployWithLibraries(cache, 'RoutineInvokerLibV1', { + const routineInvokerLibV1 = await deployer.deployWithLibraries(cache, 'RoutineInvokerLibV1', { CoverUtilV1: coverUtilV1.address, ProtoUtilV1: protoUtilV1.address, RegistryLibV1: registryLibV1.address, @@ -54,7 +51,7 @@ const deployDependencies = async () => { const governanceUtilV1 = await deployer.deployWithLibraries(cache, 'GovernanceUtilV1', { CoverUtilV1: coverUtilV1.address, - RoutineInvokerLibV1: RoutineInvokerLibV1.address, + RoutineInvokerLibV1: routineInvokerLibV1.address, StoreKeyUtil: storeKeyUtil.address }) diff --git a/util/composer/add-pools.js b/util/composer/add-pools.js index 461a20ba..3888d9cd 100644 --- a/util/composer/add-pools.js +++ b/util/composer/add-pools.js @@ -9,7 +9,7 @@ const addPodStaking = async (intermediate, cache, info, contracts, provider) => await approve(rewardToken, stakingPoolContract.address, provider) const addresses = [stakingToken, uniStakingTokenDollarPair, rewardToken, uniRewardTokenDollarPair] - const values = [stakingTarget, maxStake, platformFee, rewardPerBlock, lockupPeriodInBlocks, rewardTokenDeposit] + const values = [stakingTarget, maxStake, platformFee, rewardPerBlock.toString(), lockupPeriodInBlocks, rewardTokenDeposit] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key, name, poolType, addresses, values) } diff --git a/util/composer/covers.js b/util/composer/covers.js index deb65bcf..733af0a3 100644 --- a/util/composer/covers.js +++ b/util/composer/covers.js @@ -1,6 +1,7 @@ const { ethers } = require('ethers') const { covers } = require('../../examples/covers') const ipfs = require('../ipfs') +const rest = (time) => new Promise((resolve) => setTimeout(resolve, time)) const createCovers = async (payload) => { const { intermediate, cache, contracts } = payload @@ -13,6 +14,7 @@ const createCovers = async (payload) => { for (const i in covers) { const info = covers[i] await create(payload, info) + await rest(200) } } @@ -36,6 +38,7 @@ const create = async (payload, info) => { ] await intermediate(cache, cover, 'addCover', key, hashBytes32, dai.address, requiresWhitelist, values) + await rest(100) await intermediate(cache, cover, 'deployVault', key) } diff --git a/util/composer/initializer.js b/util/composer/initializer.js index 6d8af22e..0ef37c59 100644 --- a/util/composer/initializer.js +++ b/util/composer/initializer.js @@ -14,7 +14,7 @@ const { getExternalProtocols } = require('./external-protocols') * @return {Promise} */ const initialize = async (suite, deploymentId) => { - const chaindId = hre.network.config.chainId + const chainId = hre.network.config.chainId const [owner] = await ethers.getSigners() const cache = suite ? null : await fileCache.from(deploymentId) const network = await getNetworkInfo() @@ -103,41 +103,41 @@ const initialize = async (suite, deploymentId) => { await intermediate(cache, protocol, 'addContract', key.PROTOCOL.CNS.STAKING_POOL, stakingPoolContract.address) // @todo: only applicable to testnet - await intermediate(cache, crpool, 'approve', stakingPoolContract.address, helper.ether(13_400_300)) + await intermediate(cache, crpool, 'approve', stakingPoolContract.address, helper.ether(22_094_995_300)) addresses = [npm.address, npmUsdPair.address, crpool.address, crpoolUsdPair.address] - values = [helper.ether(100_000_000), helper.ether(10_000), helper.percentage(0.25), 10949953000, minutesToBlocks(chaindId, 5), helper.ether(13_400_300)] + values = [helper.ether(100_000_000), helper.ether(10_000), helper.percentage(0.25), 22_094_995_300, minutesToBlocks(chainId, 45), helper.ether(13_400_300)] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key.toBytes32('Crpool'), 'Crystalpool Staking', 0, addresses, values) - await intermediate(cache, hwt, 'approve', stakingPoolContract.address, helper.ether(33_303_000)) + await intermediate(cache, hwt, 'approve', stakingPoolContract.address, helper.ether(13_522_000_000)) addresses = [npm.address, npmUsdPair.address, hwt.address, hwtUsdPair.address] - values = [helper.ether(100_000_000), helper.ether(10_000), helper.percentage(0.25), 10123480000, minutesToBlocks(chaindId, 5), helper.ether(33_303_000)] + values = [helper.ether(100_000_000), helper.ether(10_000), helper.percentage(0.25), 13_522_000_000, minutesToBlocks(chainId, 120), helper.ether(25_303_000)] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key.toBytes32('Huobi'), 'Huobi Staking', 0, addresses, values) - await intermediate(cache, obk, 'approve', stakingPoolContract.address, helper.ether(12_30_330)) + await intermediate(cache, obk, 'approve', stakingPoolContract.address, helper.ether(14_505_290_000)) addresses = [npm.address, npmUsdPair.address, obk.address, obkUsdPair.address] - values = [helper.ether(100_000_000), helper.ether(50_000), helper.percentage(0.25), 35450529000, minutesToBlocks(chaindId, 5), helper.ether(12_30_330)] + values = [helper.ether(100_000_000), helper.ether(50_000), helper.percentage(0.25), 14_505_290_000, minutesToBlocks(chainId, 60), helper.ether(16_30_330)] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key.toBytes32('OBK'), 'OBK Staking', 0, addresses, values) - await intermediate(cache, sabre, 'approve', stakingPoolContract.address, helper.ether(62_000_000)) + await intermediate(cache, sabre, 'approve', stakingPoolContract.address, helper.ether(30_330_000_010)) addresses = [npm.address, npmUsdPair.address, sabre.address, sabreUsdPair.address] - values = [helper.ether(100_000_000), helper.ether(100_000), helper.percentage(0.25), 9033000001, minutesToBlocks(chaindId, 5), helper.ether(62_000_000)] + values = [helper.ether(100_000_000), helper.ether(100_000), helper.percentage(0.25), 30_330_000_010, minutesToBlocks(chainId, 180), helper.ether(42_000_000)] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key.toBytes32('SABRE'), 'SABRE Staking', 0, addresses, values) - await intermediate(cache, bec, 'approve', stakingPoolContract.address, helper.ether(52_000_000)) + await intermediate(cache, bec, 'approve', stakingPoolContract.address, helper.ether(8_940_330_000)) addresses = [npm.address, npmUsdPair.address, bec.address, becUsdPair.address] - values = [helper.ether(100_000_000), helper.ether(80_000), helper.percentage(0.25), 19403300000, minutesToBlocks(chaindId, 5), helper.ether(52_000_000)] + values = [helper.ether(100_000_000), helper.ether(80_000), helper.percentage(0.25), 8_940_330_000, minutesToBlocks(chainId, 60 * 48), helper.ether(27_000_000)] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key.toBytes32('BEC'), 'BEC Staking', 0, addresses, values) - await intermediate(cache, xd, 'approve', stakingPoolContract.address, helper.ether(49_000_000)) + await intermediate(cache, xd, 'approve', stakingPoolContract.address, helper.ether(18_559_222_222)) addresses = [npm.address, npmUsdPair.address, xd.address, xdUsdPair.address] - values = [helper.ether(100_000_000), helper.ether(190_000), helper.percentage(0.25), 21559222222, minutesToBlocks(chaindId, 5), helper.ether(49_000_000)] + values = [helper.ether(100_000_000), helper.ether(190_000), helper.percentage(0.25), 18_559_222_222, minutesToBlocks(chainId, 90), helper.ether(19_000_000)] await intermediate(cache, stakingPoolContract, 'addOrEditPool', key.toBytes32('XT'), 'XT Staking', 0, addresses, values) const stakingContract = await deployer.deployWithLibraries(cache, 'CoverStake', { AccessControlLibV1: libs.accessControlLibV1.address, BaseLibV1: libs.baseLibV1.address, CoverUtilV1: libs.coverUtilV1.address, - RoutineInvokerLibV1: libs.RoutineInvokerLibV1.address, + RoutineInvokerLibV1: libs.routineInvokerLibV1.address, NTransferUtilV2: libs.transferLib.address, ProtoUtilV1: libs.protoUtilV1.address, StoreKeyUtil: libs.storeKeyUtil.address, @@ -150,7 +150,7 @@ const initialize = async (suite, deploymentId) => { AccessControlLibV1: libs.accessControlLibV1.address, BaseLibV1: libs.baseLibV1.address, CoverUtilV1: libs.coverUtilV1.address, - RoutineInvokerLibV1: libs.RoutineInvokerLibV1.address, + RoutineInvokerLibV1: libs.routineInvokerLibV1.address, NTransferUtilV2: libs.transferLib.address, ProtoUtilV1: libs.protoUtilV1.address, StoreKeyUtil: libs.storeKeyUtil.address, @@ -172,6 +172,22 @@ const initialize = async (suite, deploymentId) => { await intermediate(cache, protocol, 'addContract', key.PROTOCOL.CNS.COVER_VAULT_FACTORY, vaultFactory.address) + const vaultDelegate = await deployer.deployWithLibraries(cache, 'VaultDelegate', + { + AccessControlLibV1: libs.accessControlLibV1.address, + BaseLibV1: libs.baseLibV1.address, + ProtoUtilV1: libs.protoUtilV1.address, + RoutineInvokerLibV1: libs.routineInvokerLibV1.address, + StoreKeyUtil: libs.storeKeyUtil.address, + StrategyLibV1: libs.strategyLibV1.address, + ValidationLibV1: libs.validationLib.address, + VaultLibV1: libs.vaultLib.address + } + , store.address + ) + + await intermediate(cache, protocol, 'addContract', key.PROTOCOL.CNS.COVER_VAULT_DELEGATE, vaultDelegate.address) + const cxTokenFactory = await deployer.deployWithLibraries(cache, 'cxTokenFactory', { AccessControlLibV1: libs.accessControlLibV1.address, @@ -207,7 +223,7 @@ const initialize = async (suite, deploymentId) => { { AccessControlLibV1: libs.accessControlLibV1.address, BaseLibV1: libs.baseLibV1.address, - RoutineInvokerLibV1: libs.RoutineInvokerLibV1.address, + RoutineInvokerLibV1: libs.routineInvokerLibV1.address, StoreKeyUtil: libs.storeKeyUtil.address, ProtoUtilV1: libs.protoUtilV1.address, CoverUtilV1: libs.coverUtilV1.address, @@ -248,7 +264,7 @@ const initialize = async (suite, deploymentId) => { AccessControlLibV1: libs.accessControlLibV1.address, BaseLibV1: libs.baseLibV1.address, PolicyHelperV1: libs.policyHelperV1.address, - RoutineInvokerLibV1: libs.RoutineInvokerLibV1.address, + RoutineInvokerLibV1: libs.routineInvokerLibV1.address, StoreKeyUtil: libs.storeKeyUtil.address, ValidationLibV1: libs.validationLib.address }, store.address) @@ -273,7 +289,7 @@ const initialize = async (suite, deploymentId) => { AccessControlLibV1: libs.accessControlLibV1.address, BaseLibV1: libs.baseLibV1.address, GovernanceUtilV1: libs.governanceLib.address, - RoutineInvokerLibV1: libs.RoutineInvokerLibV1.address, + RoutineInvokerLibV1: libs.routineInvokerLibV1.address, NTransferUtilV2: libs.transferLib.address, ProtoUtilV1: libs.protoUtilV1.address, RegistryLibV1: libs.registryLibV1.address, diff --git a/util/composer/libs.js b/util/composer/libs.js index a99a2c7b..ba45ebff 100644 --- a/util/composer/libs.js +++ b/util/composer/libs.js @@ -31,7 +31,6 @@ const deployAll = async (cache) => { }) const strategyLibV1 = await deployer.deployWithLibraries(cache, 'StrategyLibV1', { - NTransferUtilV2: transferLib.address, ProtoUtilV1: protoUtilV1.address, StoreKeyUtil: storeKeyUtil.address }) @@ -87,9 +86,9 @@ const deployAll = async (cache) => { const vaultLib = await deployer.deployWithLibraries(cache, 'VaultLibV1', { CoverUtilV1: coverUtilV1.address, RoutineInvokerLibV1: routineInvokerLibV1.address, - NTransferUtilV2: transferLib.address, ProtoUtilV1: protoUtilV1.address, PolicyHelperV1: policyHelperV1.address, + RegistryLibV1: registryLibV1.address, StoreKeyUtil: storeKeyUtil.address, StrategyLibV1: strategyLibV1.address, ValidationLibV1: validationLib.address @@ -101,10 +100,9 @@ const deployAll = async (cache) => { AccessControlLibV1: accessControlLibV1.address, BaseLibV1: baseLibV1.address, NTransferUtilV2: transferLib.address, - RoutineInvokerLibV1: routineInvokerLibV1.address, - StrategyLibV1: strategyLibV1.address, - ValidationLibV1: validationLib.address, - VaultLibV1: vaultLib.address + ProtoUtilV1: protoUtilV1.address, + RegistryLibV1: registryLibV1.address, + ValidationLibV1: validationLib.address }) const cxTokenFactoryLib = await deployer.deployWithLibraries(cache, 'cxTokenFactoryLibV1', { @@ -153,7 +151,7 @@ const deployAll = async (cache) => { stakingPoolCoreLibV1, stakingPoolLibV1, priceLibV1, - RoutineInvokerLibV1: routineInvokerLibV1, + routineInvokerLibV1, strategyLibV1, policyHelperV1 } diff --git a/util/composer/token.js b/util/composer/token.js index 10a700a0..c9f48485 100644 --- a/util/composer/token.js +++ b/util/composer/token.js @@ -49,7 +49,7 @@ const deployOrGetFromConfig = async (cache, tokens) => { } if (supportedNetworks.indexOf(hre.network.config.chainId) === -1) { - // throw new Error(`Can't deploy ${symbol} on this network.`) + throw new Error(`Can't deploy ${symbol} on this network.`) } const contract = await deployer.deploy(cache, 'FakeToken', `Fake ${name}`, symbol, supply || helper.ether(800_000_000)) diff --git a/util/composer/vault.js b/util/composer/vault.js index 9295e998..41f8b94f 100644 --- a/util/composer/vault.js +++ b/util/composer/vault.js @@ -14,10 +14,9 @@ const getVault = async (contracts, coverKey) => { AccessControlLibV1: contracts.libs.accessControlLibV1.address, BaseLibV1: contracts.libs.baseLibV1.address, NTransferUtilV2: contracts.libs.transferLib.address, - StrategyLibV1: contracts.libs.strategyLibV1.address, - RoutineInvokerLibV1: contracts.libs.RoutineInvokerLibV1.address, - ValidationLibV1: contracts.libs.validationLib.address, - VaultLibV1: contracts.libs.vaultLib.address + ProtoUtilV1: contracts.libs.protoUtilV1.address, + RegistryLibV1: contracts.libs.registryLibV1.address, + ValidationLibV1: contracts.libs.validationLib.address } }) diff --git a/util/key.js b/util/key.js index f37c4282..3f4cea5e 100644 --- a/util/key.js +++ b/util/key.js @@ -49,6 +49,7 @@ const PROTOCOL = { COVER_POLICY_ADMIN: toBytes32('cns:cover:policy:admin'), COVER_STAKE: toBytes32('cns:cover:stake'), COVER_VAULT: toBytes32('cns:cover:vault'), + COVER_VAULT_DELEGATE: toBytes32('cns:cover:vault:delegate'), COVER_STABLECOIN: toBytes32('cns:cover:sc'), COVER_CXTOKEN_FACTORY: toBytes32('cns:cover:cxtoken:factory'), COVER_VAULT_FACTORY: toBytes32('cns:cover:vault:factory'), @@ -154,6 +155,7 @@ const PROTOCOL = { COVER_STAKE: toBytes32('CoverStake'), COVER_REASSURANCE: toBytes32('CoverReassurance'), LIQUIDITY_VAULT: toBytes32('Vault'), + VAULT_DELEGATE: toBytes32('VaultDelegate'), LIQUIDITY_ENGINE: toBytes32('LiquidityEngine'), STRATEGY_AAVE: toBytes32('AaveStrategy'), STRATEGY_COMPOUND: toBytes32('CompoundStrategy') diff --git a/util/typedefs.js b/util/typedefs.js index 60748e3c..2acf486a 100644 --- a/util/typedefs.js +++ b/util/typedefs.js @@ -19,7 +19,7 @@ * @property {Object} stakingPoolCoreLibV1 - Staking pool core library * @property {Object} stakingPoolLibV1 - Staking pool library * @property {Object} priceLibV1 - Price library - * @property {Object} RoutineInvokerLibV1 - Housekeeping library + * @property {Object} routineInvokerLibV1 - Housekeeping library * @property {Object} strategyLibV1 - Price library * @property {Object} policyHelperV1 - Price library */