diff --git a/.gas-snapshot b/.gas-snapshot index 5bc6daf..47228be 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -24,16 +24,16 @@ AludelFactoryTest:test_WHEN_launching_with_a_disabled_template_THEN_it_reverts() AludelFactoryTest:test_WHEN_setting_a_different_fee_bps_and_recipient_THEN_its_passed_to_new_aludels() (gas: 663702) AludelFactoryTest:test_WHEN_updating_a_program_with_empty_fields_THEN_it_isnt_updated() (gas: 69945) AludelFactoryTest:test_WHEN_updating_an_unlisted_template_THEN_it_reverts() (gas: 22449) -AludelFactoryIntegrationTest:test_many_users_multiple_stakes() (gas: 1712688) -AludelFactoryIntegrationTest:test_stake() (gas: 413356) -AludelFactoryIntegrationTest:test_unstake() (gas: 457312) +AludelFactoryIntegrationTest:test_many_users_multiple_stakes() (gas: 1718998) +AludelFactoryIntegrationTest:test_stake() (gas: 413334) +AludelFactoryIntegrationTest:test_unstake() (gas: 458889) AludelV3Test:test_aludel_stake_invalid_permission() (gas: 459631) AludelV3Test:test_aludel_stake_not_enough_balance() (gas: 594655) -AludelV3Test:test_funding_shares() (gas: 861909) +AludelV3Test:test_funding_shares() (gas: 777083) AludelV3Test:test_stakes_invalid_vault() (gas: 6238312) AludelV3Test:test_stakes_max_stakes_reached() (gas: 3078097) AludelV3Test:test_stakes_no_amount_staked() (gas: 256169) -AludelV3Test:test_stakes_total_stakes_units_calculations() (gas: 773105) +AludelV3Test:test_stakes_total_stakes_units_calculations() (gas: 773017) AludelV3LibTest:test_calculateNewShares_no_previous_shares(uint128,uint128,uint128) (runs: 256, μ: 4498, ~: 4498) AludelV3LibTest:test_calculateNewShares_with_previous_shares(uint128,uint128,uint128) (runs: 256, μ: 5347, ~: 5347) AludelV3LibTest:test_calculateSharesLocked(uint128,uint16) (runs: 256, μ: 280185, ~: 280185) diff --git a/src/contracts/aludel/AludelV3.sol b/src/contracts/aludel/AludelV3.sol index 638acc4..68afb29 100644 --- a/src/contracts/aludel/AludelV3.sol +++ b/src/contracts/aludel/AludelV3.sol @@ -314,23 +314,11 @@ contract AludelV3 is IAludelV3, Ownable, Initializable, Powered { return AludelV3Lib.calculateStakeUnits(amount, start, end); } - function calculateUnlockedRewards( - RewardSchedule[] memory rewardSchedules, - uint256 rewardBalance, - uint256 sharesOutstanding, - uint256 timestamp - ) + function calculateUnlockedRewards(uint256 timestamp) public - pure - override - returns (uint256 unlockedRewards) - { - return AludelV3Lib.calculateUnlockedRewards( - rewardSchedules, - rewardBalance, - sharesOutstanding, - timestamp - ); + view + returns (uint256 unlockedRewards) { + return AludelV3Lib.calculateUnlockedRewards(_aludel, timestamp); } function calculateReward( @@ -633,12 +621,7 @@ contract AludelV3 is IAludelV3, Ownable, Initializable, Powered { uint256 remainingRewards = AludelV3Lib.getRemainingRewards(_aludel); // calculate vested portion of reward pool - uint256 unlockedRewards = AludelV3Lib.calculateUnlockedRewards( - _aludel.rewardSchedules, - remainingRewards, - _aludel.rewardSharesOutstanding, - block.timestamp - ); + uint256 unlockedRewards = calculateUnlockedRewards(block.timestamp); (uint256 reward, uint256 amount) = _unstake( vault, diff --git a/src/contracts/aludel/AludelV3Lib.sol b/src/contracts/aludel/AludelV3Lib.sol index c29a522..3402252 100644 --- a/src/contracts/aludel/AludelV3Lib.sol +++ b/src/contracts/aludel/AludelV3Lib.sol @@ -126,6 +126,29 @@ library AludelV3Lib { return unlockedRewards; } + function calculateCurrentUnlockedRewards( + IAludelV3.AludelData storage aludel + ) internal view returns(uint256 unlockedRewards) { + unlockedRewards = AludelV3Lib.calculateUnlockedRewards( + aludel.rewardSchedules, + getRemainingRewards(aludel), + aludel.rewardSharesOutstanding, + block.timestamp + ); + } + + function calculateUnlockedRewards( + IAludelV3.AludelData storage aludel, + uint256 timestamp + ) internal view returns(uint256 unlockedRewards) { + unlockedRewards = AludelV3Lib.calculateUnlockedRewards( + aludel.rewardSchedules, + getRemainingRewards(aludel), + aludel.rewardSharesOutstanding, + timestamp + ); + } + function calculateReward( uint256 unlockedRewards, uint256 stakeAmount, @@ -171,6 +194,8 @@ library AludelV3Lib { } + + /* state mutating functions */ function addRewardSchedule( diff --git a/src/contracts/aludel/IAludelV3.sol b/src/contracts/aludel/IAludelV3.sol index 2705034..d3d0368 100644 --- a/src/contracts/aludel/IAludelV3.sol +++ b/src/contracts/aludel/IAludelV3.sol @@ -176,14 +176,9 @@ interface IAludelV3 is IRageQuit { pure returns (uint256 stakeUnits); - function calculateUnlockedRewards( - RewardSchedule[] memory rewardSchedules, - uint256 rewardBalance, - uint256 sharesOutstanding, - uint256 timestamp - ) + function calculateUnlockedRewards(uint256 timestamp) external - pure + view returns (uint256 unlockedRewards); function calculateReward( diff --git a/src/test/AludelV3.t.sol b/src/test/AludelV3.t.sol index 3be888f..f8d31bd 100644 --- a/src/test/AludelV3.t.sol +++ b/src/test/AludelV3.t.sol @@ -136,155 +136,74 @@ contract AludelV3Test is Test { } function test_funding_shares() public { - // Scenario: Aludel is funded several times. - // Shares should unlock linearly. - // ______________________________________________ - // | Outstanding | Locked | Unlocked | - // |==============================================| - // | 1. Admin funds 600 ether, for 1 minute | - // |---------------|--------------|---------------| - // | 6e26 | 6e26 | 0 | - // |---------------|--------------|---------------| - // | 2. 1 minute elapses | - // |---------------|--------------|---------------| - // | 6e26 | 0 | 6e25 | - // |---------------|--------------|---------------| - // | 3. Admin funds 600 ether, for 1 minute | - // |---------------|--------------|---------------| - // | 6e26 * 2 | 6e25 | 6e25 | - // |---------------|--------------|---------------| - // | 4. 1 minute elapses | - // |---------------|--------------|---------------| - // | 6e26 * 2 | 0 | 6e25 * 2 | - // |---------------|--------------|---------------| - // | 4. Admin funds 600 ether, for 1 minute | - // |---------------|--------------|---------------| - // | 6e26 * 3 | 6e25 * 5 | 6e25 * 2 | - // |---------------|--------------|---------------| - // | 5. 1 minute elapses | - // |---------------|--------------|---------------| - // | 6e26 * 3 | 0 | 6e25 * 3 | - // |---------------|--------------|---------------| - // | 6. Admin funds 600 ether, for 1 minute | - // | Admin funds 600 ether, for 2 minute | - // | 1 minute elapses | - // | Forth schedule is fully unlocked | - // | Fifth is half unlocked | - // |---------------|--------------|---------------| - // | 6e26 * 5 | 6e25 * 0.5 | 6e25 * 4.5 | - // |---------------|--------------|---------------| - // | 7. 1 minute elapses | - // | Fifth schedule is now fully unlocked | - // |---------------|--------------|---------------| - // | 6e26 * 5 | 0 | 6e25 * 5 | - // |---------------|--------------|---------------| - - AludelV3.AludelData memory data = aludel.getAludelData(); + + AludelV3.AludelData memory data; Utils.fundMockToken(admin.addr(), rewardToken, REWARD_AMOUNT * 5); - vm.startPrank(admin.addr()); rewardToken.approve(address(aludel), REWARD_AMOUNT * 5); // 1. Admin funds 600 eth * 5 for 1 minute aludel.fund(REWARD_AMOUNT, SCHEDULE_DURATION); - data = aludel.getAludelData(); assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI); + assertEq( + aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), + REWARD_AMOUNT * BASE_SHARES_PER_WEI + ); - // 2. 1 minute elapses + // 2. Advance time 1 minute vm.warp(block.timestamp + SCHEDULE_DURATION); + data = aludel.getAludelData(); + assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI); assertEq(aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), 0); // 3. Admin funds 600 eth * 5 for 1 minute aludel.fund(REWARD_AMOUNT, SCHEDULE_DURATION); - data = aludel.getAludelData(); assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 2); - assertEq( - aludel.calculateUnlockedRewards( - data.rewardSchedules, - REWARD_AMOUNT, - data.rewardSharesOutstanding, - block.timestamp + SCHEDULE_DURATION - ), - REWARD_AMOUNT - ); + assertEq(aludel.calculateUnlockedRewards(block.timestamp), REWARD_AMOUNT); assertEq( aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), REWARD_AMOUNT * BASE_SHARES_PER_WEI ); - // 4. Admin funds 600 eth * 5 for 1 minute - aludel.fund(REWARD_AMOUNT, SCHEDULE_DURATION); - + // 4. Advance time 1 minute + vm.warp(block.timestamp + SCHEDULE_DURATION); data = aludel.getAludelData(); - assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 3); - assertEq( - aludel.calculateUnlockedRewards( - data.rewardSchedules, - REWARD_AMOUNT * 2, - data.rewardSharesOutstanding, - block.timestamp + SCHEDULE_DURATION - ), - REWARD_AMOUNT * 2 - ); + assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 2); + assertEq(aludel.calculateUnlockedRewards(block.timestamp), REWARD_AMOUNT * 2); assertEq( aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), - REWARD_AMOUNT * BASE_SHARES_PER_WEI * 2 + 0 ); - data = aludel.getAludelData(); - - assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 3); - assertEq( - aludel.calculateUnlockedRewards( - data.rewardSchedules, - REWARD_AMOUNT * 3, - data.rewardSharesOutstanding, - block.timestamp + SCHEDULE_DURATION - ), - REWARD_AMOUNT * 3 - ); - - // 5. 1 minute elapses + // 5. Admin funds 600 eth * 5 for 1 minute + // 1 minute elapses + aludel.fund(REWARD_AMOUNT, SCHEDULE_DURATION); vm.warp(block.timestamp + SCHEDULE_DURATION); data = aludel.getAludelData(); - assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 3); - + assertEq(aludel.calculateUnlockedRewards(block.timestamp), REWARD_AMOUNT * 3); assertEq( - aludel.calculateUnlockedRewards( - data.rewardSchedules, - REWARD_AMOUNT * 3, - data.rewardSharesOutstanding, - block.timestamp + SCHEDULE_DURATION - ), - REWARD_AMOUNT * 3 + aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), + 0 ); - assertEq(aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), 0); // 6. Admin funds 600 eth * 5 for 1 minute + // Admin funds 600 eth * 5 for 2 minute + // 1 minute elapses aludel.fund(REWARD_AMOUNT, SCHEDULE_DURATION); - - // 6. Admin funds 600 eth * 5 for 2 minute aludel.fund(REWARD_AMOUNT, SCHEDULE_DURATION * 2); - - // 6. 1 minute elapses vm.warp(block.timestamp + SCHEDULE_DURATION); data = aludel.getAludelData(); assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 5); - // previous four periods amounts and half of the fifth. + // previous four schedule amounts and half of the fifth. assertEq( - aludel.calculateUnlockedRewards( - data.rewardSchedules, - REWARD_AMOUNT * 5, - data.rewardSharesOutstanding, - block.timestamp - ), + aludel.calculateUnlockedRewards(block.timestamp), REWARD_AMOUNT * 4 + REWARD_AMOUNT / 2 ); // fifth schedule shares are half-locked @@ -292,24 +211,6 @@ contract AludelV3Test is Test { aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), REWARD_AMOUNT * BASE_SHARES_PER_WEI / 2 ); - - // 6. 1 minute elapses - vm.warp(block.timestamp + SCHEDULE_DURATION); - - data = aludel.getAludelData(); - assertEq(data.rewardSharesOutstanding, REWARD_AMOUNT * BASE_SHARES_PER_WEI * 5); - assertEq( - aludel.calculateUnlockedRewards( - data.rewardSchedules, - REWARD_AMOUNT * 5, - data.rewardSharesOutstanding, - block.timestamp - ), - REWARD_AMOUNT * 5 - ); - - // Fifth schedule shares are now fully unlocked - assertEq(aludel.calculateSharesLocked(data.rewardSchedules, block.timestamp), 0); } function test_stakes_no_amount_staked() public {