From e8fdd8578190424e8ad1036b2e49ecd4e3388ea1 Mon Sep 17 00:00:00 2001 From: 3xHarry Date: Fri, 7 Apr 2023 15:05:15 +0200 Subject: [PATCH 1/3] fix https://github.com/sherlock-audit/2023-03-Y2K-judging/issues/72 --- src/v2/Carousel/Carousel.sol | 165 ++++++++++++++++--------- src/v2/Carousel/CarouselFactory.sol | 9 ++ src/v2/interfaces/ICarousel.sol | 2 + test/V2/e2e/EndToEndCarouselTest.t.sol | 31 +++-- 4 files changed, 143 insertions(+), 64 deletions(-) diff --git a/src/v2/Carousel/Carousel.sol b/src/v2/Carousel/Carousel.sol index 09ce2866..9682a988 100644 --- a/src/v2/Carousel/Carousel.sol +++ b/src/v2/Carousel/Carousel.sol @@ -251,11 +251,11 @@ contract Carousel is VaultV2 { // check if user has enough balance if (balanceOf(_receiver, _epochId) < _assets) 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); + // if so, update the queue rolloverQueue[index].assets = _assets; rolloverQueue[index].epochId = _epochId; } else { @@ -268,6 +268,7 @@ contract Carousel is VaultV2 { }) ); } + // index will allways be higher than 0 ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length; emit RolloverQueued(_receiver, _assets, _epochId); @@ -277,33 +278,25 @@ contract Carousel is VaultV2 { @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].assets = 0; + rolloverQueue[index].epochId = 0; + } /** @notice mints deposit in rollover queue @@ -334,7 +327,7 @@ contract Carousel is VaultV2 { 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 - // @note non neglectable mint deposit creates barriers for attackers to DDOS the queue + // @note non neglectable min-deposit creates barriers for attackers to DDOS the queue uint256 assetsToDeposit = queue[i].assets; @@ -378,7 +371,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; @@ -404,8 +397,8 @@ contract Carousel is VaultV2 { uint256 executions = 0; while ((index - prevIndex) < (_operations)) { - // only roll over if last epoch is resolved - if (epochResolved[queue[index].epochId]) { + // only roll over if last epoch is resolved and user rollover position is valid + if (epochResolved[queue[index].epochId] && queue[index].assets > 0) { uint256 entitledAmount = previewWithdraw( queue[index].epochId, queue[index].assets @@ -512,29 +505,6 @@ contract Carousel is VaultV2 { } } - /** - * @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 @@ -622,16 +592,65 @@ 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].assets == 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 */ function getRolloverIndex(address _owner) public view returns (uint256) { - return ownerToRollOverQueueIndex[_owner] - 1; + return ownerToRollOverQueueIndex[_owner] - 1; } /** @notice retruns deposit fee at this time @@ -648,7 +667,7 @@ contract Carousel is VaultV2 { (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)); + 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); @@ -703,18 +722,52 @@ contract Carousel is VaultV2 { } } + function getRolloverQueueItem(uint256 _index) + public + view + returns ( + address receiver, + uint256 assets, + uint256 epochId + ) + { + receiver = rolloverQueue[_index].receiver; + assets = rolloverQueue[_index].assets; + 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 epochId epoch id */ - function getRolloverBalance(address _owner) + function getRolloverPosition(address _owner) public view returns (uint256 balance, uint256 epochId) { - balance = rolloverQueue[getRolloverIndex(_owner)].assets; - epochId = rolloverQueue[getRolloverIndex(_owner)].epochId; + if (!isEnlistedInRolloverQueue(_owner)) { + return (0, 0); + } + uint256 index = getRolloverIndex(_owner); + balance = rolloverQueue[index].assets; + 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 (bool) + { + if(ownerToRollOverQueueIndex[_owner] == 0) { + return false; + } + return rolloverQueue[getRolloverIndex(_owner)].assets != 0; } /** @notice returns the total value locked in the deposit queue @@ -785,7 +838,7 @@ contract Carousel is VaultV2 { uint256 _epochId, uint256 _assets ) { - if (ownerToRollOverQueueIndex[_receiver] != 0) { + if (isEnlistedInRolloverQueue(_receiver)) { QueueItem memory item = rolloverQueue[getRolloverIndex(_receiver)]; if ( item.epochId == _epochId && diff --git a/src/v2/Carousel/CarouselFactory.sol b/src/v2/Carousel/CarouselFactory.sol index 57f343b8..0fd3e272 100644 --- a/src/v2/Carousel/CarouselFactory.sol +++ b/src/v2/Carousel/CarouselFactory.sol @@ -200,6 +200,15 @@ 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 //////////////////////////////////////////////////////////////*/ diff --git a/src/v2/interfaces/ICarousel.sol b/src/v2/interfaces/ICarousel.sol index 040c565c..d81edf36 100644 --- a/src/v2/interfaces/ICarousel.sol +++ b/src/v2/interfaces/ICarousel.sol @@ -93,4 +93,6 @@ interface ICarousel { function depositFee() external view returns (uint256); function emissions(uint256 _epochId) external view returns (uint256); + + function cleanupRolloverQueue(address[] memory) external; } diff --git a/test/V2/e2e/EndToEndCarouselTest.t.sol b/test/V2/e2e/EndToEndCarouselTest.t.sol index 1644b57b..d02a79e0 100644 --- a/test/V2/e2e/EndToEndCarouselTest.t.sol +++ b/test/V2/e2e/EndToEndCarouselTest.t.sol @@ -187,6 +187,12 @@ contract EndToEndCarouselTest is Helper { //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); @@ -254,9 +260,11 @@ contract EndToEndCarouselTest is Helper { uint256 beforeQueueLength = Carousel(collateral).getRolloverQueueLenght(); 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); @@ -270,20 +278,27 @@ contract EndToEndCarouselTest is Helper { vm.stopPrank(); + // cleanup queue from delisted users + vm.startPrank(address(factory)); + uint256 beforeQueueLength2 = Carousel(collateral).getRolloverQueueLenght(); + assertEq(beforeQueueLength2, 2); + address[] memory addressesToDelist = new address[](1); + addressesToDelist[0] = USER; + Carousel(collateral).cleanUpRolloverQueue(addressesToDelist); + uint256 afterQueueLength2 = Carousel(collateral).getRolloverQueueLenght(); + 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(); 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); From 1d1ac0a3411208cc7a3a7d4668ff123bffb2ff21 Mon Sep 17 00:00:00 2001 From: 3xHarry Date: Fri, 5 May 2023 22:47:29 -0400 Subject: [PATCH 2/3] fix https://github.com/Y2K-Finance/Earthquake/pull/127\#pullrequestreview-1414163988 --- src/v2/Carousel/Carousel.sol | 2 +- test/V2/Carousel/CarouselTest.t.sol | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/v2/Carousel/Carousel.sol b/src/v2/Carousel/Carousel.sol index 9682a988..dd420338 100644 --- a/src/v2/Carousel/Carousel.sol +++ b/src/v2/Carousel/Carousel.sol @@ -253,7 +253,7 @@ contract Carousel is VaultV2 { revert InsufficientBalance(); // check if user has already queued up a rollover - if (isEnlistedInRolloverQueue(_receiver)) { + if (ownerToRollOverQueueIndex[_receiver] != 0) { uint256 index = getRolloverIndex(_receiver); // if so, update the queue rolloverQueue[index].assets = _assets; diff --git a/test/V2/Carousel/CarouselTest.t.sol b/test/V2/Carousel/CarouselTest.t.sol index 45cce350..93ffaa08 100644 --- a/test/V2/Carousel/CarouselTest.t.sol +++ b/test/V2/Carousel/CarouselTest.t.sol @@ -170,6 +170,16 @@ contract CarouselTest is Helper { vm.startPrank(USER); //_epochId == epoch user is depositing in / amount of shares he wants to rollover vault.enlistInRollover(_epochId, 8 ether, USER); + vault.delistInRollover(USER); + vault.enlistInRollover(_epochId, 2 ether, USER); + + assertEq(vault.getRolloverQueueLenght(), 1); + + bool isEnlisted = vault.isEnlistedInRolloverQueue(USER); + (uint256 enlistedAmount,) = vault.getRolloverPosition(USER); + assertEq(isEnlisted, true); + assertEq(enlistedAmount, 2 ether); + vm.stopPrank(); // resolve first epoch From 4da8bba43c185ecf89aa5ff6f9a35e2ced31cede Mon Sep 17 00:00:00 2001 From: X_The_Maddy <111351208+3xHarry@users.noreply.github.com> Date: Wed, 10 May 2023 07:38:11 -0400 Subject: [PATCH 3/3] fix https://github.com/sherlock-audit/2023-03-Y2K-judging/issues/2 (#128) --- configJSON.json | 113 +---- configTestEnv.json | 6 +- configTestEnvV2.json | 40 ++ script/DeployConfigured.s.sol | 4 +- script/DeployScript.s.sol | 11 +- script/Helper.sol | 3 +- script/v2/Helper.sol | 188 +++++++++ script/v2/V2DeploymentScript.s.sol | 230 ++++++++++- src/v2/Carousel/Carousel.sol | 387 +++++++++--------- src/v2/Carousel/CarouselFactory.sol | 80 +++- .../Controllers/ControllerPeggedAssetV2.sol | 59 +-- src/v2/CustomERC1155/ERC1155.sol | 122 +++++- src/v2/CustomERC1155/ERC1155Supply.sol | 5 +- src/v2/SemiFungibleVault.sol | 4 +- src/v2/TimeLock.sol | 19 +- src/v2/VaultFactoryV2.sol | 132 +++--- src/v2/VaultV2.sol | 84 ++-- src/v2/interfaces/ICarousel.sol | 8 +- src/v2/interfaces/IVaultFactoryV2.sol | 6 +- src/v2/interfaces/IVaultV2.sol | 2 - src/v2/libraries/CarouselCreator.sol | 1 - src/v2/libraries/VaultV2Creator.sol | 3 +- test/V2/Carousel/CarouselFactoryTest.t.sol | 4 +- test/V2/Carousel/CarouselTest.t.sol | 41 +- .../ControllerPeggedAssetV2Test.t.sol | 33 +- test/V2/FactoryV2Test.t.sol | 74 ++-- test/V2/Helper.sol | 4 +- test/V2/VaultV2Test.t.sol | 37 +- test/V2/e2e/EndToEndCarouselTest.t.sol | 26 +- test/V2/e2e/EndToEndV2Test.t.sol | 2 +- test/legacy_v1/Vault.sol | 2 +- 31 files changed, 1143 insertions(+), 587 deletions(-) create mode 100644 configTestEnvV2.json create mode 100644 script/v2/Helper.sol diff --git a/configJSON.json b/configJSON.json index 991a06ba..6ee9f8bb 100644 --- a/configJSON.json +++ b/configJSON.json @@ -4,113 +4,38 @@ "variables":{ "newMarkets": true, "amountOfNewMarkets": 1, - "marketsIds": [15], + "marketsIds": [2], "newEpochs": true, - "amountOfNewEpochs": 7, - "epochsIds": [6, 7, 8, 13, 14, 12, 15], - "amountOfNewFarms": 7, + "amountOfNewEpochs": 1, + "epochsIds": [2], + "amountOfNewFarms": 1, "newFarms": true, - "farmsIds" : [6, 7, 8, 13, 14, 12, 15] + "farmsIds" : [2] }, "markets":[ { - "marketId": 15, - "name":"y2kMIM_950*", - "strikePrice": 950000000000000000, - "token": "0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A", - "oracle": "0x87121F6c9A9F6E90E59591E4Cf4804873f54A95b" + "marketId": 2, + "name":"y2kUSDC_999*", + "strikePrice": 999000000000000000, + "token": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "oracle": "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3" } ], - "epochs":[ { - "marketId": 6, - "epochBegin":1673395200, - "epochEnd":1673827200, - "epochFee":50 - }, - { - "marketId": 7, - "epochBegin":1673395200, - "epochEnd":1673827200, + "marketId": 2, + "epochBegin":1683279838, + "epochEnd":1683366238, "epochFee":50 - }, - { - "marketId": 8, - "epochBegin":1673395200, - "epochEnd":1673827200, - "epochFee":50 - }, - { - "marketId": 13, - "epochBegin":1673395200, - "epochEnd":1673827200, - "epochFee":50 - }, - { - "marketId": 14, - "epochBegin":1673395200, - "epochEnd":1673827200, - "epochFee":50 - - }, - { - "marketId": 12, - "epochBegin":1673827200, - "epochEnd":1676246400, - "epochFee":50 - }, - { - "marketId": 15, - "epochBegin":1673827200, - "epochEnd":1676246400, - "epochFee":50 - } + } ], - "farms":[ { - "marketId": 6, - "epochEnd":1673827200, - "farmRewardsHEDGE": "6006000000000000000000", - "farmRewardsRISK" : "667000000000000000000" - }, - { - "marketId": 7, - "epochEnd":1673827200, - "farmRewardsHEDGE": "6006000000000000000000", - "farmRewardsRISK" : "667000000000000000000" - }, - { - "marketId": 8, - "epochEnd":1673827200, - "farmRewardsHEDGE": "6006000000000000000000", - "farmRewardsRISK" : "667000000000000000000" - }, - { - "marketId": 13, - "epochEnd":1673827200, - "farmRewardsHEDGE": "6006000000000000000000", - "farmRewardsRISK" : "667000000000000000000" - }, - { - "marketId": 14, - "epochEnd":1673827200, - "farmRewardsHEDGE": "6006000000000000000000", - "farmRewardsRISK" : "667000000000000000000" - }, - { - "marketId": 12, - "epochEnd":1676246400, - "farmRewardsHEDGE": "15014000000000000000000", - "farmRewardsRISK" : "1168000000000000000000" - }, - { - "marketId": 15, - "epochEnd":1676246400, - "farmRewardsHEDGE": "15014000000000000000000", - "farmRewardsRISK" : "1168000000000000000000" - } + "marketId": 2, + "epochEnd":1683366238, + "farmRewardsHEDGE": "106000000000000000000", + "farmRewardsRISK" : "57000000000000000000" + } ] } \ No newline at end of file diff --git a/configTestEnv.json b/configTestEnv.json index 76883734..4fa6996c 100644 --- a/configTestEnv.json +++ b/configTestEnv.json @@ -28,9 +28,9 @@ "tokenUSDT": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", "oracleUSDT": "0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7", - "vaultFactory": "0xB30Fd345FCd25104424C86D2d28eCaBB09c6bf1e", - "controller": "0x656488c0E45EEC4f1a5A46b4541f81fBA1873f11", - "rewardsFactory": "0x182bE471706d34405753214c8fC11Ec843a3B8BE", + "vaultFactory": "0xa39A84d1a3a0cedA92cdb7eBD9E3b6216fabAcE7", + "controller": "0x1ae6ca8892d7F0DaCBd3f282eaabDFbDbee3DE43", + "rewardsFactory": "0x22351212C4066d65F7B02f6f75Bc3347Ab17b870", "keeperDepeg": "0x4208cd74b0dE2880d7B69700B749c2962381bD80", "keeperEndEpoch": "0xcDf0Bda38eb465EC17Eda5B25830197075F4cabc" }] diff --git a/configTestEnvV2.json b/configTestEnvV2.json new file mode 100644 index 00000000..a098ff02 --- /dev/null +++ b/configTestEnvV2.json @@ -0,0 +1,40 @@ +{"configAddresses": + [{ + "y2k": "0x5D59e5837F7e5d0F710178Eda34d9eCF069B36D2", + "treasury": "0xaC0D2cF77a8F8869069fc45821483701A264933B", + "admin": "0xaC0D2cF77a8F8869069fc45821483701A264933B", + "policy": "0xaC0D2cF77a8F8869069fc45821483701A264933B", + + "weth": "0x6BE37a65E46048B1D12C0E08d9722402A5247Ff1", + "arbitrum_sequencer": "0xFdB631F5EE196F0ed6FAa767959853A9F217697D", + "gelatoOpsV2": "0xB3f5503f93d5Ef84b06993a1975B9D21B962892F", + "gelatoTaskTreasury": "0xB2f34fd4C16e656163dADFeEaE4Ae0c1F13b140A", + + "tokenFrax": "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", + "oracleFrax": "0x0809E3d38d1B4214958faf06D8b1B1a2b73f2ab8", + + "tokenMIM": "0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A", + "oracleMIM": "0x87121F6c9A9F6E90E59591E4Cf4804873f54A95b", + + "tokenFEI": "0x4A717522566C7A09FD2774cceDC5A8c43C5F9FD2", + "oracleFEI": "0x7c4720086E6feb755dab542c46DE4f728E88304d", + + "tokenUSDC": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "oracleUSDC": "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", + + "tokenDAI": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + "oracleDAI": "0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB", + + "tokenUSDT": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + "oracleUSDT": "0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7", + + "vaultFactory": "0xa39A84d1a3a0cedA92cdb7eBD9E3b6216fabAcE7", + "controller": "0x1ae6ca8892d7F0DaCBd3f282eaabDFbDbee3DE43", + "rewardsFactory": "0x22351212C4066d65F7B02f6f75Bc3347Ab17b870", + "keeperDepeg": "0x4208cd74b0dE2880d7B69700B749c2962381bD80", + "keeperEndEpoch": "0xcDf0Bda38eb465EC17Eda5B25830197075F4cabc", + "carouselFactoryV2": "0xFd3DB836C652F80402398A9AdE2d3F3C5EEB22B1", + "controllerV2": "0xb4B8FDD25AC2dad1B681891BB8563a7Fe187da42" + }] +} + diff --git a/script/DeployConfigured.s.sol b/script/DeployConfigured.s.sol index 4d44e56d..d02df168 100644 --- a/script/DeployConfigured.s.sol +++ b/script/DeployConfigured.s.sol @@ -5,7 +5,7 @@ import "./Helper.sol"; /// @author MiguelBits -//forge script ConfigScript --rpc-url $ARBITRUM_RPC_URL --private-key $PRIVATE_KEY --broadcast --etherscan-api-key $arbiscanApiKey --verify --skip-simulation --gas-estimate-multiplier 200 --slow -vv +//forge script ConfigScript --rpc-url $ARBITRUM_RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --skip-simulation --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 contract ConfigScript is Script, HelperConfig { @@ -13,7 +13,7 @@ contract ConfigScript is Script, HelperConfig { function run() public { - ConfigAddresses memory addresses = getConfigAddresses(false); + ConfigAddresses memory addresses = getConfigAddresses(true); console2.log("Address admin", addresses.admin); console2.log("Address arbitrum_sequencer", addresses.arbitrum_sequencer); console2.log("Address oracleDAI", addresses.oracleDAI); diff --git a/script/DeployScript.s.sol b/script/DeployScript.s.sol index 3d151d97..71ef9c3e 100644 --- a/script/DeployScript.s.sol +++ b/script/DeployScript.s.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.13; import "./Helper.sol"; /// @author MiguelBits -//forge script DeployScript --rpc-url $ARBITRUM_RPC_URL --private-key $PRIVATE_KEY --broadcast --skip-simulation --gas-estimate-multiplier 200 --slow -vv +//forge script DeployScript --rpc-url $ARBITRUM_RPC_URL --private-key $PRIVATE_KEY --broadcast --skip-simulation --slow --verify -vv contract DeployScript is Script, HelperConfig { function setupY2K() public{ - ConfigAddresses memory addresses = getConfigAddresses(false); //true if test env + ConfigAddresses memory addresses = getConfigAddresses(true); //true if test env contractToAddresses(addresses); setVariables(); } @@ -45,6 +45,9 @@ contract DeployScript is Script, HelperConfig { ConfigMarket memory markets = getConfigMarket(i); //TODO verify require(markets.marketId == marketId, "marketId of markets and loop are not the same"); + + console.log("marketId", marketId); + console.log("vaultFactory", address(vaultFactory)); vaultFactory.createNewMarket( 1, @@ -104,8 +107,8 @@ contract DeployScript is Script, HelperConfig { StakingRewards(_rRisk).notifyRewardAmount(stringToUint(_rewardsAmountRISK)); //unpause - StakingRewards(_rHedge).unpause(); - StakingRewards(_rRisk).unpause(); + // StakingRewards(_rHedge).unpause(); + // StakingRewards(_rRisk).unpause(); } } \ No newline at end of file diff --git a/script/Helper.sol b/script/Helper.sol index 4372715c..fa6b0647 100644 --- a/script/Helper.sol +++ b/script/Helper.sol @@ -6,7 +6,8 @@ import "forge-std/StdJson.sol"; import "../src/legacy_v1/VaultFactory.sol"; import "../src/legacy_v1/Controller.sol"; //TODO change this after deploy y2k token -import "../src/legacy_v1/rewards/PausableRewardsFactory.sol"; +// import "../src/legacy_v1/rewards/PausableRewardsFactory.sol"; +import "../src/legacy_v1/rewards/RewardsFactory.sol"; import "../src/tokens/Y2K.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "./keepers/KeeperDepeg.sol"; diff --git a/script/v2/Helper.sol b/script/v2/Helper.sol new file mode 100644 index 00000000..0cb50148 --- /dev/null +++ b/script/v2/Helper.sol @@ -0,0 +1,188 @@ +// 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 "../../src/v2/Carousel/CarouselFactory.sol"; +import "../../src/v2/Controllers/ControllerPeggedAssetV2.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 carouselFactoryV2; + address controllerV2; + 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; + CarouselFactory vaultFactory; + ControllerPeggedAssetV2 controller; + address y2k; + 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 = CarouselFactory(configAddresses.carouselFactoryV2); + controller = ControllerPeggedAssetV2(configAddresses.controllerV2); + // rewardsFactory = RewardsFactory(configAddresses.rewardsFactory); + y2k = address(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, "/configTestEnvV2.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..7283950c 100644 --- a/script/v2/V2DeploymentScript.s.sol +++ b/script/v2/V2DeploymentScript.s.sol @@ -5,24 +5,33 @@ 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; - // ConfigAddresses memory addresses = getConfigAddresses(false); + function run() public { + + ConfigAddresses memory addresses = getConfigAddresses(true); // console2.log("Address admin", addresses.admin); // console2.log("Address arbitrum_sequencer", addresses.arbitrum_sequencer); // console2.log("Address oracleDAI", addresses.oracleDAI); @@ -45,31 +54,78 @@ contract V2DeploymentScript is Script { uint256 privateKey = vm.envUint("PRIVATE_KEY"); require(privateKey != 0, "PRIVATE_KEY is not set"); - console2.log("Broadcast privateKey", privateKey); - vm.startBroadcast(privateKey); + // console2.log("Broadcast privateKey", privateKey); + // 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)); + vm.startBroadcast(); + + // address timeLock = address(new TimeLock(policy)); + + // CarouselFactory vaultFactory = new CarouselFactory(addresses.weth, treasury, policy, addresses.y2k); + // vaultFactory = new VaultFactory(addresses.treasury, addresses.weth, addresses.policy); + // factory = 0xAd2f15ff7d167c800281ef52fa098Fae33429cc6; - // CarouselFactory vaultFactory = new CarouselFactory(policy, weth, treasury, emissionToken); + // controller = 0xDf878548b17429a6e6a3ff66Fb629e347738aA56; - VaultFactoryV2 vaultFactory = new VaultFactoryV2(weth, treasury, timeLock); + // 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", address(vaultFactory)); + // console.log("controller", controller); + + + // deployMarketsV2(address(vaultFactory)); + + // IERC20(emissionToken).approve(factory, 100 ether); + + + ( address prem, address collat, uint256 marketId) = CarouselFactory(0xFd3DB836C652F80402398A9AdE2d3F3C5EEB22B1).createNewCarouselMarket( + CarouselFactory.CarouselMarketConfigurationCalldata( + addresses.tokenUSDC, + 999000000000000000, + addresses.oracleUSDC, + addresses.weth, + "y2kUSDC_999*", + "https://y2k.finance", + 0xb4B8FDD25AC2dad1B681891BB8563a7Fe187da42, + 100000000, + 10, + 1 ether + ) + ); + + // console.log("Prem", prem); + // console.log("Collat", collat); + // console.log("marketId", marketId); + + + console2.log("CarouselFactory address", addresses.carouselFactoryV2); + + // IERC20(addresses.y2k).approve(spender, amount);(addresses.carouselFactoryV2, 200 ether); - // vaultFactory.setController(address(controller)); + console.log("addresses.y2k", addresses.y2k); + console.log("msg.sender", msg.sender); + console.log("balance y2k", IERC20(addresses.y2k).balanceOf(msg.sender)); + console.log("approval", IERC20(addresses.y2k).allowance(msg.sender, 0xFd3DB836C652F80402398A9AdE2d3F3C5EEB22B1)); + + // console.log("eId", eId); //stop setUp(); @@ -86,4 +142,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/v2/Carousel/Carousel.sol b/src/v2/Carousel/Carousel.sol index dd420338..e103e255 100644 --- a/src/v2/Carousel/Carousel.sol +++ b/src/v2/Carousel/Carousel.sol @@ -27,7 +27,6 @@ contract Carousel is VaultV2 { QueueItem[] public rolloverQueue; QueueItem[] public depositQueue; mapping(uint256 => uint256) public rolloverAccounting; - mapping(uint256 => mapping(address => uint256)) public _emissionsBalances; mapping(uint256 => uint256) public emissions; /*////////////////////////////////////////////////////////////// @@ -46,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(); @@ -121,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 ) @@ -139,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 @@ -154,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; } /*/////////////////////////////////////////////////////////////// @@ -202,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); } /** @@ -235,54 +221,53 @@ 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, _epochId) { + ) 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) { uint256 index = getRolloverIndex(_receiver); // if so, update the queue - rolloverQueue[index].assets = _assets; + 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; } - // index will allways be higher than 0 - 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 { - // @note + // @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(); @@ -294,9 +279,8 @@ contract Carousel is VaultV2 { // set assets to 0 but keep the queue item uint256 index = getRolloverIndex(_owner); - rolloverQueue[index].assets = 0; + rolloverQueue[index].shares = 0; rolloverQueue[index].epochId = 0; - } /** @notice mints deposit in rollover queue @@ -324,29 +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 // @note non neglectable min-deposit creates barriers for attackers to DDOS the queue - uint256 assetsToDeposit = queue[i].assets; + uint256 assetsToDeposit = queue[i].shares; if (depositFee > 0) { - (uint256 feeAmount, uint256 assetsAfterFee) = getEpochDepositFee(_epochId, assetsToDeposit); + ( + uint256 feeAmount, + uint256 assetsAfterFee + ) = getEpochDepositFee(_epochId, assetsToDeposit); assetsToDeposit = assetsAfterFee; - _asset().safeTransfer(treasury, feeAmount); + _asset().safeTransfer(treasury(), feeAmount); } - _mintShares( - queue[i].receiver, - _epochId, - assetsToDeposit - relayerFee - ); + // 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, - assetsToDeposit - relayerFee + assetsToDeposit ); depositQueue.pop(); if (i == 0) break; @@ -357,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 @@ -398,35 +394,37 @@ contract Carousel is VaultV2 { while ((index - prevIndex) < (_operations)) { // only roll over if last epoch is resolved and user rollover position is valid - if (epochResolved[queue[index].epochId] && queue[index].assets > 0) { + 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 (entitledAmount > queue[index].assets) { - uint256 diff = entitledAmount - queue[index].assets; - // get diff amount in assets - uint256 diffInAssets = diff.mulDivUp(finalTVL[queue[index].epochId], claimTVL[queue[index].epochId]); + if (entitledAmount > queue[index].shares) { + // skip the rollover for the user if the assets cannot cover the relayer fee instead of revert. - if (queue[index].assets < relayerFee) { + if (entitledAmount <= relayerFee) { index++; continue; } - uint256 originalDepositValue = queue[index].assets - diffInAssets; + // 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, originalDepositValue ); - // transfer emission tokens out of contract otherwise user could not access them as vault shares are burned - _burnEmissions( - queue[index].receiver, - queue[index].epochId, - originalDepositValue - ); // @note emission token is a known token which has no before transfer hooks which makes transfer safer emissionsToken.safeTransfer( queue[index].receiver, @@ -436,23 +434,27 @@ contract Carousel is VaultV2 { ) ); - emit Withdraw( + emit WithdrawWithEmissions( msg.sender, queue[index].receiver, queue[index].receiver, _epochId, originalDepositValue, - entitledAmount + entitledAmount, + previewEmissionsWithdraw( + queue[index].epochId, + originalDepositValue + ) ); - uint256 assetsToMint = queue[index].assets - relayerFee; - _mintShares(queue[index].receiver, _epochId, assetsToMint); + uint256 amountToMint = queue[index].shares - relayerFee; + _mintShares(queue[index].receiver, _epochId, amountToMint); 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++; @@ -488,9 +490,12 @@ contract Carousel is VaultV2 { uint256 assetsToDeposit = _assets; if (depositFee > 0) { - (uint256 feeAmount, uint256 assetsAfterFee) = getEpochDepositFee(_id, _assets); + ( + uint256 feeAmount, + uint256 assetsAfterFee + ) = getEpochDepositFee(_id, _assets); assetsToDeposit = assetsAfterFee; - _asset().safeTransfer(treasury, feeAmount); + _asset().safeTransfer(treasury(), feeAmount); } _mintShares(_receiver, _id, assetsToDeposit); @@ -498,7 +503,7 @@ 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); @@ -516,66 +521,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 @@ -596,20 +575,28 @@ contract Carousel is VaultV2 { * @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]) { + 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].assets == 0) { + if (rolloverQueue[queueIndex].shares == 0) { // overwrite the item to be removed with the last item in the queue - rolloverQueue[queueIndex] = rolloverQueue[rolloverQueue.length - 1]; + 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; + ownerToRollOverQueueIndex[rolloverQueue[queueIndex].receiver] = + queueIndex + + 1; // remove receiver from index mapping delete ownerToRollOverQueueIndex[owner]; } @@ -643,26 +630,25 @@ contract Carousel is VaultV2 { ); } - /** @notice returns the rollover index * @dev will revert if user is not in rollover queue * @param _owner address of the owner * @return rollover index */ function getRolloverIndex(address _owner) public view returns (uint256) { - return ownerToRollOverQueueIndex[_owner] - 1; + 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 - */ + * @return assetsAfterFee assets after fee + */ function getEpochDepositFee(uint256 _id, uint256 _assets) public view - returns (uint256 feeAmount, uint256 _assetsAfterFee) + 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) @@ -671,7 +657,7 @@ contract Carousel is VaultV2 { // 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; + assetsAfterFee = _assets - feeAmount; } /** @notice returns the emissions to withdraw @@ -687,74 +673,110 @@ 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 assets, + uint256 shares, uint256 epochId ) { receiver = rolloverQueue[_index].receiver; - assets = rolloverQueue[_index].assets; + 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 getRolloverPosition(address _owner) public view - returns (uint256 balance, uint256 epochId) + returns (uint256 shares, uint256 epochId) { if (!isEnlistedInRolloverQueue(_owner)) { return (0, 0); } uint256 index = getRolloverIndex(_owner); - balance = rolloverQueue[index].assets; + 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 @@ -763,11 +785,11 @@ contract Carousel is VaultV2 { public view returns (bool) - { - if(ownerToRollOverQueueIndex[_owner] == 0) { + { + if (ownerToRollOverQueueIndex[_owner] == 0) { return false; } - return rolloverQueue[getRolloverIndex(_owner)].assets != 0; + return rolloverQueue[getRolloverIndex(_owner)].shares != 0; } /** @notice returns the total value locked in the deposit queue @@ -775,27 +797,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; } @@ -809,7 +820,6 @@ contract Carousel is VaultV2 { address token; uint256 strike; address controller; - address treasury; address emissionsToken; uint256 relayerFee; uint256 depositFee; @@ -831,18 +841,18 @@ contract Carousel is VaultV2 { /** @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 (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(); } _; @@ -864,17 +874,27 @@ 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 * @param epochId the epoch id - * @param assets the amount of assets + * @param shares the amount of assets */ event DepositInQueue( address indexed sender, address indexed receiver, uint256 epochId, - uint256 assets + uint256 shares ); /** @notice emitted when shares are minted by relayer @@ -885,27 +905,12 @@ contract Carousel is VaultV2 { /** @notice emitted when a rollover is queued * @param sender the address of the sender - * @param assets the amount of assets + * @param shares the amount of assets * @param epochId the epoch id */ event RolloverQueued( address indexed sender, - uint256 assets, + uint256 shares, 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 0fd3e272..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( @@ -121,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 @@ -140,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, @@ -152,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(); } /*////////////////////////////////////////////////////////////// @@ -170,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); } @@ -205,10 +253,9 @@ contract CarouselFactory is VaultFactoryV2 { public onlyTimeLocker { - ICarousel(_vault).cleanupRolloverQueue(_addresses); + ICarousel(_vault).cleanupRolloverQueue(_addresses); } - /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -233,6 +280,7 @@ contract CarouselFactory is VaultFactoryV2 { error InvalidRelayerFee(); error InvalidVaultIndex(); error InvalidDepositFee(); + error QueueNotEmpty(); /*////////////////////////////////////////////////////////////// EVENTS @@ -246,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..d1cb9089 100644 --- a/src/v2/Controllers/ControllerPeggedAssetV2.sol +++ b/src/v2/Controllers/ControllerPeggedAssetV2.sol @@ -14,8 +14,8 @@ 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; /*////////////////////////////////////////////////////////////// CONSTRUCTOR @@ -24,12 +24,10 @@ contract ControllerPeggedAssetV2 { /** @notice Contract constructor * @param _factory VaultFactory address * @param _l2Sequencer Arbitrum sequencer address - * @param _treasury Treasury address */ constructor( address _factory, - address _l2Sequencer, - address _treasury + address _l2Sequencer ) { if (_factory == address(0)) revert ZeroAddress(); @@ -37,7 +35,6 @@ contract ControllerPeggedAssetV2 { vaultFactory = IVaultFactoryV2(_factory); sequencerUptimeFeed = AggregatorV2V3Interface(_l2Sequencer); - treasury = _treasury; } /*////////////////////////////////////////////////////////////// @@ -59,7 +56,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); @@ -108,14 +105,14 @@ contract ControllerPeggedAssetV2 { // send fees to treasury and remaining TVL to respective counterparty vault // strike price reached so premium is entitled to collateralTVL - collateralFee - premiumVault.sendTokens(_epochId, premiumFee, treasury); + premiumVault.sendTokens(_epochId, premiumFee, IVaultFactoryV2(vaultFactory).treasury()); premiumVault.sendTokens( _epochId, premiumTVL - premiumFee, address(collateralVault) ); // strike price is reached so collateral is still entitled to premiumTVL - premiumFee but looses collateralTVL - collateralVault.sendTokens(_epochId, collateralFee, treasury); + collateralVault.sendTokens(_epochId, collateralFee, IVaultFactoryV2(vaultFactory).treasury()); collateralVault.sendTokens( _epochId, collateralTVL - collateralFee, @@ -133,7 +130,7 @@ contract ControllerPeggedAssetV2 { ), true, block.timestamp, - price + uint256(price) ); } @@ -155,6 +152,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(); @@ -183,7 +187,7 @@ contract ControllerPeggedAssetV2 { collateralVault.setClaimTVL(_epochId, collateralTVLAfterFee); // send premium fees to treasury and remaining TVL to collateral vault - premiumVault.sendTokens(_epochId, premiumFee, treasury); + premiumVault.sendTokens(_epochId, premiumFee, IVaultFactoryV2(vaultFactory).treasury()); // strike price reached so collateral is entitled to collateralTVLAfterFee premiumVault.sendTokens( _epochId, @@ -267,17 +271,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 +297,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 +367,7 @@ contract ControllerPeggedAssetV2 { error EpochNotExpired(); error VaultNotZeroTVL(); error VaultZeroTVL(); + error PriceOutdated(); /*////////////////////////////////////////////////////////////// EVENTS @@ -366,7 +379,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 +387,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 index b07dee0e..59bef99e 100644 --- a/src/v2/CustomERC1155/ERC1155.sol +++ b/src/v2/CustomERC1155/ERC1155.sol @@ -39,7 +39,13 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { /** * @dev See {IERC165-supportsInterface}. */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC165, IERC165) + returns (bool) + { return interfaceId == type(IERC1155).interfaceId || interfaceId == type(IERC1155MetadataURI).interfaceId || @@ -67,8 +73,17 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * * - `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"); + 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]; } @@ -86,7 +101,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { override returns (uint256[] memory) { - require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); + require( + accounts.length == ids.length, + "ERC1155: accounts and ids length mismatch" + ); uint256[] memory batchBalances = new uint256[](accounts.length); @@ -100,14 +118,24 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { /** * @dev See {IERC1155-setApprovalForAll}. */ - function setApprovalForAll(address operator, bool approved) public virtual override { + 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) { + function isApprovedForAll(address account, address operator) + public + view + virtual + override + returns (bool) + { return _operatorApprovals[account][operator]; } @@ -173,7 +201,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _beforeTokenTransfer(operator, from, to, ids, amounts, data); uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); unchecked { _balances[id][from] = fromBalance - amount; } @@ -203,7 +234,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory amounts, bytes memory data ) internal virtual { - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); require(to != address(0), "ERC1155: transfer to the zero address"); address operator = _msgSender(); @@ -215,7 +249,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256 amount = amounts[i]; uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); unchecked { _balances[id][from] = fromBalance - amount; } @@ -226,7 +263,14 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _afterTokenTransfer(operator, from, to, ids, amounts, data); - _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + _doSafeBatchTransferAcceptanceCheck( + operator, + from, + to, + ids, + amounts, + data + ); } /** @@ -282,7 +326,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _afterTokenTransfer(operator, address(0), to, ids, amounts, data); - // remove _doSafeTransferAcceptanceCheck to prevent reverting in queue + // 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); } @@ -305,7 +349,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { 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"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); address operator = _msgSender(); @@ -319,7 +366,14 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _afterTokenTransfer(operator, address(0), to, ids, amounts, data); - _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); + _doSafeBatchTransferAcceptanceCheck( + operator, + address(0), + to, + ids, + amounts, + data + ); } /** @@ -371,7 +425,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { 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"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); address operator = _msgSender(); @@ -382,7 +439,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256 amount = amounts[i]; uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + require( + fromBalance >= amount, + "ERC1155: burn amount exceeds balance" + ); unchecked { _balances[id][from] = fromBalance - amount; } @@ -475,7 +535,15 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { bytes memory data ) private { if (to.isContract()) { - try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { + try + IERC1155Receiver(to).onERC1155Received( + operator, + from, + id, + amount, + data + ) + returns (bytes4 response) { if (response != IERC1155Receiver.onERC1155Received.selector) { revert("ERC1155: ERC1155Receiver rejected tokens"); } @@ -496,10 +564,18 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { bytes memory data ) private { if (to.isContract()) { - try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( - bytes4 response - ) { - if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + 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) { @@ -510,7 +586,11 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { } } - function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { + function _asSingletonArray(uint256 element) + private + pure + returns (uint256[] memory) + { uint256[] memory array = new uint256[](1); array[0] = element; diff --git a/src/v2/CustomERC1155/ERC1155Supply.sol b/src/v2/CustomERC1155/ERC1155Supply.sol index 2303c3c6..730927ad 100644 --- a/src/v2/CustomERC1155/ERC1155Supply.sol +++ b/src/v2/CustomERC1155/ERC1155Supply.sol @@ -54,7 +54,10 @@ abstract contract ERC1155Supply is ERC1155 { uint256 id = ids[i]; uint256 amount = amounts[i]; uint256 supply = _totalSupply[id]; - require(supply >= amount, "ERC1155: burn amount exceeds totalSupply"); + 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 e21c615f..52df7005 100644 --- a/src/v2/SemiFungibleVault.sol +++ b/src/v2/SemiFungibleVault.sol @@ -5,9 +5,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { - ERC1155Supply -} from "./CustomERC1155/ERC1155Supply.sol"; +import {ERC1155Supply} from "./CustomERC1155/ERC1155Supply.sol"; import {ERC1155} from "./CustomERC1155/ERC1155.sol"; import {ISemiFungibleVault} from "./interfaces/ISemiFungibleVault.sol"; diff --git a/src/v2/TimeLock.sol b/src/v2/TimeLock.sol index 145ed35f..a2b5640a 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 = 3 days; // short timeframe to not discriminate against epoch lenght but rather focus on transprancy of critical operations 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 d81edf36..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); @@ -95,4 +91,8 @@ interface ICarousel { 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 ead966a3..d88ee259 100644 --- a/src/v2/libraries/CarouselCreator.sol +++ b/src/v2/libraries/CarouselCreator.sol @@ -35,7 +35,6 @@ library CarouselCreator { _marketConfig.token, _marketConfig.strike, _marketConfig.controller, - _marketConfig.treasury, _marketConfig.emissionsToken, _marketConfig.relayerFee, _marketConfig.depositFee, 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 55cafce8..5dfab297 100644 --- a/test/V2/Carousel/CarouselFactoryTest.t.sol +++ b/test/V2/Carousel/CarouselFactoryTest.t.sol @@ -71,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); diff --git a/test/V2/Carousel/CarouselTest.t.sol b/test/V2/Carousel/CarouselTest.t.sol index 93ffaa08..88d208ef 100644 --- a/test/V2/Carousel/CarouselTest.t.sol +++ b/test/V2/Carousel/CarouselTest.t.sol @@ -41,7 +41,6 @@ contract CarouselTest is Helper { TOKEN, STRIKE, controller, - TREASURY, emissionsToken, relayerFee, depositFee, @@ -76,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); @@ -101,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); @@ -137,14 +135,12 @@ contract CarouselTest is Helper { _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 { @@ -239,7 +235,9 @@ contract CarouselTest is Helper { helperDepositInEpochs(_epochId,USER5, true); helperDepositInEpochs(_epochId,USER6, true); - assertEq(vault.getDepositQueueLenght(), 6); + assertEq(vault.getDepositQueueTVL(), 60 ether); + + assertEq(vault.getDepositQueueLength(), 6); // check balance of relayer uint256 balanceBefore = IERC20(UNDERLYING).balanceOf(address(this)); @@ -256,17 +254,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 { @@ -318,9 +310,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 @@ -329,8 +319,6 @@ 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)); @@ -349,23 +337,14 @@ contract CarouselTest is Helper { //@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) >> 1) << 1); // zero out last bit to avoid rounding errors - + 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(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(USER6, _epochId), prevEpochUserBalance - relayerFee); } @@ -403,5 +382,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..05f2caa6 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,8 +38,7 @@ contract ControllerPeggedAssetV2Test is Helper { controller = new ControllerPeggedAssetV2( address(factory), - address(0x1), - TREASURY + address(new Sequencer()) ); UNDERLYING = address(new MintableToken("UnderLyingToken", "utkn")); @@ -71,8 +71,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 +89,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 +118,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 e0b1ccaa..1a882365 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -10,8 +10,8 @@ contract Helper is Test { uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; uint256 public constant COLLATERAL_MINUS_FEES_DIV10 = 2198999999839855145; uint256 public constant NEXT_COLLATERAL_MINUS_FEES = 21827317001456829250; - uint256 public constant USER1_EMISSIONS_AFTER_WITHDRAW = 1096655439903230405185; - uint256 public constant USER2_EMISSIONS_AFTER_WITHDRAW = 96655439903230405185; + uint256 public constant USER1_EMISSIONS_AFTER_WITHDRAW = 1096655439903230405190; + uint256 public constant USER2_EMISSIONS_AFTER_WITHDRAW = 96655439903230405190; uint256 public constant USER_AMOUNT_AFTER_WITHDRAW = 13112658495640855090; address public constant ADMIN = address(0x1); address public constant WETH = address(0x888); 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 d02a79e0..48f8be13 100644 --- a/test/V2/e2e/EndToEndCarouselTest.t.sol +++ b/test/V2/e2e/EndToEndCarouselTest.t.sol @@ -55,7 +55,7 @@ contract EndToEndCarouselTest is Helper { emissionsToken ); - controller = new ControllerPeggedAssetV2(address(factory), ARBITRUM_SEQUENCER, TREASURY); + controller = new ControllerPeggedAssetV2(address(factory), ARBITRUM_SEQUENCER); factory.whitelistController(address(controller)); relayerFee = 2 gwei; @@ -162,8 +162,8 @@ 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); @@ -174,13 +174,9 @@ contract EndToEndCarouselTest is Helper { //assert balance and emissions assertEq(Carousel(collateral).balanceOf(USER, epochId), collatBalanceAfterFee - relayerFee); - assertEq(Carousel(collateral).balanceOfEmissions(USER, epochId), collatBalanceAfterFee - relayerFee); assertEq(Carousel(collateral).balanceOf(USER2, epochId), collatBalanceAfterFee - relayerFee); - assertEq(Carousel(collateral).balanceOfEmissions(USER2, epochId), collatBalanceAfterFee - relayerFee); assertEq(Carousel(premium).balanceOf(USER, epochId), premiumBalanceAfterFee - relayerFee); - assertEq(Carousel(premium).balanceOfEmissions(USER, epochId), premiumBalanceAfterFee - relayerFee); assertEq(Carousel(premium).balanceOf(USER2, epochId), 0); - assertEq(Carousel(premium).balanceOfEmissions(USER2, epochId), 0); vm.startPrank(USER); @@ -188,7 +184,7 @@ contract EndToEndCarouselTest is Helper { Carousel(collateral).enlistInRollover(epochId, 8 ether, USER); bool isEnlisted = Carousel(collateral).isEnlistedInRolloverQueue(USER); - (uint256 enlistedAmount, uint256 id) = Carousel(collateral).getRolloverPosition(USER); + (uint256 enlistedAmount,) = Carousel(collateral).getRolloverPosition(USER); assertEq(isEnlisted, true); assertEq(enlistedAmount, 8 ether); @@ -257,7 +253,7 @@ contract EndToEndCarouselTest is Helper { vm.startPrank(USER); //delist rollover - uint256 beforeQueueLength = Carousel(collateral).getRolloverQueueLenght(); + uint256 beforeQueueLength = Carousel(collateral).getRolloverQueueLength(); Carousel(collateral).delistInRollover(USER); //assert delisted rollover position in queue assets are 0 @@ -269,8 +265,8 @@ contract EndToEndCarouselTest is Helper { //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 - relayerFee)); //withdraw after rollover Carousel(collateral).withdraw(nextEpochId, balanceInNextEpoch, USER, USER); @@ -280,12 +276,12 @@ contract EndToEndCarouselTest is Helper { // cleanup queue from delisted users vm.startPrank(address(factory)); - uint256 beforeQueueLength2 = Carousel(collateral).getRolloverQueueLenght(); + 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).getRolloverQueueLenght(); + uint256 afterQueueLength2 = Carousel(collateral).getRolloverQueueLength(); assertEq(afterQueueLength2, 1); vm.stopPrank(); @@ -296,13 +292,13 @@ contract EndToEndCarouselTest is Helper { assertTrue(Carousel(collateral).getRolloverIndex(USER2) == 0); //delist rollover - beforeQueueLength = Carousel(collateral).getRolloverQueueLenght(); + beforeQueueLength = Carousel(collateral).getRolloverQueueLength(); Carousel(collateral).delistInRollover(USER2); //assert balance in next epoch balanceInNextEpoch = Carousel(collateral).balanceOf(USER2, nextEpochId); - //assert rollover minus relayer fee + //assert rollover minus relayer fee which is subtracted based on the value of the shares of the prev epoch assertTrue(balanceInNextEpoch == 8 ether - relayerFee); //withdraw after rollover diff --git a/test/V2/e2e/EndToEndV2Test.t.sol b/test/V2/e2e/EndToEndV2Test.t.sol index 0618fa38..7d0d6290 100644 --- a/test/V2/e2e/EndToEndV2Test.t.sol +++ b/test/V2/e2e/EndToEndV2Test.t.sol @@ -58,7 +58,7 @@ contract EndToEndV2Test is Helper { address(timelock) ); - controller = new ControllerPeggedAssetV2(address(factory), ARBITRUM_SEQUENCER, TREASURY); + controller = new ControllerPeggedAssetV2(address(factory), ARBITRUM_SEQUENCER); factory.whitelistController(address(controller)); diff --git a/test/legacy_v1/Vault.sol b/test/legacy_v1/Vault.sol index 192ebb43..8804ee5d 100644 --- a/test/legacy_v1/Vault.sol +++ b/test/legacy_v1/Vault.sol @@ -84,7 +84,7 @@ contract Vault is SemiFungibleVault, ReentrancyGuard { /** @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(); + if (block.timestamp >= idEpochBegin[id]) revert EpochAlreadyStarted(); _; }