Skip to content

Commit

Permalink
feat: staking
Browse files Browse the repository at this point in the history
  • Loading branch information
the-emerald committed Jan 3, 2025
1 parent 8270aa1 commit 22f0acd
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 57 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"discord.enabled": false
}
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
src = "src"
out = "out"
libs = ["lib"]
gas_reports = ["WanderStaking"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[fuzz]
runs = 8192
max_test_rejects = 16384
6 changes: 6 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

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

// import {Script, console} from "forge-std/Script.sol";
// import {WanderStaking} from "../src/WanderStaking.sol";

// contract CounterScript is Script {
// WanderStaking public counter;

// function setUp() public {}

// function run() public {
// vm.startBroadcast();

// counter = new WanderStaking();

// vm.stopBroadcast();
// }
// }
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

15 changes: 15 additions & 0 deletions src/TestToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestToken is ERC20 {
constructor()
ERC20("Test Token", "TEST")
{}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
73 changes: 73 additions & 0 deletions src/WanderStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";

contract WanderStaking is Ownable, Pausable {
using SafeERC20 for IERC20;
IERC20 public immutable token;

event Stake(address indexed user, uint256 amount);
event Unstake(address indexed user, uint256 amount);

error ZeroAmount();
error InsufficientBalance();

mapping(address => uint256) userStake;
uint256 internal totalStaked;

constructor(address initialOwner, IERC20 _token)
Ownable(initialOwner)
{
token = _token;
}

function pause() public onlyOwner {
_pause();
}

function unpause() public onlyOwner() {
_unpause();
}

function stake(uint256 amount) external whenNotPaused {
if (amount == 0) {
revert ZeroAmount();
}

userStake[msg.sender] += amount;
totalStaked += amount;

emit Stake(msg.sender, amount);

token.safeTransferFrom(msg.sender, address(this), amount);
}

function unstake(uint256 amount) external whenNotPaused {
if (amount == 0) {
revert ZeroAmount();
}

if (userStake[msg.sender] < amount) {
revert InsufficientBalance();
}

userStake[msg.sender] -= amount;
totalStaked -= amount;

emit Unstake(msg.sender, amount);

token.safeTransfer(msg.sender, amount);
}

function getTotalStaked() external view returns (uint256) {
return totalStaked;
}

function getAmountStaked(address user) external view returns (uint256) {
return userStake[user];
}
}
24 changes: 0 additions & 24 deletions test/Counter.t.sol

This file was deleted.

85 changes: 85 additions & 0 deletions test/WanderStaking.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {WanderStaking} from "../src/WanderStaking.sol";
import {TestToken} from "../src/TestToken.sol";

import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract WanderStakingTest is Test {
WanderStaking public staking;
TestToken public token;

function setUp() public {
token = new TestToken();

staking = new WanderStaking(address(this), IERC20(token));
token.approve(address(staking), ~uint256(0));
}

function test_stake(uint64 _amount) public {
vm.assume(_amount > 0);

uint256 amount = uint256(_amount) * (10 ** 18);
token.mint(address(this), amount);

staking.stake(amount);

assert(staking.getTotalStaked() == amount);
}

function test_stake_insufficient_balance(uint64 _amount) public {
vm.assume(_amount > 0);
uint256 amount = uint256(_amount) * (10 ** 18);

token.mint(address(this), amount);

vm.expectRevert();
staking.stake(amount * 2);
}

function test_unstake(uint64 _amount, uint64 _unstakeAmount) public {
vm.assume(_amount > 0);
vm.assume(_unstakeAmount > 0);
vm.assume(_amount >= _unstakeAmount);
uint256 amount = uint256(_amount) * (10 ** 18);
uint256 unstakeAmount = uint256(_unstakeAmount) * (10 ** 18);

token.mint(address(this), amount);

staking.stake(amount);
staking.unstake(unstakeAmount);

assert(staking.getTotalStaked() == (amount - unstakeAmount));
}

function test_unstake_over(uint64 _amount, uint64 _unstakeAmount) public {
vm.assume(_amount > 0);
vm.assume(_unstakeAmount > 0);
vm.assume(_amount < _unstakeAmount);
uint256 amount = uint256(_amount) * (10 ** 18);
uint256 unstakeAmount = uint256(_unstakeAmount) * (10 ** 18);

// Pad staking contract with some tokens first
vm.startPrank(msg.sender);
token.mint(msg.sender, amount * 2);
token.approve(address(staking), ~uint256(0));
staking.stake(amount * 2);
vm.stopPrank();

token.mint(address(this), amount);
staking.stake(amount);

vm.expectRevert(WanderStaking.InsufficientBalance.selector);
staking.unstake(unstakeAmount);
}

function test_unstake_new(uint64 _unstakeAmount) public {
vm.assume(_unstakeAmount > 0);
uint256 unstakeAmount = uint256(_unstakeAmount) * (10 ** 18);

vm.expectRevert();
staking.unstake(unstakeAmount);
}
}

0 comments on commit 22f0acd

Please sign in to comment.