Skip to content

Commit

Permalink
feat: refactor for staking token to be generic (#43)
Browse files Browse the repository at this point in the history
* feat: refactor for staking token to be generic

* adds all miissing files

* clean up

* more cleanup

* feat: wagmi config, regen, and renamg imports in rewards-distribution

---------

Co-authored-by: gomes <[email protected]>
  • Loading branch information
0xean and gomesalexandre authored May 17, 2024
1 parent ddc25b9 commit c8d12d2
Show file tree
Hide file tree
Showing 24 changed files with 392 additions and 324 deletions.
2 changes: 1 addition & 1 deletion foundry/.env.arbitrum-one.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ETHERSCAN_API_KEY="" # this must be an arbiscan api key
FOX_TOKEN_ADDRESS=""
STAKING_TOKEN_ADDRESS=""
PRIVATE_KEY=""
RPC_URL="https://arbitrum-mainnet.infura.io/v3/<INFURA_API_KEY>"
VERIFIER_URL="https://api.arbiscan.io/api"
2 changes: 1 addition & 1 deletion foundry/.env.arbitrum-sepolia.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ETHERSCAN_API_KEY="" # this must be an arbiscan api key
FOX_TOKEN_ADDRESS=""
STAKING_TOKEN_ADDRESS=""
PRIVATE_KEY=""
RPC_URL="https://arbitrum-sepolia.infura.io/v3/<INFURA_API_KEY>"
VERIFIER_URL="https://api-sepolia.arbiscan.io/api"
2 changes: 1 addition & 1 deletion foundry/.env.local.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ETHERSCAN_API_KEY="not-required"
FOX_TOKEN_ADDRESS="0xc770EEfAd204B5180dF6a14Ee197D99d808ee52d"
STAKING_TOKEN_ADDRESS="0xc770EEfAd204B5180dF6a14Ee197D99d808ee52d"
PRIVATE_KEY=""
RPC_URL="http://localhost:8545"
VERIFIER_URL="not-required"
2 changes: 1 addition & 1 deletion foundry/.env.mainnet.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ETHERSCAN_API_KEY=""
FOX_TOKEN_ADDRESS="0xc770EEfAd204B5180dF6a14Ee197D99d808ee52d"
STAKING_TOKEN_ADDRESS="0xc770EEfAd204B5180dF6a14Ee197D99d808ee52d"
PRIVATE_KEY=""
RPC_URL="https://mainnet.infura.io/v3/<INFURA_API_KEY>"
VERIFIER_URL="https://api.etherscan.io/api"
2 changes: 1 addition & 1 deletion foundry/.env.sepolia.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ETHERSCAN_API_KEY=""
FOX_TOKEN_ADDRESS=""
STAKING_TOKEN_ADDRESS=""
PRIVATE_KEY=""
RPC_URL="https://sepolia.infura.io/v3/<INFURA_API_KEY>"
VERIFIER_URL="https://api-sepolia.etherscan.io/api"
16 changes: 9 additions & 7 deletions foundry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
1. Install foundry https://book.getfoundry.sh/getting-started/installation
2. Install slither `brew install slither-analyzer`
3. Set up .env files:
1. Copy an example .env file for your chosen environment:
```shell
cp .env.<your-chosen-env>.example .env.<your-chosen-env>
```
2. Fill in the needed env vars there
1. Copy an example .env file for your chosen environment:
```shell
cp .env.<your-chosen-env>.example .env.<your-chosen-env>
```
2. Fill in the needed env vars there

### Private key

Expand All @@ -32,7 +32,9 @@ https://faucet.circle.com/
## Deploying

### Local deployment

Deploying locally is different to deploying directly to a network for 2 reasons:

1. Its being deployed to a fork of another network (typically ethereum mainnet) rather than a real network.
2. There's no local instance of etherscan, so etherscan verification is skipped.
Expand All @@ -46,7 +48,7 @@ anvil --rpc-url $RPC_URL
### Deployment steps

```shell
cd foundry
cd foundry
# Install
forge install
Expand All @@ -70,5 +72,5 @@ forge verify-contract \
--compiler-version "v0.8.25" \
--etherscan-api-key $ARBISCAN_API_KEY \
$CONTRACT_IMPLEMENTATION_ADDRESS \
src/FoxStakingV1.sol:FoxStakingV1
src/StakingV1.sol:StakingV1
```
4 changes: 2 additions & 2 deletions foundry/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ forge clean

if [ "$ENVIRONMENT" = "local" ]; then
# Local environment-specific commands (no etherscan verification)
FOX_TOKEN_ADDRESS="$FOX_TOKEN_ADDRESS" forge script script/DeployFoxStaking.s.sol:DeployFoxStaking \
STAKING_TOKEN_ADDRESS="$STAKING_TOKEN_ADDRESS" forge script script/DeployStaking.s.sol:DeployStaking \
--fork-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
-vvvvv
else
# All other environments use etherscan verification (which automatically reroutes to arbiscan as needed)
FOX_TOKEN_ADDRESS="$FOX_TOKEN_ADDRESS" forge script script/DeployFoxStaking.s.sol:DeployFoxStaking \
STAKING_TOKEN_ADDRESS="$STAKING_TOKEN_ADDRESS" forge script script/DeployStaking.s.sol:DeployStaking \
--rpc-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
Expand Down
25 changes: 0 additions & 25 deletions foundry/script/DeployFoxStaking.s.sol

This file was deleted.

25 changes: 25 additions & 0 deletions foundry/script/DeployStaking.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import "forge-std/Script.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {StakingV1} from "../src/StakingV1.sol";

contract DeployStaking is Script {
address stakingTokenAddress;

function setUp() public {
stakingTokenAddress = vm.envAddress("STAKING_TOKEN_ADDRESS");
}

function run() public {
vm.startBroadcast();
address stakingProxy = Upgrades.deployUUPSProxy(
"StakingV1.sol",
abi.encodeCall(StakingV1.initialize, (stakingTokenAddress))
);
vm.stopBroadcast();
console.log("Contract deployed at:", stakingProxy);
}
}
35 changes: 19 additions & 16 deletions foundry/src/FoxStakingV1.sol → foundry/src/StakingV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/ut
import {StakingInfo} from "./StakingInfo.sol";
import {UnstakingRequest} from "./UnstakingRequest.sol";

contract FoxStakingV1 is
contract StakingV1 is
Initializable,
PausableUpgradeable,
UUPSUpgradeable,
OwnableUpgradeable,
ReentrancyGuardUpgradeable
{
using SafeERC20 for IERC20;
IERC20 public foxToken;
IERC20 public stakingToken;
mapping(address => StakingInfo) public stakingInfo;
bool public stakingPaused;
bool public withdrawalsPaused;
Expand Down Expand Up @@ -57,11 +57,11 @@ contract FoxStakingV1 is
_disableInitializers();
}

function initialize(address foxTokenAddress) external initializer {
function initialize(address stakingTokenAddress) external initializer {
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
__Pausable_init();
foxToken = IERC20(foxTokenAddress);
stakingToken = IERC20(stakingTokenAddress);
cooldownPeriod = 28 days;
lastUpdateTimestamp = block.timestamp;
}
Expand Down Expand Up @@ -158,10 +158,10 @@ contract FoxStakingV1 is
info.earnedRewards;
}

/// @notice Allows a user to stake a specified amount of FOX tokens and assign a RUNE address for rewards - which can be changed later on.
/// @notice Allows a user to stake a specified amount of staking tokens and assign a RUNE address for rewards - which can be changed later on.
/// This has to be initiated by the user itself i.e msg.sender only, cannot be called by an address for another
/// @param amount The amount of FOX tokens to be staked.
/// @param runeAddress The RUNE address to be associated with the user's staked FOX position.
/// @param amount The amount of staking tokens to be staked.
/// @param runeAddress The RUNE address to be associated with the user's staked staking position.
function stake(
uint256 amount,
string memory runeAddress
Expand All @@ -170,9 +170,9 @@ contract FoxStakingV1 is
bytes(runeAddress).length == 43,
"Rune address must be 43 characters"
);
require(amount > 0, "FOX amount to stake must be greater than 0");
require(amount > 0, "amount to stake must be greater than 0");
updateReward(msg.sender);
foxToken.safeTransferFrom(msg.sender, address(this), amount);
stakingToken.safeTransferFrom(msg.sender, address(this), amount);

StakingInfo storage info = stakingInfo[msg.sender];
info.stakingBalance += amount;
Expand All @@ -182,9 +182,9 @@ contract FoxStakingV1 is
emit Stake(msg.sender, amount, runeAddress);
}

