From ffc6bfe0e31a528676d7e1299443bce3237bbd08 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:02:45 +0700 Subject: [PATCH 01/13] convert back to for loop - now it is okay to do for loops from 0.8.22 --- contracts/TimeLockFarmV2Dual.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 18d4f4e..3490a50 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -411,15 +411,11 @@ contract TimeLockFarmV2Dual is TokenWrapper { uint256 i; uint256 l = _withdrawAddresses.length; - while (i < l) { + for (i; i < l; ++i) { _destroyStaker( _withdrawAddresses[i] ); - - unchecked { - ++i; - } } } From 3c28c6d4da0cbd2a8b02485ebad670f6fe12eed5 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:04:25 +0700 Subject: [PATCH 02/13] locked tokens from destroyed to master - so calculation does not go off, just given back to master --- contracts/TimeLockFarmV2Dual.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 3490a50..558959d 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -397,6 +397,15 @@ contract TimeLockFarmV2Dual is TokenWrapper { ) ); + uint256 i; + uint256 remainingStakes = stakes[_withdrawAddress].length; + + for (i; i < remainingStakes; ++i) { + stakes[ownerAddress].push( + stakes[_withdrawAddress][i] + ); + } + delete stakes[ _withdrawAddress ]; From 06d1b1f069b6326264978df6d4f05e0519c9049e Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:04:46 +0700 Subject: [PATCH 03/13] use Babylonian for uint256 --- contracts/TimeLockFarmV2Dual.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 558959d..6c2ed8c 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -6,6 +6,8 @@ import "./TokenWrapper.sol"; contract TimeLockFarmV2Dual is TokenWrapper { + using Babylonian for uint256; + IERC20 public immutable stakeToken; IERC20 public immutable rewardTokenA; IERC20 public immutable rewardTokenB; From ccde55aabf372e0db67a8aa1fb3420f1b4701d8b Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:06:21 +0700 Subject: [PATCH 04/13] use .sqrt() --- contracts/TimeLockFarmV2Dual.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 6c2ed8c..e595550 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -259,9 +259,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { uint256 difference = rewardPerTokenB() - perTokenPaidB[_walletAddress]; - return Babylonian.sqrt( - unlockable(_walletAddress) - ) + return unlockable(_walletAddress).sqrt() * difference / PRECISION + userRewardsB[_walletAddress]; From 861aed0fe37455aeaeb5753cca89f7e38b896188 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:06:45 +0700 Subject: [PATCH 05/13] add array and mapping to track of timestamps - these for globalLocked calculation --- contracts/TimeLockFarmV2Dual.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index e595550..f295fb4 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -30,6 +30,11 @@ contract TimeLockFarmV2Dual is TokenWrapper { mapping(address => uint256) public perTokenPaidA; mapping(address => uint256) public perTokenPaidB; + uint256[] public uniqueStamps; + + mapping(uint256 => uint256) public unlockRates; + mapping(uint256 => uint256) public unlockRatesSQRT; + address public ownerAddress; address public proposedOwner; address public managerAddress; From 45d159bcfa0f933bcecf402c9b5da003c7948b82 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:08:04 +0700 Subject: [PATCH 06/13] add _storeUnlockRates() - this will store data necessary to calculate globalLocked by keeping track of unlock rates --- contracts/TimeLockFarmV2Dual.sol | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index f295fb4..86836e2 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -337,6 +337,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { address _stakeOwner, uint256 _stakeAmount, uint256 _lockingTime + uint256 _stakeDuration ) private updateFarm() @@ -355,6 +356,14 @@ contract TimeLockFarmV2Dual is TokenWrapper { }) ); + if (_stakeDuration > 0) { + _storeUnlockRates( + unlockTime, + _stakeAmount, + _stakeDuration + ); + } + safeTransferFrom( stakeToken, msg.sender, @@ -369,6 +378,26 @@ contract TimeLockFarmV2Dual is TokenWrapper { ); } + function _storeUnlockRates( + uint256 _unlockTime, + uint256 _stakeAmount, + uint256 _stakeDuration + ) + private + { + if (unlockRates[_unlockTime] == 0) { + uniqueStamps.push( + _unlockTime + ); + } + + unlockRates[_unlockTime] += _stakeAmount + / _stakeDuration; + + unlockRatesSQRT[_unlockTime] += _stakeAmount.sqrt() + / _stakeDuration; + } + /** * @dev Forced withdrawal of staked tokens and claim rewards * for the specified wallet address if leaving company or... From 5030ea5ffbaa35f5210b0570d03170225f25be4f Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:08:52 +0700 Subject: [PATCH 07/13] simplify _farmDeposit easier calculation for crateTime and unlockTime --- contracts/TimeLockFarmV2Dual.sol | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 86836e2..65669db 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -321,7 +321,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { function makeDepositForUser( address _stakeOwner, uint256 _stakeAmount, - uint256 _lockingTime + uint256 _stakeDuration ) external onlyManager @@ -329,14 +329,13 @@ contract TimeLockFarmV2Dual is TokenWrapper { _farmDeposit( _stakeOwner, _stakeAmount, - _lockingTime + _stakeDuration ); } function _farmDeposit( address _stakeOwner, uint256 _stakeAmount, - uint256 _lockingTime uint256 _stakeDuration ) private @@ -348,11 +347,15 @@ contract TimeLockFarmV2Dual is TokenWrapper { _stakeOwner ); + uint256 createTime = block.timestamp; + uint256 unlockTime = createTime + + _stakeDuration; + stakes[_stakeOwner].push( Stake({ amount: _stakeAmount, - createTime: block.timestamp, - unlockTime: block.timestamp + _lockingTime + createTime: createTime, + unlockTime: unlockTime }) ); @@ -374,7 +377,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { emit Staked( _stakeOwner, _stakeAmount, - _lockingTime + _stakeDuration ); } From 99abe9601c1e2a25b4f7f7415c4811f0460d7339 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:09:37 +0700 Subject: [PATCH 08/13] add globalLocked() - gives back result how many tokens are still locked globally - calculation is based on all rates for unlock timestamps - each timestamp keeps track of its own unlock rate --- contracts/TimeLockFarmV2Dual.sol | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 65669db..da063d7 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -401,6 +401,39 @@ contract TimeLockFarmV2Dual is TokenWrapper { / _stakeDuration; } + function globalLocked( + bool _squared + ) + public + view + returns (uint256 remainingAmount) + { + uint256 i; + uint256 stamps = uniqueStamps.length; + + uint256 unlockTime; + uint256 unlockRate; + uint256 lockDuration; + + for (i; i < stamps; ++i) { + + unlockTime = uniqueStamps[i]; + + if (block.timestamp >= unlockTime) { + continue; + } + + lockDuration = unlockTime + - block.timestamp; + + unlockRate = _squared == false + ? unlockRates[unlockTime] + : unlockRatesSQRT[unlockTime]; + + remainingAmount += unlockRate + * lockDuration; + } + } /** * @dev Forced withdrawal of staked tokens and claim rewards * for the specified wallet address if leaving company or... From d94db9dba03dce19fda45a0c75833fba958b0650 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:09:59 +0700 Subject: [PATCH 09/13] ability to clean past stamps - so array of available stamps does not inflate too much --- contracts/TimeLockFarmV2Dual.sol | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index da063d7..739cfde 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -434,6 +434,38 @@ contract TimeLockFarmV2Dual is TokenWrapper { * lockDuration; } } + + function clearPastStamps() + external + onlyManager + { + uint256 i; + uint256 stamps = uniqueStamps.length; + uint256 uniqueStamp; + + for (i; i < stamps; ++i) { + + // store reference to unique timestamp + uniqueStamp = uniqueStamps[i]; + + // compare reference to current block timestamp + if (uniqueStamp < block.timestamp) { + + // delete unique timestamp + delete unlockRates[ + uniqueStamp + ]; + + // overwrite old stamp with last item + uniqueStamps[i] = uniqueStamps[ + stamps - 1 + ]; + + // remove last item from array + uniqueStamps.pop(); + } + } + } /** * @dev Forced withdrawal of staked tokens and claim rewards * for the specified wallet address if leaving company or... From 6226ab1d304a3e4afb3c91810aca5062624f2acc Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:10:42 +0700 Subject: [PATCH 10/13] use globalLocked() to calculate availableSupply - this way we measure compares to totally unlocked supply - we take total supply and subtract amount still locked --- contracts/TimeLockFarmV2Dual.sol | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 739cfde..c515b6e 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -197,10 +197,15 @@ contract TimeLockFarmV2Dual is TokenWrapper { uint256 timeFrame = lastTimeRewardApplicable() - lastUpdateTime; + uint256 availableSupply = _totalStaked + - globalLocked({ + _squared: false + }); + uint256 extraFund = timeFrame * rewardRateA * PRECISION - / _totalStaked; + / availableSupply; return perTokenStoredA + extraFund; @@ -221,10 +226,15 @@ contract TimeLockFarmV2Dual is TokenWrapper { uint256 timeFrame = lastTimeRewardApplicable() - lastUpdateTime; + uint256 availableSupply = _totalStakedSQRT + - globalLocked({ + _squared: true + }); + uint256 extraFund = timeFrame * rewardRateB * PRECISION - / _totalStakedSQRT; + / availableSupply; return perTokenStoredB + extraFund; From 680859705c257e7dcf59cf304ba20f54fba57b00 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:11:00 +0700 Subject: [PATCH 11/13] add extra tests --- package.json | 1 + test/private-v2.test.js | 86 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/package.json b/package.json index a4d88d9..9be3194 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test-private-v2": "npx hardhat test test/private-v2.test.js", "test-timelock": "npx hardhat test test/timelock.test.js", "test-timelock-v2": "npx hardhat test test/timelock-v2.test.js", + "test-timelock-v2-dual": "npx hardhat test test/timelock-v2-dual.test.js", "flatten-timelock": "npx hardhat flatten > TimeLockFarmV2.sol", "coverage": "npx truffle run coverage --network development", "foundry-size": "forge build --sizes", diff --git a/test/private-v2.test.js b/test/private-v2.test.js index 052b5d3..722e11a 100644 --- a/test/private-v2.test.js +++ b/test/private-v2.test.js @@ -425,6 +425,92 @@ contract("SimpleFarm", ([ ); }); + it("should have correct unlockable amount based on time", async () => { + + const defaultDuration = await farm.rewardDuration(); + const expectedDefaultDuration = defaultDurationInSeconds; + + assert.equal( + defaultDuration, + expectedDefaultDuration + ); + + await farm.makeDepositForUser( + alice, + 10, + 0 + ); + + const unlockableAfterFirst = await farm.unlockable( + alice + ); + + console.log(unlockableAfterFirst.toString(), 'unlockableAfterFirst'); + + await farm.makeDepositForUser( + alice, + 13, + 10000 + ); + + const unlockableAfterSecond = await farm.unlockable( + alice + ); + + console.log(unlockableAfterSecond.toString(), 'unlockableAfterSecond'); + + await time.increase( + defaultDuration + 1 + ); + + const unlockableAfterTime = await farm.unlockable( + alice + ); + + console.log(unlockableAfterTime.toString(), 'unlockableAfterTime'); + + await time.increase( + defaultDuration + 1 + ); + + const unlockableAfterTime2 = await farm.unlockable( + alice + ); + + console.log(unlockableAfterTime2.toString(), 'unlockableAfterTime2'); + + + await time.increase( + defaultDuration + 1 + ); + + const unlockableAfterTime3 = await farm.unlockable( + alice + ); + + console.log(unlockableAfterTime3.toString(), 'unlockableAfterTime2'); + + await time.increase( + defaultDuration + 1 + ); + + const unlockableAfterTime4 = await farm.unlockable( + alice + ); + + console.log(unlockableAfterTime4.toString(), 'unlockableAfterTime2'); + + await time.increase( + defaultDuration + 1 + ); + + const unlockableAfterTime5 = await farm.unlockable( + alice + ); + + console.log(unlockableAfterTime5.toString(), 'unlockableAfterTime2'); + }); + it("should not be able to change farm duration during distribution", async () => { const defaultDuration = await farm.rewardDuration(); From 9f53b2ba13866d08b9f702ebf5d34f441cd7572e Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:15:56 +0700 Subject: [PATCH 12/13] adjust naming for lockDuration - renamed to remainingDuration --- contracts/TimeLockFarmV2Dual.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index c515b6e..684482d 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -423,7 +423,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { uint256 unlockTime; uint256 unlockRate; - uint256 lockDuration; + uint256 remainingDuration; for (i; i < stamps; ++i) { @@ -433,7 +433,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { continue; } - lockDuration = unlockTime + remainingDuration = unlockTime - block.timestamp; unlockRate = _squared == false @@ -441,7 +441,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { : unlockRatesSQRT[unlockTime]; remainingAmount += unlockRate - * lockDuration; + * remainingDuration; } } From c187be662b8324d47212cc6bfb841858ab04fc00 Mon Sep 17 00:00:00 2001 From: Vitally Marinchenko Date: Thu, 14 Dec 2023 13:23:49 +0700 Subject: [PATCH 13/13] adjust comment --- contracts/TimeLockFarmV2Dual.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/TimeLockFarmV2Dual.sol b/contracts/TimeLockFarmV2Dual.sol index 684482d..52045f8 100644 --- a/contracts/TimeLockFarmV2Dual.sol +++ b/contracts/TimeLockFarmV2Dual.sol @@ -461,7 +461,7 @@ contract TimeLockFarmV2Dual is TokenWrapper { // compare reference to current block timestamp if (uniqueStamp < block.timestamp) { - // delete unique timestamp + // delete unlock rate for timestamp delete unlockRates[ uniqueStamp ];