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

Test cases for Staking Contract #35

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions Bootcampers/Manoj/Session03/StakingContractAudit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
1. The Lockup period and Reward Amount should be immutable, as their values are constant even after the contract is deployed.
Currently, they are not set as immutable.

2. In the User struct, the stack id is initialized with uint8, which should be uint256 to avoid limiting the number of stakes.
The same issue is present in the code with { mapping(address => uint8) public userStakeCount; }.

3. The constructor constructor(IERC20 _token) incorrectly uses Ownable(msg.sender),
as there is no need for Ownable to take any parameters in the constructor.

4. The logic
if (rewardPool >= REWARD_AMOUNT) {
user.stakeAmount += REWARD_AMOUNT;
rewardPool -= REWARD_AMOUNT;
}
needs clarification. Rewards are added to the stake amount only if the reward pool is sufficient,
which might cause confusion and potential fund manipulation.

5. In the initializeUser function, setting userStakeData[msg.sender][0] to a zero User struct is redundant.

Final Code :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
* @title StakingContract
* @dev A contract that allows users to stake ERC20 tokens, earn rewards, and unstake after a lockup period.
*/
contract BRBStaking is Ownable {
IERC20 public token;
uint256 public totalStaked;
uint256 public rewardPool;
uint256 public immutable LOCKUP_PERIOD;
uint256 public immutable REWARD_AMOUNT;

/**
* @dev Struct to represent a user's staking information.
*/
struct User {
address userAddress;
uint256 stakeAmount;
bool initialized;
uint256 timeStamp;
uint256 stakeID;
}

mapping(address => mapping(uint256 => User)) public userStakeData;
mapping(address => uint256) public userStakeCount;

event UserInitialized(address indexed user);
event TokensStaked(address indexed user, uint256 amount, uint256 stakeID);
event TokensUnstaked(address indexed user, uint256 amount, uint256 stakeID);
event RewardsAdded(uint256 amount);

constructor(IERC20 _token, uint256 _lockupPeriod, uint256 _rewardAmount) {
token = _token;
LOCKUP_PERIOD = _lockupPeriod;
REWARD_AMOUNT = _rewardAmount;
}

function initializeUser() external {
require(!userStakeData[msg.sender][0].initialized, "User already initialized");
User memory user = User(msg.sender, 0, true, 0, 0);
userStakeData[msg.sender][0] = user;
emit UserInitialized(msg.sender);
}

function stake(uint256 _amount) external {
require(userStakeData[msg.sender][0].initialized, "User not initialized");
require(token.transferFrom(msg.sender, address(this), _amount), "Token transfer failed");

uint256 stakeID = userStakeCount[msg.sender];
User memory user = User(msg.sender, _amount, true, block.timestamp, stakeID);
userStakeData[msg.sender][stakeID] = user;

userStakeCount[msg.sender]++;
totalStaked += _amount;

emit TokensStaked(msg.sender, _amount, stakeID);
}

function unstake(uint256 _stakeID) external {
User storage user = userStakeData[msg.sender][_stakeID];
require(user.initialized, "Stake not found");
require(block.timestamp >= user.timeStamp + LOCKUP_PERIOD, "Lockup period not completed");

uint256 amountToTransfer = user.stakeAmount;
if (rewardPool >= REWARD_AMOUNT) {
amountToTransfer += REWARD_AMOUNT;
rewardPool -= REWARD_AMOUNT;
}

totalStaked -= user.stakeAmount;
delete userStakeData[msg.sender][_stakeID];

require(token.transfer(msg.sender, amountToTransfer), "Token transfer failed");

emit TokensUnstaked(msg.sender, user.stakeAmount, _stakeID);
}

function addReward(uint256 _amount) external onlyOwner {
require(token.transferFrom(msg.sender, address(this), _amount), "Token transfer failed");
rewardPool += _amount;

emit RewardsAdded(_amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: test

on: workflow_dispatch

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
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: test
14 changes: 14 additions & 0 deletions Bootcampers/Manoj/Session03/brbStakingContract/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
6 changes: 6 additions & 0 deletions Bootcampers/Manoj/Session03/brbStakingContract/.gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"solidity.compileUsingRemoteVersion": "v0.8.26+commit.8a97fa7a"
}
77 changes: 77 additions & 0 deletions Bootcampers/Manoj/Session03/brbStakingContract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## BRB Staking with Foundry

### Details of Staking Contract:
* Initialization Requirement: “Users must be able to initialize their staking profile.”
* Staking Tokens: “Users should be able to stake ERC20 tokens in the contract.”
* Lockup Period: “A 7-day lockup period must be enforced before users can unstake their tokens.”
* Unstaking Tokens: “Users should be able to unstake their tokens after the 7-day lockup period.”
* Fixed Reward: “Upon successful unstaking after the lockup period, users should receive a fixed reward of 100 tokens.”
* Reward Addition by Admin: “The admin should be able to add tokens to the reward pool.”



### Details on Foundry Framework
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
6 changes: 6 additions & 0 deletions Bootcampers/Manoj/Session03/brbStakingContract/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Bootcampers/Manoj/Session03/brbStakingContract/remapping.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@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/
openzeppelin-contracts/=lib/openzeppelin-contracts/
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

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

contract CounterScript is Script {
function setUp() public {}

function run() public {
vm.broadcast();
}
}
Loading