/// @notice Initiates the unstake process for a specified amount of FOX, starting the cooldown period (28 days).
/// @notice Initiates the unstake process for a specified amount of staking token, starting the cooldown period (28 days).
/// This has to be initiated by the user itself i.e msg.sender only, cannot be called by an address for another
/// @param amount The amount of FOX tokens to be unstaked.
/// @param amount The amount of staking tokens to be unstaked.
function unstake(
uint256 amount
) external whenNotPaused whenUnstakingNotPaused nonReentrant {
Expand Down Expand Up @@ -249,7 +249,10 @@ contract FoxStakingV1 is
}
info.unstakingBalance -= unstakingRequest.unstakingBalance;
totalCoolingDown -= unstakingRequest.unstakingBalance;
foxToken.safeTransfer(msg.sender, unstakingRequest.unstakingBalance);
stakingToken.safeTransfer(
msg.sender,
unstakingRequest.unstakingBalance
);
emit Withdraw(msg.sender, unstakingRequest.unstakingBalance);
}

Expand Down Expand Up @@ -284,7 +287,7 @@ contract FoxStakingV1 is

/// @notice Allows a user to initially set (or update) their THORChain (RUNE) address for receiving staking rewards.
/// This has to be initiated by the user itself i.e msg.sender only, cannot be called by an address for another
/// @param runeAddress The new RUNE address to be associated with the user's staked FOX position.
/// @param runeAddress The new RUNE address to be associated with the user's staked position.
function setRuneAddress(string memory runeAddress) external {
require(
bytes(runeAddress).length == 43,
Expand All @@ -296,10 +299,10 @@ contract FoxStakingV1 is
emit SetRuneAddress(msg.sender, oldRuneAddress, runeAddress);
}

/// @notice View the staked balance of FOX tokens for a given address.
/// @notice View the staked balance of tokens for a given address.
/// This can be initiated by any address with any address as param, as this has view modifier i.e everything is public on-chain
/// @param account The address we're getting the staked FOX balance for.
/// @return total The total amount of FOX tokens held.
/// @param account The address we're getting the staked staking balance for.
/// @return total The total amount of staking tokens held.
function balanceOf(address account) external view returns (uint256 total) {
StakingInfo memory info = stakingInfo[account];
return info.stakingBalance + info.unstakingBalance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ pragma solidity ^0.8.25;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {FoxStakingV1} from "../src/FoxStakingV1.sol";
import {StakingV1} from "../src/StakingV1.sol";
import {StakingInfo} from "../src/StakingInfo.sol";
import {MockFOXToken} from "./utils/MockFOXToken.sol";
import {FoxStakingTestDeployer} from "./utils/FoxStakingTestDeployer.sol";
import {StakingTestDeployer} from "./utils/StakingTestDeployer.sol";

contract FOXStakingTestStaking is Test {
FoxStakingTestDeployer public deployer;
FoxStakingV1 public foxStaking;
StakingTestDeployer public deployer;
StakingV1 public foxStaking;
MockFOXToken public foxToken;
address userOne = address(0xBEEF);
address userTwo = address(0xDEAD);
Expand All @@ -27,12 +27,12 @@ contract FOXStakingTestStaking is Test {

function setUp() public {
foxToken = new MockFOXToken();
deployer = new FoxStakingTestDeployer();
deployer = new StakingTestDeployer();
address proxyAddress = deployer.deployV1(
address(this),
address(foxToken)
);
foxStaking = FoxStakingV1(proxyAddress);
foxStaking = StakingV1(proxyAddress);

// Free FOX tokens for users
foxToken.makeItRain(userOne, amount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ pragma solidity ^0.8.25;

import "forge-std/Test.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {FoxStakingV1} from "../src/FoxStakingV1.sol";
import {StakingV1} from "../src/StakingV1.sol";
import {MockFOXToken} from "./utils/MockFOXToken.sol";
import {FoxStakingTestDeployer} from "./utils/FoxStakingTestDeployer.sol";
import {StakingTestDeployer} from "./utils/StakingTestDeployer.sol";

contract FOXStakingTestOwnership is Test {
FoxStakingTestDeployer public deployer;
FoxStakingV1 public foxStaking;
contract StakingTestOwnership is Test {
StakingTestDeployer public deployer;
StakingV1 public foxStaking;
MockFOXToken public foxToken;
address nonOwner = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;

function setUp() public {
foxToken = new MockFOXToken();
deployer = new FoxStakingTestDeployer();
deployer = new StakingTestDeployer();
address proxyAddress = deployer.deployV1(
address(this),
address(foxToken)
);
foxStaking = FoxStakingV1(proxyAddress);
foxStaking = StakingV1(proxyAddress);
}

function testOwnerCanUpdateCooldownPeriod() public {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
pragma solidity ^0.8.25;

import "forge-std/Test.sol";
import {FoxStakingV1} from "../src/FoxStakingV1.sol";
import {StakingV1} from "../src/StakingV1.sol";
import {MockFOXToken} from "./utils/MockFOXToken.sol";
import {FoxStakingTestDeployer} from "./utils/FoxStakingTestDeployer.sol";
import {StakingTestDeployer} from "./utils/StakingTestDeployer.sol";

contract FOXStakingTestRuneAddress is Test {
FoxStakingTestDeployer public deployer;
FoxStakingV1 public foxStaking;
StakingTestDeployer public deployer;
StakingV1 public foxStaking;
MockFOXToken public foxToken;
address user = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045);

function setUp() public {
foxToken = new MockFOXToken();
deployer = new FoxStakingTestDeployer();
deployer = new StakingTestDeployer();
address proxyAddress = deployer.deployV1(
address(this),
address(foxToken)
);
foxStaking = FoxStakingV1(proxyAddress);
foxStaking = StakingV1(proxyAddress);
}

function testCanSetRuneAddress() public {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ pragma solidity ^0.8.25;

import "forge-std/Test.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {FoxStakingV1} from "../src/FoxStakingV1.sol";
import {StakingV1} from "../src/StakingV1.sol";
import {StakingInfo} from "../src/StakingInfo.sol";
import {MockFOXToken} from "./utils/MockFOXToken.sol";
import {FoxStakingTestDeployer} from "./utils/FoxStakingTestDeployer.sol";
import {StakingTestDeployer} from "./utils/StakingTestDeployer.sol";

contract FOXStakingTestStaking is Test {
FoxStakingTestDeployer public deployer;
FoxStakingV1 public foxStaking;
StakingTestDeployer public deployer;
StakingV1 public foxStaking;
MockFOXToken public foxToken;

function setUp() public {
foxToken = new MockFOXToken();
deployer = new FoxStakingTestDeployer();
deployer = new StakingTestDeployer();
address proxyAddress = deployer.deployV1(
address(this),
address(foxToken)
);
foxStaking = FoxStakingV1(proxyAddress);
foxStaking = StakingV1(proxyAddress);
}

function testCannotStakeWhenStakingPaused() public {
Expand Down Expand Up @@ -154,7 +154,7 @@ contract FOXStakingTestStaking is Test {
vm.assertEq(unstakingBalance, 0);

// Try to stake 0
vm.expectRevert("FOX amount to stake must be greater than 0");
vm.expectRevert("amount to stake must be greater than 0");
foxStaking.stake(0, runeAddress);

// Check user staking balances are unchanged
Expand Down
Loading

0 comments on commit c8d12d2

Please sign in to comment.