diff --git a/script/v2/Helper.sol b/script/v2/Helper.sol new file mode 100644 index 00000000..6297c70d --- /dev/null +++ b/script/v2/Helper.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier; +pragma solidity 0.8.17; + +import "forge-std/Script.sol"; +import "forge-std/StdJson.sol"; +import "forge-std/Test.sol"; +//TODO change this after deploy y2k token +import "@openzeppelin/contracts/utils/Strings.sol"; +// import "../keepers/KeeperDepeg.sol"; +// import "../keepers/KeeperEndEpoch.sol"; + +/// @author MiguelBits + +contract HelperV2 is Script { + using stdJson for string; + + struct ConfigVariables{ + uint256 amountOfNewEpochs; + uint256 amountOfNewFarms; + uint256 amountOfNewMarkets; + uint256[] epochsIds; + uint256[] farmsIds; + uint256[] marketsIds; + bool newEpochs; + bool newFarms; + bool newMarkets; + } + struct ConfigAddresses { + address admin; + address arbitrum_sequencer; + address controller; + address gelatoOpsV2; + address gelatoTaskTreasury; + address keeperDepeg; + address keeperEndEpoch; + address oracleDAI; + address oracleFEI; + address oracleFRAX; + address oracleMIM; + address oracleUSDC; + address oracleUSDT; + address policy; + address rewardsFactory; + address tokenDAI; + address tokenFEI; + address tokenFRAX; + address tokenMIM; + address tokenUSDC; + address tokenUSDT; + address treasury; + address vaultFactory; + address weth; + address y2k; + } + + struct ConfigMarket { + uint256 marketId; + string name; + address oracle; + int256 strikePrice; + address token; + } + + struct ConfigEpochs { + uint256 epochBegin; + uint256 epochEnd; + uint256 epochFee; + uint256 marketId; + } + + struct ConfigFarms { + uint256 epochEnd; + string farmRewardsHEDGE; + string farmRewardsRISK; + uint marketId; + } + + + + ConfigVariables configVariables; + + function setVariables() public { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/configJSON.json"); + string memory json = vm.readFile(path); + bytes memory parseJsonByteCode = json.parseRaw(".variables"); + configVariables = abi.decode(parseJsonByteCode, (ConfigVariables)); + // console2.log("ConfigVariables ", rawConstants.amountOfNewMarkets); + } + + // function contractToAddresses(ConfigAddresses memory configAddresses) public { + // vaultFactory = VaultFactory(configAddresses.vaultFactory); + // controller = Controller(configAddresses.controller); + // rewardsFactory = RewardsFactory(configAddresses.rewardsFactory); + // y2k = Y2K(configAddresses.y2k); + // keeperDepeg = KeeperGelatoDepeg(configAddresses.keeperDepeg); + // keeperEndEpoch = KeeperGelatoEndEpoch(configAddresses.keeperEndEpoch); + // } + + // function startKeepers(uint _marketIndex, uint _epochEnd) public { + // keeperDepeg.startTask(_marketIndex, _epochEnd); + // keeperEndEpoch.startTask(_marketIndex, _epochEnd); + // } + + function getConfigAddresses(bool isTestEnv) public returns (ConfigAddresses memory) { + string memory root = vm.projectRoot(); + string memory path; + if(isTestEnv){ + path = string.concat(root, "/configTestEnv.json"); + } + else{ + path = string.concat(root, "/configAddresses.json"); + } + string memory json = vm.readFile(path); + bytes memory parseJsonByteCode = json.parseRaw(".configAddresses[0]"); + ConfigAddresses memory rawConstants = abi.decode(parseJsonByteCode, (ConfigAddresses)); + //console2.log("ConfigAddresses ", rawConstants.weth); + return rawConstants; + } + + function getConfigMarket(uint256 index) public returns (ConfigMarket memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/configJSON.json"); + string memory json = vm.readFile(path); + string memory indexString = string.concat(".markets", "[", Strings.toString(index), "]"); + bytes memory transactionDetails = json.parseRaw(indexString); + ConfigMarket memory rawConstants = abi.decode(transactionDetails, (ConfigMarket)); + //console2.log("ConfigMarkets ", rawConstants.name); + return rawConstants; + } + + function getConfigEpochs(uint256 index) public returns (ConfigEpochs memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/configJSON.json"); + string memory json = vm.readFile(path); + string memory indexString = string.concat(".epochs", "[", Strings.toString(index), "]"); + bytes memory transactionDetails = json.parseRaw(indexString); + ConfigEpochs memory rawConstants = abi.decode(transactionDetails, (ConfigEpochs)); + //console2.log("ConfigEpochs ", rawConstants.name); + return rawConstants; + } + + function getConfigFarms(uint256 index) public returns (ConfigFarms memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/configJSON.json"); + string memory json = vm.readFile(path); + string memory indexString = string.concat(".farms", "[", Strings.toString(index), "]"); + bytes memory transactionDetails = json.parseRaw(indexString); + ConfigFarms memory rawConstants = abi.decode(transactionDetails, (ConfigFarms)); + //console2.log("ConfigEpochs ", rawConstants.name); + return rawConstants; + } + + function verifyConfig(ConfigMarket memory marketConstants) public view { + // require(marketConstants.epochBegin < marketConstants.epochEnd, "epochBegin is not < epochEnd"); + // require(marketConstants.epochEnd > block.timestamp, "epochEnd in the past"); + // require(marketConstants.strikePrice > 900000000000000000, "strikePrice is not above 0.90"); + // require(marketConstants.strikePrice < 1000000000000000000, "strikePrice is not below 1.00"); + // //TODO add more checks + } + + function verifyConfig(ConfigMarket memory marketConstants, ConfigEpochs memory epochConstants) public view { + // require(epochConstants.epochBegin > marketConstants.epochEnd, "epochBegin is not > marketEnd"); + // require(epochConstants.epochBegin < epochConstants.epochEnd, "epochBegin is not < epochEnd"); + // require(epochConstants.epochEnd > block.timestamp, "epochEnd in the past"); + // //TODO add more checks + + } + + function stringToUint(string memory s) public pure returns (uint) { + bytes memory b = bytes(s); + uint result = 0; + for (uint256 i = 0; i < b.length; i++) { + uint256 c = uint256(uint8(b[i])); + if (c >= 48 && c <= 57) { + result = result * 10 + (c - 48); + } + } + return result; + } + +} \ No newline at end of file diff --git a/script/v2/V2DeploymentScript.s.sol b/script/v2/V2DeploymentScript.s.sol index aeb81664..68b6cd98 100644 --- a/script/v2/V2DeploymentScript.s.sol +++ b/script/v2/V2DeploymentScript.s.sol @@ -5,24 +5,36 @@ pragma solidity ^0.8.17; import "forge-std/Script.sol"; import "forge-std/StdJson.sol"; import "forge-std/Test.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../src/v2/VaultFactoryV2.sol"; import "../../src/v2/Carousel/CarouselFactory.sol"; +import "../../src/v2/Controllers/ControllerPeggedAssetV2.sol"; import "../../src/v2/TimeLock.sol"; +import "./Helper.sol"; + + + //forge script V2DeploymentScript --rpc-url $ARBITRUM_RPC_URL --broadcast --verify -slow -vv // forge verify-contract --chain-id 42161 --num-of-optimizations 1000000 --watch --constructor-args $(cast abi-encode "constructor(address,address,address,address,uint256)" 0xaC0D2cF77a8F8869069fc45821483701A264933B 0xaC0D2cF77a8F8869069fc45821483701A264933B 0x65c936f008BC34fE819bce9Fa5afD9dc2d49977f 0x447deddf312ad609e2f85fd23130acd6ba48e8b7 1668384000) --compiler-version v0.8.15+commit.e14f2714 0x69b614f03554c7e0da34645c65852cc55400d0f9 src/rewards/StakingRewards.sol:StakingRewards $arbiscanApiKey // forge script script/v2/V2DeploymentScript.s.sol --rpc-url $ARBITRUM_RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --skip-simulation --slow -vvvv -contract V2DeploymentScript is Script { +contract V2DeploymentScript is Script, HelperV2 { using stdJson for string; - function run() public { - + address policy = 0xCCA23C05a9Cf7e78830F3fd55b1e8CfCCbc5E50F; + address weth = 0x6BE37a65E46048B1D12C0E08d9722402A5247Ff1; + address treasury = 0xCCA23C05a9Cf7e78830F3fd55b1e8CfCCbc5E50F; + address emissionToken = 0x5D59e5837F7e5d0F710178Eda34d9eCF069B36D2; + address factory; - // ConfigAddresses memory addresses = getConfigAddresses(false); + address controller; + function run() public { + + ConfigAddresses memory addresses = getConfigAddresses(false); // console2.log("Address admin", addresses.admin); // console2.log("Address arbitrum_sequencer", addresses.arbitrum_sequencer); // console2.log("Address oracleDAI", addresses.oracleDAI); @@ -49,27 +61,72 @@ contract V2DeploymentScript is Script { vm.startBroadcast(privateKey); console2.log("Broadcast sender", msg.sender); + + + policy = msg.sender; + treasury = msg.sender; + - address policy = 0xaC0D2cF77a8F8869069fc45821483701A264933B; - address weth = 0xaC0D2cF77a8F8869069fc45821483701A264933B; - address treasury = 0x65c936f008BC34fE819bce9Fa5afD9dc2d49977f; - address emissionToken = 0xaC0D2cF77a8F8869069fc45821483701A264933B; + console2.log("Broadcast policy", policy); + console2.log("Broadcast treasury", treasury); - address timeLock = address(new TimeLock(policy)); + // address timeLock = address(new TimeLock(policy)); - // CarouselFactory vaultFactory = new CarouselFactory(policy, weth, treasury, emissionToken); + // CarouselFactory vaultFactory = new CarouselFactory(weth, treasury, policy, emissionToken); + factory = 0xAd2f15ff7d167c800281ef52fa098Fae33429cc6; - VaultFactoryV2 vaultFactory = new VaultFactoryV2(weth, treasury, timeLock); + controller = 0xDf878548b17429a6e6a3ff66Fb629e347738aA56; + + // VaultFactoryV2 vaultFactory = new VaultFactoryV2(weth, treasury, timeLock); // console2.log("Broadcast admin ", addresses.admin); // console2.log("Broadcast policy", addresses.policy); //start setUp(); // vaultFactory = new VaultFactory(addresses.treasury, addresses.weth, addresses.policy); - // controller = new Controller(address(vaultFactory), addresses.arbitrum_sequencer); + // controller = address(new ControllerPeggedAssetV2(address(vaultFactory), addresses.arbitrum_sequencer, treasury)); + + // vaultFactory.whitelistController(controller); + + console.log("factory", factory); + console.log("controller", controller); + + + // deployMarketsV2(address(vaultFactory)); - // vaultFactory.setController(address(controller)); + // IERC20(emissionToken).approve(factory, 100 ether); + + // ( address prem, address collat, uint256 marketId) = CarouselFactory(factory).createNewCarouselMarket( + // CarouselFactory.CarouselMarketConfigurationCalldata( + // addresses.tokenMIM, + // 999000000000000000, + // addresses.oracleMIM, + // weth, + // "y2kMIM_999*", + // "https://y2k.finance", + // controller, + // 1 gwei, + // 10, + // 1 ether + // ) + // ); + + // console.log("Prem", prem); + // console.log("Collat", collat); + // console.log("marketId", marketId); + + + (uint256 eId, ) = CarouselFactory(factory).createEpochWithEmissions( + 50136727949622076191748106171773774901339026601219072444023567158965921292263, + 1682575989, + 1682748789, + 50, + 1 ether, + 2 ether + ); + + // console.log("eId", eId); //stop setUp(); @@ -86,4 +143,144 @@ contract V2DeploymentScript is Script { vm.stopBroadcast(); } + + function deployMarketsV2( address factory) public { + + ConfigAddresses memory addresses = getConfigAddresses(false); + + + IERC20(emissionToken).approve(address(factory), 200 ether); + + ( address prem, address collat, uint256 marketId) = CarouselFactory(factory).createNewCarouselMarket( + CarouselFactory.CarouselMarketConfigurationCalldata( + addresses.tokenUSDC, + 1 ether - 1, + addresses.oracleUSDC, + weth, + "y2kUSDC_999*", + "https://y2k.finance", + address(controller), + 1 gwei, + 10, + 1 ether + ) + ); + + CarouselFactory(factory).createEpochWithEmissions( + marketId, + 1683891705, + 1689162105, + 50, + 1 ether, + 10 ether + ); + + // CarouselFactory(factory).createEpochWithEmissions( + // marketId, + // 1689419676, + // 1689506076, + // 50, + // 1 ether, + // 10 ether + // ); + + // CarouselFactory(factory).createEpochWithEmissions( + // marketId, + // 1689592476, + // 1689678876, + // 50, + // 1 ether, + // 10 ether + // ); + + ( prem, collat, marketId) = CarouselFactory(factory).createNewCarouselMarket( + CarouselFactory.CarouselMarketConfigurationCalldata( + addresses.tokenUSDT, + 1 ether - 1, + addresses.oracleUSDT, + weth, + "y2kUSDT_999*", + "https://y2k.finance", + address(controller), + 1 gwei, + 10, + 1 ether + ) + ); + + // CarouselFactory(factory).createEpochWithEmissions( + // marketId, + // 1683891705, + // 1689162105, + // 50, + // 1 ether, + // 10 ether + // ); + + CarouselFactory(factory).createEpochWithEmissions( + marketId, + 1689419676, + 1689506076, + 50, + 1 ether, + 10 ether + ); + + // CarouselFactory(factory).createEpochWithEmissions( + // marketId, + // 1689592476, + // 1689678876, + // 50, + // 1 ether, + // 10 ether + // ); + + ( prem, collat, marketId) = CarouselFactory(factory).createNewCarouselMarket( + CarouselFactory.CarouselMarketConfigurationCalldata( + addresses.tokenDAI, + 1 ether - 1, + addresses.oracleDAI, + weth, + "y2kDAI_999*", + "https://y2k.finance", + address(controller), + 1 gwei, + 10, + 1 ether + ) + ); + + // CarouselFactory(factory).createEpochWithEmissions( + // marketId, + // 1683891705, + // 1689162105, + // 50, + // 1 ether, + // 10 ether + // ); + + // CarouselFactory(factory).createEpochWithEmissions( + // marketId, + // 1689419676, + // 1689506076, + // 50, + // 1 ether, + // 10 ether + // ); + + CarouselFactory(factory).createEpochWithEmissions( + marketId, + 1689592476, + 1689678876, + 50, + 1 ether, + 10 ether + ); + + + } + + function deployEpoch( address factory) public { + + } } \ No newline at end of file diff --git a/src/legacy_v1/Controller.sol b/src/legacy_v1/Controller.sol index 04c92d99..96ed593c 100644 --- a/src/legacy_v1/Controller.sol +++ b/src/legacy_v1/Controller.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {ERC20} from "@solmate/tokens/ERC20.sol"; import "./Vault.sol"; diff --git a/src/legacy_v1/SemiFungibleVault.sol b/src/legacy_v1/SemiFungibleVault.sol index 27514292..97235a83 100644 --- a/src/legacy_v1/SemiFungibleVault.sol +++ b/src/legacy_v1/SemiFungibleVault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; diff --git a/src/legacy_v1/Vault.sol b/src/legacy_v1/Vault.sol index 192ebb43..947d194f 100644 --- a/src/legacy_v1/Vault.sol +++ b/src/legacy_v1/Vault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {SemiFungibleVault} from "./SemiFungibleVault.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; diff --git a/src/legacy_v1/VaultFactory.sol b/src/legacy_v1/VaultFactory.sol index e91908e9..eb2265ee 100644 --- a/src/legacy_v1/VaultFactory.sol +++ b/src/legacy_v1/VaultFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {Vault} from "./Vault.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/src/legacy_v1/interfaces/IController.sol b/src/legacy_v1/interfaces/IController.sol index 744c070c..d5cd0aff 100644 --- a/src/legacy_v1/interfaces/IController.sol +++ b/src/legacy_v1/interfaces/IController.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.15; +pragma solidity 0.8.17; interface IController { function getVaultFactory() external view returns (address); diff --git a/src/legacy_v1/interfaces/IStakingRewards.sol b/src/legacy_v1/interfaces/IStakingRewards.sol index 2ca5af3e..53c750c3 100644 --- a/src/legacy_v1/interfaces/IStakingRewards.sol +++ b/src/legacy_v1/interfaces/IStakingRewards.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.15; +pragma solidity 0.8.17; // https://docs.synthetix.io/contracts/source/interfaces/istakingrewards interface IStakingRewards { diff --git a/src/legacy_v1/interfaces/IVault.sol b/src/legacy_v1/interfaces/IVault.sol index 384b96f5..4de1bdd4 100644 --- a/src/legacy_v1/interfaces/IVault.sol +++ b/src/legacy_v1/interfaces/IVault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; /// @author MiguelBits contract IVault { diff --git a/src/legacy_v1/interfaces/IVaultFactory.sol b/src/legacy_v1/interfaces/IVaultFactory.sol index 7d32844d..2bb23d8d 100644 --- a/src/legacy_v1/interfaces/IVaultFactory.sol +++ b/src/legacy_v1/interfaces/IVaultFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; /// @author MiguelBits diff --git a/src/legacy_v1/interfaces/IWETH.sol b/src/legacy_v1/interfaces/IWETH.sol index 7c247552..40040faa 100644 --- a/src/legacy_v1/interfaces/IWETH.sol +++ b/src/legacy_v1/interfaces/IWETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.15; +pragma solidity 0.8.17; interface IWETH { function deposit() external payable; diff --git a/src/legacy_v1/rewards/IStakingRewards.sol b/src/legacy_v1/rewards/IStakingRewards.sol index 2ca5af3e..53c750c3 100644 --- a/src/legacy_v1/rewards/IStakingRewards.sol +++ b/src/legacy_v1/rewards/IStakingRewards.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.15; +pragma solidity 0.8.17; // https://docs.synthetix.io/contracts/source/interfaces/istakingrewards interface IStakingRewards { diff --git a/src/legacy_v1/rewards/Owned.sol b/src/legacy_v1/rewards/Owned.sol index b2743aea..4c948bd6 100644 --- a/src/legacy_v1/rewards/Owned.sol +++ b/src/legacy_v1/rewards/Owned.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.15; +pragma solidity 0.8.17; // https://docs.synthetix.io/contracts/source/contracts/owned contract Owned { diff --git a/src/legacy_v1/rewards/PausableRewardsFactory.sol b/src/legacy_v1/rewards/PausableRewardsFactory.sol index a47b9db9..1be82b85 100644 --- a/src/legacy_v1/rewards/PausableRewardsFactory.sol +++ b/src/legacy_v1/rewards/PausableRewardsFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {StakingRewards} from "./PausableStakingRewards.sol"; import {VaultFactory} from "../VaultFactory.sol"; diff --git a/src/legacy_v1/rewards/PausableStakingRewards.sol b/src/legacy_v1/rewards/PausableStakingRewards.sol index 891ea9fe..f2e154ed 100644 --- a/src/legacy_v1/rewards/PausableStakingRewards.sol +++ b/src/legacy_v1/rewards/PausableStakingRewards.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; diff --git a/src/legacy_v1/rewards/RewardBalances.sol b/src/legacy_v1/rewards/RewardBalances.sol index 612586e3..1a56d5a2 100644 --- a/src/legacy_v1/rewards/RewardBalances.sol +++ b/src/legacy_v1/rewards/RewardBalances.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {StakingRewards} from "./StakingRewards.sol"; diff --git a/src/legacy_v1/rewards/RewardsDistributionRecipient.sol b/src/legacy_v1/rewards/RewardsDistributionRecipient.sol index 878479bf..2c3130f4 100644 --- a/src/legacy_v1/rewards/RewardsDistributionRecipient.sol +++ b/src/legacy_v1/rewards/RewardsDistributionRecipient.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.15; +pragma solidity 0.8.17; // Inheritance import "./Owned.sol"; diff --git a/src/legacy_v1/rewards/RewardsFactory.sol b/src/legacy_v1/rewards/RewardsFactory.sol index 921bc7f6..ff051d4c 100644 --- a/src/legacy_v1/rewards/RewardsFactory.sol +++ b/src/legacy_v1/rewards/RewardsFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {StakingRewards} from "./StakingRewards.sol"; import {VaultFactory} from "../VaultFactory.sol"; diff --git a/src/legacy_v1/rewards/StakingRewards.sol b/src/legacy_v1/rewards/StakingRewards.sol index e123e8bf..5458a65c 100644 --- a/src/legacy_v1/rewards/StakingRewards.sol +++ b/src/legacy_v1/rewards/StakingRewards.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; diff --git a/src/oracles/PegOracle.sol b/src/oracles/PegOracle.sol index 9f935180..3a61fc9c 100644 --- a/src/oracles/PegOracle.sol +++ b/src/oracles/PegOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "@chainlink/interfaces/AggregatorV3Interface.sol"; diff --git a/src/tokens/Y2K.sol b/src/tokens/Y2K.sol index 18a855c8..afb569b7 100644 --- a/src/tokens/Y2K.sol +++ b/src/tokens/Y2K.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {ERC20} from "@solmate/tokens/ERC20.sol"; diff --git a/src/v2/Carousel/Carousel.sol b/src/v2/Carousel/Carousel.sol index ed460af3..b56da25e 100644 --- a/src/v2/Carousel/Carousel.sol +++ b/src/v2/Carousel/Carousel.sol @@ -20,13 +20,13 @@ contract Carousel is VaultV2 { // Earthquake parameters uint256 public relayerFee; uint256 public depositFee; + uint256 public minQueueDeposit; IERC20 public immutable emissionsToken; mapping(address => uint256) public ownerToRollOverQueueIndex; QueueItem[] public rolloverQueue; QueueItem[] public depositQueue; mapping(uint256 => uint256) public rolloverAccounting; - mapping(uint256 => mapping(address => uint256)) public _emissionsBalances; mapping(uint256 => uint256) public emissions; /*////////////////////////////////////////////////////////////// @@ -45,8 +45,7 @@ contract Carousel is VaultV2 { _data.tokenURI, _data.token, _data.strike, - _data.controller, - _data.treasury + _data.controller ) { if (_data.relayerFee < 10000) revert RelayerFeeToLow(); @@ -55,6 +54,7 @@ contract Carousel is VaultV2 { emissionsToken = IERC20(_data.emissionsToken); relayerFee = _data.relayerFee; depositFee = _data.depositFee; + minQueueDeposit = _data.minQueueDeposit; // set epoch 0 to be allways available to deposit into Queue epochExists[0] = true; @@ -71,7 +71,8 @@ contract Carousel is VaultV2 { //////////////////////////////////////////////////////////////*/ /** @notice Deposit function - @param _id epoch id + @dev if receiver intends to deposit into queue and is contract, it must implement 1155 receiver interface otherwise funds will be stuck + @param _id epoch id, if 0 deposit will be queued; @param _assets uint256 of how many assets you want to deposit; @param _receiver address of the receiver of the shares provided by this function, that represent the ownership of the deposited asset; */ @@ -84,7 +85,7 @@ contract Carousel is VaultV2 { override(VaultV2) epochIdExists(_id) epochHasNotStarted(_id) - minRequiredDeposit(_assets) + minRequiredDeposit(_assets, _id) nonReentrant { // make sure that epoch exists @@ -102,7 +103,7 @@ contract Carousel is VaultV2 { external payable override(VaultV2) - minRequiredDeposit(msg.value) + minRequiredDeposit(msg.value, _id) epochIdExists(_id) epochHasNotStarted(_id) nonReentrant @@ -118,16 +119,16 @@ contract Carousel is VaultV2 { } /** - @notice Withdraw entitled deposited assets, checking if a depeg event - @param _id uint256 identifier of the epoch you want to withdraw from; - @param _assets uint256 of how many assets you want to withdraw, this value will be used to calculate how many assets you are entitle to according the vaults claimTVL; - @param _receiver Address of the receiver of the assets provided by this function, that represent the ownership of the transfered asset; - @param _owner Address of the owner of these said assets; - @return shares How many shares the owner is entitled to, according to the conditions; + @notice Withdraw entitled assets and burn shares of epoch + @param _id uint256 identifier of the epoch; + @param _shares uint256 amount of shares to withdraw, this value will be used to calculate how many assets you are entitle to according the vaults claimTVL; + @param _receiver Address of the receiver of the assets provided by this function, that represent the ownership of the transfered asset; + @param _owner Address of the _shares owner; + @return assets How many assets the owner is entitled to, according to the epoch outcome; */ function withdraw( uint256 _id, - uint256 _assets, + uint256 _shares, address _receiver, address _owner ) @@ -136,9 +137,9 @@ contract Carousel is VaultV2 { override(VaultV2) epochIdExists(_id) epochHasEnded(_id) - notRollingOver(_owner, _id, _assets) + notRollingOver(_owner, _id, _shares) nonReentrant - returns (uint256 shares) + returns (uint256 assets) { // make sure that epoch exists // epoch is resolved @@ -151,32 +152,31 @@ contract Carousel is VaultV2 { isApprovedForAll(_owner, msg.sender) == false ) revert OwnerDidNotAuthorize(msg.sender, _owner); - _burn(_owner, _id, _assets); - _burnEmissions(_owner, _id, _assets); - uint256 entitledShares; - uint256 entitledEmissions = previewEmissionsWithdraw(_id, _assets); + _burn(_owner, _id, _shares); + uint256 entitledEmissions = previewEmissionsWithdraw(_id, _shares); if (epochNull[_id] == false) { - entitledShares = previewWithdraw(_id, _assets); + assets = previewWithdraw(_id, _shares); } else { - entitledShares = _assets; + assets = _shares; } - if (entitledShares > 0) { - SemiFungibleVault.asset.safeTransfer(_receiver, entitledShares); + if (assets > 0) { + SemiFungibleVault.asset.safeTransfer(_receiver, assets); } if (entitledEmissions > 0) { emissionsToken.safeTransfer(_receiver, entitledEmissions); } - emit Withdraw( + emit WithdrawWithEmissions( msg.sender, _receiver, _owner, _id, - _assets, - entitledShares + _shares, + assets, + entitledEmissions ); - return entitledShares; + return assets; } /*/////////////////////////////////////////////////////////////// @@ -199,17 +199,6 @@ contract Carousel is VaultV2 { "ERC1155: caller is not owner nor approved" ); _safeTransferFrom(from, to, id, amount, data); - // emissions transfer - uint256 fromBalance = _emissionsBalances[id][from]; - require( - fromBalance >= amount, - "ERC1155: insufficient balance for transfer" - ); - unchecked { - _emissionsBalances[id][from] = fromBalance - amount; - } - _emissionsBalances[id][to] += amount; - emit TransferSingleEmissions(_msgSender(), from, to, id, amount); } /** @@ -232,75 +221,66 @@ contract Carousel is VaultV2 { /** @notice enlists in rollover queue @dev user needs to have >= _assets in epoch (_epochId) @param _epochId epoch id - @param _assets uint256 of how many assets deposited; + @param _shares uint256 amount of shares to rollover; @param _receiver address of the receiver of the emissions; */ function enlistInRollover( uint256 _epochId, - uint256 _assets, + uint256 _shares, address _receiver - ) public epochIdExists(_epochId) minRequiredDeposit(_assets) { + ) public epochIdExists(_epochId) minRequiredDeposit(_shares, _epochId) { // check if sender is approved by owner if ( msg.sender != _receiver && isApprovedForAll(_receiver, msg.sender) == false ) revert OwnerDidNotAuthorize(msg.sender, _receiver); // check if user has enough balance - if (balanceOf(_receiver, _epochId) < _assets) + if (balanceOf(_receiver, _epochId) < _shares) revert InsufficientBalance(); // check if user has already queued up a rollover - if (ownerToRollOverQueueIndex[_receiver] != 0) { - // if so, update the queue + if (isEnlistedInRolloverQueue(_receiver)) { uint256 index = getRolloverIndex(_receiver); - rolloverQueue[index].assets = _assets; + // if so, update the queue + rolloverQueue[index].shares = _shares; rolloverQueue[index].epochId = _epochId; } else { // if not, add to queue rolloverQueue.push( QueueItem({ - assets: _assets, + shares: _shares, receiver: _receiver, epochId: _epochId }) ); + // index will allways be higher than 0 + ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length; } - ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length; - emit RolloverQueued(_receiver, _assets, _epochId); + emit RolloverQueued(_receiver, _shares, _epochId); } /** @notice delists from rollover queue @param _owner address that is delisting from rollover queue */ function delistInRollover(address _owner) public { - // check if user has already queued up a rollover - if (ownerToRollOverQueueIndex[_owner] == 0) revert NoRolloverQueued(); + // @note + // its not possible for users to delete the QueueItem from the array because + // during rollover, earlier users in rollover queue, can grief attack later users by deleting their queue item + // instead we just set the assets to 0 and the epochId to 0 as a flag to indicate that the user is no longer in the queue + + // check if user is enlisted in rollover queue + if (!isEnlistedInRolloverQueue(_owner)) revert NoRolloverQueued(); // check if sender is approved by owner if ( msg.sender != _owner && isApprovedForAll(_owner, msg.sender) == false ) revert OwnerDidNotAuthorize(msg.sender, _owner); - // swich the last item in the queue with the item to be removed + // set assets to 0 but keep the queue item uint256 index = getRolloverIndex(_owner); - uint256 length = rolloverQueue.length; - if (index == length - 1) { - // if only one item in queue - rolloverQueue.pop(); - delete ownerToRollOverQueueIndex[_owner]; - } else { - // overwrite the item to be removed with the last item in the queue - rolloverQueue[index] = rolloverQueue[length - 1]; - // remove the last item in the queue - rolloverQueue.pop(); - // update the index of prev last user ( mapping index is allways array index + 1) - ownerToRollOverQueueIndex[rolloverQueue[index].receiver] = - index + - 1; - // remove receiver from index mapping - delete ownerToRollOverQueueIndex[_owner]; - } + rolloverQueue[index].shares = 0; + rolloverQueue[index].epochId = 0; } /** @notice mints deposit in rollover queue @@ -328,19 +308,38 @@ contract Carousel is VaultV2 { // queue is executed from the tail to the head // get last index of queue uint256 i = length - 1; + uint256 relayerFeeShortfall; while ((length - _operations) <= i) { // this loop impelements FILO (first in last out) stack to reduce gas cost and improve code readability // changing it to FIFO (first in first out) would require more code changes and would be more expensive - _mintShares( - queue[i].receiver, - _epochId, - queue[i].assets - relayerFee - ); + // @note non neglectable min-deposit creates barriers for attackers to DDOS the queue + + uint256 assetsToDeposit = queue[i].shares; + + if (depositFee > 0) { + ( + uint256 feeAmount, + uint256 assetsAfterFee + ) = getEpochDepositFee(_epochId, assetsToDeposit); + assetsToDeposit = assetsAfterFee; + _asset().safeTransfer(treasury(), feeAmount); + } + + // if minDeposit has chagned during QueueItem is in the queue and relayerFee is now higher than deposit amount + // mint 0 and pay relayerFeeShortfall to relayer + if (assetsToDeposit > relayerFee) { + assetsToDeposit -= relayerFee; + } else { + relayerFeeShortfall += (relayerFee - assetsToDeposit); + assetsToDeposit = 0; + } + + _mintShares(queue[i].receiver, _epochId, assetsToDeposit); emit Deposit( msg.sender, queue[i].receiver, _epochId, - queue[i].assets - relayerFee + assetsToDeposit ); depositQueue.pop(); if (i == 0) break; @@ -351,7 +350,10 @@ contract Carousel is VaultV2 { emit RelayerMinted(_epochId, _operations); - asset.safeTransfer(msg.sender, _operations * relayerFee); + asset.safeTransfer( + msg.sender, + (_operations * relayerFee) - relayerFeeShortfall + ); } /** @notice mints for rollovers @@ -365,7 +367,7 @@ contract Carousel is VaultV2 { nonReentrant { // epoch has not started - // dont allow minting if epochId is 0 + // dont allow rollover if epochId is 0 if (_epochId == 0) revert InvalidEpochId(); uint256 length = rolloverQueue.length; @@ -391,57 +393,74 @@ contract Carousel is VaultV2 { uint256 executions = 0; while ((index - prevIndex) < (_operations)) { - // only roll over if last epoch is resolved - if (epochResolved[queue[index].epochId]) { - uint256 entitledShares = previewWithdraw( + // only roll over if last epoch is resolved and user rollover position is valid + if ( + epochResolved[queue[index].epochId] && queue[index].shares > 0 + ) { + uint256 entitledAmount = previewWithdraw( queue[index].epochId, - queue[index].assets + queue[index].shares ); + // mint only if user won epoch he is rolling over - if (entitledShares > queue[index].assets) { + if (entitledAmount > queue[index].shares) { + // @note previewAmountInShares can only be called if epoch is in profit + uint256 relayerFeeInShares = previewAmountInShares( + queue[index].epochId, + relayerFee + ); + // skip the rollover for the user if the assets cannot cover the relayer fee instead of revert. - if (queue[index].assets < relayerFee) { + if (queue[index].shares < relayerFeeInShares) { index++; continue; } + + // to calculate originalDepositValue get the diff between shares and value of shares + // convert this value amount value back to shares + // subtract from assets + uint256 originalDepositValue = queue[index].shares - + previewAmountInShares( + queue[index].epochId, + (entitledAmount - queue[index].shares) // subtract profit from share value + ); // @note we know shares were locked up to this point _burn( queue[index].receiver, queue[index].epochId, - queue[index].assets - ); - // transfer emission tokens out of contract otherwise user could not access them as vault shares are burned - _burnEmissions( - queue[index].receiver, - queue[index].epochId, - queue[index].assets + originalDepositValue ); // @note emission token is a known token which has no before transfer hooks which makes transfer safer emissionsToken.safeTransfer( queue[index].receiver, previewEmissionsWithdraw( queue[index].epochId, - queue[index].assets + originalDepositValue ) ); - emit Withdraw( + emit WithdrawWithEmissions( msg.sender, queue[index].receiver, queue[index].receiver, _epochId, - queue[index].assets, - entitledShares + originalDepositValue, + entitledAmount, + previewEmissionsWithdraw( + queue[index].epochId, + originalDepositValue + ) ); - uint256 assetsToMint = queue[index].assets - relayerFee; - _mintShares(queue[index].receiver, _epochId, assetsToMint); + uint256 amountToMint = queue[index].shares - + relayerFeeInShares; + _mintShares(queue[index].receiver, _epochId, amountToMint); // amountToMint == shares emit Deposit( msg.sender, queue[index].receiver, _epochId, - assetsToMint + amountToMint ); - rolloverQueue[index].assets = assetsToMint; + rolloverQueue[index].shares = amountToMint; rolloverQueue[index].epochId = _epochId; // only pay relayer for successful mints executions++; @@ -477,15 +496,12 @@ contract Carousel is VaultV2 { uint256 assetsToDeposit = _assets; if (depositFee > 0) { - (uint256 maxX, , uint256 minX) = getEpochConfig(_id); - // deposit fee is calcualted linearly between time of epoch creation and epoch starting (deposit window) - // this is because late depositors have an informational advantage - uint256 fee = _calculateFeePercent(int256(minX), int256(maxX)); - // min minRequiredDeposit modifier ensures that _assets has high enough value to not devide by 0 - // 0.5% = multiply by 10000 then divide by 50 - uint256 feeAmount = _assets.mulDivDown(fee, 10000); - assetsToDeposit = _assets - feeAmount; - _asset().safeTransfer(treasury, feeAmount); + ( + uint256 feeAmount, + uint256 assetsAfterFee + ) = getEpochDepositFee(_id, _assets); + assetsToDeposit = assetsAfterFee; + _asset().safeTransfer(treasury(), feeAmount); } _mintShares(_receiver, _id, assetsToDeposit); @@ -493,36 +509,13 @@ contract Carousel is VaultV2 { emit Deposit(msg.sender, _receiver, _id, _assets); } else { depositQueue.push( - QueueItem({assets: _assets, receiver: _receiver, epochId: _id}) + QueueItem({shares: _assets, receiver: _receiver, epochId: _id}) ); emit DepositInQueue(msg.sender, _receiver, _id, _assets); } } - /** - * @notice calculates fee percent based on time - * @param minX min x value - * @param maxX max x value - */ - function _calculateFeePercent(int256 minX, int256 maxX) - internal - view - returns (uint256 _y) - { - /** - * Two Point Form - * https://www.cuemath.com/geometry/two-point-form/ - * https://ethereum.stackexchange.com/a/143172 - */ - // minY will always be 0 thats why is (maxY - minY) shorten to maxY - int256 maxY = int256(depositFee) * int256(FixedPointMathLib.WAD); - _y = uint256( // cast to uint256 - ((((maxY) / (maxX - minX)) * (int256(block.timestamp) - maxX)) + - maxY) / (int256(FixedPointMathLib.WAD)) // two point math // scale down - ); - } - /** @notice mints shares of vault for user @param to address of receiver @param id epoch id @@ -534,66 +527,40 @@ contract Carousel is VaultV2 { uint256 amount ) internal { _mint(to, id, amount, EMPTY); - _mintEmissions(to, id, amount); } - /** @notice mints emission shares based of vault shares for user - @param to address of receiver - @param id epoch id - @param amount amount of shares to mint - */ - function _mintEmissions( - address to, - uint256 id, - uint256 amount - ) internal { - require(to != address(0), "ERC1155: mint to the zero address"); - - _emissionsBalances[id][to] += amount; - emit TransferSingleEmissions(_msgSender(), address(0), to, id, amount); - } + /*/////////////////////////////////////////////////////////////// + ADMIN FUNCTIONS + //////////////////////////////////////////////////////////////*/ - /** @notice burns emission shares of vault for user - @param from address of sender - @param id epoch id - @param amount amount of shares to burn + /** + @notice This function is called by the controller if the epoch has started, but the counterparty vault has no value. In this case the users can withdraw their deposit. Additionally, emissions are transferred to the treasury. + @param _id uint256 identifier of the epoch */ - function _burnEmissions( - address from, - uint256 id, - uint256 amount - ) internal { - require(from != address(0), "ERC1155: burn from the zero address"); - - uint256 fromBalance = _emissionsBalances[id][from]; - require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); - unchecked { - _emissionsBalances[id][from] = fromBalance - amount; + function setEpochNull(uint256 _id) + public + override + onlyController + epochIdExists(_id) + epochHasEnded(_id) + { + epochNull[_id] = true; + if (emissions[_id] > 0) { + emissionsToken.safeTransfer(treasury(), emissions[_id]); + emissions[_id] = 0; } - - emit TransferSingleEmissions( - _msgSender(), - from, - address(0), - id, - amount - ); } - /*/////////////////////////////////////////////////////////////// - ADMIN FUNCTIONS - //////////////////////////////////////////////////////////////*/ - /** @notice sets emissions * @param _epochId epoch id - * @param _emissionsRate emissions rate + * @param _emissionAmount emissions rate */ - function setEmissions(uint256 _epochId, uint256 _emissionsRate) + function setEmissions(uint256 _epochId, uint256 _emissionAmount) external onlyFactory epochIdExists(_epochId) { - emissions[_epochId] = _emissionsRate; + emissions[_epochId] = _emissionAmount; } /** @notice changes relayer fee @@ -610,11 +577,67 @@ contract Carousel is VaultV2 { depositFee = _depositFee; } + /** @notice cleans up rollover queue + * @dev this function can only be called if there is no active deposit window + * @param _addressesToDelist addresses to delist + */ + function cleanUpRolloverQueue(address[] memory _addressesToDelist) + external + onlyFactory + epochHasStarted(epochs[epochs.length - 1]) + { + // check that there is no active deposit window; + for (uint256 i = 0; i < _addressesToDelist.length; i++) { + address owner = _addressesToDelist[i]; + uint256 index = ownerToRollOverQueueIndex[owner]; + if (index == 0) continue; + uint256 queueIndex = index - 1; + if (rolloverQueue[queueIndex].shares == 0) { + // overwrite the item to be removed with the last item in the queue + rolloverQueue[queueIndex] = rolloverQueue[ + rolloverQueue.length - 1 + ]; + // remove the last item in the queue + rolloverQueue.pop(); + // update the index of prev last user ( mapping index is allways array index + 1) + ownerToRollOverQueueIndex[rolloverQueue[queueIndex].receiver] = + queueIndex + + 1; + // remove receiver from index mapping + delete ownerToRollOverQueueIndex[owner]; + } + } + } + /*/////////////////////////////////////////////////////////////// Getter Functions //////////////////////////////////////////////////////////////*/ + /** + * @notice calculates fee percent based on time + * @param minX min x value + * @param maxX max x value + */ + function calculateFeePercent(int256 minX, int256 maxX) + public + view + returns (uint256 _y) + { + /** + * Two Point Form + * https://www.cuemath.com/geometry/two-point-form/ + * https://ethereum.stackexchange.com/a/143172 + */ + // minY will always be 0 thats why is (maxY - minY) shorten to maxY + int256 maxY = int256(depositFee) * int256(FixedPointMathLib.WAD); + _y = uint256( // cast to uint256 + ((((maxY) / (maxX - minX)) * (int256(block.timestamp) - maxX)) + + maxY) / (int256(FixedPointMathLib.WAD)) // two point math // scale down + ); + } + /** @notice returns the rollover index + * @dev will revert if user is not in rollover queue * @param _owner address of the owner * @return rollover index */ @@ -622,6 +645,27 @@ contract Carousel is VaultV2 { return ownerToRollOverQueueIndex[_owner] - 1; } + /** @notice retruns deposit fee at this time + * @param _id epoch id + * @param _assets amount of assets + * @return feeAmount fee amount + * @return assetsAfterFee assets after fee + */ + function getEpochDepositFee(uint256 _id, uint256 _assets) + public + view + returns (uint256 feeAmount, uint256 assetsAfterFee) + { + (uint256 maxX, , uint256 minX) = getEpochConfig(_id); + // deposit fee is calcualted linearly between time of epoch creation and epoch starting (deposit window) + // this is because late depositors have an informational advantage + uint256 fee = calculateFeePercent(int256(minX), int256(maxX)); + // min minRequiredDeposit modifier ensures that _assets has high enough value to not devide by 0 + // 0.5% = multiply by 10000 then divide by 50 + feeAmount = _assets.mulDivDown(fee, 10000); + assetsAfterFee = _assets - feeAmount; + } + /** @notice returns the emissions to withdraw * @param _id epoch id * @param _assets amount of assets to withdraw @@ -635,53 +679,123 @@ contract Carousel is VaultV2 { entitledAmount = _assets.mulDivDown(emissions[_id], finalTVL[_id]); } + /** @notice returns the emissions to withdraw + * @param _id epoch id + * @param _assets amount of shares + * @return entitledShareAmount amount of emissions to withdraw + */ + function previewAmountInShares(uint256 _id, uint256 _assets) + public + view + returns (uint256 entitledShareAmount) + { + if (claimTVL[_id] != 0) { + entitledShareAmount = _assets.mulDivDown( + finalTVL[_id], + claimTVL[_id] + ); + } else { + entitledShareAmount = 0; + } + } + /** @notice returns the deposit queue length * @return queue length for the deposit */ - function getDepositQueueLenght() public view returns (uint256) { + function getDepositQueueLength() public view returns (uint256) { return depositQueue.length; } /** @notice returns the queue length for the rollover * @return queue length for the rollover */ - function getRolloverQueueLenght() public view returns (uint256) { + function getRolloverQueueLength() public view returns (uint256) { return rolloverQueue.length; } /** @notice returns the total value locked in the rollover queue * @return tvl total value locked in the rollover queue */ - function getRolloverTVL(uint256 _epochId) + function getRolloverTVLByEpochId(uint256 _epochId) public view returns (uint256 tvl) { for (uint256 i = 0; i < rolloverQueue.length; i++) { + uint256 assets = previewWithdraw( + rolloverQueue[i].epochId, + rolloverQueue[i].shares + ); if ( rolloverQueue[i].epochId == _epochId && - (previewWithdraw( - rolloverQueue[i].epochId, - rolloverQueue[i].assets - ) > rolloverQueue[i].assets) + (assets > rolloverQueue[i].shares) // check if position is in profit and getting rollover ) { - tvl += rolloverQueue[i].assets; + tvl += assets; } } } + function getRolloverTVL() public + view + returns (uint256 tvl) { + for (uint256 i = 0; i < rolloverQueue.length; i++) { + uint256 assets = previewWithdraw( + rolloverQueue[i].epochId, + rolloverQueue[i].shares + ); + if ( + (assets > rolloverQueue[i].shares) // check if position is in profit and getting rollover + ) { + tvl += assets; + } + } + } + + function getRolloverQueueItem(uint256 _index) + public + view + returns ( + address receiver, + uint256 shares, + uint256 epochId + ) + { + receiver = rolloverQueue[_index].receiver; + shares = rolloverQueue[_index].shares; + epochId = rolloverQueue[_index].epochId; + } + /** @notice returns users rollover balance and epoch which is rolling over * @param _owner address of the user - * @return balance balance of the user + * @return shares balance of the user in rollover position * @return epochId epoch id */ - function getRolloverBalance(address _owner) + function getRolloverPosition(address _owner) + public + view + returns (uint256 shares, uint256 epochId) + { + if (!isEnlistedInRolloverQueue(_owner)) { + return (0, 0); + } + uint256 index = getRolloverIndex(_owner); + shares = rolloverQueue[index].shares; + epochId = rolloverQueue[index].epochId; + } + + /** @notice returns is user is enlisted in the rollover queue + * @param _owner address of the user + * @return bool is user enlisted in the rollover queue + */ + function isEnlistedInRolloverQueue(address _owner) public view - returns (uint256 balance, uint256 epochId) + returns (bool) { - balance = rolloverQueue[getRolloverIndex(_owner)].assets; - epochId = rolloverQueue[getRolloverIndex(_owner)].epochId; + if (ownerToRollOverQueueIndex[_owner] == 0) { + return false; + } + return rolloverQueue[getRolloverIndex(_owner)].shares != 0; } /** @notice returns the total value locked in the deposit queue @@ -689,27 +803,16 @@ contract Carousel is VaultV2 { */ function getDepositQueueTVL() public view returns (uint256 tvl) { for (uint256 i = 0; i < depositQueue.length; i++) { - tvl += depositQueue[i].assets; + tvl += depositQueue[i].shares; } } - /** @notice returns the total emissions balance - * @return totalEmissions total emissions balance - */ - function balanceOfEmissions(address _owner, uint256 _id) - public - view - returns (uint256) - { - return _emissionsBalances[_id][_owner]; - } - /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ struct QueueItem { - uint256 assets; + uint256 shares; address receiver; uint256 epochId; } @@ -723,10 +826,10 @@ contract Carousel is VaultV2 { address token; uint256 strike; address controller; - address treasury; address emissionsToken; uint256 relayerFee; uint256 depositFee; + uint256 minQueueDeposit; } /*////////////////////////////////////////////////////////////// @@ -736,26 +839,26 @@ contract Carousel is VaultV2 { /** @notice checks if deposit is greater than relayer fee * @param _assets amount of assets to deposit */ - modifier minRequiredDeposit(uint256 _assets) { - if (_assets < relayerFee) revert MinDeposit(); + modifier minRequiredDeposit(uint256 _assets, uint256 _epochId) { + if (_epochId == 0 && _assets < minQueueDeposit) revert MinDeposit(); _; } /** @notice checks if not rolling over * @param _receiver address of the receiver * @param _epochId epoch id - * @param _assets amount of assets to deposit + * @param _shares amount of assets to deposit */ modifier notRollingOver( address _receiver, uint256 _epochId, - uint256 _assets + uint256 _shares ) { - if (ownerToRollOverQueueIndex[_receiver] != 0) { + if (isEnlistedInRolloverQueue(_receiver)) { QueueItem memory item = rolloverQueue[getRolloverIndex(_receiver)]; if ( item.epochId == _epochId && - (balanceOf(_receiver, _epochId) - item.assets) < _assets + (balanceOf(_receiver, _epochId) - item.shares) < _shares ) revert AlreadyRollingOver(); } _; @@ -777,6 +880,16 @@ contract Carousel is VaultV2 { EVENTS //////////////////////////////////////////////////////////////*/ + event WithdrawWithEmissions( + address caller, + address receiver, + address indexed owner, + uint256 indexed id, + uint256 assets, + uint256 shares, + uint256 emissions + ); + /** @notice emitted when a deposit is queued * @param sender the address of the sender * @param receiver the address of the receiver @@ -806,19 +919,4 @@ contract Carousel is VaultV2 { uint256 assets, uint256 epochId ); - - /** @notice emitted when emissions are transfered - * @param operator the address of the operator - * @param from the address of the sender - * @param to the address of the receiver - * @param id the id of the emissions - * @param value the amount of emissions - */ - event TransferSingleEmissions( - address indexed operator, - address indexed from, - address indexed to, - uint256 id, - uint256 value - ); } diff --git a/src/v2/Carousel/CarouselFactory.sol b/src/v2/Carousel/CarouselFactory.sol index dc557a1c..ca53751e 100644 --- a/src/v2/Carousel/CarouselFactory.sol +++ b/src/v2/Carousel/CarouselFactory.sol @@ -55,14 +55,24 @@ contract CarouselFactory is VaultFactoryV2 { if (_marketCalldata.oracle == address(0)) revert AddressZero(); if (_marketCalldata.underlyingAsset == address(0)) revert AddressZero(); - if (tokenToOracle[_marketCalldata.token] == address(0)) { - tokenToOracle[_marketCalldata.token] = _marketCalldata.oracle; - } + marketId = getMarketId( + _marketCalldata.token, + _marketCalldata.strike, + _marketCalldata.underlyingAsset + ); - marketId = getMarketId(_marketCalldata.token, _marketCalldata.strike); if (marketIdToVaults[marketId][0] != address(0)) revert MarketAlreadyExists(); + marketIdInfo[marketId] = MarketInfo( + _marketCalldata.token, + _marketCalldata.strike, + _marketCalldata.underlyingAsset + ); + + // set oracle for the market + marketToOracle[marketId] = _marketCalldata.oracle; + //y2kUSDC_99*PREMIUM premium = CarouselCreator.createCarousel( CarouselCreator.CarouselMarketConfiguration( @@ -77,7 +87,8 @@ contract CarouselFactory is VaultFactoryV2 { treasury, address(emissionsToken), _marketCalldata.relayerFee, - _marketCalldata.depositFee + _marketCalldata.depositFee, + _marketCalldata.minQueueDeposit ) ); @@ -95,7 +106,8 @@ contract CarouselFactory is VaultFactoryV2 { treasury, address(emissionsToken), _marketCalldata.relayerFee, - _marketCalldata.depositFee + _marketCalldata.depositFee, + _marketCalldata.minQueueDeposit ) ); @@ -119,6 +131,18 @@ contract CarouselFactory is VaultFactoryV2 { return (premium, collateral, marketId); } + function createNewMarket(MarketConfigurationCalldata memory) + external + override + returns ( + address, + address, + uint256 + ) + { + revert(); + } + /** @notice Function to create a new epoch with emissions @param _marketId uint256 of the marketId @param _epochBegin uint40 of the epoch begin @@ -138,7 +162,7 @@ contract CarouselFactory is VaultFactoryV2 { uint256 _collatEmissions ) public returns (uint256 epochId, address[2] memory vaults) { // no need for onlyOwner modifier as createEpoch already has modifier - (epochId, vaults) = createEpoch( + (epochId, vaults) = _createEpoch( _marketId, _epochBegin, _epochEnd, @@ -150,6 +174,27 @@ contract CarouselFactory is VaultFactoryV2 { emissionsToken.safeTransferFrom(treasury, vaults[1], _collatEmissions); ICarousel(vaults[1]).setEmissions(epochId, _collatEmissions); + + emit EpochCreatedWithEmissions( + epochId, + _marketId, + _epochBegin, + _epochEnd, + _withdrawalFee, + _permiumEmissions, + _collatEmissions + ); + } + + // to prevent the creation of epochs without emissions + // this function is not used + function createEpoch( + uint256, /*_marketId*/ + uint40, /*_epochBegin*/ + uint40, /*_epochEnd*/ + uint16 /*_withdrawalFee*/ + ) public override returns (uint256, address[2] memory) { + revert(); } /*////////////////////////////////////////////////////////////// @@ -168,10 +213,15 @@ contract CarouselFactory is VaultFactoryV2 { address[2] memory vaults = marketIdToVaults[_marketIndex]; if (vaults[0] == address(0)) revert MarketDoesNotExist(_marketIndex); - ICarousel insr = ICarousel(vaults[0]); - ICarousel risk = ICarousel(vaults[1]); - insr.changeRelayerFee(_relayerFee); - risk.changeRelayerFee(_relayerFee); + + ICarousel premium = ICarousel(vaults[0]); + ICarousel collat = ICarousel(vaults[1]); + + if (premium.getDepositQueueLength() > 0) revert QueueNotEmpty(); + if (collat.getDepositQueueLength() > 0) revert QueueNotEmpty(); + + premium.changeRelayerFee(_relayerFee); + collat.changeRelayerFee(_relayerFee); emit ChangedRelayerFee(_relayerFee, _marketIndex); } @@ -198,6 +248,14 @@ contract CarouselFactory is VaultFactoryV2 { ); } + // admin function to cleanup rollover queue by passing in array of addresses and vault address + function cleanupRolloverQueue(address[] memory _addresses, address _vault) + public + onlyTimeLocker + { + ICarousel(_vault).cleanupRolloverQueue(_addresses); + } + /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -212,6 +270,7 @@ contract CarouselFactory is VaultFactoryV2 { address controller; uint256 relayerFee; uint256 depositFee; + uint256 minQueueDeposit; } /*////////////////////////////////////////////////////////////// @@ -221,6 +280,7 @@ contract CarouselFactory is VaultFactoryV2 { error InvalidRelayerFee(); error InvalidVaultIndex(); error InvalidDepositFee(); + error QueueNotEmpty(); /*////////////////////////////////////////////////////////////// EVENTS @@ -234,4 +294,14 @@ contract CarouselFactory is VaultFactoryV2 { ); event ChangedRelayerFee(uint256 relayerFee, uint256 marketIndex); + + event EpochCreatedWithEmissions( + uint256 epochId, + uint256 marketId, + uint40 epochBegin, + uint40 epochEnd, + uint16 withdrawalFee, + uint256 premiumEmissions, + uint256 collateralEmissions + ); } diff --git a/src/v2/Controllers/ControllerPeggedAssetV2.sol b/src/v2/Controllers/ControllerPeggedAssetV2.sol index 0587c869..6de0c752 100644 --- a/src/v2/Controllers/ControllerPeggedAssetV2.sol +++ b/src/v2/Controllers/ControllerPeggedAssetV2.sol @@ -14,6 +14,7 @@ contract ControllerPeggedAssetV2 { IVaultFactoryV2 public immutable vaultFactory; AggregatorV2V3Interface internal sequencerUptimeFeed; + uint256 private constant MAX_UPDATE_TRESHOLD = 2 days; uint16 private constant GRACE_PERIOD_TIME = 3600; address public immutable treasury; @@ -59,7 +60,7 @@ contract ControllerPeggedAssetV2 { if (premiumVault.epochExists(_epochId) == false) revert EpochNotExist(); - int256 price = getLatestPrice(premiumVault.token()); + int256 price = getLatestPrice(_marketId); if (int256(premiumVault.strike()) <= price) revert PriceNotAtStrikePrice(price); @@ -133,7 +134,7 @@ contract ControllerPeggedAssetV2 { ), true, block.timestamp, - price + uint256(price) ); } @@ -155,6 +156,13 @@ contract ControllerPeggedAssetV2 { collateralVault.epochExists(_epochId) == false ) revert EpochNotExist(); + if ( + premiumVault.totalAssets(_epochId) == 0 || + collateralVault.totalAssets(_epochId) == 0 + ) { + revert VaultZeroTVL(); + } + (, uint40 epochEnd, ) = premiumVault.getEpochConfig(_epochId); if (block.timestamp <= uint256(epochEnd)) revert EpochNotExpired(); @@ -267,17 +275,16 @@ contract ControllerPeggedAssetV2 { GETTERS //////////////////////////////////////////////////////////////*/ /** @notice Lookup token price - * @param _token Target token address + * @param _marketId Target token address * @return nowPrice Current token price */ - function getLatestPrice(address _token) public view returns (int256) { - ( - , - /*uint80 roundId*/ + function getLatestPrice(uint256 _marketId) public view returns (int256) { + ( + /*uint80 roundId*/, int256 answer, - uint256 startedAt, /*uint256 updatedAt*/ /*uint80 answeredInRound*/ - , - + uint256 startedAt, + /*uint256 updatedAt*/, + /*uint80 answeredInRound*/ ) = sequencerUptimeFeed.latestRoundData(); // Answer == 0: Sequencer is up @@ -294,10 +301,19 @@ contract ControllerPeggedAssetV2 { } AggregatorV3Interface priceFeed = AggregatorV3Interface( - vaultFactory.tokenToOracle(_token) + vaultFactory.marketToOracle(_marketId) ); - (uint80 roundID, int256 price, , , uint80 answeredInRound) = priceFeed - .latestRoundData(); + ( + uint80 roundID, + int256 price, + , + uint256 updatedAt, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + + if (updatedAt < block.timestamp - MAX_UPDATE_TRESHOLD) + revert PriceOutdated(); + uint256 decimals = priceFeed.decimals(); if (decimals < 18) { @@ -355,6 +371,7 @@ contract ControllerPeggedAssetV2 { error EpochNotExpired(); error VaultNotZeroTVL(); error VaultZeroTVL(); + error PriceOutdated(); /*////////////////////////////////////////////////////////////// EVENTS @@ -366,7 +383,7 @@ contract ControllerPeggedAssetV2 { * @param tvl TVL * @param strikeMet Flag if event isDisaster * @param time time - * @param depegPrice Price that triggered depeg + * @param strikeData Data that triggered depeg */ event EpochResolved( uint256 indexed epochId, @@ -374,7 +391,7 @@ contract ControllerPeggedAssetV2 { VaultTVL tvl, bool strikeMet, uint256 time, - int256 depegPrice + uint256 strikeData ); /** @notice Sets epoch to null when event is emitted diff --git a/src/v2/CustomERC1155/ERC1155.sol b/src/v2/CustomERC1155/ERC1155.sol new file mode 100644 index 00000000..59bef99e --- /dev/null +++ b/src/v2/CustomERC1155/ERC1155.sol @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/ERC1155.sol) + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/utils/Context.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +/** + * @dev Implementation of the basic standard multi-token. + * See https://eips.ethereum.org/EIPS/eip-1155 + * Originally based on code by Enjin: https://github.com/enjin/erc-1155 + * + * _Available since v3.1._ + */ +contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { + using Address for address; + + // Mapping from token ID to account balances + mapping(uint256 => mapping(address => uint256)) private _balances; + + // Mapping from account to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json + string private _uri; + + /** + * @dev See {_setURI}. + */ + constructor(string memory uri_) { + _setURI(uri_); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC165, IERC165) + returns (bool) + { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the same URI for *all* token types. It relies + * on the token type ID substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * Clients calling this function must replace the `\{id\}` substring with the + * actual token type ID. + */ + function uri(uint256) public view virtual override returns (string memory) { + return _uri; + } + + /** + * @dev See {IERC1155-balanceOf}. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) + public + view + virtual + override + returns (uint256) + { + require( + account != address(0), + "ERC1155: address zero is not a valid owner" + ); + return _balances[id][account]; + } + + /** + * @dev See {IERC1155-balanceOfBatch}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + public + view + virtual + override + returns (uint256[] memory) + { + require( + accounts.length == ids.length, + "ERC1155: accounts and ids length mismatch" + ); + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + + return batchBalances; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) + public + virtual + override + { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC1155-isApprovedForAll}. + */ + function isApprovedForAll(address account, address operator) + public + view + virtual + override + returns (bool) + { + return _operatorApprovals[account][operator]; + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual override { + require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: caller is not owner nor approved" + ); + _safeTransferFrom(from, to, id, amount, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual override { + require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: transfer caller is not owner nor approved" + ); + _safeBatchTransferFrom(from, to, ids, amounts, data); + } + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + uint256[] memory ids = _asSingletonArray(id); + uint256[] memory amounts = _asSingletonArray(amount); + + _beforeTokenTransfer(operator, from, to, ids, amounts, data); + + uint256 fromBalance = _balances[id][from]; + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + + emit TransferSingle(operator, from, to, id, amount); + + _afterTokenTransfer(operator, from, to, ids, amounts, data); + + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, to, ids, amounts, data); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + + _afterTokenTransfer(operator, from, to, ids, amounts, data); + + _doSafeBatchTransferAcceptanceCheck( + operator, + from, + to, + ids, + amounts, + data + ); + } + + /** + * @dev Sets a new URI for all token types, by relying on the token type ID + * substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * By this mechanism, any occurrence of the `\{id\}` substring in either the + * URI or any of the amounts in the JSON file at said URI will be replaced by + * clients with the token type ID. + * + * For example, the `https://token-cdn-domain/\{id\}.json` URI would be + * interpreted by clients as + * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + * for token type ID 0x4cce0. + * + * See {uri}. + * + * Because these URIs cannot be meaningfully represented by the {URI} event, + * this function emits no events. + */ + function _setURI(string memory newuri) internal virtual { + _uri = newuri; + } + + /** + * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + + address operator = _msgSender(); + uint256[] memory ids = _asSingletonArray(id); + uint256[] memory amounts = _asSingletonArray(amount); + + _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); + + _balances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + + _afterTokenTransfer(operator, address(0), to, ids, amounts, data); + + // remove _doSafeTransferAcceptanceCheck to prevent reverting in queue + // if receiver is a contract and does not implement the ERC1155Holder interface funds will be stuck + // _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); + + for (uint256 i = 0; i < ids.length; i++) { + _balances[ids[i]][to] += amounts[i]; + } + + emit TransferBatch(operator, address(0), to, ids, amounts); + + _afterTokenTransfer(operator, address(0), to, ids, amounts, data); + + _doSafeBatchTransferAcceptanceCheck( + operator, + address(0), + to, + ids, + amounts, + data + ); + } + + /** + * @dev Destroys `amount` tokens of token type `id` from `from` + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must have at least `amount` tokens of token type `id`. + */ + function _burn( + address from, + uint256 id, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = _msgSender(); + uint256[] memory ids = _asSingletonArray(id); + uint256[] memory amounts = _asSingletonArray(amount); + + _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + + emit TransferSingle(operator, from, address(0), id, amount); + + _afterTokenTransfer(operator, from, address(0), ids, amounts, ""); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + */ + function _burnBatch( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); + + for (uint256 i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require( + fromBalance >= amount, + "ERC1155: burn amount exceeds balance" + ); + unchecked { + _balances[id][from] = fromBalance - amount; + } + } + + emit TransferBatch(operator, from, address(0), ids, amounts); + + _afterTokenTransfer(operator, from, address(0), ids, amounts, ""); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits an {ApprovalForAll} event. + */ + function _setApprovalForAll( + address owner, + address operator, + bool approved + ) internal virtual { + require(owner != operator, "ERC1155: setting approval status for self"); + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning, as well as batched variants. + * + * The same hook is called on both single and batched variants. For single + * transfers, the length of the `ids` and `amounts` arrays will be 1. + * + * Calling conditions (for each `id` and `amount` pair): + * + * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * of token type `id` will be transferred to `to`. + * - When `from` is zero, `amount` tokens of token type `id` will be minted + * for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` + * will be burned. + * - `from` and `to` are never both zero. + * - `ids` and `amounts` have the same, non-zero length. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual {} + + /** + * @dev Hook that is called after any token transfer. This includes minting + * and burning, as well as batched variants. + * + * The same hook is called on both single and batched variants. For single + * transfers, the length of the `id` and `amount` arrays will be 1. + * + * Calling conditions (for each `id` and `amount` pair): + * + * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * of token type `id` will be transferred to `to`. + * - When `from` is zero, `amount` tokens of token type `id` will be minted + * for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` + * will be burned. + * - `from` and `to` are never both zero. + * - `ids` and `amounts` have the same, non-zero length. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual {} + + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) private { + if (to.isContract()) { + try + IERC1155Receiver(to).onERC1155Received( + operator, + from, + id, + amount, + data + ) + returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) private { + if (to.isContract()) { + try + IERC1155Receiver(to).onERC1155BatchReceived( + operator, + from, + ids, + amounts, + data + ) + returns (bytes4 response) { + if ( + response != IERC1155Receiver.onERC1155BatchReceived.selector + ) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _asSingletonArray(uint256 element) + private + pure + returns (uint256[] memory) + { + uint256[] memory array = new uint256[](1); + array[0] = element; + + return array; + } +} diff --git a/src/v2/CustomERC1155/ERC1155Supply.sol b/src/v2/CustomERC1155/ERC1155Supply.sol new file mode 100644 index 00000000..730927ad --- /dev/null +++ b/src/v2/CustomERC1155/ERC1155Supply.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/extensions/ERC1155Supply.sol) + +pragma solidity ^0.8.0; + +import "./ERC1155.sol"; + +/** + * @dev Extension of ERC1155 that adds tracking of total supply per id. + * + * Useful for scenarios where Fungible and Non-fungible tokens have to be + * clearly identified. Note: While a totalSupply of 1 might mean the + * corresponding is an NFT, there is no guarantees that no other token with the + * same id are not going to be minted. + */ +abstract contract ERC1155Supply is ERC1155 { + mapping(uint256 => uint256) private _totalSupply; + + /** + * @dev Total amount of tokens in with a given id. + */ + function totalSupply(uint256 id) public view virtual returns (uint256) { + return _totalSupply[id]; + } + + /** + * @dev Indicates whether any token exist with a given id, or not. + */ + function exists(uint256 id) public view virtual returns (bool) { + return ERC1155Supply.totalSupply(id) > 0; + } + + /** + * @dev See {ERC1155-_beforeTokenTransfer}. + */ + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual override { + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + + if (from == address(0)) { + for (uint256 i = 0; i < ids.length; ++i) { + _totalSupply[ids[i]] += amounts[i]; + } + } + + if (to == address(0)) { + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + uint256 supply = _totalSupply[id]; + require( + supply >= amount, + "ERC1155: burn amount exceeds totalSupply" + ); + unchecked { + _totalSupply[id] = supply - amount; + } + } + } + } +} diff --git a/src/v2/SemiFungibleVault.sol b/src/v2/SemiFungibleVault.sol index 3e7c2f15..52df7005 100644 --- a/src/v2/SemiFungibleVault.sol +++ b/src/v2/SemiFungibleVault.sol @@ -5,10 +5,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { - ERC1155Supply -} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {ERC1155Supply} from "./CustomERC1155/ERC1155Supply.sol"; +import {ERC1155} from "./CustomERC1155/ERC1155.sol"; import {ISemiFungibleVault} from "./interfaces/ISemiFungibleVault.sol"; /// @author MiguelBits diff --git a/src/v2/TimeLock.sol b/src/v2/TimeLock.sol index 145ed35f..b5976604 100644 --- a/src/v2/TimeLock.sol +++ b/src/v2/TimeLock.sol @@ -9,8 +9,9 @@ contract TimeLock { mapping(bytes32 => bool) public queued; address public policy; + address public factory; - uint32 public constant MIN_DELAY = 3 days; + uint32 public constant MIN_DELAY = 7 days; uint32 public constant MAX_DELAY = 30 days; uint32 public constant GRACE_PERIOD = 14 days; @@ -77,7 +78,7 @@ contract TimeLock { string calldata _func, bytes calldata _data, uint256 _timestamp - ) external onlyOwner returns (bytes memory) { + ) external payable onlyOwner returns (bytes memory) { bytes32 txId = getTxId(_target, _value, _func, _data, _timestamp); //check tx id queued @@ -109,6 +110,9 @@ contract TimeLock { data = _data; } + if (msg.value != _value) { + revert ValueNotMatchError(msg.value, _value); + } // call target (bool ok, bytes memory res) = _target.call{value: _value}(data); if (!ok) { @@ -176,6 +180,16 @@ contract TimeLock { emit ChangeOwner(_newOwner); } + /** @notice change owner on factory + * @param _newOwner new owner + */ + function changeOwnerOnFactory(address _newOwner, address _factory) + external + onlyOwner + { + IVaultFactoryV2(_factory).transferOwnership(_newOwner); + } + /*/////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -188,6 +202,7 @@ contract TimeLock { error TimestampExpiredError(uint256 blocktimestamp, uint256 timestamp); error TxFailedError(string func); error AddressZero(); + error ValueNotMatchError(uint256 msgValue, uint256 value); /** @notice queues transaction when emitted @param txId unique id of the transaction diff --git a/src/v2/VaultFactoryV2.sol b/src/v2/VaultFactoryV2.sol index 751d5fe5..67f83aee 100644 --- a/src/v2/VaultFactoryV2.sol +++ b/src/v2/VaultFactoryV2.sol @@ -22,8 +22,9 @@ contract VaultFactoryV2 is Ownable { mapping(uint256 => address[2]) public marketIdToVaults; //[0] premium and [1] collateral vault mapping(uint256 => uint256[]) public marketIdToEpochs; //all epochs in the market + mapping(uint256 => MarketInfo) public marketIdInfo; // marketId configuration mapping(uint256 => uint16) public epochFee; // epochId to fee - mapping(address => address) public tokenToOracle; //token address to respective oracle smart contract address + mapping(uint256 => address) public marketToOracle; //token address to respective oracle smart contract address mapping(address => bool) public controllers; /*////////////////////////////////////////////////////////////// @@ -57,26 +58,49 @@ contract VaultFactoryV2 is Ownable { */ function createNewMarket(MarketConfigurationCalldata memory _marketCalldata) external + virtual onlyOwner returns ( address premium, address collateral, uint256 marketId ) + { + return _createNewMarket(_marketCalldata); + } + + function _createNewMarket( + MarketConfigurationCalldata memory _marketCalldata + ) + internal + returns ( + address premium, + address collateral, + uint256 marketId + ) { if (!controllers[_marketCalldata.controller]) revert ControllerNotSet(); if (_marketCalldata.token == address(0)) revert AddressZero(); if (_marketCalldata.oracle == address(0)) revert AddressZero(); if (_marketCalldata.underlyingAsset == address(0)) revert AddressZero(); - if (tokenToOracle[_marketCalldata.token] == address(0)) { - tokenToOracle[_marketCalldata.token] = _marketCalldata.oracle; - } + marketId = getMarketId( + _marketCalldata.token, + _marketCalldata.strike, + _marketCalldata.underlyingAsset + ); + marketIdInfo[marketId] = MarketInfo( + _marketCalldata.token, + _marketCalldata.strike, + _marketCalldata.underlyingAsset + ); - marketId = getMarketId(_marketCalldata.token, _marketCalldata.strike); if (marketIdToVaults[marketId][0] != address(0)) revert MarketAlreadyExists(); + // set oracle for the market + marketToOracle[marketId] = _marketCalldata.oracle; + //y2kUSDC_99*PREMIUM premium = VaultV2Creator.createVaultV2( VaultV2Creator.MarketConfiguration( @@ -139,7 +163,21 @@ contract VaultFactoryV2 is Ownable { uint40 _epochBegin, uint40 _epochEnd, uint16 _withdrawalFee - ) public onlyOwner returns (uint256 epochId, address[2] memory vaults) { + ) + public + virtual + onlyOwner + returns (uint256 epochId, address[2] memory vaults) + { + return _createEpoch(_marketId, _epochBegin, _epochEnd, _withdrawalFee); + } + + function _createEpoch( + uint256 _marketId, + uint40 _epochBegin, + uint40 _epochEnd, + uint16 _withdrawalFee + ) internal returns (uint256 epochId, address[2] memory vaults) { vaults = marketIdToVaults[_marketId]; if (vaults[0] == address(0) || vaults[1] == address(0)) { @@ -202,7 +240,7 @@ contract VaultFactoryV2 is Ownable { /** @notice Function to whitelist controller smart contract, only owner or timelocker can add more controllers. - owner can set controller once, all future controllers must be set by timelocker. + @dev owner can set controller once, all future controllers must be set by timelocker. @param _controller Address of the controller smart contract */ function whitelistController(address _controller) public { @@ -220,31 +258,6 @@ contract VaultFactoryV2 is Ownable { } } - /** - @notice Admin function, whitelists an address on vault for sendTokens function - @param _treasury Treasury address - @param _marketId Target market index - */ - function changeTreasury(uint256 _marketId, address _treasury) - public - onlyTimeLocker - { - if (_treasury == address(0)) revert AddressZero(); - - address[2] memory vaults = marketIdToVaults[_marketId]; - - if (vaults[0] == address(0) || vaults[1] == address(0)) { - revert MarketDoesNotExist(_marketId); - } - - IVaultV2(vaults[0]).whiteListAddress(_treasury); - IVaultV2(vaults[1]).whiteListAddress(_treasury); - IVaultV2(vaults[0]).setTreasury(treasury); - IVaultV2(vaults[1]).setTreasury(treasury); - - emit AddressWhitelisted(_treasury, _marketId); - } - /** @notice Admin function, whitelists an address on vault for sendTokens function @param _marketId Target market index @@ -304,25 +317,27 @@ contract VaultFactoryV2 is Ownable { /** @notice Timelocker function, changes oracle address for a given token - @param _token Target token address + @param _marketId Target token address @param _oracle Oracle address */ - function changeOracle(address _token, address _oracle) + function changeOracle(uint256 _marketId, address _oracle) public onlyTimeLocker { if (_oracle == address(0)) revert AddressZero(); - if (_token == address(0)) revert AddressZero(); + if (_marketId == 0) revert MarketDoesNotExist(_marketId); + if (marketToOracle[_marketId] == address(0)) + revert MarketDoesNotExist(_marketId); - tokenToOracle[_token] = _oracle; - emit OracleChanged(_token, _oracle); + marketToOracle[_marketId] = _oracle; + emit OracleChanged(_marketId, _oracle); } /** @notice Timelocker function, changes owner address @param _owner Address of the new _owner */ - function changeOwner(address _owner) public onlyTimeLocker { + function transferOwnership(address _owner) public override onlyTimeLocker { if (_owner == address(0)) revert AddressZero(); _transferOwnership(_owner); } @@ -368,16 +383,35 @@ contract VaultFactoryV2 is Ownable { /** @notice Function to compute the marketId from a token and a strike price - @param token Address of the token - @param strikePrice uint256 of the strike price + @param _token Address of the token + @param _strikePrice uint256 of the strike price + @param _underlying Address of the underlying @return marketId uint256 of the marketId */ - function getMarketId(address token, uint256 strikePrice) + function getMarketId( + address _token, + uint256 _strikePrice, + address _underlying + ) public pure returns (uint256 marketId) { + return + uint256( + keccak256(abi.encodePacked(_token, _strikePrice, _underlying)) + ); + } + + // get marketInfo + function getMarketInfo(uint256 _marketId) public - pure - returns (uint256 marketId) + view + returns ( + address token, + uint256 strike, + address underlyingAsset + ) { - return uint256(keccak256(abi.encodePacked(token, strikePrice))); + token = marketIdInfo[_marketId].token; + strike = marketIdInfo[_marketId].strike; + underlyingAsset = marketIdInfo[_marketId].underlyingAsset; } /** @@ -421,6 +455,12 @@ contract VaultFactoryV2 is Ownable { IVaultV2 collateral; } + struct MarketInfo { + address token; + uint256 strike; + address underlyingAsset; + } + /*////////////////////////////////////////////////////////////// MODIFIERS //////////////////////////////////////////////////////////////*/ @@ -512,10 +552,10 @@ contract VaultFactoryV2 is Ownable { ); /** @notice Oracle is changed when event is emitted - * @param _token Target token address + * @param _marketId Target token address * @param _oracle Target oracle address */ - event OracleChanged(address indexed _token, address _oracle); + event OracleChanged(uint256 indexed _marketId, address _oracle); /** @notice Address whitelisted is changed when event is emitted * @param _wAddress whitelisted address diff --git a/src/v2/VaultV2.sol b/src/v2/VaultV2.sol index d481a293..8084e874 100644 --- a/src/v2/VaultV2.sol +++ b/src/v2/VaultV2.sol @@ -6,6 +6,7 @@ import { } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "./SemiFungibleVault.sol"; import {IVaultV2} from "./interfaces/IVaultV2.sol"; +import {IVaultFactoryV2} from "./interfaces/IVaultFactoryV2.sol"; import {IWETH} from "./interfaces/IWETH.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { @@ -28,7 +29,6 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { uint256 public immutable strike; // Earthquake bussiness logic bool public immutable isWETH; - address public treasury; address public counterPartyVault; address public factory; address public controller; @@ -55,7 +55,6 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { @param _token address of the token that will be used as collateral; @param _strike uint256 representing the strike price of the vault; @param _controller address of the controller of the vault; - @param _treasury address of the treasury of the vault; */ constructor( bool _isWETH, @@ -65,19 +64,15 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { string memory _tokenURI, address _token, uint256 _strike, - address _controller, - address _treasury + address _controller ) SemiFungibleVault(IERC20(_assetAddress), _name, _symbol, _tokenURI) { if (_controller == address(0)) revert AddressZero(); if (_token == address(0)) revert AddressZero(); if (_assetAddress == address(0)) revert AddressZero(); - if (_treasury == address(0)) revert AddressZero(); token = _token; strike = _strike; factory = msg.sender; controller = _controller; - treasury = _treasury; - whitelistedAddresses[_treasury] = true; isWETH = _isWETH; } @@ -86,9 +81,9 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { //////////////////////////////////////////////////////////////*/ /** - @param _id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param _assets uint256 representing how many assets the user wants to deposit, a fee will be taken from this value; - @param _receiver address of the receiver of the assets provided by this function, that represent the ownership of the deposited asset; + @param _id uint256 epoch identifier; + @param _assets uint256 amount of assets the user wants to deposit denominated in underlying asset decimals; + @param _receiver address of the receiver of the shares minted; */ function deposit( uint256 _id, @@ -117,7 +112,7 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { /** @notice Deposit ETH function @param _id uint256 representing the id of the epoch; - @param _receiver address of the receiver of the shares provided by this function, that represent the ownership of the deposited asset; + @param _receiver address of the receiver of the shares minted; */ function depositETH(uint256 _id, address _receiver) external @@ -138,16 +133,16 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { } /** - @notice Withdraw entitled deposited assets, checking if a depeg event - @param _id uint256 identifier of the epoch you want to withdraw from; - @param _assets uint256 of how many assets you want to withdraw, this value will be used to calculate how many assets you are entitle to according the vaults claimTVL; - @param _receiver Address of the receiver of the assets provided by this function, that represent the ownership of the transfered asset; - @param _owner Address of the owner of these said assets; - @return shares How many shares the owner is entitled to, according to the conditions; + @notice Withdraw entitled assets and burn shares of epoch + @param _id uint256 identifier of the epoch; + @param _shares uint256 amount of shares to withdraw, this value will be used to calculate how many assets you are entitle to according the vaults claimTVL; + @param _receiver Address of the receiver of the assets provided by this function, that represent the ownership of the transfered asset; + @param _owner Address of the _shares owner; + @return assets How many assets the owner is entitled to, according to the epoch outcome; */ function withdraw( uint256 _id, - uint256 _assets, + uint256 _shares, address _receiver, address _owner ) @@ -157,7 +152,7 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { epochIdExists(_id) epochHasEnded(_id) nonReentrant - returns (uint256 shares) + returns (uint256 assets) { if (_receiver == address(0)) revert AddressZero(); @@ -166,17 +161,15 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { isApprovedForAll(_owner, msg.sender) == false ) revert OwnerDidNotAuthorize(msg.sender, _owner); - _burn(_owner, _id, _assets); - - uint256 entitledShares; + _burn(_owner, _id, _shares); if (epochNull[_id] == false) { - entitledShares = previewWithdraw(_id, _assets); + assets = previewWithdraw(_id, _shares); } else { - entitledShares = _assets; + assets = _shares; } - if (entitledShares > 0) { - SemiFungibleVault.asset.safeTransfer(_receiver, entitledShares); + if (assets > 0) { + SemiFungibleVault.asset.safeTransfer(_receiver, assets); } emit Withdraw( @@ -184,11 +177,11 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { _receiver, _owner, _id, - _assets, - entitledShares + _shares, + assets ); - return entitledShares; + return assets; } /*/////////////////////////////////////////////////////////////// @@ -251,22 +244,13 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { /** @notice Factory function, whitelist address - @param _wAddress New treasury address - */ + @param _wAddress whitelist destination address + */ function whiteListAddress(address _wAddress) public onlyFactory { if (_wAddress == address(0)) revert AddressZero(); whitelistedAddresses[_wAddress] = !whitelistedAddresses[_wAddress]; } - /** - @notice Factory function, changes treasury address - @param _treasury New treasury address - */ - function setTreasury(address _treasury) public onlyFactory { - if (_treasury == address(0)) revert AddressZero(); - treasury = _treasury; - } - /** @notice Factory function, changes _counterPartyVault address @param _counterPartyVault New _counterPartyVault address @@ -313,8 +297,11 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { if (_amount > finalTVL[_id]) revert AmountExceedsTVL(); if (epochAccounting[_id] + _amount > finalTVL[_id]) revert AmountExceedsTVL(); - if (!whitelistedAddresses[_receiver] && _receiver != counterPartyVault) - revert DestinationNotAuthorized(_receiver); + if ( + !whitelistedAddresses[_receiver] && + _receiver != counterPartyVault && + _receiver != treasury() + ) revert DestinationNotAuthorized(_receiver); epochAccounting[_id] += _amount; SemiFungibleVault.asset.safeTransfer(_receiver, _amount); } @@ -339,6 +326,7 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { */ function setEpochNull(uint256 _id) public + virtual onlyController epochIdExists(_id) epochHasEnded(_id) @@ -352,18 +340,18 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { /** @notice Shows assets conversion output from withdrawing assets @param _id uint256 epoch identifier - @param _assets amount of user shares to withdraw + @param _shares amount of user shares to withdraw */ - function previewWithdraw(uint256 _id, uint256 _assets) + function previewWithdraw(uint256 _id, uint256 _shares) public view override(SemiFungibleVault) - returns (uint256 entitledAmount) + returns (uint256 entitledAssets) { // entitledAmount amount is derived from the claimTVL and the finalTVL // if user deposited 1000 assets and the claimTVL is 50% lower than finalTVL, the user is entitled to 500 assets // if user deposited 1000 assets and the claimTVL is 50% higher than finalTVL, the user is entitled to 1500 assets - entitledAmount = _assets.mulDivDown(claimTVL[_id], finalTVL[_id]); + entitledAssets = _shares.mulDivDown(claimTVL[_id], finalTVL[_id]); } /** @notice Lookup total epochs length @@ -395,6 +383,10 @@ contract VaultV2 is IVaultV2, SemiFungibleVault, ReentrancyGuard { epochCreation = epochConfig[_id].epochCreation; } + function treasury() public returns (address) { + return IVaultFactoryV2(factory).treasury(); + } + function _asset() internal view returns (IERC20) { return asset; } diff --git a/src/v2/interfaces/ICarousel.sol b/src/v2/interfaces/ICarousel.sol index 040c565c..3d35d7ef 100644 --- a/src/v2/interfaces/ICarousel.sol +++ b/src/v2/interfaces/ICarousel.sol @@ -82,10 +82,6 @@ interface ICarousel { view returns (uint256); - function getDepositQueueLenght() external view; - - function getRolloverQueueLenght() external view; - function emissionsToken() external view returns (address); function relayerFee() external view returns (uint256); @@ -93,4 +89,10 @@ interface ICarousel { function depositFee() external view returns (uint256); function emissions(uint256 _epochId) external view returns (uint256); + + function cleanupRolloverQueue(address[] memory) external; + + function getDepositQueueLength() external view returns (uint256); + + function getRolloverQueueLength() external view returns (uint256); } diff --git a/src/v2/interfaces/IVaultFactoryV2.sol b/src/v2/interfaces/IVaultFactoryV2.sol index f30e9370..5c89d2a4 100644 --- a/src/v2/interfaces/IVaultFactoryV2.sol +++ b/src/v2/interfaces/IVaultFactoryV2.sol @@ -11,9 +11,13 @@ interface IVaultFactoryV2 { string memory name ) external returns (address); + function treasury() external view returns (address); + function getVaults(uint256) external view returns (address[2] memory); function getEpochFee(uint256) external view returns (uint16); - function tokenToOracle(address token) external view returns (address); + function marketToOracle(uint256 _marketId) external view returns (address); + + function transferOwnership(address newOwner) external; } diff --git a/src/v2/interfaces/IVaultV2.sol b/src/v2/interfaces/IVaultV2.sol index 7a5a3f62..ddd090bc 100644 --- a/src/v2/interfaces/IVaultV2.sol +++ b/src/v2/interfaces/IVaultV2.sol @@ -61,6 +61,4 @@ interface IVaultV2 { external view returns (bool); - - function setTreasury(address _treasury) external; } diff --git a/src/v2/libraries/CarouselCreator.sol b/src/v2/libraries/CarouselCreator.sol index 6dfb4eb9..d88ee259 100644 --- a/src/v2/libraries/CarouselCreator.sol +++ b/src/v2/libraries/CarouselCreator.sol @@ -16,6 +16,7 @@ library CarouselCreator { address emissionsToken; uint256 relayerFee; uint256 depositFee; + uint256 minQueueDeposit; } function createCarousel(CarouselMarketConfiguration memory _marketConfig) @@ -34,10 +35,10 @@ library CarouselCreator { _marketConfig.token, _marketConfig.strike, _marketConfig.controller, - _marketConfig.treasury, _marketConfig.emissionsToken, _marketConfig.relayerFee, - _marketConfig.depositFee + _marketConfig.depositFee, + _marketConfig.minQueueDeposit ) ) ); diff --git a/src/v2/libraries/VaultV2Creator.sol b/src/v2/libraries/VaultV2Creator.sol index 0314292c..8aa63346 100644 --- a/src/v2/libraries/VaultV2Creator.sol +++ b/src/v2/libraries/VaultV2Creator.sol @@ -29,8 +29,7 @@ library VaultV2Creator { _marketConfig.tokenURI, _marketConfig.token, _marketConfig.strike, - _marketConfig.controller, - _marketConfig.treasury + _marketConfig.controller ) ); } diff --git a/test/V2/Carousel/CarouselFactoryTest.t.sol b/test/V2/Carousel/CarouselFactoryTest.t.sol index d211d194..5dfab297 100644 --- a/test/V2/Carousel/CarouselFactoryTest.t.sol +++ b/test/V2/Carousel/CarouselFactoryTest.t.sol @@ -60,7 +60,8 @@ contract CarouselFactoryTest is Helper { symbol, controller, relayerFee, - depositFee + depositFee, + 1 ether ) ); @@ -70,8 +71,8 @@ contract CarouselFactoryTest is Helper { assertEq(factory.getVaults(marketId)[1], collateral); // test oracle is set - assertTrue(factory.tokenToOracle(token) == oracle); - assertEq(marketId, factory.getMarketId(token, strike)); + assertTrue(factory.marketToOracle(marketId) == oracle); + assertEq(marketId, factory.getMarketId(token, strike, underlying)); // test if counterparty is set assertEq(IVaultV2(premium).counterPartyVault(), collateral); @@ -266,7 +267,8 @@ contract CarouselFactoryTest is Helper { symbol, controller, relayerFee, - depositFee) + depositFee, + 1 ether) ); } diff --git a/test/V2/Carousel/CarouselTest.t.sol b/test/V2/Carousel/CarouselTest.t.sol index 7caa6858..9c087440 100644 --- a/test/V2/Carousel/CarouselTest.t.sol +++ b/test/V2/Carousel/CarouselTest.t.sol @@ -41,10 +41,10 @@ contract CarouselTest is Helper { TOKEN, STRIKE, controller, - TREASURY, emissionsToken, relayerFee, - depositFee + depositFee, + 1 ether ) ); @@ -75,7 +75,7 @@ contract CarouselTest is Helper { vm.stopPrank(); uint256 _queueLength = 1; - assertEq(vault.getDepositQueueLenght(), _queueLength); + assertEq(vault.getDepositQueueLength(), _queueLength); // test revert cases // should revert if epochId is 0 as this epoch is not supposed to minted ever vm.expectRevert(Carousel.InvalidEpochId.selector); @@ -100,7 +100,6 @@ contract CarouselTest is Helper { vm.stopPrank(); // test user balances assertEq(vault.balanceOf(USER, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER, _epochId), 10 ether - relayerFee); // test relayer balance assertEq(IERC20(UNDERLYING).balanceOf(relayer), relayerFee * 1); @@ -126,16 +125,22 @@ contract CarouselTest is Helper { vault.deposit(0, 10 ether, USER2); vm.stopPrank(); - _queueLength = 2; + // test deposit into queue with non 1155 complient receiver + // queue should not revert + vm.startPrank(USER2); + address faultyReceiver = address(new MintableToken("faulty contract", "faulty")); + IERC20(UNDERLYING).approve(address(vault), 10 ether); + vault.deposit(0, 10 ether, faultyReceiver); + vm.stopPrank(); + + _queueLength = 3; - assertEq(vault.getDepositQueueLenght(), _queueLength); + assertEq(vault.getDepositQueueLength(), _queueLength); // should only do as many operations as queue length // please check logs: test forge test -m testDepositInQueue -vvvv vault.mintDepositInQueue(_epochId, 230000); assertEq(vault.balanceOf(USER, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER, _epochId), 10 ether - relayerFee); assertEq(vault.balanceOf(USER2, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER2, _epochId), 10 ether - relayerFee); } function testEnListInRollover() public { @@ -220,7 +225,7 @@ contract CarouselTest is Helper { helperDepositInEpochs(_epochId,USER5, true); helperDepositInEpochs(_epochId,USER6, true); - assertEq(vault.getDepositQueueLenght(), 6); + assertEq(vault.getDepositQueueLength(), 6); // check balance of relayer uint256 balanceBefore = IERC20(UNDERLYING).balanceOf(address(this)); @@ -237,17 +242,11 @@ contract CarouselTest is Helper { // check balances assertEq(vault.balanceOf(USER, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER, _epochId), 10 ether - relayerFee); assertEq(vault.balanceOf(USER2, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER2, _epochId), 10 ether - relayerFee); assertEq(vault.balanceOf(USER3, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER3, _epochId), 10 ether - relayerFee); assertEq(vault.balanceOf(USER4, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER4, _epochId), 10 ether - relayerFee); assertEq(vault.balanceOf(USER5, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER5, _epochId), 10 ether - relayerFee); assertEq(vault.balanceOf(USER6, _epochId), 10 ether - relayerFee); - assertEq(vault.balanceOfEmissions(USER6, _epochId), 10 ether - relayerFee); } function testRolloverMultiple() public { @@ -299,9 +298,7 @@ contract CarouselTest is Helper { // check balances assertEq(vault.balanceOf(USER, _epochId), 0); - assertEq(vault.balanceOfEmissions(USER, _epochId), 0); assertEq(vault.balanceOf(USER2, _epochId), 0); - assertEq(vault.balanceOfEmissions(USER2, _epochId), 0); // simulate prev epoch win stdstore @@ -310,7 +307,8 @@ contract CarouselTest is Helper { .with_key(prevEpoch) .checked_write(1000 ether); - console.log("rollover queue length", vault.getRolloverQueueLenght()); + // get value of prev epoch sahres for user + uint256 prevEpochShareValue = vault.previewWithdraw(prevEpoch, vault.balanceOf(USER, prevEpoch)); // mint rollovers again // this should mint shares as prev epoch is in profit @@ -325,21 +323,17 @@ contract CarouselTest is Helper { uint256 _relayerFee = (balanceAfter - balanceBefore) / 6; assertEq(_relayerFee, relayerFee); + uint256 relayerFeeInShares = vault.previewAmountInShares(prevEpoch,relayerFee); + //@note after rollover, prev value of shares should subtract by original deposit value + uint256 prevEpochSharesValueAfterRollover = vault.previewWithdraw(prevEpoch, vault.balanceOf(USER, prevEpoch)); + assertEq(((prevEpochSharesValueAfterRollover >> 1) << 1) , (((prevEpochShareValue) - (prevEpochUserBalance) - 16) >> 1) << 1); // zero out last bit to avoid rounding errors // check balances - assertEq(vault.balanceOf(USER, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOfEmissions(USER, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOf(USER2, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOfEmissions(USER2, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOf(USER3, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOfEmissions(USER3, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOf(USER4, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOfEmissions(USER4, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOf(USER5, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOfEmissions(USER5, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOf(USER6, _epochId), prevEpochUserBalance - relayerFee); - assertEq(vault.balanceOfEmissions(USER6, _epochId), prevEpochUserBalance - relayerFee); - - + assertEq(vault.balanceOf(USER, _epochId), prevEpochUserBalance - relayerFeeInShares ); + assertEq(vault.balanceOf(USER2, _epochId), prevEpochUserBalance - relayerFeeInShares); + assertEq(vault.balanceOf(USER3, _epochId), prevEpochUserBalance - relayerFeeInShares); + assertEq(vault.balanceOf(USER4, _epochId), prevEpochUserBalance - relayerFeeInShares); + assertEq(vault.balanceOf(USER5, _epochId), prevEpochUserBalance - relayerFeeInShares); + assertEq(vault.balanceOf(USER6, _epochId), prevEpochUserBalance - relayerFeeInShares); } @@ -377,5 +371,9 @@ contract CarouselTest is Helper { vm.stopPrank(); } + // deployer contract acts as factory and must emulate VaultFactoryV2.treasury() + function treasury() public view returns (address) { + return TREASURY; + } } \ No newline at end of file diff --git a/test/V2/Controllers/ControllerPeggedAssetV2Test.t.sol b/test/V2/Controllers/ControllerPeggedAssetV2Test.t.sol index 15e31b20..927c0ea9 100644 --- a/test/V2/Controllers/ControllerPeggedAssetV2Test.t.sol +++ b/test/V2/Controllers/ControllerPeggedAssetV2Test.t.sol @@ -10,6 +10,7 @@ import "../../../src/v2/VaultFactoryV2.sol"; import "../../../src/v2/TimeLock.sol"; contract ControllerPeggedAssetV2Test is Helper { + using stdStorage for StdStorage; VaultV2 vault; VaultV2 counterpartyVault; ControllerPeggedAssetV2 controller; @@ -37,7 +38,7 @@ contract ControllerPeggedAssetV2Test is Helper { controller = new ControllerPeggedAssetV2( address(factory), - address(0x1), + address(new Sequencer()), TREASURY ); @@ -71,8 +72,8 @@ contract ControllerPeggedAssetV2Test is Helper { ) ); - begin = uint40(block.timestamp); - end = uint40(block.timestamp + 1 days); + begin = uint40(block.timestamp+ 30 days); + end = uint40(block.timestamp + 35 days); withdrawalFee = 10; (epochId, ) = factory.createEpoch( @@ -89,6 +90,16 @@ contract ControllerPeggedAssetV2Test is Helper { function testTriggerDepeg() public { // TODO // revert cases + stdstore + .target(address(factory)) + .sig("marketToOracle(uint256)") + .with_key(marketId) + .checked_write(address(this)); // set oracle with faulty updated at time + + vm.warp(begin + 1); + + vm.expectRevert(ControllerPeggedAssetV2.PriceOutdated.selector); + controller.triggerDepeg(marketId, epochId); // success case } @@ -108,4 +119,19 @@ contract ControllerPeggedAssetV2Test is Helper { // success case } + function latestRoundData() public view returns (uint80, int256, uint256, uint256, uint80) { + return (100, int256(STRIKE) - int256(1), 0, block.timestamp - 3 days, 100); + } + + function decimals() public view returns (uint8) { + return 18; + } + +} + + +contract Sequencer is Helper { + function latestRoundData() public view returns (uint80, int256, uint256, uint256, uint80) { + return (100, 0, block.timestamp - 1 days, block.timestamp, 100); + } } \ No newline at end of file diff --git a/test/V2/FactoryV2Test.t.sol b/test/V2/FactoryV2Test.t.sol index 79b3a0a3..b2f761f0 100644 --- a/test/V2/FactoryV2Test.t.sol +++ b/test/V2/FactoryV2Test.t.sol @@ -9,10 +9,11 @@ import "../../src/v2/interfaces/IVaultV2.sol"; contract FactoryV2Test is Helper { VaultFactoryV2 factory; + TimeLock timelock; address controller; function setUp() public { - TimeLock timelock = new TimeLock(ADMIN); + timelock = new TimeLock(ADMIN); factory = new VaultFactoryV2( WETH, @@ -24,7 +25,6 @@ contract FactoryV2Test is Helper { factory.whitelistController(address(controller)); } - function testFactoryCreation() public { TimeLock timelock = new TimeLock(ADMIN); @@ -159,8 +159,8 @@ contract FactoryV2Test is Helper { assertEq(factory.getVaults(marketId)[1], collateral); // test oracle is set - assertTrue(factory.tokenToOracle(token) == oracle); - assertEq(marketId, factory.getMarketId(token, strike)); + assertTrue(factory.marketToOracle(marketId) == oracle); + assertEq(marketId, factory.getMarketId(token, strike, underlying)); // test if counterparty is set assertEq(IVaultV2(premium).counterPartyVault(), collateral); @@ -264,27 +264,6 @@ contract FactoryV2Test is Helper { assertEq(epochs[1], epochId2); } - - function testChangeTreasuryOnVault() public { - // test revert cases - uint256 marketId = createMarketHelper(); - - vm.expectRevert(VaultFactoryV2.NotTimeLocker.selector); - factory.changeTreasury(uint256(0x2), address(0x20)); - - vm.startPrank(address(factory.timelocker())); - vm.expectRevert(abi.encodeWithSelector(VaultFactoryV2.MarketDoesNotExist.selector, uint256(0x2))); - factory.changeTreasury(uint256(0x2), address(0x20)); - vm.expectRevert(VaultFactoryV2.AddressZero.selector); - factory.changeTreasury(marketId, address(0)); - - // test success case - factory.changeTreasury(marketId, address(0x20)); - address[2] memory vaults = factory.getVaults(marketId); - assertTrue(IVaultV2(vaults[0]).whitelistedAddresses(address(0x20))); - assertTrue(IVaultV2(vaults[1]).whitelistedAddresses(address(0x20))); - vm.stopPrank(); - } function testSetTreasury() public { // test revert cases @@ -330,21 +309,52 @@ contract FactoryV2Test is Helper { // address oldOracle = address(0x3); address newOracle = address(0x4); - createMarketHelper(); + uint256 marketId = createMarketHelper(); vm.expectRevert(VaultFactoryV2.NotTimeLocker.selector); - factory.changeOracle(token,newOracle); + factory.changeOracle(marketId,newOracle); vm.startPrank(address(factory.timelocker())); + vm.expectRevert(abi.encodeWithSelector(VaultFactoryV2.MarketDoesNotExist.selector, uint256(0))); + factory.changeOracle(uint256(0), newOracle); + vm.expectRevert(abi.encodeWithSelector(VaultFactoryV2.MarketDoesNotExist.selector, uint256(1))); + factory.changeOracle(uint256(1), newOracle); vm.expectRevert(VaultFactoryV2.AddressZero.selector); - factory.changeOracle(address(0), newOracle); - vm.expectRevert(VaultFactoryV2.AddressZero.selector); - factory.changeOracle(token, address(0)); + factory.changeOracle(marketId, address(0)); // test success case - factory.changeOracle(token, newOracle); + factory.changeOracle(marketId, newOracle); + vm.stopPrank(); + assertEq(factory.marketToOracle(marketId), newOracle); + } + + function testTransferOwnership() public { + // test revert cases + vm.expectRevert(VaultFactoryV2.NotTimeLocker.selector); + factory.transferOwnership(address(0x20)); + + // imitate timelocker + vm.startPrank(address(factory.timelocker())); + vm.expectRevert(VaultFactoryV2.AddressZero.selector); + factory.transferOwnership(address(0)); + + // test success case + factory.transferOwnership(address(0x20)); + assertEq(factory.owner(), address(0x20)); + vm.stopPrank(); + + // interact through timelocker + vm.startPrank(address(0x222222)); + // test revert cases + vm.expectRevert(abi.encodeWithSelector(TimeLock.NotOwner.selector, address(0x222222))); + timelock.changeOwnerOnFactory(address(0x222222), address(factory)); + vm.stopPrank(); + + vm.startPrank(ADMIN); + // test success case + timelock.changeOwnerOnFactory(address(0x21), address(factory)); + assertEq(factory.owner(), address(0x21)); vm.stopPrank(); - assertEq(factory.tokenToOracle(token), newOracle); } function createMarketHelper() public returns(uint256 marketId){ diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index feb1f88f..9732f282 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -7,12 +7,12 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract Helper is Test { uint256 public constant STRIKE = 1000000000000000000; - uint256 public constant COLLATERAL_MINUS_FEES = 21989999998407999999; - uint256 public constant COLLATERAL_MINUS_FEES_DIV10 = 2198999999840799999; - uint256 public constant NEXT_COLLATERAL_MINUS_FEES = 21827317001456829250; - uint256 public constant USER1_EMISSIONS_AFTER_WITHDRAW = 1099999999999999999749; - uint256 public constant USER2_EMISSIONS_AFTER_WITHDRAW = 99999999999999999749; - uint256 public constant USER_AMOUNT_AFTER_WITHDRAW = 13112658495641799945; + uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; + uint256 public constant COLLATERAL_MINUS_FEES_DIV10 = 2198999999839855145; + uint256 public constant NEXT_COLLATERAL_MINUS_FEES = 21827317001324992496; + uint256 public constant USER1_EMISSIONS_AFTER_WITHDRAW = 1096655439903230405190; + uint256 public constant USER2_EMISSIONS_AFTER_WITHDRAW = 96655439903230405190; + uint256 public constant USER_AMOUNT_AFTER_WITHDRAW = 13112658495821846450; address public constant ADMIN = address(0x1); address public constant WETH = address(0x888); address public constant TREASURY = address(0x777); diff --git a/test/V2/VaultV2Test.t.sol b/test/V2/VaultV2Test.t.sol index 1e5b7d41..e7265f06 100644 --- a/test/V2/VaultV2Test.t.sol +++ b/test/V2/VaultV2Test.t.sol @@ -23,8 +23,7 @@ contract VaultV2Test is Helper { "randomURI", TOKEN, STRIKE, - controller, - TREASURY + controller ); vm.warp(120000); MintableToken(UNDERLYING).mint(address(this)); @@ -37,8 +36,7 @@ contract VaultV2Test is Helper { "randomURI", TOKEN, STRIKE, - controller, - TREASURY + controller ); vault.setCounterPartyVault(address(counterpartyVault)); @@ -58,8 +56,7 @@ contract VaultV2Test is Helper { "randomURI", TOKEN, STRIKE, - controller, - TREASURY + controller ); vm.expectRevert(VaultV2.AddressZero.selector); @@ -71,8 +68,7 @@ contract VaultV2Test is Helper { "randomURI", address(0), // wrong token STRIKE, - controller, - TREASURY + controller ); vm.expectRevert(VaultV2.AddressZero.selector); @@ -84,21 +80,7 @@ contract VaultV2Test is Helper { "randomURI", TOKEN, STRIKE, - address(0), // wrong controller - TREASURY - ); - - vm.expectRevert(VaultV2.AddressZero.selector); - new VaultV2( - false, - UNDERLYING, - "Vault", - "v", - "randomURI", - TOKEN, - STRIKE, - controller, - address(0) // wrong treasury + address(0) // wrong controller ); // test success case @@ -110,8 +92,7 @@ contract VaultV2Test is Helper { "randomURI", TOKEN, STRIKE, - controller, - TREASURY + controller ); assertEq(address(vault2.asset()), UNDERLYING); @@ -120,7 +101,6 @@ contract VaultV2Test is Helper { assertEq(vault2.token(), TOKEN); assertEq(vault2.strike(), STRIKE); assertEq(vault2.controller(), controller); - assertTrue(vault2.whitelistedAddresses(TREASURY)); assertEq(vault2.factory(), address(this)); } @@ -540,4 +520,9 @@ contract VaultV2Test is Helper { vault.setEpoch(_epochBegin, _epochEnd, _epochId); counterpartyVault.setEpoch(_epochBegin, _epochEnd, _epochId); } + + // deployer contract acts as factory and must emulate VaultFactoryV2.treasury() + function treasury() public view returns (address) { + return TREASURY; + } } diff --git a/test/V2/e2e/EndToEndCarouselTest.t.sol b/test/V2/e2e/EndToEndCarouselTest.t.sol index 03921492..138e6427 100644 --- a/test/V2/e2e/EndToEndCarouselTest.t.sol +++ b/test/V2/e2e/EndToEndCarouselTest.t.sol @@ -81,7 +81,8 @@ contract EndToEndCarouselTest is Helper { symbol, address(controller), relayerFee, - depositFee) + depositFee, + 1 ether) ); // deploy epoch @@ -161,28 +162,33 @@ contract EndToEndCarouselTest is Helper { //assert queue length collateralQueueLength = 2; premiumQueueLength = 1; - assertEq(Carousel(collateral).getDepositQueueLenght(), collateralQueueLength); - assertEq(Carousel(premium).getDepositQueueLenght(), premiumQueueLength); + assertEq(Carousel(collateral).getDepositQueueLength(), collateralQueueLength); + assertEq(Carousel(premium).getDepositQueueLength(), premiumQueueLength); //mint deposit in queue Carousel(collateral).mintDepositInQueue(epochId, collateralQueueLength); Carousel(premium).mintDepositInQueue(epochId, premiumQueueLength); + (,uint256 collatBalanceAfterFee) = Carousel(collateral).getEpochDepositFee(epochId, 10 ether); + (,uint256 premiumBalanceAfterFee) = Carousel(premium).getEpochDepositFee(epochId, 2 ether); + //assert balance and emissions - assertEq(Carousel(collateral).balanceOf(USER, epochId), 10 ether - relayerFee); - assertEq(Carousel(collateral).balanceOfEmissions(USER, epochId), 10 ether - relayerFee); - assertEq(Carousel(collateral).balanceOf(USER2, epochId), 10 ether - relayerFee); - assertEq(Carousel(collateral).balanceOfEmissions(USER2, epochId), 10 ether - relayerFee); - assertEq(Carousel(premium).balanceOf(USER, epochId), 2 ether - relayerFee); - assertEq(Carousel(premium).balanceOfEmissions(USER, epochId), 2 ether - relayerFee); + assertEq(Carousel(collateral).balanceOf(USER, epochId), collatBalanceAfterFee - relayerFee); + assertEq(Carousel(collateral).balanceOf(USER2, epochId), collatBalanceAfterFee - relayerFee); + assertEq(Carousel(premium).balanceOf(USER, epochId), premiumBalanceAfterFee - relayerFee); assertEq(Carousel(premium).balanceOf(USER2, epochId), 0); - assertEq(Carousel(premium).balanceOfEmissions(USER2, epochId), 0); vm.startPrank(USER); //enlist in rollover for next epoch Carousel(collateral).enlistInRollover(epochId, 8 ether, USER); + bool isEnlisted = Carousel(collateral).isEnlistedInRolloverQueue(USER); + (uint256 enlistedAmount, uint256 id) = Carousel(collateral).getRolloverPosition(USER); + + assertEq(isEnlisted, true); + assertEq(enlistedAmount, 8 ether); + vm.stopPrank(); vm.startPrank(USER2); @@ -247,18 +253,20 @@ contract EndToEndCarouselTest is Helper { vm.startPrank(USER); //delist rollover - uint256 beforeQueueLength = Carousel(collateral).getRolloverQueueLenght(); + uint256 beforeQueueLength = Carousel(collateral).getRolloverQueueLength(); Carousel(collateral).delistInRollover(USER); - //assert rollover queue length - uint256 afterQueueLength = Carousel(collateral).getRolloverQueueLenght(); - assertEq(afterQueueLength, beforeQueueLength - 1); + //assert delisted rollover position in queue assets are 0 + bool ie = Carousel(collateral).isEnlistedInRolloverQueue(USER); + ( uint256 amountAfterDelisting,) = Carousel(collateral).getRolloverPosition(USER); + assertTrue(!ie); + assertEq(amountAfterDelisting, 0); //assert balance in next epoch uint256 balanceInNextEpoch = Carousel(collateral).balanceOf(USER, nextEpochId); - //assert rollover minus relayer fee - assertEq(balanceInNextEpoch, 8 ether - relayerFee); + //assert rollover minus relayer fee which is subtracted based on the value of the shares of the prev epoch + assertEq(balanceInNextEpoch, (8 ether - Carousel(collateral).previewAmountInShares(epochId, relayerFee))); //withdraw after rollover Carousel(collateral).withdraw(nextEpochId, balanceInNextEpoch, USER, USER); @@ -266,25 +274,32 @@ contract EndToEndCarouselTest is Helper { vm.stopPrank(); + // cleanup queue from delisted users + vm.startPrank(address(factory)); + uint256 beforeQueueLength2 = Carousel(collateral).getRolloverQueueLength(); + assertEq(beforeQueueLength2, 2); + address[] memory addressesToDelist = new address[](1); + addressesToDelist[0] = USER; + Carousel(collateral).cleanUpRolloverQueue(addressesToDelist); + uint256 afterQueueLength2 = Carousel(collateral).getRolloverQueueLength(); + assertEq(afterQueueLength2, 1); + vm.stopPrank(); + //withdraw USER2 vm.startPrank(USER2); - //assert rollover index + //assert rollover index, should be 0 since USER1 lising was cleaned up assertTrue(Carousel(collateral).getRolloverIndex(USER2) == 0); //delist rollover - beforeQueueLength = Carousel(collateral).getRolloverQueueLenght(); + beforeQueueLength = Carousel(collateral).getRolloverQueueLength(); Carousel(collateral).delistInRollover(USER2); - //assert rollover queue length - afterQueueLength = Carousel(collateral).getRolloverQueueLenght(); - assertEq(afterQueueLength, beforeQueueLength - 1); - //assert balance in next epoch balanceInNextEpoch = Carousel(collateral).balanceOf(USER2, nextEpochId); - //assert rollover minus relayer fee - assertTrue(balanceInNextEpoch == 8 ether - relayerFee); + //assert rollover minus relayer fee which is subtracted based on the value of the shares of the prev epoch + assertTrue(balanceInNextEpoch == 8 ether - Carousel(collateral).previewAmountInShares(epochId, relayerFee)); //withdraw after rollover Carousel(collateral).withdraw(nextEpochId, balanceInNextEpoch, USER2, USER2); diff --git a/test/legacy_v1/Controller.sol b/test/legacy_v1/Controller.sol deleted file mode 100644 index 04c92d99..00000000 --- a/test/legacy_v1/Controller.sol +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {ERC20} from "@solmate/tokens/ERC20.sol"; -import "./Vault.sol"; -import "./VaultFactory.sol"; -import "@chainlink/interfaces/AggregatorV3Interface.sol"; -import "@chainlink/interfaces/AggregatorV2V3Interface.sol"; - -/// @author MiguelBits - -contract Controller { - VaultFactory public immutable vaultFactory; - AggregatorV2V3Interface internal sequencerUptimeFeed; - - uint256 private constant GRACE_PERIOD_TIME = 3600; - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - error MarketDoesNotExist(uint256 marketId); - error SequencerDown(); - error GracePeriodNotOver(); - error ZeroAddress(); - error EpochFinishedAlready(); - error PriceNotAtStrikePrice(int256 price); - error EpochNotStarted(); - error EpochExpired(); - error OraclePriceZero(); - error RoundIDOutdated(); - error EpochNotExist(); - error EpochNotExpired(); - error VaultNotZeroTVL(); - - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /** @notice Depegs insurance vault when event is emitted - * @param epochMarketID Current market epoch ID - * @param tvl Current TVL - * @param isDisaster Flag if event isDisaster - * @param epoch Current epoch - * @param time Current time - * @param depegPrice Price that triggered depeg - */ - event DepegInsurance( - bytes32 epochMarketID, - VaultTVL tvl, - bool isDisaster, - uint256 epoch, - uint256 time, - int256 depegPrice - ); - - event NullEpoch( - bytes32 epochMarketID, - VaultTVL tvl, - uint256 epoch, - uint256 time - ); - - struct VaultTVL { - uint256 RISK_claimTVL; - uint256 RISK_finalTVL; - uint256 INSR_claimTVL; - uint256 INSR_finalTVL; - } - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /** @notice Contract constructor - * @param _factory VaultFactory address - * @param _l2Sequencer Arbitrum sequencer address - */ - constructor(address _factory, address _l2Sequencer) { - if (_factory == address(0)) revert ZeroAddress(); - - if (_l2Sequencer == address(0)) revert ZeroAddress(); - - vaultFactory = VaultFactory(_factory); - sequencerUptimeFeed = AggregatorV2V3Interface(_l2Sequencer); - } - - /*////////////////////////////////////////////////////////////// - FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** @notice Trigger depeg event - * @param marketIndex Target market index - * @param epochEnd End of epoch set for market - */ - function triggerDepeg(uint256 marketIndex, uint256 epochEnd) public { - address[] memory vaultsAddress = vaultFactory.getVaults(marketIndex); - Vault insrVault = Vault(vaultsAddress[0]); - Vault riskVault = Vault(vaultsAddress[1]); - - if (vaultsAddress[0] == address(0) || vaultsAddress[1] == address(0)) - revert MarketDoesNotExist(marketIndex); - - if (insrVault.idExists(epochEnd) == false) revert EpochNotExist(); - - if (insrVault.strikePrice() <= getLatestPrice(insrVault.tokenInsured())) - revert PriceNotAtStrikePrice( - getLatestPrice(insrVault.tokenInsured()) - ); - - if (insrVault.idEpochBegin(epochEnd) > block.timestamp) - revert EpochNotStarted(); - - if (block.timestamp > epochEnd) revert EpochExpired(); - - //require this function cannot be called twice in the same epoch for the same vault - if (insrVault.idEpochEnded(epochEnd)) revert EpochFinishedAlready(); - if (riskVault.idEpochEnded(epochEnd)) revert EpochFinishedAlready(); - - insrVault.endEpoch(epochEnd); - riskVault.endEpoch(epochEnd); - - insrVault.setClaimTVL(epochEnd, riskVault.idFinalTVL(epochEnd)); - riskVault.setClaimTVL(epochEnd, insrVault.idFinalTVL(epochEnd)); - - insrVault.sendTokens(epochEnd, address(riskVault)); - riskVault.sendTokens(epochEnd, address(insrVault)); - - VaultTVL memory tvl = VaultTVL( - riskVault.idClaimTVL(epochEnd), - riskVault.idFinalTVL(epochEnd), - insrVault.idClaimTVL(epochEnd), - insrVault.idFinalTVL(epochEnd) - ); - - AggregatorV3Interface priceFeed = AggregatorV3Interface( - vaultFactory.tokenToOracle(insrVault.tokenInsured()) - ); - (, int256 price, , , ) = priceFeed.latestRoundData(); - - emit DepegInsurance( - keccak256( - abi.encodePacked( - marketIndex, - insrVault.idEpochBegin(epochEnd), - epochEnd - ) - ), - tvl, - true, - epochEnd, - block.timestamp, - price - ); - } - - /** @notice Trigger epoch end without depeg event - * @param marketIndex Target market index - * @param epochEnd End of epoch set for market - */ - function triggerEndEpoch(uint256 marketIndex, uint256 epochEnd) public { - if (block.timestamp <= epochEnd) revert EpochNotExpired(); - - address[] memory vaultsAddress = vaultFactory.getVaults(marketIndex); - - Vault insrVault = Vault(vaultsAddress[0]); - Vault riskVault = Vault(vaultsAddress[1]); - - if (vaultsAddress[0] == address(0) || vaultsAddress[1] == address(0)) - revert MarketDoesNotExist(marketIndex); - - if ( - insrVault.idExists(epochEnd) == false || - riskVault.idExists(epochEnd) == false - ) revert EpochNotExist(); - - //require this function cannot be called twice in the same epoch for the same vault - if (insrVault.idEpochEnded(epochEnd)) revert EpochFinishedAlready(); - if (riskVault.idEpochEnded(epochEnd)) revert EpochFinishedAlready(); - - insrVault.endEpoch(epochEnd); - riskVault.endEpoch(epochEnd); - - insrVault.setClaimTVL(epochEnd, 0); - riskVault.setClaimTVL( - epochEnd, - insrVault.idFinalTVL(epochEnd) + riskVault.idFinalTVL(epochEnd) - ); - insrVault.sendTokens(epochEnd, address(riskVault)); - - VaultTVL memory tvl = VaultTVL( - riskVault.idClaimTVL(epochEnd), - riskVault.idFinalTVL(epochEnd), - insrVault.idClaimTVL(epochEnd), - insrVault.idFinalTVL(epochEnd) - ); - - emit DepegInsurance( - keccak256( - abi.encodePacked( - marketIndex, - insrVault.idEpochBegin(epochEnd), - epochEnd - ) - ), - tvl, - false, - epochEnd, - block.timestamp, - getLatestPrice(insrVault.tokenInsured()) - ); - } - - /** @notice Trigger epoch invalid when one vault has 0 TVL - * @param marketIndex Target market index - * @param epochEnd End of epoch set for market - */ - function triggerNullEpoch(uint256 marketIndex, uint256 epochEnd) public { - address[] memory vaultsAddress = vaultFactory.getVaults(marketIndex); - - Vault insrVault = Vault(vaultsAddress[0]); - Vault riskVault = Vault(vaultsAddress[1]); - - if (vaultsAddress[0] == address(0) || vaultsAddress[1] == address(0)) - revert MarketDoesNotExist(marketIndex); - - if ( - insrVault.idExists(epochEnd) == false || - riskVault.idExists(epochEnd) == false - ) revert EpochNotExist(); - - if (block.timestamp < insrVault.idEpochBegin(epochEnd)) - revert EpochNotStarted(); - - if ( - insrVault.idExists(epochEnd) == false || - riskVault.idExists(epochEnd) == false - ) revert EpochNotExist(); - - //require this function cannot be called twice in the same epoch for the same vault - if (insrVault.idEpochEnded(epochEnd)) revert EpochFinishedAlready(); - if (riskVault.idEpochEnded(epochEnd)) revert EpochFinishedAlready(); - - //set claim TVL to 0 if total assets are 0 - if (insrVault.totalAssets(epochEnd) == 0) { - insrVault.endEpoch(epochEnd); - riskVault.endEpoch(epochEnd); - - insrVault.setClaimTVL(epochEnd, 0); - riskVault.setClaimTVL(epochEnd, riskVault.idFinalTVL(epochEnd)); - - riskVault.setEpochNull(epochEnd); - } else if (riskVault.totalAssets(epochEnd) == 0) { - insrVault.endEpoch(epochEnd); - riskVault.endEpoch(epochEnd); - - insrVault.setClaimTVL(epochEnd, insrVault.idFinalTVL(epochEnd)); - riskVault.setClaimTVL(epochEnd, 0); - - insrVault.setEpochNull(epochEnd); - } else revert VaultNotZeroTVL(); - - VaultTVL memory tvl = VaultTVL( - riskVault.idClaimTVL(epochEnd), - riskVault.idFinalTVL(epochEnd), - insrVault.idClaimTVL(epochEnd), - insrVault.idFinalTVL(epochEnd) - ); - - emit NullEpoch( - keccak256( - abi.encodePacked( - marketIndex, - insrVault.idEpochBegin(epochEnd), - epochEnd - ) - ), - tvl, - epochEnd, - block.timestamp - ); - } - - /*////////////////////////////////////////////////////////////// - GETTERS - //////////////////////////////////////////////////////////////*/ - /** @notice Lookup token price - * @param _token Target token address - * @return nowPrice Current token price - */ - function getLatestPrice(address _token) - public - view - returns (int256 nowPrice) - { - ( - , - /*uint80 roundId*/ - int256 answer, - uint256 startedAt, /*uint256 updatedAt*/ /*uint80 answeredInRound*/ - , - - ) = sequencerUptimeFeed.latestRoundData(); - - // Answer == 0: Sequencer is up - // Answer == 1: Sequencer is down - bool isSequencerUp = answer == 0; - if (!isSequencerUp) { - revert SequencerDown(); - } - - // Make sure the grace period has passed after the sequencer is back up. - uint256 timeSinceUp = block.timestamp - startedAt; - if (timeSinceUp <= GRACE_PERIOD_TIME) { - revert GracePeriodNotOver(); - } - - AggregatorV3Interface priceFeed = AggregatorV3Interface( - vaultFactory.tokenToOracle(_token) - ); - (uint80 roundID, int256 price, , , uint80 answeredInRound) = priceFeed - .latestRoundData(); - - if (priceFeed.decimals() < 18) { - uint256 decimals = 10**(18 - (priceFeed.decimals())); - price = price * int256(decimals); - } else if (priceFeed.decimals() == 18) { - price = price; - } else { - uint256 decimals = 10**((priceFeed.decimals() - 18)); - price = price / int256(decimals); - } - - if (price <= 0) revert OraclePriceZero(); - - if (answeredInRound < roundID) revert RoundIDOutdated(); - - return price; - } - - /** @notice Lookup target VaultFactory address - * @dev need to find way to express typecasts in NatSpec - */ - function getVaultFactory() external view returns (address) { - return address(vaultFactory); - } -} diff --git a/test/legacy_v1/ControllerHelper.sol b/test/legacy_v1/ControllerHelper.sol index c97701b0..165934cb 100644 --- a/test/legacy_v1/ControllerHelper.sol +++ b/test/legacy_v1/ControllerHelper.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {PegOracle} from "../src/oracles/PegOracle.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {PegOracle} from "../../src/oracles/PegOracle.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; -import {DepegOracle} from "./oracles/DepegOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; +import {DepegOracle} from "../oracles/DepegOracle.sol"; /// @author nexusflip diff --git a/test/legacy_v1/ControllerTest.t.sol b/test/legacy_v1/ControllerTest.t.sol index f9c921d4..8ed39420 100644 --- a/test/legacy_v1/ControllerTest.t.sol +++ b/test/legacy_v1/ControllerTest.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {PegOracle} from "../src/oracles/PegOracle.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {PegOracle} from "../../src/oracles/PegOracle.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {ControllerHelper} from "./ControllerHelper.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; -import {DepegOracle} from "./oracles/DepegOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; +import {DepegOracle} from "../oracles/DepegOracle.sol"; /// @author nexusflip /// @author MiguelBits diff --git a/test/legacy_v1/GovToken.sol b/test/legacy_v1/GovToken.sol index 6d6788de..e1ca814f 100644 --- a/test/legacy_v1/GovToken.sol +++ b/test/legacy_v1/GovToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import {ERC20} from "@solmate/tokens/ERC20.sol"; diff --git a/test/legacy_v1/LockTest.t.sol b/test/legacy_v1/LockTest.t.sol index 23d351d1..55e2684b 100644 --- a/test/legacy_v1/LockTest.t.sol +++ b/test/legacy_v1/LockTest.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {LockRewards} from "../src/lock-rewards/LockRewards.sol"; +import {LockRewards} from "../../src/lock-rewards/LockRewards.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; /// @author MiguelBits diff --git a/test/legacy_v1/OracleHelper.sol b/test/legacy_v1/OracleHelper.sol index f89f7bc7..c248e809 100644 --- a/test/legacy_v1/OracleHelper.sol +++ b/test/legacy_v1/OracleHelper.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {PegOracle} from "../src/oracles/PegOracle.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; -import {FakeFakeOracle} from "./oracles/FakeFakeOracle.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {PegOracle} from "../../src/oracles/PegOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; +import {FakeFakeOracle} from "../oracles/FakeFakeOracle.sol"; import "@chainlink/interfaces/AggregatorV3Interface.sol"; /// @author nexusflip diff --git a/test/legacy_v1/OracleTest.t.sol b/test/legacy_v1/OracleTest.t.sol index c7c704c7..45aaf6fa 100644 --- a/test/legacy_v1/OracleTest.t.sol +++ b/test/legacy_v1/OracleTest.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; import {OracleHelper} from "./OracleHelper.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; -import {PegOracle} from "../src/oracles/PegOracle.sol"; -import {FakeFakeOracle} from "../test/oracles/FakeFakeOracle.sol"; +import {PegOracle} from "../../src/oracles/PegOracle.sol"; +import {FakeFakeOracle} from "../oracles/FakeFakeOracle.sol"; import "@chainlink/interfaces/AggregatorV3Interface.sol"; /// @author nexusflip diff --git a/test/legacy_v1/OwnerHelper.sol b/test/legacy_v1/OwnerHelper.sol index 4a830b21..a2e5bf24 100644 --- a/test/legacy_v1/OwnerHelper.sol +++ b/test/legacy_v1/OwnerHelper.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; -import {Owned} from "../src/rewards/Owned.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; +import {Owned} from "../../src/legacy_v1/rewards/Owned.sol"; /// @author nexusflip diff --git a/test/legacy_v1/OwnerTest.t.sol b/test/legacy_v1/OwnerTest.t.sol index dfd56114..a2036ce8 100644 --- a/test/legacy_v1/OwnerTest.t.sol +++ b/test/legacy_v1/OwnerTest.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; import {OwnerHelper} from "./OwnerHelper.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; -import {Owned} from "../src/rewards/Owned.sol"; +import {Owned} from "../../src/legacy_v1/rewards/Owned.sol"; /// @author nexusflip /// @author MiguelBits diff --git a/test/legacy_v1/RewardsBalanceHelper.sol b/test/legacy_v1/RewardsBalanceHelper.sol index 094a8a54..406fb05e 100644 --- a/test/legacy_v1/RewardsBalanceHelper.sol +++ b/test/legacy_v1/RewardsBalanceHelper.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {RewardsFactory} from "../src/rewards/RewardsFactory.sol"; -import {RewardBalances} from "../src/rewards/RewardBalances.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {RewardsFactory} from "../../src/legacy_v1/rewards/RewardsFactory.sol"; +import {RewardBalances} from "../../src/legacy_v1/rewards/RewardBalances.sol"; import {GovToken} from "./GovToken.sol"; /// @author nexusflip diff --git a/test/legacy_v1/RewardsBalanceTest.t.sol b/test/legacy_v1/RewardsBalanceTest.t.sol index ff8bdbc1..56ce75cf 100644 --- a/test/legacy_v1/RewardsBalanceTest.t.sol +++ b/test/legacy_v1/RewardsBalanceTest.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {StakingRewards} from "../src/rewards/StakingRewards.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {StakingRewards} from "../../src/legacy_v1/rewards/StakingRewards.sol"; import {RewardsBalanceHelper} from "./RewardsBalanceHelper.sol"; -import {RewardBalances} from "../src/rewards/RewardBalances.sol"; +import {RewardBalances} from "../../src/legacy_v1/rewards/RewardBalances.sol"; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; /// @author nexusflip diff --git a/test/legacy_v1/RewardsFactoryHelper.sol b/test/legacy_v1/RewardsFactoryHelper.sol index e6f4a041..daab3332 100644 --- a/test/legacy_v1/RewardsFactoryHelper.sol +++ b/test/legacy_v1/RewardsFactoryHelper.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {RewardsFactory} from "../src/rewards/RewardsFactory.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {RewardsFactory} from "../../src/legacy_v1/rewards/RewardsFactory.sol"; import {GovToken} from "./GovToken.sol"; /// @author nexusflip diff --git a/test/legacy_v1/RewardsFactoryTest.t.sol b/test/legacy_v1/RewardsFactoryTest.t.sol index 7b335145..a2294dbd 100644 --- a/test/legacy_v1/RewardsFactoryTest.t.sol +++ b/test/legacy_v1/RewardsFactoryTest.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {RewardsFactory} from "../src/rewards/RewardsFactory.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {RewardsFactory} from "../../src/legacy_v1/rewards/RewardsFactory.sol"; import {RewardsFactoryHelper} from "./RewardsFactoryHelper.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; -import {StakingRewards} from "../src/rewards/StakingRewards.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; +import {StakingRewards} from "../../src/legacy_v1/rewards/StakingRewards.sol"; /// @author nexusflip diff --git a/test/legacy_v1/SemiFungibleVault.sol b/test/legacy_v1/SemiFungibleVault.sol deleted file mode 100644 index 27514292..00000000 --- a/test/legacy_v1/SemiFungibleVault.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {ERC20} from "@solmate/tokens/ERC20.sol"; -import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; -import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; -import { - ERC1155Supply -} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; - -/// @author MiguelBits -/// @author SlumDog - -abstract contract SemiFungibleVault is ERC1155Supply { - using SafeTransferLib for ERC20; - using FixedPointMathLib for uint256; - - /*/////////////////////////////////////////////////////////////// - IMMUTABLES AND STORAGE - //////////////////////////////////////////////////////////////*/ - ERC20 public immutable asset; - string public name; - string public symbol; - bytes internal constant EMPTY = ""; - - /*/////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /** @notice Deposit into vault when event is emitted - * @param caller Address of deposit caller - * @param owner receiver who will own of the tokens representing this deposit - * @param id Vault id - * @param assets Amount of owner assets to deposit into vault - */ - event Deposit( - address caller, - address indexed owner, - uint256 indexed id, - uint256 assets - ); - - /** @notice Withdraw from vault when event is emitted - * @param caller Address of withdraw caller - * @param receiver Address of receiver of assets - * @param owner Owner of shares - * @param id Vault id - * @param assets Amount of owner assets to withdraw from vault - * @param shares Amount of owner shares to burn - */ - event Withdraw( - address caller, - address receiver, - address indexed owner, - uint256 indexed id, - uint256 assets, - uint256 shares - ); - - /** @notice Contract constructor - * @param _asset ERC20 token - * @param _name Token name - * @param _symbol Token symbol - */ - constructor( - ERC20 _asset, - string memory _name, - string memory _symbol - ) ERC1155("") { - asset = _asset; - name = _name; - symbol = _symbol; - } - - /*/////////////////////////////////////////////////////////////// - DEPOSIT/WITHDRAWAL LOGIC - //////////////////////////////////////////////////////////////*/ - - /** @notice Triggers deposit into vault and mints shares for receiver - * @param id Vault id - * @param assets Amount of tokens to deposit - * @param receiver Receiver of shares - */ - function deposit( - uint256 id, - uint256 assets, - address receiver - ) public virtual { - // Need to transfer before minting or ERC777s could reenter. - asset.safeTransferFrom(msg.sender, address(this), assets); - - _mint(receiver, id, assets, EMPTY); - - emit Deposit(msg.sender, receiver, id, assets); - } - - /** @notice Triggers withdraw from vault and burns receivers' shares - * @param id Vault id - * @param assets Amount of tokens to withdraw - * @param receiver Receiver of assets - * @param owner Owner of shares - * @return shares Amount of shares burned - */ - function withdraw( - uint256 id, - uint256 assets, - address receiver, - address owner - ) external virtual returns (uint256 shares) { - require( - msg.sender == owner || isApprovedForAll(owner, msg.sender), - "Only owner can withdraw, or owner has approved receiver for all" - ); - - shares = previewWithdraw(id, assets); - - _burn(owner, id, shares); - - emit Withdraw(msg.sender, receiver, owner, id, assets, shares); - asset.safeTransfer(receiver, assets); - } - - /*/////////////////////////////////////////////////////////////// - ACCOUNTING LOGIC - //////////////////////////////////////////////////////////////*/ - - /**@notice Returns total assets for token - * @param _id uint256 token id of token - */ - function totalAssets(uint256 _id) public view virtual returns (uint256) { - return totalSupply(_id); - } - - /** - @notice Shows assets conversion output from withdrawing assets - @param id uint256 token id of token - @param assets Total number of assets - */ - function previewWithdraw(uint256 id, uint256 assets) - public - view - virtual - returns (uint256) - {} -} diff --git a/test/legacy_v1/Vault.sol b/test/legacy_v1/Vault.sol deleted file mode 100644 index 192ebb43..00000000 --- a/test/legacy_v1/Vault.sol +++ /dev/null @@ -1,382 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {SemiFungibleVault} from "./SemiFungibleVault.sol"; -import {ERC20} from "@solmate/tokens/ERC20.sol"; -import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; -import {IWETH} from "./interfaces/IWETH.sol"; -import { - ReentrancyGuard -} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; - -/// @author MiguelBits -contract Vault is SemiFungibleVault, ReentrancyGuard { - using FixedPointMathLib for uint256; - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error AddressZero(); - error AddressNotFactory(address _contract); - error AddressNotController(address _contract); - error MarketEpochDoesNotExist(); - error EpochAlreadyStarted(); - error EpochNotFinished(); - error FeeMoreThan150(uint256 _fee); - error ZeroValue(); - error OwnerDidNotAuthorize(address _sender, address _owner); - error EpochEndMustBeAfterBegin(); - error MarketEpochExists(); - error FeeCannotBe0(); - - /*/////////////////////////////////////////////////////////////// - IMMUTABLES AND STORAGE - //////////////////////////////////////////////////////////////*/ - - address public immutable tokenInsured; - address public treasury; - int256 public immutable strikePrice; - address public immutable factory; - address public controller; - - uint256[] public epochs; - - /*////////////////////////////////////////////////////////////// - MAPPINGS - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => uint256) public idFinalTVL; - mapping(uint256 => uint256) public idClaimTVL; - // @audit uint32 for timestamp is enough for the next 80 years - mapping(uint256 => uint256) public idEpochBegin; - // @audit id can be uint32 - mapping(uint256 => bool) public idEpochEnded; - // @audit id can be uint32 - mapping(uint256 => bool) public idExists; - mapping(uint256 => uint256) public epochFee; - mapping(uint256 => bool) public epochNull; - - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - - /** @notice Only factory addresses can call functions that use this modifier - */ - modifier onlyFactory() { - if (msg.sender != factory) revert AddressNotFactory(msg.sender); - _; - } - - /** @notice Only controller addresses can call functions that use this modifier - */ - modifier onlyController() { - if (msg.sender != controller) revert AddressNotController(msg.sender); - _; - } - - /** @notice Only market addresses can call functions that use this modifier - */ - modifier marketExists(uint256 id) { - if (idExists[id] != true) revert MarketEpochDoesNotExist(); - _; - } - - /** @notice You can only call functions that use this modifier before the current epoch has started - */ - modifier epochHasNotStarted(uint256 id) { - if (block.timestamp > idEpochBegin[id]) revert EpochAlreadyStarted(); - _; - } - - /** @notice You can only call functions that use this modifier after the current epoch has started - */ - modifier epochHasEnded(uint256 id) { - if (idEpochEnded[id] == false) revert EpochNotFinished(); - _; - } - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /** - @notice constructor - @param _assetAddress token address representing your asset to be deposited; - @param _name token name for the ERC1155 mints. Insert the name of your token; Example: Y2K_USDC_1.2$ - @param _symbol token symbol for the ERC1155 mints. insert here if risk or hedge + Symbol. Example: HedgeY2K or riskY2K; - @param _token address of the oracle to lookup the price in chainlink oracles; - @param _strikePrice uint256 representing the price to trigger the depeg event; - @param _controller address of the controller contract, this contract can trigger the depeg events; - */ - constructor( - address _assetAddress, - string memory _name, - string memory _symbol, - address _treasury, - address _token, - int256 _strikePrice, - address _controller - ) SemiFungibleVault(ERC20(_assetAddress), _name, _symbol) { - if (_treasury == address(0)) revert AddressZero(); - - if (_controller == address(0)) revert AddressZero(); - - if (_token == address(0)) revert AddressZero(); - - tokenInsured = _token; - treasury = _treasury; - strikePrice = _strikePrice; - factory = msg.sender; - controller = _controller; - } - - /*/////////////////////////////////////////////////////////////// - DEPOSIT/WITHDRAWAL LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - @notice Deposit function from ERC4626, with payment of a fee to a treasury implemented; - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param assets uint256 representing how many assets the user wants to deposit, a fee will be taken from this value; - @param receiver address of the receiver of the assets provided by this function, that represent the ownership of the deposited asset; - */ - function deposit( - uint256 id, - uint256 assets, - address receiver - ) public override marketExists(id) epochHasNotStarted(id) nonReentrant { - if (receiver == address(0)) revert AddressZero(); - assert(asset.transferFrom(msg.sender, address(this), assets)); - - _mint(receiver, id, assets, EMPTY); - - emit Deposit(msg.sender, receiver, id, assets); - } - - /** - @notice Deposit ETH function - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param receiver address of the receiver of the shares provided by this function, that represent the ownership of the deposited asset; - */ - function depositETH(uint256 id, address receiver) - external - payable - marketExists(id) - epochHasNotStarted(id) - nonReentrant - { - require(msg.value > 0, "ZeroValue"); - if (receiver == address(0)) revert AddressZero(); - - IWETH(address(asset)).deposit{value: msg.value}(); - _mint(receiver, id, msg.value, EMPTY); - - emit Deposit(msg.sender, receiver, id, msg.value); - } - - /** - @notice Withdraw entitled deposited assets, checking if a depeg event - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param assets uint256 of how many assets you want to withdraw, this value will be used to calculate how many assets you are entitle to according to the events; - @param receiver Address of the receiver of the assets provided by this function, that represent the ownership of the transfered asset; - @param owner Address of the owner of these said assets; - @return shares How many shares the owner is entitled to, according to the conditions; - */ - function withdraw( - uint256 id, - uint256 assets, - address receiver, - address owner - ) - external - override - epochHasEnded(id) - marketExists(id) - returns (uint256 shares) - { - if (receiver == address(0)) revert AddressZero(); - - if (msg.sender != owner && isApprovedForAll(owner, msg.sender) == false) - revert OwnerDidNotAuthorize(msg.sender, owner); - - uint256 entitledShares; - _burn(owner, id, assets); - - if (epochNull[id] == false) { - entitledShares = previewWithdraw(id, assets); - //Taking fee from the premium - if (entitledShares > assets) { - uint256 premium = entitledShares - assets; - uint256 feeValue = calculateWithdrawalFeeValue(premium, id); - entitledShares = entitledShares - feeValue; - assert(asset.transfer(treasury, feeValue)); - } - } else { - entitledShares = assets; - } - if (entitledShares > 0) { - assert(asset.transfer(receiver, entitledShares)); - } - - emit Withdraw(msg.sender, receiver, owner, id, assets, entitledShares); - - return entitledShares; - } - - /*/////////////////////////////////////////////////////////////// - ACCOUNTING LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - @notice returns total assets for the id of given epoch - @param _id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - */ - function totalAssets(uint256 _id) - public - view - override - marketExists(_id) - returns (uint256) - { - return totalSupply(_id); - } - - /** - @notice Calculates how much ether the %fee is taking from @param amount - @param amount Amount to withdraw from vault - @param _epoch Target epoch - @return feeValue Current fee value - */ - function calculateWithdrawalFeeValue(uint256 amount, uint256 _epoch) - public - view - returns (uint256 feeValue) - { - // 0.5% = multiply by 1000 then divide by 5 - return amount.mulDivUp(epochFee[_epoch], 1000); - } - - /*/////////////////////////////////////////////////////////////// - Factory FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - @notice Factory function, changes treasury address - @param _treasury New treasury address - */ - function changeTreasury(address _treasury) public onlyFactory { - if (_treasury == address(0)) revert AddressZero(); - treasury = _treasury; - } - - /** - @notice Factory function, changes controller address - @param _controller New controller address - */ - function changeController(address _controller) public onlyFactory { - if (_controller == address(0)) revert AddressZero(); - controller = _controller; - } - - /** - @notice Function to deploy hedge assets for given epochs, after the creation of this vault - @param epochBegin uint256 in UNIX timestamp, representing the begin date of the epoch. Example: Epoch begins in 31/May/2022 at 00h 00min 00sec: 1654038000 - @param epochEnd uint256 in UNIX timestamp, representing the end date of the epoch and also the ID for the minting functions. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1656630000 - @param _withdrawalFee uint256 of the fee value, multiply your % value by 10, Example: if you want fee of 0.5% , insert 5 - */ - function createAssets( - uint256 epochBegin, - uint256 epochEnd, - uint256 _withdrawalFee - ) public onlyFactory { - if (_withdrawalFee > 150) revert FeeMoreThan150(_withdrawalFee); - - if (_withdrawalFee == 0) revert FeeCannotBe0(); - - if (idExists[epochEnd] == true) revert MarketEpochExists(); - - if (epochBegin >= epochEnd) revert EpochEndMustBeAfterBegin(); - - idExists[epochEnd] = true; - idEpochBegin[epochEnd] = epochBegin; - epochs.push(epochEnd); - - epochFee[epochEnd] = _withdrawalFee; - } - - /*/////////////////////////////////////////////////////////////// - CONTROLLER LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - @notice Controller can call this function to trigger the end of the epoch, storing the TVL of that epoch and if a depeg event occurred - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000 - */ - function endEpoch(uint256 id) public onlyController marketExists(id) { - idEpochEnded[id] = true; - idFinalTVL[id] = totalAssets(id); - } - - /** - @notice Function to be called after endEpoch, by the Controller only, this function stores the TVL of the counterparty vault in a mapping to be used for later calculations of the entitled withdraw - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000 - @param claimTVL uint256 representing the TVL the counterparty vault has, storing this value in a mapping - */ - function setClaimTVL(uint256 id, uint256 claimTVL) - public - onlyController - marketExists(id) - { - idClaimTVL[id] = claimTVL; - } - - /** - solhint-disable-next-line max-line-length - @notice Function to be called after endEpoch and setClaimTVL functions, respecting the calls in order, after storing the TVL of the end of epoch and the TVL amount to claim, this function will allow the transfer of tokens to the counterparty vault - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000 - @param _counterparty Address of the other vault, meaning address of the risk vault, if this is an hedge vault, and vice-versa - */ - function sendTokens(uint256 id, address _counterparty) - public - onlyController - marketExists(id) - { - assert(asset.transfer(_counterparty, idFinalTVL[id])); - } - - function setEpochNull(uint256 id) public onlyController marketExists(id) { - epochNull[id] = true; - } - - /*/////////////////////////////////////////////////////////////// - INTERNAL HOOKS LOGIC - //////////////////////////////////////////////////////////////*/ - /** - @notice Shows assets conversion output from withdrawing assets - @param id uint256 token id of token - @param assets Total number of assets - */ - function previewWithdraw(uint256 id, uint256 assets) - public - view - override - returns (uint256 entitledAmount) - { - // in case the risk wins aka no depeg event - // risk users can withdraw the hedge (that is paid by the hedge buyers) and risk; withdraw = (risk + hedge) - // hedge pay for each hedge seller = ( risk / tvl before the hedge payouts ) * tvl in hedge pool - // in case there is a depeg event, the risk users can only withdraw the hedge - entitledAmount = assets.mulDivUp(idClaimTVL[id], idFinalTVL[id]); - // in case the hedge wins aka depegging - // hedge users pay the hedge to risk users anyway, - // hedge guy can withdraw risk (that is transfered from the risk pool), - // withdraw = % tvl that hedge buyer owns - // otherwise hedge users cannot withdraw any Eth - } - - /** @notice Lookup total epochs length - */ - function epochsLength() public view returns (uint256) { - return epochs.length; - } -} diff --git a/test/legacy_v1/VaultFactory.sol b/test/legacy_v1/VaultFactory.sol deleted file mode 100644 index e91908e9..00000000 --- a/test/legacy_v1/VaultFactory.sol +++ /dev/null @@ -1,654 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {Vault} from "./Vault.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IController} from "./interfaces/IController.sol"; - -/// @author MiguelBits - -contract TimeLock { - mapping(bytes32 => bool) public queued; - - address public policy; - - uint256 public constant MIN_DELAY = 7 days; - uint256 public constant MAX_DELAY = 30 days; - uint256 public constant GRACE_PERIOD = 14 days; - - error NotOwner(address sender); - error AlreadyQueuedError(bytes32 txId); - error TimestampNotInRangeError(uint256 blocktimestamp, uint256 timestamp); - error NotQueuedError(bytes32 txId); - error TimestampNotPassedError(uint256 blocktimestamp, uint256 timestamp); - error TimestampExpiredError(uint256 blocktimestamp, uint256 timestamp); - error TxFailedError(string func); - - event Queue( - bytes32 indexed txId, - address indexed target, - string func, - uint256 index, - uint256 data, - address to, - address token, - uint256 timestamp - ); - - event Execute( - bytes32 indexed txId, - address indexed target, - string func, - uint256 index, - uint256 data, - address to, - address token, - uint256 timestamp - ); - - event Delete( - bytes32 indexed txId, - address indexed target, - string func, - uint256 index, - uint256 data, - address to, - address token, - uint256 timestamp - ); - - modifier onlyOwner() { - if (msg.sender != policy) revert NotOwner(msg.sender); - _; - } - - constructor(address _policy) { - policy = _policy; - } - - /** - * @dev leave params zero if not using them - * @notice Queue a transaction - * @param _target The target contract - * @param _func The function to call - * @param _index The market index of the vault to call the function on - * @param _data The data to pass to the function - * @param _to The address to change the params to - * @param _token The token to change the params to - * @param _timestamp The timestamp to execute the transaction - */ - function queue( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) external onlyOwner { - //create tx id - bytes32 txId = getTxId( - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ); - - //check tx id unique - if (queued[txId]) { - revert AlreadyQueuedError(txId); - } - - //check timestamp - if ( - _timestamp < block.timestamp + MIN_DELAY || - _timestamp > block.timestamp + MAX_DELAY - ) { - revert TimestampNotInRangeError(block.timestamp, _timestamp); - } - - //queue tx - queued[txId] = true; - - emit Queue( - txId, - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ); - } - - /** - * @dev leave params zero if not using them - * @notice Execute a Queued a transaction - * @param _target The target contract - * @param _func The function to call - * @param _index The market index of the vault to call the function on - * @param _data The data to pass to the function - * @param _to The address to change the params to - * @param _token The token to change the params to - * @param _timestamp The timestamp after which to execute the transaction - */ - function execute( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) external onlyOwner { - bytes32 txId = getTxId( - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ); - - //check tx id queued - if (!queued[txId]) { - revert NotQueuedError(txId); - } - - //check block.timestamp > timestamp - if (block.timestamp < _timestamp) { - revert TimestampNotPassedError(block.timestamp, _timestamp); - } - if (block.timestamp > _timestamp + GRACE_PERIOD) { - revert TimestampExpiredError( - block.timestamp, - _timestamp + GRACE_PERIOD - ); - } - - //delete tx from queue - queued[txId] = false; - - //execute tx - if (compareStringsbyBytes(_func, "changeTreasury")) { - VaultFactory(_target).changeTreasury(_to, _index); - } else if (compareStringsbyBytes(_func, "changeController")) { - VaultFactory(_target).changeController(_index, _to); - } else if (compareStringsbyBytes(_func, "changeOracle")) { - VaultFactory(_target).changeOracle(_token, _to); - } else { - revert TxFailedError(_func); - } - - emit Execute( - txId, - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ); - } - - function cancel( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) external onlyOwner { - bytes32 txId = getTxId( - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ); - - //check tx id queued - if (!queued[txId]) { - revert NotQueuedError(txId); - } - - //delete tx from queue - queued[txId] = false; - - emit Delete( - txId, - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ); - } - - function getTxId( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) public pure returns (bytes32 txId) { - return - keccak256( - abi.encode( - _target, - _func, - _index, - _data, - _to, - _token, - _timestamp - ) - ); - } - - function compareStringsbyBytes(string memory s1, string memory s2) - public - pure - returns (bool) - { - return - keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2)); - } - - function changeOwner(address _newOwner) external onlyOwner { - policy = _newOwner; - } -} - -contract VaultFactory is Ownable { - address public immutable WETH; - // solhint-enable var-name-mixedcase - address public treasury; - address public controller; - uint256 public marketIndex; - - TimeLock public timelocker; - - struct MarketVault { - uint256 index; - uint256 epochBegin; - uint256 epochEnd; - Vault hedge; - Vault risk; - uint256 withdrawalFee; - } - - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - - modifier onlyTimeLocker() { - if (msg.sender != address(timelocker)) revert NotTimeLocker(); - _; - } - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error MarketDoesNotExist(uint256 marketIndex); - error AddressZero(); - error AddressNotController(); - error AddressFactoryNotInController(); - error ControllerNotSet(); - error NotTimeLocker(); - error ControllerAlreadySet(); - - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /** @notice Market is created when event is emitted - * @param mIndex Current market index - * @param hedge Hedge vault address - * @param risk Risk vault address - * @param token Token address - * @param name Market name - */ - event MarketCreated( - uint256 indexed mIndex, - address hedge, - address risk, - address token, - string name, - int256 strikePrice - ); - - /** @notice Epoch is created when event is emitted - * @param marketEpochId Current market epoch id - * @param mIndex Current market index - * @param startEpoch Epoch start time - * @param endEpoch Epoch end time - * @param hedge Hedge vault address - * @param risk Risk vault address - * @param token Token address - * @param name Market name - * @param strikePrice Vault strike price - */ - event EpochCreated( - bytes32 indexed marketEpochId, - uint256 indexed mIndex, - uint256 startEpoch, - uint256 endEpoch, - address hedge, - address risk, - address token, - string name, - int256 strikePrice, - uint256 withdrawalFee - ); - - /** @notice Controller is set when event is emitted - * @param newController Address for new controller - */ - event controllerSet(address indexed newController); - - /** @notice Treasury is changed when event is emitted - * @param _treasury Treasury address - * @param _marketIndex Target market index - */ - event changedTreasury(address _treasury, uint256 indexed _marketIndex); - - /** @notice Vault fee is changed when event is emitted - * @param _marketIndex Target market index - * @param _feeRate Target fee rate - */ - event changedVaultFee(uint256 indexed _marketIndex, uint256 _feeRate); - - /** @notice Controller is changed when event is emitted - * @param _marketIndex Target market index - * @param controller Target controller address - */ - event changedController( - uint256 indexed _marketIndex, - address indexed controller - ); - event changedOracle(address indexed _token, address _oracle); - - /*////////////////////////////////////////////////////////////// - MAPPINGS - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => address[]) public indexVaults; //[0] hedge and [1] risk vault - mapping(uint256 => uint256[]) public indexEpochs; //all epochs in the market - mapping(address => address) public tokenToOracle; //token address to respective oracle smart contract address - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /** @notice Contract constructor - * @param _treasury Treasury address - * @param _weth Wrapped Ether token address - */ - constructor( - address _treasury, - address _weth, - address _policy - ) { - timelocker = new TimeLock(_policy); - - if (_weth == address(0)) revert AddressZero(); - - if (_treasury == address(0)) revert AddressZero(); - - WETH = _weth; - marketIndex = 0; - treasury = _treasury; - } - - /*////////////////////////////////////////////////////////////// - ADMIN FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - @notice Function to create two new vaults, hedge and risk, with the respective params, and storing the oracle for the token provided - @param _withdrawalFee uint256 of the fee value, multiply your % value by 10, Example: if you want fee of 0.5% , insert 5 - @param _token Address of the oracle to lookup the price in chainlink oracles - @param _strikePrice uint256 representing the price to trigger the depeg event, needs to be 18 decimals - @param epochBegin uint256 in UNIX timestamp, representing the begin date of the epoch. Example: Epoch begins in 31/May/2022 at 00h 00min 00sec: 1654038000 - @param epochEnd uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1656630000 - @param _oracle Address representing the smart contract to lookup the price of the given _token param - @return insr Address of the deployed hedge vault - @return rsk Address of the deployed risk vault - */ - function createNewMarket( - uint256 _withdrawalFee, - address _token, - int256 _strikePrice, - uint256 epochBegin, - uint256 epochEnd, - address _oracle, - string memory _name - ) public onlyOwner returns (address insr, address rsk) { - if (controller == address(0)) revert ControllerNotSet(); - - if (IController(controller).getVaultFactory() != address(this)) - revert AddressFactoryNotInController(); - - marketIndex += 1; - - //y2kUSDC_99*RISK or y2kUSDC_99*HEDGE - - Vault hedge = new Vault( - WETH, - string(abi.encodePacked(_name, "HEDGE")), - "hY2K", - treasury, - _token, - _strikePrice, - controller - ); - - Vault risk = new Vault( - WETH, - string(abi.encodePacked(_name, "RISK")), - "rY2K", - treasury, - _token, - _strikePrice, - controller - ); - - indexVaults[marketIndex] = [address(hedge), address(risk)]; - - if (tokenToOracle[_token] == address(0)) { - tokenToOracle[_token] = _oracle; - } - - emit MarketCreated( - marketIndex, - address(hedge), - address(risk), - _token, - _name, - _strikePrice - ); - - MarketVault memory marketVault = MarketVault( - marketIndex, - epochBegin, - epochEnd, - hedge, - risk, - _withdrawalFee - ); - - _createEpoch(marketVault); - - return (address(hedge), address(risk)); - } - - /** - @notice Function to deploy hedge assets for given epochs, after the creation of this vault, where the Index is the date of the end of epoch - @param index uint256 of the market index to create more assets in - @param epochBegin uint256 in UNIX timestamp, representing the begin date of the epoch. Example: Epoch begins in 31/May/2022 at 00h 00min 00sec: 1654038000 - @param epochEnd uint256 in UNIX timestamp, representing the end date of the epoch and also the ID for the minting functions. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1656630000 - @param _withdrawalFee uint256 of the fee value, multiply your % value by 10, Example: if you want fee of 0.5% , insert 5 - */ - function deployMoreAssets( - uint256 index, - uint256 epochBegin, - uint256 epochEnd, - uint256 _withdrawalFee - ) public onlyOwner { - if (controller == address(0)) revert ControllerNotSet(); - - if (index > marketIndex) { - revert MarketDoesNotExist(index); - } - address hedge = indexVaults[index][0]; - address risk = indexVaults[index][1]; - - MarketVault memory marketVault = MarketVault( - index, - epochBegin, - epochEnd, - Vault(hedge), - Vault(risk), - _withdrawalFee - ); - - _createEpoch(marketVault); - } - - function _createEpoch(MarketVault memory _marketVault) internal { - _marketVault.hedge.createAssets( - _marketVault.epochBegin, - _marketVault.epochEnd, - _marketVault.withdrawalFee - ); - _marketVault.risk.createAssets( - _marketVault.epochBegin, - _marketVault.epochEnd, - _marketVault.withdrawalFee - ); - - indexEpochs[_marketVault.index].push(_marketVault.epochEnd); - - emit EpochCreated( - keccak256( - abi.encodePacked( - _marketVault.index, - _marketVault.epochBegin, - _marketVault.epochEnd - ) - ), - _marketVault.index, - _marketVault.epochBegin, - _marketVault.epochEnd, - address(_marketVault.hedge), - address(_marketVault.risk), - _marketVault.hedge.tokenInsured(), - _marketVault.hedge.name(), - _marketVault.hedge.strikePrice(), - _marketVault.withdrawalFee - ); - } - - /** - @notice Admin function, sets the controller address one time use function only - @param _controller Address of the controller smart contract - */ - function setController(address _controller) public onlyOwner { - if (controller == address(0)) { - if (_controller == address(0)) revert AddressZero(); - controller = _controller; - - emit controllerSet(_controller); - } else { - revert ControllerAlreadySet(); - } - } - - /** - @notice Admin function, changes the assigned treasury address - @param _treasury Treasury address - @param _marketIndex Target market index - */ - function changeTreasury(address _treasury, uint256 _marketIndex) - public - onlyTimeLocker - { - if (_treasury == address(0)) revert AddressZero(); - - treasury = _treasury; - address[] memory vaults = indexVaults[_marketIndex]; - Vault insr = Vault(vaults[0]); - Vault risk = Vault(vaults[1]); - insr.changeTreasury(_treasury); - risk.changeTreasury(_treasury); - - emit changedTreasury(_treasury, _marketIndex); - } - - /** - @notice Admin function, changes controller address - @param _marketIndex Target market index - @param _controller Address of the controller smart contract - */ - function changeController(uint256 _marketIndex, address _controller) - public - onlyTimeLocker - { - if (_controller == address(0)) revert AddressZero(); - - address[] memory vaults = indexVaults[_marketIndex]; - Vault insr = Vault(vaults[0]); - Vault risk = Vault(vaults[1]); - insr.changeController(_controller); - risk.changeController(_controller); - - emit changedController(_marketIndex, _controller); - } - - /** - @notice Admin function, changes oracle address for a given token - @param _token Target token address - @param _oracle Oracle address - */ - function changeOracle(address _token, address _oracle) - public - onlyTimeLocker - { - if (_oracle == address(0)) revert AddressZero(); - if (_token == address(0)) revert AddressZero(); - - tokenToOracle[_token] = _oracle; - emit changedOracle(_token, _oracle); - } - - /*////////////////////////////////////////////////////////////// - GETTERS - //////////////////////////////////////////////////////////////*/ - - /** - @notice Function the retrieve the addresses of the hedge and risk vaults, in an array, in the respective order - @param index uint256 of the market index which to the vaults are associated to - @return vaults Address array of two vaults addresses, [0] being the hedge vault, [1] being the risk vault - */ - function getVaults(uint256 index) - public - view - returns (address[] memory vaults) - { - return indexVaults[index]; - } -} diff --git a/test/legacy_v1/VaultFactoryHelper.sol b/test/legacy_v1/VaultFactoryHelper.sol index e83407b7..ed0147f7 100644 --- a/test/legacy_v1/VaultFactoryHelper.sol +++ b/test/legacy_v1/VaultFactoryHelper.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; /// @author nexusflip /// @author MiguelBits diff --git a/test/legacy_v1/VaultFactoryTest.t.sol b/test/legacy_v1/VaultFactoryTest.t.sol index 4b878d78..cf035f71 100644 --- a/test/legacy_v1/VaultFactoryTest.t.sol +++ b/test/legacy_v1/VaultFactoryTest.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; import {VaultFactoryHelper} from "./VaultFactoryHelper.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; /// @author nexusflip /// @author MiguelBits diff --git a/test/legacy_v1/VaultHelper.sol b/test/legacy_v1/VaultHelper.sol index 15fa6e54..f14b08ef 100644 --- a/test/legacy_v1/VaultHelper.sol +++ b/test/legacy_v1/VaultHelper.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; /// @author nexusflip /// @author MiguelBits diff --git a/test/legacy_v1/VaultTest.t.sol b/test/legacy_v1/VaultTest.t.sol index 71b3ca01..43d3ce0a 100644 --- a/test/legacy_v1/VaultTest.t.sol +++ b/test/legacy_v1/VaultTest.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultFactory, TimeLock} from "../src/VaultFactory.sol"; -import {Controller} from "../src/Controller.sol"; +import {Vault} from "../../src/legacy_v1/Vault.sol"; +import {VaultFactory, TimeLock} from "../../src/legacy_v1/VaultFactory.sol"; +import {Controller} from "../../src/legacy_v1/Controller.sol"; import {VaultHelper} from "./VaultHelper.sol"; -import {FakeOracle} from "./oracles/FakeOracle.sol"; +import {FakeOracle} from "../oracles/FakeOracle.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; /// @author nexusflip diff --git a/test/legacy_v1/interfaces/IController.sol b/test/legacy_v1/interfaces/IController.sol deleted file mode 100644 index 744c070c..00000000 --- a/test/legacy_v1/interfaces/IController.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.15; - -interface IController { - function getVaultFactory() external view returns (address); - - function triggerDepeg(uint256 marketIndex, uint256 epochEnd) external; - - function triggerEndEpoch(uint256 marketIndex, uint256 epochEnd) external; - - function triggerNullEpoch(uint256 marketIndex, uint256 epochEnd) external; -} diff --git a/test/legacy_v1/interfaces/IKeeper.sol b/test/legacy_v1/interfaces/IKeeper.sol deleted file mode 100644 index d2cab720..00000000 --- a/test/legacy_v1/interfaces/IKeeper.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -contract KeeperGelato { - function startTask(uint256 _marketIndex, uint256 _epochID) external {} - - function checker(uint256 _marketIndex, uint256 _epochID) - external - view - returns (bool canExec, bytes memory execPayload) - {} - - function deposit(uint256 _amount) external payable {} - - function withdraw(uint256 _amount) external {} -} diff --git a/test/legacy_v1/interfaces/IStakingRewards.sol b/test/legacy_v1/interfaces/IStakingRewards.sol deleted file mode 100644 index 2ca5af3e..00000000 --- a/test/legacy_v1/interfaces/IStakingRewards.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity 0.8.15; - -// https://docs.synthetix.io/contracts/source/interfaces/istakingrewards -interface IStakingRewards { - // Mutative - function exit() external; - - function getReward() external; - - function stake(uint256 amount) external; - - function withdraw(uint256 amount) external; - - // Views - function balanceOf(address account) external view returns (uint256); - - function earned(address account) external view returns (uint256); - - function getRewardForDuration() external view returns (uint256); - - function lastTimeRewardApplicable() external view returns (uint256); - - function rewardPerToken() external view returns (uint256); - - function totalSupply() external view returns (uint256); -} diff --git a/test/legacy_v1/interfaces/IVault.sol b/test/legacy_v1/interfaces/IVault.sol deleted file mode 100644 index 384b96f5..00000000 --- a/test/legacy_v1/interfaces/IVault.sol +++ /dev/null @@ -1,173 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/// @author MiguelBits -contract IVault { - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error AddressZero(); - error AddressNotFactory(address _contract); - error AddressNotController(address _contract); - error MarketEpochDoesNotExist(); - error EpochAlreadyStarted(); - error EpochNotFinished(); - error FeeMoreThan150(uint256 _fee); - error ZeroValue(); - error OwnerDidNotAuthorize(address _sender, address _owner); - error EpochEndMustBeAfterBegin(); - error MarketEpochExists(); - error FeeCannotBe0(); - - /*/////////////////////////////////////////////////////////////// - AND STORAGE - //////////////////////////////////////////////////////////////*/ - - address public tokenInsured; - address public treasury; - int256 public strikePrice; - address public factory; - address public controller; - - uint256[] public epochs; - uint256 public timewindow; - - /*////////////////////////////////////////////////////////////// - MAPPINGS - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => uint256) public idFinalTVL; - mapping(uint256 => uint256) public idClaimTVL; - // @audit uint32 for timestamp is enough for the next 80 years - mapping(uint256 => uint256) public idEpochBegin; - // @audit id can be uint32 - mapping(uint256 => bool) public idEpochEnded; - // @audit id can be uint32 - mapping(uint256 => bool) public idExists; - mapping(uint256 => uint256) public epochFee; - mapping(uint256 => bool) public epochNull; - - /*/////////////////////////////////////////////////////////////// - DEPOSIT/WITHDRAWAL LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - @notice Deposit function from ERC4626, with payment of a fee to a treasury implemented; - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param assets uint256 representing how many assets the user wants to deposit, a fee will be taken from this value; - @param receiver address of the receiver of the assets provided by this function, that represent the ownership of the deposited asset; - */ - function deposit( - uint256 id, - uint256 assets, - address receiver - ) public {} - - /** - @notice Deposit ETH function - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param receiver address of the receiver of the shares provided by this function, that represent the ownership of the deposited asset; - */ - function depositETH(uint256 id, address receiver) external payable {} - - /** - @notice Withdraw entitled deposited assets, checking if a depeg event - @param id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - @param assets uint256 of how many assets you want to withdraw, this value will be used to calculate how many assets you are entitle to according to the events; - @param receiver Address of the receiver of the assets provided by this function, that represent the ownership of the transfered asset; - @param owner Address of the owner of these said assets; - @return shares How many shares the owner is entitled to, according to the conditions; - */ - function withdraw( - uint256 id, - uint256 assets, - address receiver, - address owner - ) external returns (uint256 shares) {} - - /*/////////////////////////////////////////////////////////////// - ACCOUNTING LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - @notice returns total assets for the id of given epoch - @param _id uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1654038000; - */ - function totalAssets(uint256 _id) public view returns (uint256) {} - - /** - @notice Calculates how much ether the %fee is taking from @param amount - @param amount Amount to withdraw from vault - @param _epoch Target epoch - @return feeValue Current fee value - */ - function calculateWithdrawalFeeValue(uint256 amount, uint256 _epoch) - public - view - returns (uint256 feeValue) - {} - - /*/////////////////////////////////////////////////////////////// - Factory FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - @notice Factory function, changes treasury address - @param _treasury New treasury address - */ - function changeTreasury(address _treasury) public {} - - /** - @notice Factory function, changes vault time window - @param _timewindow New vault time window - */ - function changeTimewindow(uint256 _timewindow) public {} - - /** - @notice Factory function, changes controller address - @param _controller New controller address - */ - function changeController(address _controller) public {} - - /** - @notice Function to deploy hedge assets for given epochs, after the creation of this vault - @param epochBegin uint256 in UNIX timestamp, representing the begin date of the epoch. Example: Epoch begins in 31/May/2022 at 00h 00min 00sec: 1654038000 - @param epochEnd uint256 in UNIX timestamp, representing the end date of the epoch and also the ID for the minting functions. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1656630000 - @param _withdrawalFee uint256 of the fee value, multiply your % value by 10, Example: if you want fee of 0.5% , insert 5 - */ - function createAssets( - uint256 epochBegin, - uint256 epochEnd, - uint256 _withdrawalFee - ) public {} - - /*/////////////////////////////////////////////////////////////// - INTERNAL HOOKS LOGIC - //////////////////////////////////////////////////////////////*/ - /** - @notice Shows assets conversion output from withdrawing assets - @param id uint256 token id of token - @param assets Total number of assets - */ - function previewWithdraw(uint256 id, uint256 assets) - public - view - returns (uint256 entitledAmount) - { - // in case the risk wins aka no depeg event - // risk users can withdraw the hedge (that is paid by the hedge buyers) and risk; withdraw = (risk + hedge) - // hedge pay for each hedge seller = ( risk / tvl before the hedge payouts ) * tvl in hedge pool - // in case there is a depeg event, the risk users can only withdraw the hedge - // in case the hedge wins aka depegging - // hedge users pay the hedge to risk users anyway, - // hedge guy can withdraw risk (that is transfered from the risk pool), - // withdraw = % tvl that hedge buyer owns - // otherwise hedge users cannot withdraw any Eth - } - - /** @notice Lookup total epochs length - */ - function epochsLength() public view returns (uint256) { - return epochs.length; - } -} diff --git a/test/legacy_v1/interfaces/IVaultFactory.sol b/test/legacy_v1/interfaces/IVaultFactory.sol deleted file mode 100644 index 7d32844d..00000000 --- a/test/legacy_v1/interfaces/IVaultFactory.sol +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/// @author MiguelBits - -contract TimeLock { - mapping(bytes32 => bool) public queued; - - error NotOwner(address sender); - error AlreadyQueuedError(bytes32 txId); - error TimestampNotInRangeError(uint256 blocktimestamp, uint256 timestamp); - error NotQueuedError(bytes32 txId); - error TimestampNotPassedError(uint256 blocktimestamp, uint256 timestamp); - error TimestampExpiredError(uint256 blocktimestamp, uint256 timestamp); - error TxFailedError(string func); - - event Queue( - bytes32 indexed txId, - address indexed target, - string func, - uint256 index, - uint256 data, - address to, - address token, - uint256 timestamp - ); - - event Execute( - bytes32 indexed txId, - address indexed target, - string func, - uint256 index, - uint256 data, - address to, - address token, - uint256 timestamp - ); - - event Delete( - bytes32 indexed txId, - address indexed target, - string func, - uint256 index, - uint256 data, - address to, - address token, - uint256 timestamp - ); - - /** - * @dev leave params zero if not using them - * @notice Queue a transaction - * @param _target The target contract - * @param _func The function to call - * @param _index The market index of the vault to call the function on - * @param _data The data to pass to the function - * @param _to The address to change the params to - * @param _token The token to change the params to - * @param _timestamp The timestamp to execute the transaction - */ - function queue( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) external {} - - /** - * @dev leave params zero if not using them - * @notice Execute a Queued a transaction - * @param _target The target contract - * @param _func The function to call - * @param _index The market index of the vault to call the function on - * @param _data The data to pass to the function - * @param _to The address to change the params to - * @param _token The token to change the params to - * @param _timestamp The timestamp after which to execute the transaction - */ - function execute( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) external {} - - function cancel( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) external {} - - function getTxId( - address _target, - string calldata _func, - uint256 _index, - uint256 _data, - address _to, - address _token, - uint256 _timestamp - ) public pure returns (bytes32 txId) {} - - function compareStringsbyBytes(string memory s1, string memory s2) - external - pure - returns (bool) - {} - - function changeOwner(address _newOwner) external {} -} - -contract IVaultFactory { - /*////////////////////////////////////////////////////////////// - MAPPINGS - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => address[]) public indexVaults; //[0] hedge and [1] risk vault - mapping(uint256 => uint256[]) public indexEpochs; //all epochs in the market - mapping(address => address) public tokenToOracle; //token address to respective oracle smart contract address - - /*////////////////////////////////////////////////////////////// - ADMIN FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - @notice Function to create two new vaults, hedge and risk, with the respective params, and storing the oracle for the token provided - @param _withdrawalFee uint256 of the fee value, multiply your % value by 10, Example: if you want fee of 0.5% , insert 5 - @param _token Address of the oracle to lookup the price in chainlink oracles - @param _strikePrice uint256 representing the price to trigger the depeg event, needs to be 18 decimals - @param epochBegin uint256 in UNIX timestamp, representing the begin date of the epoch. Example: Epoch begins in 31/May/2022 at 00h 00min 00sec: 1654038000 - @param epochEnd uint256 in UNIX timestamp, representing the end date of the epoch. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1656630000 - @param _oracle Address representing the smart contract to lookup the price of the given _token param - @return insr Address of the deployed hedge vault - @return rsk Address of the deployed risk vault - */ - function createNewMarket( - uint256 _withdrawalFee, - address _token, - int256 _strikePrice, - uint256 epochBegin, - uint256 epochEnd, - address _oracle, - string memory _name - ) public returns (address insr, address rsk) {} - - /** - @notice Function to deploy hedge assets for given epochs, after the creation of this vault, where the Index is the date of the end of epoch - @param index uint256 of the market index to create more assets in - @param epochBegin uint256 in UNIX timestamp, representing the begin date of the epoch. Example: Epoch begins in 31/May/2022 at 00h 00min 00sec: 1654038000 - @param epochEnd uint256 in UNIX timestamp, representing the end date of the epoch and also the ID for the minting functions. Example: Epoch ends in 30th June 2022 at 00h 00min 00sec: 1656630000 - @param _withdrawalFee uint256 of the fee value, multiply your % value by 10, Example: if you want fee of 0.5% , insert 5 - */ - function deployMoreAssets( - uint256 index, - uint256 epochBegin, - uint256 epochEnd, - uint256 _withdrawalFee - ) public {} - - /** - @notice Admin function, sets the controller address - @param _controller Address of the controller smart contract - */ - function setController(address _controller) public {} - - /** - @notice Admin function, changes the assigned treasury address - @param _treasury Treasury address - @param _marketIndex Target market index - */ - function changeTreasury(address _treasury, uint256 _marketIndex) public {} - - /** - @notice Admin function, changes vault time window - @param _marketIndex Target market index - @param _timewindow New time window - */ - function changeTimewindow(uint256 _marketIndex, uint256 _timewindow) - public - {} - - /** - @notice Admin function, changes controller address - @param _marketIndex Target market index - @param _controller Address of the controller smart contract - */ - function changeController(uint256 _marketIndex, address _controller) - public - {} - - /** - @notice Admin function, changes oracle address for a given token - @param _token Target token address - @param _oracle Oracle address - */ - function changeOracle(address _token, address _oracle) public {} - - /*////////////////////////////////////////////////////////////// - GETTERS - //////////////////////////////////////////////////////////////*/ - - /** - @notice Function the retrieve the addresses of the hedge and risk vaults, in an array, in the respective order - @param index uint256 of the market index which to the vaults are associated to - @return vaults Address array of two vaults addresses, [0] being the hedge vault, [1] being the risk vault - */ - function getVaults(uint256 index) - public - view - returns (address[] memory vaults) - {} -} diff --git a/test/legacy_v1/interfaces/IWETH.sol b/test/legacy_v1/interfaces/IWETH.sol deleted file mode 100644 index 7c247552..00000000 --- a/test/legacy_v1/interfaces/IWETH.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.15; - -interface IWETH { - function deposit() external payable; - - function transfer(address to, uint256 value) external returns (bool); - - function withdraw(uint256) external; -} diff --git a/test/legacy_v1/rewards/IStakingRewards.sol b/test/legacy_v1/rewards/IStakingRewards.sol deleted file mode 100644 index 2ca5af3e..00000000 --- a/test/legacy_v1/rewards/IStakingRewards.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity 0.8.15; - -// https://docs.synthetix.io/contracts/source/interfaces/istakingrewards -interface IStakingRewards { - // Mutative - function exit() external; - - function getReward() external; - - function stake(uint256 amount) external; - - function withdraw(uint256 amount) external; - - // Views - function balanceOf(address account) external view returns (uint256); - - function earned(address account) external view returns (uint256); - - function getRewardForDuration() external view returns (uint256); - - function lastTimeRewardApplicable() external view returns (uint256); - - function rewardPerToken() external view returns (uint256); - - function totalSupply() external view returns (uint256); -} diff --git a/test/legacy_v1/rewards/Owned.sol b/test/legacy_v1/rewards/Owned.sol deleted file mode 100644 index b2743aea..00000000 --- a/test/legacy_v1/rewards/Owned.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity 0.8.15; - -// https://docs.synthetix.io/contracts/source/contracts/owned -contract Owned { - address public owner; - address public nominatedOwner; - - event OwnerNominated(address newOwner); - event OwnerChanged(address oldOwner, address newOwner); - - modifier onlyOwner() { - _onlyOwner(); - _; - } - - constructor(address _owner) { - require(_owner != address(0), "Owner address cannot be 0"); - owner = _owner; - emit OwnerChanged(address(0), _owner); - } - - function nominateNewOwner(address _owner) external onlyOwner { - nominatedOwner = _owner; - emit OwnerNominated(_owner); - - emit OwnerChanged(owner, nominatedOwner); - owner = nominatedOwner; - nominatedOwner = address(0); - } - - function acceptOwnership() external { - require( - msg.sender == nominatedOwner, - "You must be nominated before you can accept ownership" - ); - emit OwnerChanged(owner, nominatedOwner); - owner = nominatedOwner; - nominatedOwner = address(0); - } - - function _onlyOwner() private view { - require( - msg.sender == owner, - "Only the contract owner may perform this action" - ); - } -} diff --git a/test/legacy_v1/rewards/PausableRewardsFactory.sol b/test/legacy_v1/rewards/PausableRewardsFactory.sol deleted file mode 100644 index a47b9db9..00000000 --- a/test/legacy_v1/rewards/PausableRewardsFactory.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {StakingRewards} from "./PausableStakingRewards.sol"; -import {VaultFactory} from "../VaultFactory.sol"; -import {Vault} from "../Vault.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -/// @author MiguelBits - -contract RewardsFactory is Ownable { - address public govToken; - address public factory; - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - error MarketDoesNotExist(uint256 marketId); - error EpochDoesNotExist(); - - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /** @notice Creates staking rewards when event is emitted - * @param marketEpochId Current market epoch ID - * @param mIndex Current market index - * @param hedgeFarm Hedge farm address - * @param riskFarm Risk farm address - */ - event CreatedStakingReward( - bytes32 indexed marketEpochId, - uint256 indexed mIndex, - address hedgeFarm, - address riskFarm - ); - - /** @notice Contract constructor - * @param _govToken Governance token address - * @param _factory VaultFactory address - */ - constructor(address _govToken, address _factory) { - govToken = _govToken; - factory = _factory; - } - - /*////////////////////////////////////////////////////////////// - METHODS - //////////////////////////////////////////////////////////////*/ - - /** @notice Trigger staking rewards event - * @param _marketIndex Target market index - * @param _epochEnd End of epoch set for market - * @return insr Insurance rewards address, first tuple address entry - * @return risk Risk rewards address, second tuple address entry - */ - function createStakingRewards(uint256 _marketIndex, uint256 _epochEnd) - external - onlyOwner - returns (address insr, address risk) - { - VaultFactory vaultFactory = VaultFactory(factory); - - address _insrToken = vaultFactory.getVaults(_marketIndex)[0]; - address _riskToken = vaultFactory.getVaults(_marketIndex)[1]; - - if (_insrToken == address(0) || _riskToken == address(0)) - revert MarketDoesNotExist(_marketIndex); - - if ( - Vault(_insrToken).idExists(_epochEnd) == false || - Vault(_riskToken).idExists(_epochEnd) == false - ) revert EpochDoesNotExist(); - - StakingRewards insrStake = new StakingRewards( - owner(), - owner(), - govToken, - _insrToken, - _epochEnd - ); - StakingRewards riskStake = new StakingRewards( - owner(), - owner(), - govToken, - _riskToken, - _epochEnd - ); - - emit CreatedStakingReward( - keccak256( - abi.encodePacked( - _marketIndex, - Vault(_insrToken).idEpochBegin(_epochEnd), - _epochEnd - ) - ), - _marketIndex, - address(insrStake), - address(riskStake) - ); - - return (address(insrStake), address(riskStake)); - } -} diff --git a/test/legacy_v1/rewards/PausableStakingRewards.sol b/test/legacy_v1/rewards/PausableStakingRewards.sol deleted file mode 100644 index 891ea9fe..00000000 --- a/test/legacy_v1/rewards/PausableStakingRewards.sol +++ /dev/null @@ -1,244 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; -import { - ReentrancyGuard -} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; - -// Inheritance -import {IStakingRewards} from "./IStakingRewards.sol"; -import {RewardsDistributionRecipient} from "./RewardsDistributionRecipient.sol"; -import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; -import { - ERC1155Holder -} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import {ERC20} from "@solmate/tokens/ERC20.sol"; -import "./Owned.sol"; - -// https://docs.synthetix.io/contracts/source/contracts/stakingrewards - -contract StakingRewards is - IStakingRewards, - RewardsDistributionRecipient, - ReentrancyGuard, - Pausable, - ERC1155Holder -{ - using SafeMath for uint256; - using SafeTransferLib for ERC20; - - /* ========== STATE VARIABLES ========== */ - - ERC20 public immutable rewardsToken; - IERC1155 public immutable stakingToken; - uint256 public periodFinish = 0; - uint256 public rewardRate; - uint256 public rewardsDuration; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - uint256 public id; - - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - - uint256 private _totalSupply; - mapping(address => uint256) private _balances; - - /* ========== EVENTS ========== */ - - event RewardAdded(uint256 reward); - event Staked(address indexed user, uint256 id, uint256 amount); - event Withdrawn(address indexed user, uint256 id, uint256 amount); - event RewardPaid(address indexed user, uint256 reward); - event RewardsDurationUpdated(uint256 newDuration); - event Recovered(address token, uint256 amount); - - /* ========== MODIFIERS ========== */ - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(); - lastUpdateTime = lastTimeRewardApplicable(); - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - /* ========== CONSTRUCTOR ========== */ - - constructor( - address _owner, - address _rewardsDistribution, - address _rewardsToken, - address _stakingToken, - uint256 _epochEnd - ) Owned(_owner) { - _pause(); - rewardsToken = ERC20(_rewardsToken); - stakingToken = IERC1155(_stakingToken); - rewardsDistribution = _rewardsDistribution; - id = _epochEnd; - rewardsDuration = _epochEnd - block.timestamp; - } - - /* ========== MUTATIVE FUNCTIONS ========== */ - function stake(uint256 amount) - external - nonReentrant - updateReward(msg.sender) - { - require(amount != 0, "Cannot stake 0"); - _totalSupply = _totalSupply.add(amount); - _balances[msg.sender] = _balances[msg.sender].add(amount); - stakingToken.safeTransferFrom( - msg.sender, - address(this), - id, - amount, - "" - ); - emit Staked(msg.sender, id, amount); - } - - function exit() external { - withdraw(_balances[msg.sender]); - getReward(); - } - - function withdraw(uint256 amount) - public - nonReentrant - updateReward(msg.sender) - { - require(amount > 0, "Cannot withdraw 0"); - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - stakingToken.safeTransferFrom( - address(this), - msg.sender, - id, - amount, - "" - ); - emit Withdrawn(msg.sender, id, amount); - } - - function getReward() - public - nonReentrant - whenNotPaused - updateReward(msg.sender) - { - uint256 reward = rewards[msg.sender]; - if (reward > 0) { - rewards[msg.sender] = 0; - rewardsToken.safeTransfer(msg.sender, reward); - emit RewardPaid(msg.sender, reward); - } - } - - /* ========== VIEWS ========== */ - - function totalSupply() external view returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) external view returns (uint256) { - return _balances[account]; - } - - function getRewardForDuration() external view returns (uint256) { - return rewardRate.mul(rewardsDuration); - } - - function lastTimeRewardApplicable() public view returns (uint256) { - return block.timestamp < periodFinish ? block.timestamp : periodFinish; - } - - function rewardPerToken() public view returns (uint256) { - if (_totalSupply == 0) { - return rewardPerTokenStored; - } - return - rewardPerTokenStored.add( - lastTimeRewardApplicable() - .sub(lastUpdateTime) - .mul(rewardRate) - .mul(1e18) - .div(_totalSupply) - ); - } - - function earned(address account) public view returns (uint256) { - return - _balances[account] - .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - /* ========== RESTRICTED FUNCTIONS ========== */ - - function notifyRewardAmount(uint256 reward) - external - override - onlyRewardsDistribution - updateReward(address(0)) - { - if (block.timestamp >= periodFinish) { - rewardRate = reward.div(rewardsDuration); - } else { - uint256 remaining = periodFinish.sub(block.timestamp); - uint256 leftover = remaining.mul(rewardRate); - rewardRate = reward.add(leftover).div(rewardsDuration); - } - - // Ensure the provided reward amount is not more than the balance in the contract. - // This keeps the reward rate in the right range, preventing overflows due to - // very high values of rewardRate in the earned and rewardsPerToken functions; - // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow. - uint256 balance = rewardsToken.balanceOf(address(this)); - require( - rewardRate <= balance.div(rewardsDuration), - "Provided reward too high" - ); - - lastUpdateTime = block.timestamp; - periodFinish = block.timestamp.add(rewardsDuration); - emit RewardAdded(reward); - } - - // Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders - function recoverERC20(address tokenAddress, uint256 tokenAmount) - external - onlyOwner - { - require( - tokenAddress != address(stakingToken), - "Cannot withdraw the staking token" - ); - ERC20(tokenAddress).safeTransfer(owner, tokenAmount); - emit Recovered(tokenAddress, tokenAmount); - } - - function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner { - require( - block.timestamp > periodFinish, - "Previous rewards period must be complete before changing the duration for the new period" - ); - rewardsDuration = _rewardsDuration; - emit RewardsDurationUpdated(rewardsDuration); - } - - function pause() external onlyOwner { - _pause(); - } - - function unpause() external onlyOwner { - _unpause(); - } -} diff --git a/test/legacy_v1/rewards/RewardBalances.sol b/test/legacy_v1/rewards/RewardBalances.sol deleted file mode 100644 index 612586e3..00000000 --- a/test/legacy_v1/rewards/RewardBalances.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import {StakingRewards} from "./StakingRewards.sol"; - -contract RewardBalances { - using SafeMath for uint256; - address[] public stakingRewardsContracts; - address public owner; - - /* ========== CONSTRUCTOR ========== */ - constructor(address[] memory _stakingRewardsContracts) { - stakingRewardsContracts = _stakingRewardsContracts; - owner = msg.sender; - } - - function balanceOf(address account) external view returns (uint256) { - uint256 balance; // initialize to 0 - - for (uint256 i; i < stakingRewardsContracts.length; i++) { // i initialize to 0 - balance = balance.add( - StakingRewards(stakingRewardsContracts[i]).earned(account) - ); - } - - return balance; - } - - function appendStakingContractAddress(address _stakingRewardsContract) - public - { - require(msg.sender == owner, "RewardBalances: FORBIDDEN"); - require(_stakingRewardsContract != address(0), "RewardBalances: ZERO_ADDRESS"); - - stakingRewardsContracts.push(_stakingRewardsContract); - } - - - function appendStakingContractAddressesLoop(address[] memory _stakingRewardsContracts) - external - { - require(msg.sender == owner, "RewardBalances: FORBIDDEN"); - - for (uint256 i; i < _stakingRewardsContracts.length; i++) { // initializes to 0 - appendStakingContractAddress(_stakingRewardsContracts[i]); - } - } - - function removeStakingContractAddress(uint8 _index) external { - require(msg.sender == owner, "RewardBalances: FORBIDDEN"); - require(_index <= stakingRewardsContracts.length-1, "RewardBalances: OUT_OF_BOUNDS"); - - delete stakingRewardsContracts[_index]; - } -} diff --git a/test/legacy_v1/rewards/RewardsDistributionRecipient.sol b/test/legacy_v1/rewards/RewardsDistributionRecipient.sol deleted file mode 100644 index 878479bf..00000000 --- a/test/legacy_v1/rewards/RewardsDistributionRecipient.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity 0.8.15; - -// Inheritance -import "./Owned.sol"; - -// https://docs.synthetix.io/contracts/source/contracts/rewardsdistributionrecipient -abstract contract RewardsDistributionRecipient is Owned { - address public rewardsDistribution; - - modifier onlyRewardsDistribution() { - require( - msg.sender == rewardsDistribution, - "Caller is not RewardsDistribution contract" - ); - _; - } - - function notifyRewardAmount(uint256 reward) external virtual; - - function setRewardsDistribution(address _rewardsDistribution) - external - onlyOwner - { - rewardsDistribution = _rewardsDistribution; - } -} diff --git a/test/legacy_v1/rewards/RewardsFactory.sol b/test/legacy_v1/rewards/RewardsFactory.sol deleted file mode 100644 index 921bc7f6..00000000 --- a/test/legacy_v1/rewards/RewardsFactory.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {StakingRewards} from "./StakingRewards.sol"; -import {VaultFactory} from "../VaultFactory.sol"; -import {Vault} from "../Vault.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -/// @author MiguelBits - -contract RewardsFactory is Ownable { - address public govToken; - address public factory; - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - error MarketDoesNotExist(uint256 marketId); - error EpochDoesNotExist(); - - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /** @notice Creates staking rewards when event is emitted - * @param marketEpochId Current market epoch ID - * @param mIndex Current market index - * @param hedgeFarm Hedge farm address - * @param riskFarm Risk farm address - */ - event CreatedStakingReward( - bytes32 indexed marketEpochId, - uint256 indexed mIndex, - address hedgeFarm, - address riskFarm - ); - - /** @notice Contract constructor - * @param _govToken Governance token address - * @param _factory VaultFactory address - */ - constructor(address _govToken, address _factory) { - govToken = _govToken; - factory = _factory; - } - - /*////////////////////////////////////////////////////////////// - METHODS - //////////////////////////////////////////////////////////////*/ - - /** @notice Trigger staking rewards event - * @param _marketIndex Target market index - * @param _epochEnd End of epoch set for market - * @return insr Insurance rewards address, first tuple address entry - * @return risk Risk rewards address, second tuple address entry - */ - function createStakingRewards(uint256 _marketIndex, uint256 _epochEnd) - external - onlyOwner - returns (address insr, address risk) - { - VaultFactory vaultFactory = VaultFactory(factory); - - address _insrToken = vaultFactory.getVaults(_marketIndex)[0]; - address _riskToken = vaultFactory.getVaults(_marketIndex)[1]; - - if (_insrToken == address(0) || _riskToken == address(0)) - revert MarketDoesNotExist(_marketIndex); - - if ( - Vault(_insrToken).idExists(_epochEnd) == false || - Vault(_riskToken).idExists(_epochEnd) == false - ) revert EpochDoesNotExist(); - - StakingRewards insrStake = new StakingRewards( - owner(), - owner(), - govToken, - _insrToken, - _epochEnd - ); - StakingRewards riskStake = new StakingRewards( - owner(), - owner(), - govToken, - _riskToken, - _epochEnd - ); - - emit CreatedStakingReward( - keccak256( - abi.encodePacked( - _marketIndex, - Vault(_insrToken).idEpochBegin(_epochEnd), - _epochEnd - ) - ), - _marketIndex, - address(insrStake), - address(riskStake) - ); - - return (address(insrStake), address(riskStake)); - } -} diff --git a/test/legacy_v1/rewards/StakingRewards.sol b/test/legacy_v1/rewards/StakingRewards.sol deleted file mode 100644 index e123e8bf..00000000 --- a/test/legacy_v1/rewards/StakingRewards.sol +++ /dev/null @@ -1,243 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; -import { - ReentrancyGuard -} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; - -// Inheritance -import {IStakingRewards} from "./IStakingRewards.sol"; -import {RewardsDistributionRecipient} from "./RewardsDistributionRecipient.sol"; -import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; -import { - ERC1155Holder -} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import {ERC20} from "@solmate/tokens/ERC20.sol"; -import "./Owned.sol"; - -// https://docs.synthetix.io/contracts/source/contracts/stakingrewards - -contract StakingRewards is - IStakingRewards, - RewardsDistributionRecipient, - ReentrancyGuard, - Pausable, - ERC1155Holder -{ - using SafeMath for uint256; - using SafeTransferLib for ERC20; - - /* ========== STATE VARIABLES ========== */ - - ERC20 public immutable rewardsToken; - IERC1155 public immutable stakingToken; - uint256 public periodFinish = 0; - uint256 public rewardRate; - uint256 public rewardsDuration; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - uint256 public id; - - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - - uint256 private _totalSupply; - mapping(address => uint256) private _balances; - - /* ========== EVENTS ========== */ - - event RewardAdded(uint256 reward); - event Staked(address indexed user, uint256 id, uint256 amount); - event Withdrawn(address indexed user, uint256 id, uint256 amount); - event RewardPaid(address indexed user, uint256 reward); - event RewardsDurationUpdated(uint256 newDuration); - event Recovered(address token, uint256 amount); - - /* ========== MODIFIERS ========== */ - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(); - lastUpdateTime = lastTimeRewardApplicable(); - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - /* ========== CONSTRUCTOR ========== */ - - constructor( - address _owner, - address _rewardsDistribution, - address _rewardsToken, - address _stakingToken, - uint256 _epochEnd - ) Owned(_owner) { - rewardsToken = ERC20(_rewardsToken); - stakingToken = IERC1155(_stakingToken); - rewardsDistribution = _rewardsDistribution; - id = _epochEnd; - rewardsDuration = _epochEnd - block.timestamp; - } - - /* ========== MUTATIVE FUNCTIONS ========== */ - function stake(uint256 amount) - external - nonReentrant - updateReward(msg.sender) - { - require(amount != 0, "Cannot stake 0"); - _totalSupply = _totalSupply.add(amount); - _balances[msg.sender] = _balances[msg.sender].add(amount); - stakingToken.safeTransferFrom( - msg.sender, - address(this), - id, - amount, - "" - ); - emit Staked(msg.sender, id, amount); - } - - function exit() external { - withdraw(_balances[msg.sender]); - getReward(); - } - - function withdraw(uint256 amount) - public - nonReentrant - updateReward(msg.sender) - { - require(amount > 0, "Cannot withdraw 0"); - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - stakingToken.safeTransferFrom( - address(this), - msg.sender, - id, - amount, - "" - ); - emit Withdrawn(msg.sender, id, amount); - } - - function getReward() - public - nonReentrant - whenNotPaused - updateReward(msg.sender) - { - uint256 reward = rewards[msg.sender]; - if (reward > 0) { - rewards[msg.sender] = 0; - rewardsToken.safeTransfer(msg.sender, reward); - emit RewardPaid(msg.sender, reward); - } - } - - /* ========== VIEWS ========== */ - - function totalSupply() external view returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) external view returns (uint256) { - return _balances[account]; - } - - function getRewardForDuration() external view returns (uint256) { - return rewardRate.mul(rewardsDuration); - } - - function lastTimeRewardApplicable() public view returns (uint256) { - return block.timestamp < periodFinish ? block.timestamp : periodFinish; - } - - function rewardPerToken() public view returns (uint256) { - if (_totalSupply == 0) { - return rewardPerTokenStored; - } - return - rewardPerTokenStored.add( - lastTimeRewardApplicable() - .sub(lastUpdateTime) - .mul(rewardRate) - .mul(1e18) - .div(_totalSupply) - ); - } - - function earned(address account) public view returns (uint256) { - return - _balances[account] - .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - /* ========== RESTRICTED FUNCTIONS ========== */ - - function notifyRewardAmount(uint256 reward) - external - override - onlyRewardsDistribution - updateReward(address(0)) - { - if (block.timestamp >= periodFinish) { - rewardRate = reward.div(rewardsDuration); - } else { - uint256 remaining = periodFinish.sub(block.timestamp); - uint256 leftover = remaining.mul(rewardRate); - rewardRate = reward.add(leftover).div(rewardsDuration); - } - - // Ensure the provided reward amount is not more than the balance in the contract. - // This keeps the reward rate in the right range, preventing overflows due to - // very high values of rewardRate in the earned and rewardsPerToken functions; - // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow. - uint256 balance = rewardsToken.balanceOf(address(this)); - require( - rewardRate <= balance.div(rewardsDuration), - "Provided reward too high" - ); - - lastUpdateTime = block.timestamp; - periodFinish = block.timestamp.add(rewardsDuration); - emit RewardAdded(reward); - } - - // Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders - function recoverERC20(address tokenAddress, uint256 tokenAmount) - external - onlyOwner - { - require( - tokenAddress != address(stakingToken), - "Cannot withdraw the staking token" - ); - ERC20(tokenAddress).safeTransfer(owner, tokenAmount); - emit Recovered(tokenAddress, tokenAmount); - } - - function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner { - require( - block.timestamp > periodFinish, - "Previous rewards period must be complete before changing the duration for the new period" - ); - rewardsDuration = _rewardsDuration; - emit RewardsDurationUpdated(rewardsDuration); - } - - function pause() external onlyOwner { - _pause(); - } - - function unpause() external onlyOwner { - _unpause(); - } -} diff --git a/test/oracles/DepegOracle.sol b/test/oracles/DepegOracle.sol index 29c3d6a2..d96eb49d 100644 --- a/test/oracles/DepegOracle.sol +++ b/test/oracles/DepegOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "@chainlink/interfaces/AggregatorV3Interface.sol"; diff --git a/test/oracles/FakeFakeOracle.sol b/test/oracles/FakeFakeOracle.sol index 8f093512..84edd265 100644 --- a/test/oracles/FakeFakeOracle.sol +++ b/test/oracles/FakeFakeOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.15; +pragma solidity 0.8.17; import "@chainlink/interfaces/AggregatorV3Interface.sol"; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..2061ba95 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,580 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@solidity-parser/parser@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.0.tgz#1fb418c816ca1fc3a1e94b08bcfe623ec4e1add4" + integrity sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q== + dependencies: + antlr4ts "^0.5.0-alpha.4" + +ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +antlr4@^4.11.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.12.0.tgz#e2323fbb057c77068a174914b0533398aeaba56a" + integrity sha512-23iB5IzXJZRZeK9TigzUyrNc9pSmNqAerJRBcNq1ETrmttMWRgaYZzC561IgEO3ygKsDJTYDTozABXa4b/fTQQ== + +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +ast-parents@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3" + integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cosmiconfig@^8.0.0: + version "8.1.3" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.3.tgz#0e614a118fcc2d9e5afc2f87d53cd09931015689" + integrity sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw== + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2, fast-diff@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +glob@^7.0.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +husky@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier-plugin-solidity@*: + version "1.1.3" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz#9a35124f578404caf617634a8cab80862d726cba" + integrity sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg== + dependencies: + "@solidity-parser/parser" "^0.16.0" + semver "^7.3.8" + solidity-comments-extractor "^0.0.7" + +prettier@^2.6.2, prettier@^2.8.3: + version "2.8.7" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" + integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + integrity sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pkg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-1.0.0.tgz#e19a15e78aca2e124461dc92b2e3943ef93494d9" + integrity sha512-L0/+vjdV3UjcQaXRlQcObpCyySS8GEqVNYGYRDAGlYOcw4J1y33WMIAD3XvYWIPnBeyN5Ilp5RHoifs3gR1S2g== + dependencies: + resolve-from "^2.0.0" + +scripty@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/scripty/-/scripty-2.1.1.tgz#41a9fb1472c042a4a631780df81dff079cdf8b15" + integrity sha512-yAutoO7+MfJcc7UAjAOp1CNbAI6GhxXYB63yFLEJe80BY1VkKQWrJ2xrdd57rh/UBnUIKmO31hzKUHWGxIupbA== + dependencies: + async "^2.6.4" + glob "^7.0.3" + lodash "^4.17.11" + resolve-pkg "^1.0.0" + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.8: + version "7.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== + dependencies: + lru-cache "^6.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +solhint-plugin-prettier@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz#e3b22800ba435cd640a9eca805a7f8bc3e3e6a6b" + integrity sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA== + dependencies: + prettier-linter-helpers "^1.0.0" + +solhint@^3.3.7: + version "3.4.1" + resolved "https://registry.yarnpkg.com/solhint/-/solhint-3.4.1.tgz#8ea15b21c13d1be0b53fd46d605a24d0b36a0c46" + integrity sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg== + dependencies: + "@solidity-parser/parser" "^0.16.0" + ajv "^6.12.6" + antlr4 "^4.11.0" + ast-parents "^0.0.1" + chalk "^4.1.2" + commander "^10.0.0" + cosmiconfig "^8.0.0" + fast-diff "^1.2.0" + glob "^8.0.3" + ignore "^5.2.4" + js-yaml "^4.1.0" + lodash "^4.17.21" + pluralize "^8.0.0" + semver "^6.3.0" + strip-ansi "^6.0.1" + table "^6.8.1" + text-table "^0.2.0" + optionalDependencies: + prettier "^2.8.3" + +solidity-comments-extractor@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" + integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== + +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==