Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update StakingContractMainnet to support vault tokens #11

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
43 changes: 43 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: test

on:
workflow_dispatch:
pull_request:
push:
branches:
- "main"

env:
FOUNDRY_PROFILE: ci

jobs:
foundry:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: forge-test

- name: Forge style
run: |
forge fmt --check
49 changes: 49 additions & 0 deletions broadcast/StakingContract.s.sol/421614/run-1728531911.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions script/NftVaultManager.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.13;

import "forge-std/Script.sol";
import "../src/Vault/NftVaultManager.sol";

contract NftVaultManagerScript is Script {
function run() public {
vm.startBroadcast();

new NftVaultManager();

vm.stopBroadcast();
}
}
3 changes: 1 addition & 2 deletions script/StakingContract.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import "../src/Rewards/StakingContractMainnet.sol";

contract StakingContractScript is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
vm.startBroadcast();

new StakingContractMainnet();

Expand Down
1 change: 1 addition & 0 deletions sh/deployArbitrum.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ source .env

# To deploy and verify our contract
forge script script/MagicswapV2.s.sol:MagicswapV2Script --aws --rpc-url $ARBITRUM_RPC --broadcast --verify -vvvv
forge script script/StakingContract.s.sol:StakingContractScript --aws --rpc-url $ARBITRUM_RPC --broadcast --verify -vvvv
2 changes: 2 additions & 0 deletions sh/deployArbitrumSepolia.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ source .env

