diff --git a/contracts/Reliquary.sol b/contracts/Reliquary.sol index 4274eb6..e77d9b9 100644 --- a/contracts/Reliquary.sol +++ b/contracts/Reliquary.sol @@ -5,6 +5,7 @@ import "./interfaces/IReliquary.sol"; import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol"; import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "openzeppelin-contracts/contracts/utils/math/Math.sol"; import "openzeppelin-contracts/contracts/utils/Multicall.sol"; import "openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol"; import "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol"; @@ -89,6 +90,7 @@ contract Reliquary is IReliquary, ERC721Burnable, ERC721Enumerable, AccessContro event LogPoolAddition( uint indexed pid, uint allocPoint, + uint startTime, IERC20 indexed poolToken, IRewarder indexed rewarder, INFTDescriptor nftDescriptor @@ -185,6 +187,7 @@ contract Reliquary is IReliquary, ERC721Burnable, ERC721Enumerable, AccessContro IRewarder _rewarder, uint[] calldata requiredMaturity, uint[] calldata allocPoints, + uint startTime, string memory name, INFTDescriptor _nftDescriptor ) external override onlyRole(OPERATOR) { @@ -214,6 +217,7 @@ contract Reliquary is IReliquary, ERC721Burnable, ERC721Enumerable, AccessContro poolInfo.push( PoolInfo({ + startTime: startTime, allocPoint: allocPoint, lastRewardTime: block.timestamp, accOathPerShare: 0, @@ -228,7 +232,7 @@ contract Reliquary is IReliquary, ERC721Burnable, ERC721Enumerable, AccessContro }) ); - emit LogPoolAddition((poolToken.length - 1), allocPoint, _poolToken, _rewarder, _nftDescriptor); + emit LogPoolAddition((poolToken.length - 1), allocPoint, startTime, _poolToken, _rewarder, _nftDescriptor); } /* @@ -715,9 +719,14 @@ contract Reliquary is IReliquary, ERC721Burnable, ERC721Enumerable, AccessContro */ function _updateEntry(uint amount, uint relicId) internal { PositionInfo storage position = positionForId[relicId]; - uint weight = _findWeight(amount, position.amount); - uint maturity = block.timestamp - position.entry; - position.entry += maturity * weight / 1e18; + PoolInfo storage pool = poolInfo[position.poolId]; + if(block.timestamp < pool.startTime) { + position.entry = pool.startTime; + } else { + uint weight = _findWeight(amount, position.amount); + uint maturity = block.timestamp - position.entry; + position.entry += maturity * weight / 1e18; + } } /* @@ -732,7 +741,7 @@ contract Reliquary is IReliquary, ERC721Burnable, ERC721Enumerable, AccessContro return 0; } - uint maturity = block.timestamp - position.entry; + uint maturity = block.timestamp - Math.min(position.entry, block.timestamp); for (newLevel = length - 1; true; newLevel = _uncheckedDec(newLevel)) { if (maturity >= levelInfo.requiredMaturity[newLevel]) { if (position.level != newLevel) { diff --git a/contracts/interfaces/IReliquary.sol b/contracts/interfaces/IReliquary.sol index 142ba3f..5472275 100644 --- a/contracts/interfaces/IReliquary.sol +++ b/contracts/interfaces/IReliquary.sol @@ -34,6 +34,7 @@ struct PositionInfo { + `name` Name of pool to be displayed in NFT image */ struct PoolInfo { + uint startTime; uint accOathPerShare; uint lastRewardTime; uint allocPoint; @@ -63,6 +64,7 @@ interface IReliquary is IERC721Enumerable { IRewarder _rewarder, uint[] calldata requiredMaturity, uint[] calldata allocPoints, + uint startTime, string memory name, INFTDescriptor _nftDescriptor ) external; diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 6341066..863fab7 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -39,6 +39,7 @@ contract Deploy is Script { IRewarder(address(0)), wethCurve, wethLevels, + block.timestamp, "ETH Pool", nftDescriptor ); diff --git a/test/foundry/DepositHelper.t.sol b/test/foundry/DepositHelper.t.sol index 4c9f8a5..435331b 100644 --- a/test/foundry/DepositHelper.t.sol +++ b/test/foundry/DepositHelper.t.sol @@ -28,7 +28,7 @@ contract DepositHelperTest is Test { vault = IERC4626(0x58C60B6dF933Ff5615890dDdDCdD280bad53f1C1); INFTDescriptor nftDescriptor = INFTDescriptor(new NFTDescriptorSingle4626(IReliquary(reliquary))); reliquary.grantRole(keccak256(bytes("OPERATOR")), address(this)); - reliquary.addPool(1000, vault, IRewarder(address(0)), wethCurve, wethLevels, "ETH Crypt", nftDescriptor); + reliquary.addPool(1000, vault, IRewarder(address(0)), wethCurve, wethLevels, block.timestamp, "ETH Crypt", nftDescriptor); helper = new DepositHelper(address(reliquary)); weth = IERC20(vault.asset()); diff --git a/test/foundry/Invariants.t.sol b/test/foundry/Invariants.t.sol index a2a3b3d..595637d 100644 --- a/test/foundry/Invariants.t.sol +++ b/test/foundry/Invariants.t.sol @@ -25,7 +25,7 @@ contract Invariants is Test { TestToken testToken = new TestToken("Test Token", "TT", 6); INFTDescriptor nftDescriptor = INFTDescriptor(new NFTDescriptor(IReliquary(reliquary))); reliquary.grantRole(keccak256(bytes("OPERATOR")), address(this)); - reliquary.addPool(1000, testToken, IRewarder(address(0)), curve, levels, "Test Token", nftDescriptor); + reliquary.addPool(1000, testToken, IRewarder(address(0)), curve, levels, block.timestamp, "Test Token", nftDescriptor); ReliquaryUser user = new ReliquaryUser(address(reliquary), address(testToken)); Skipper skipper = new Skipper(); diff --git a/test/foundry/Reliquary.t.sol b/test/foundry/Reliquary.t.sol index 3dfb52f..24ebc44 100644 --- a/test/foundry/Reliquary.t.sol +++ b/test/foundry/Reliquary.t.sol @@ -20,6 +20,7 @@ contract ReliquaryTest is ERC721Holder, Test { uint[] requiredMaturity = [0, 1 days, 7 days, 14 days, 30 days, 90 days, 180 days, 365 days]; uint[] allocPoints = [100, 120, 150, 200, 300, 400, 500, 750]; + uint startTime; event Deposit( uint indexed pid, @@ -57,6 +58,8 @@ contract ReliquaryTest is ERC721Holder, Test { testToken = new TestToken("Test Token", "TT", 6); nftDescriptor = INFTDescriptor(address(new NFTDescriptor(IReliquary(address(reliquary))))); + startTime = block.timestamp + 2 days; + reliquary.grantRole(keccak256(bytes("OPERATOR")), address(this)); reliquary.addPool( 100, @@ -64,6 +67,7 @@ contract ReliquaryTest is ERC721Holder, Test { IRewarder(address(0)), requiredMaturity, allocPoints, + startTime, "ETH Pool", nftDescriptor ); @@ -75,6 +79,26 @@ contract ReliquaryTest is ERC721Holder, Test { function testPoolLength() public { assertTrue(reliquary.poolLength() == 1); } + function testMaturingOnlyOnceStartTimeReachedForPool() public { + uint relicId = reliquary.createRelicAndDeposit(address(this), 0, 10 ether); + PositionInfo memory positionAfterDeposit = reliquary.getPositionForId(relicId); + assertEq(positionAfterDeposit.entry, startTime); + assertEq(positionAfterDeposit.level, 0); + + skip(1 days); + // according to the level definition, we would be level 1 after 1 day + // but since we've set the start time to the future, it should still be level 0 + reliquary.updatePosition(relicId); + PositionInfo memory positionAfter1Day = reliquary.getPositionForId(relicId); + assertEq(positionAfter1Day.level, 0); + + skip(2 days); + // now we have passed the start time by 1 day and should therefore be level 1 + reliquary.updatePosition(relicId); + PositionInfo memory positionAfter3Days= reliquary.getPositionForId(relicId); + assertEq(positionAfter3Days.level, 1); + + } function testModifyPool() public { vm.expectEmit(true, true, false, true); @@ -82,6 +106,7 @@ contract ReliquaryTest is ERC721Holder, Test { reliquary.modifyPool(0, 100, IRewarder(address(0)), "USDC Pool", nftDescriptor, true); } + function testRevertOnModifyInvalidPool() public { vm.expectRevert(bytes("set: pool does not exist")); reliquary.modifyPool(1, 100, IRewarder(address(0)), "USDC Pool", nftDescriptor, true);