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

Zealy Quest #36

Open
wants to merge 1 commit 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
24 changes: 24 additions & 0 deletions Bootcampers/Jishantu_Kripal_Bordoloi/Session 2/ERC20Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
import "@openzeppelin/[email protected]/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/[email protected]/access/Ownable.sol";
import "@openzeppelin/[email protected]/token/ERC20/extensions/ERC20Permit.sol";

contract BRBBootcampToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("BRB Bootcamp Token", "BRBBT")
Ownable(initialOwner)
ERC20Permit("BRB Bootcamp Token")
{
_mint(msg.sender, 7777 * 10 ** decimals());
}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}


221 changes: 221 additions & 0 deletions Bootcampers/Jishantu_Kripal_Bordoloi/Session 3/Foundrytest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Actors
address owner;
address user;
address uninitializedUser;

// Constants
uint256 constant rewardAmount = 100 * 10**18;

function setUp() public {
owner = address(0x111);
user = address(0x222);
uninitializedUser = address(0x333);

vm.startPrank(owner);

token = new BRBToken(owner);
stakingContract = new BRBStaking(token);

token.transfer(user, 1000 ether);
vm.stopPrank();
}

// User should be able to initialize their staking profile - JUST ONCE
function testInitializeUserTwice() public {
vm.startPrank(user);
stakingContract.initializeUser();

vm.expectRevert("User already initialized");
stakingContract.initializeUser();

vm.stopPrank();
}

// TEST initializeUser() function
function testInitializeUser() public {
// Prank as user
vm.prank(user);
stakingContract.initializeUser();

// Verify user initialization
(address userAddress, , bool initialized, ,) = stakingContract.userStakeData(user, 0);
assertEq(userAddress, user);
assertTrue(initialized);
}

// Test stake() function
function testStake() public {
uint256 stakeAmount = 100 * 10**18;

// User to approve and stake
vm.startPrank(user);
token.approve(address(stakingContract), stakeAmount);
stakingContract.initializeUser();
stakingContract.stake(stakeAmount);

vm.stopPrank();

// Verify staking
(address userAddress, uint256 stakeAmountStored, bool initialized, ,uint256 stakeID) = stakingContract.userStakeData(user, 1);
assertEq(userAddress, user);
assertEq(stakeAmountStored, stakeAmount);
assertEq(stakeID, 1);
assertTrue(initialized);
}

// TEST unstake() function
function testUnstakeFunction() public {
uint256 stakeAmount = 100 * 10**18;

// Prank as user to approve and stake
vm.startPrank(user);
token.approve(address(stakingContract), stakeAmount);
stakingContract.initializeUser();
stakingContract.stake(stakeAmount);
vm.stopPrank();

// Owner Adds Reward
vm.startPrank(owner);
token.approve(address(stakingContract), rewardAmount);
stakingContract.addReward(rewardAmount);
vm.stopPrank();

// Fast forward time by 7 days
vm.warp(block.timestamp + 7 days);

// Check user balance before unstake
uint256 userBalanceBefore = token.balanceOf(user);

vm.startPrank(user);
stakingContract.unstake(1);
vm.stopPrank();

// Check user balance after unstake
uint256 userBalanceAfter = token.balanceOf(user);
assertEq(userBalanceAfter, userBalanceBefore + stakeAmount + rewardAmount);
}

// Test that only the owner can call addReward()
function testAddRewardOwnership() public {
uint256 amountToAdd = 200 * 10**18;

// Attempt to call addReward by a non-owner should revert
vm.startPrank(user);
vm.expectRevert("Ownable: caller is not the owner");
stakingContract.addReward(amountToAdd);
vm.stopPrank();

// Owner should be able to call addReward
vm.startPrank(owner);
token.approve(address(stakingContract), amountToAdd);
stakingContract.addReward(amountToAdd);
vm.stopPrank();
}

// Test that uninitialized user cannot stake or unstake
function testUninitializedUserRestrictions() public {
uint256 stakeAmount = 50 * 10**18;

vm.startPrank(uninitializedUser);

// Approve tokens for staking
token.approve(address(stakingContract), stakeAmount);

// Try to stake without initializing
vm.expectRevert("User not initialized");
stakingContract.stake(stakeAmount);

// Try to unstake without initializing or staking
vm.expectRevert("User not initialized");
stakingContract.unstake(1);

vm.stopPrank();
}