# To deploy and verify our contract
forge script script/MagicswapV2.s.sol:MagicswapV2Script --aws --rpc-url $ARBITRUM_SEPOLIA_RPC --broadcast --verify -vvvv
forge script script/StakingContract.s.sol:StakingContractScript --aws --rpc-url $ARBITRUM_SEPOLIA_RPC --broadcast --verify -vvvv
forge script script/NftVaultManager.s.sol:NftVaultManagerScript --aws --rpc-url $ARBITRUM_SEPOLIA_RPC --broadcast --verify -vvvv
63 changes: 56 additions & 7 deletions src/Rewards/StakingContractMainnet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract StakingContractMainnet is ReentrancyGuard {
address token; // 2nd slot
address rewardToken; // 3rd slot
uint32 endTime; // 3rd slot
bool isRewardRounded; // 3rd slot
uint256 rewardPerLiquidity; // 4th slot
uint32 lastRewardTime; // 5th slot
uint112 rewardRemaining; // 5th slot
Expand Down Expand Up @@ -74,11 +75,14 @@ contract StakingContractMainnet is ReentrancyGuard {
event Unsubscribe(uint256 indexed id, address indexed user);
event Claim(uint256 indexed id, address indexed user, uint256 amount);

function createIncentive(address token, address rewardToken, uint112 rewardAmount, uint32 startTime, uint32 endTime)
external
nonReentrant
returns (uint256 incentiveId)
{
function createIncentive(
address token,
address rewardToken,
uint112 rewardAmount,
uint32 startTime,
uint32 endTime,
bool isRewardRounded
) external nonReentrant returns (uint256 incentiveId) {
alecananian marked this conversation as resolved.
Show resolved Hide resolved
if (rewardAmount <= 0) revert InvalidInput();

if (startTime < block.timestamp) startTime = uint32(block.timestamp);
Expand All @@ -99,6 +103,7 @@ contract StakingContractMainnet is ReentrancyGuard {
rewardToken: rewardToken,
lastRewardTime: startTime,
endTime: endTime,
isRewardRounded: isRewardRounded,
rewardRemaining: rewardAmount,
liquidityStaked: 0,
// Initial value of rewardPerLiquidity can be arbitrarily set to a non-zero value.
Expand Down Expand Up @@ -229,6 +234,14 @@ contract StakingContractMainnet is ReentrancyGuard {
emit Unstake(token, msg.sender, amount);
}

function subscribeToIncentives(uint256[] memory incentiveIds) external {
uint256 n = incentiveIds.length;

for (uint256 i = 0; i < n; i = _increment(i)) {
subscribeToIncentive(incentiveIds[i]);
}
}

function subscribeToIncentive(uint256 incentiveId) public nonReentrant {
if (incentiveId > incentiveCount || incentiveId <= 0) revert InvalidInput();

Expand Down Expand Up @@ -302,6 +315,24 @@ contract StakingContractMainnet is ReentrancyGuard {
}
}

/// @dev Claims rewards for all incentives in the list, skipping reward rounding.
function claimAllRewards(uint256[] calldata incentiveIds)
external
nonReentrant
returns (uint256[] memory rewards)
{
uint256 n = incentiveIds.length;
rewards = new uint256[](n);
for (uint256 i = 0; i < n; i = _increment(i)) {
if (incentiveIds[i] > incentiveCount || incentiveIds[i] <= 0) revert InvalidInput();

Incentive storage incentive = incentives[incentiveIds[i]];
_accrueRewards(incentive);
rewards[i] =
_claimReward(incentive, incentiveIds[i], userStakes[msg.sender][incentive.token].liquidity, true);
}
}

function _accrueRewards(Incentive storage incentive) internal {
uint256 lastRewardTime = incentive.lastRewardTime;

Expand Down Expand Up @@ -332,13 +363,31 @@ contract StakingContractMainnet is ReentrancyGuard {
function _claimReward(Incentive storage incentive, uint256 incentiveId, uint112 usersLiquidity)
internal
returns (uint256 reward)
{
return _claimReward(incentive, incentiveId, usersLiquidity, false);
}

function _claimReward(Incentive storage incentive, uint256 incentiveId, uint112 usersLiquidity, bool skipRounding)
internal
returns (uint256 reward)
{
reward = _calculateReward(incentive, incentiveId, usersLiquidity);

rewardPerLiquidityLast[msg.sender][incentiveId] = incentive.rewardPerLiquidity;
uint256 rewardDelta;
// Check if the reward should be rounded
if (!skipRounding && incentive.isRewardRounded) {
uint8 decimals = ERC20(incentive.rewardToken).decimals();
uint256 roundedReward = reward / 10 ** decimals * 10 ** decimals;
// Delta of rewards to be left claimable for the user in the future
rewardDelta = reward - roundedReward;
reward = roundedReward;
}

ERC20(incentive.rewardToken).safeTransfer(msg.sender, reward);
// Calculate the reward per liquidity delta based on actual rewards given
uint256 rewardPerLiquidityDelta = rewardDelta * type(uint112).max / usersLiquidity;
rewardPerLiquidityLast[msg.sender][incentiveId] = incentive.rewardPerLiquidity - rewardPerLiquidityDelta;

ERC20(incentive.rewardToken).safeTransfer(msg.sender, reward);
emit Claim(incentiveId, msg.sender, reward);
}

Expand Down
43 changes: 41 additions & 2 deletions src/Rewards/test/StakingContractMainnet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import "./TestSetup.sol";

contract CreateIncentiveTest is TestSetup {
function testCreateIncentive(uint112 amount, uint32 startTime, uint32 endTime) public {
_createIncentive(address(tokenA), address(tokenB), amount, startTime, endTime);
_createIncentive(address(tokenA), address(tokenB), amount, startTime, endTime, false);
}

function testFailCreateIncentiveInvalidRewardToken(uint32 startTime, uint32 endTime) public {
_createIncentive(address(tokenA), zeroAddress, 1, startTime, endTime);
_createIncentive(address(tokenA), zeroAddress, 1, startTime, endTime, false);
}

function testUpdateIncentive(
Expand Down Expand Up @@ -147,6 +147,45 @@ contract CreateIncentiveTest is TestSetup {
assertEqInexact(reward0 + reward1 + soloReward, totalReward, 10);
}

function testClaimRoundedRewards() public {
uint112 amount = testIncentiveAmount;
uint256 duration = testIncentiveDuration;
uint256 incentiveId = _createIncentive(
address(tokenA), address(tokenB), amount, uint32(block.timestamp), uint32(block.timestamp + duration), true
);
uint256[] memory incentiveIds = new uint256[](1);
incentiveIds[0] = incentiveId;
StakingContractMainnet.Incentive memory incentive = _getIncentive(incentiveId);

// 2 users stake and subscribe
_stake(address(tokenA), 1, johnDoe, true);
_stake(address(tokenA), 1, janeDoe, true);
_subscribeToIncentive(incentiveId, johnDoe);
_subscribeToIncentive(incentiveId, janeDoe);

// 1/30 the time has passed
vm.warp(incentive.lastRewardTime + 86400);

// Each user got 1/60 of the total reward amount
(,, uint256 johnDoeReward) = _calculateReward(incentiveId, johnDoe);
(,, uint256 janeDoeReward) = _calculateReward(incentiveId, janeDoe);
assertEq(johnDoeReward, 16666666666666666666);
assertEq(janeDoeReward, 16666666666666666666);

// 1 user claims
vm.prank(johnDoe);
uint256[] memory johnDoeClaimed = stakingContract.claimRewards(incentiveIds);
assertEq(johnDoeClaimed[0], 16000000000000000000);

// User still has some rewards pending
(,, johnDoeReward) = _calculateReward(incentiveId, johnDoe);
assertEq(johnDoeReward, 666666666666666666);

// Other user still has the same reward
(,, janeDoeReward) = _calculateReward(incentiveId, janeDoe);
assertEq(janeDoeReward, 16666666666666666666);
}

function testUnstakeSaveRewards() public {
_stake(address(tokenA), 1, johnDoe, true);
_subscribeToIncentive(ongoingIncentive, johnDoe);
Expand Down
36 changes: 25 additions & 11 deletions src/Rewards/test/TestSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,19 @@ contract TestSetup is Test {

vm.warp(currentTime - duration);
pastIncentive = _createIncentive(
address(tokenA), address(tokenB), amount, uint32(block.timestamp), uint32(block.timestamp + duration)
address(tokenA), address(tokenB), amount, uint32(block.timestamp), uint32(block.timestamp + duration), false
);
vm.warp(currentTime);
ongoingIncentive = _createIncentive(
address(tokenA), address(tokenB), amount, uint32(block.timestamp), uint32(block.timestamp + duration)
address(tokenA), address(tokenB), amount, uint32(block.timestamp), uint32(block.timestamp + duration), false
);
futureIncentive = _createIncentive(
address(tokenA),
address(tokenB),
amount,
uint32(block.timestamp + duration),
uint32(block.timestamp + duration * 2)
uint32(block.timestamp + duration * 2),
false
);
}

Expand All @@ -83,25 +84,29 @@ contract TestSetup is Test {
assertTrue(true);
}

function _createIncentive(address token, address rewardToken, uint112 amount, uint32 startTime, uint32 endTime)
public
returns (uint256)
{
function _createIncentive(
address token,
address rewardToken,
uint112 amount,
uint32 startTime,
uint32 endTime,
bool isRewardRounded
) public returns (uint256) {
uint256 count = stakingContract.incentiveCount();
uint256 thisBalance = Token(rewardToken).balanceOf(address(this));
uint256 stakingContractBalance = Token(rewardToken).balanceOf(address(stakingContract));

if (amount <= 0) {
vm.expectRevert(invalidInput);
return stakingContract.createIncentive(token, rewardToken, amount, startTime, endTime);
return stakingContract.createIncentive(token, rewardToken, amount, startTime, endTime, isRewardRounded);
}

if (endTime <= startTime || endTime <= block.timestamp) {
vm.expectRevert(invalidTimeFrame);
return stakingContract.createIncentive(token, rewardToken, amount, startTime, endTime);
return stakingContract.createIncentive(token, rewardToken, amount, startTime, endTime, isRewardRounded);
}

uint256 id = stakingContract.createIncentive(token, rewardToken, amount, startTime, endTime);
uint256 id = stakingContract.createIncentive(token, rewardToken, amount, startTime, endTime, isRewardRounded);

StakingContractMainnet.Incentive memory incentive = _getIncentive(id);

Expand Down Expand Up @@ -426,13 +431,22 @@ contract TestSetup is Test {
address token,
address rewardToken,
uint32 endTime,
bool isRewardRounded,
uint256 rewardPerLiquidity,
uint32 lastRewardTime,
uint112 rewardRemaining,
uint112 liquidityStaked
) = stakingContract.incentives(id);
incentive = StakingContractMainnet.Incentive(
creator, token, rewardToken, endTime, rewardPerLiquidity, lastRewardTime, rewardRemaining, liquidityStaked
creator,
token,
rewardToken,
endTime,
isRewardRounded,
rewardPerLiquidity,
lastRewardTime,
rewardRemaining,
liquidityStaked
);
}

Expand Down
32 changes: 16 additions & 16 deletions src/UniswapV2/core/test/UniswapV2Pair.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ contract UniswapV2PairTest is Test {
}

function testSwapRegression(uint96 _reserve0, uint96 _reserve1, uint72 _amount0In, uint72 _amount1In) public {
vm.assume(_reserve0 > 10000e18);
vm.assume(_reserve1 > 10000e18);
vm.assume(_amount0In > 0.001e18);
vm.assume(_amount1In > 0.001e18);
_reserve0 = uint96(bound(_reserve0, 10000e18, type(uint96).max));
_reserve1 = uint96(bound(_reserve1, 10000e18, type(uint96).max));
_amount0In = uint72(bound(_amount0In, 0.001e18, type(uint72).max));
_amount1In = uint72(bound(_amount1In, 0.001e18, type(uint72).max));

_addLiquidity(address(pair), _reserve0, _reserve1, user3);
_addLiquidity(address(pairOriginal), _reserve0, _reserve1, user3);
Expand Down Expand Up @@ -172,10 +172,10 @@ contract UniswapV2PairTest is Test {
}

function testSkimRegression(uint96 _reserve0, uint96 _reserve1, uint72 _amount0In, uint72 _amount1In) public {
vm.assume(_reserve0 > 10000e18);
vm.assume(_reserve1 > 10000e18);
vm.assume(_amount0In > 0.001e18);
vm.assume(_amount1In > 0.001e18);
_reserve0 = uint96(bound(_reserve0, 10000e18, type(uint96).max));
_reserve1 = uint96(bound(_reserve1, 10000e18, type(uint96).max));
_amount0In = uint72(bound(_amount0In, 0.001e18, type(uint72).max));
_amount1In = uint72(bound(_amount1In, 0.001e18, type(uint72).max));

_addLiquidity(address(pair), _reserve0, _reserve1, user3);
_addLiquidity(address(pairOriginal), _reserve0, _reserve1, user3);
Expand Down Expand Up @@ -204,10 +204,10 @@ contract UniswapV2PairTest is Test {
}

function testSyncRegression(uint96 _reserve0, uint96 _reserve1, uint72 _amount0In, uint72 _amount1In) public {
vm.assume(_reserve0 > 10000e18);
vm.assume(_reserve1 > 10000e18);
vm.assume(_amount0In > 0.001e18);
vm.assume(_amount1In > 0.001e18);
_reserve0 = uint96(bound(_reserve0, 10000e18, type(uint96).max));
_reserve1 = uint96(bound(_reserve1, 10000e18, type(uint96).max));
_amount0In = uint72(bound(_amount0In, 0.001e18, type(uint72).max));
_amount1In = uint72(bound(_amount1In, 0.001e18, type(uint72).max));

_addLiquidity(address(pair), _reserve0, _reserve1, user3);
_addLiquidity(address(pairOriginal), _reserve0, _reserve1, user3);
Expand All @@ -234,10 +234,10 @@ contract UniswapV2PairTest is Test {
uint72 _amount1In,
uint256 _hijackAmount
) public {
vm.assume(_reserve0 > 10000e18);
vm.assume(_reserve1 > 10000e18);
vm.assume(_amount0In > 0.001e18);
vm.assume(_amount1In > 0.001e18);
_reserve0 = uint96(bound(_reserve0, 10000e18, type(uint96).max));
_reserve1 = uint96(bound(_reserve1, 10000e18, type(uint96).max));
_amount0In = uint72(bound(_amount0In, 0.001e18, type(uint72).max));
_amount1In = uint72(bound(_amount1In, 0.001e18, type(uint72).max));
vm.assume(_amount0In > _hijackAmount);

_addLiquidity(address(pairWithFees), _reserve0, _reserve1, user3);
Expand Down
Loading
Loading