// Test that reward is exactly 100 tokens for all stakers
function testRewardDistribution() public {
uint256 stakeAmount = 100 * 10**18;

vm.startPrank(user);
token.approve(address(stakingContract), stakeAmount);
stakingContract.initializeUser();
stakingContract.stake(stakeAmount);
vm.stopPrank();

// Owner Adds Reward
vm.startPrank(owner);
token.approve(address(stakingContract), rewardAmount);
stakingContract.addReward(rewardAmount);
vm.stopPrank();

// Fast forward time by 7 days
vm.warp(block.timestamp + 7 days);

vm.startPrank(user);
stakingContract.unstake(1);
vm.stopPrank();

// Check final balance should be the initial balance + stake + reward
uint256 finalBalance = token.balanceOf(user);
assertEq(finalBalance, 1000 ether + rewardAmount); // Initial balance is 1000 ether
}

// Test that unstaking before 7 days should revert
function testUnstakeBefore7Days() public {
uint256 stakeAmount = 100 * 10**18;

// Prank as user to approve and stake
vm.startPrank(user);
token.approve(address(stakingContract), stakeAmount);
stakingContract.initializeUser();
stakingContract.stake(stakeAmount);
vm.stopPrank();

// Try to unstake before 7 days
vm.startPrank(user);
vm.expectRevert("Staking period not yet completed");
stakingContract.unstake(1);
vm.stopPrank();
}

// Test event emissions
function testEventEmissions() public {
uint256 stakeAmount = 100 * 10**18;

vm.startPrank(user);
token.approve(address(stakingContract), stakeAmount);
stakingContract.initializeUser();

// Expect Stake event
vm.expectEmit(true, true, true, true);
emit Stake(user, stakeAmount, 1);
stakingContract.stake(stakeAmount);

vm.stopPrank();

// Owner Adds Reward
vm.startPrank(owner);
token.approve(address(stakingContract), rewardAmount);

// Expect RewardAdded event
vm.expectEmit(true, true, true, true);
emit RewardAdded(owner, rewardAmount);
stakingContract.addReward(rewardAmount);
vm.stopPrank();

// Fast forward time by 7 days
vm.warp(block.timestamp + 7 days);

vm.startPrank(user);

// Expect Unstake event
vm.expectEmit(true, true, true, true);
emit Unstake(user, 1);
stakingContract.unstake(1);
vm.stopPrank();
}

// Add the missing event definitions
event Stake(address indexed user, uint256 amount, uint256 indexed stakeID);
event Unstake(address indexed user, uint256 indexed stakeID);
event RewardAdded(address indexed owner, uint256 amount);
122 changes: 122 additions & 0 deletions Bootcampers/Jishantu_Kripal_Bordoloi/Session 3/StakingAudit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
Final Corrected 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);
}
}


// Issues are discussed below:

1. Data Type for Stack ID and User Stake Count

Issue: In the User struct, stack id is initialized as uint8, and userStakeCount is mapped with uint8. This limits the number of stakes.
Improvement: Change the data type to uint256 to avoid limitations on the number of stakes.

2. Constructor Initialization

Issue: The constructor includes Ownable(msg.sender), which is unnecessary as the constructor does not take parameters.
Improvement: Remove Ownable(msg.sender) from the constructor.


3. Redundant Initialization in initializeUser Function

Issue: The initializeUser function initializes userStakeData[msg.sender][0] with a zero User struct, which is redundant.
Improvement: Remove the redundant initialization to streamline the function.
Proposed Code Adjustments

4. Immutability of Lockup Period and Reward Amount

Issue: Lockup period and Reward Amount are not set as immutable despite being constants.
Improvement: Use the immutable keyword for these variables to ensure they remain constant after deployment.


5. Reward Pool Logic

Issue: The current logic for adding rewards to the stake amount is unclear and could lead to confusion or manipulation.
Improvement: Clarify the reward distribution logic to ensure it is straightforward and secure.