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

Token Staking - unstaking #5

Closed
wants to merge 2 commits into from
Closed
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
30 changes: 28 additions & 2 deletions core/contracts/staking/TokenStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ import "../shared/IReceiveApproval.sol";
contract TokenStaking is IReceiveApproval {
using SafeERC20 for IERC20;

struct Staker {
uint256 balance;
uint256 startStakingTimestamp;
}

IERC20 internal immutable token;

mapping(address => uint256) public balanceOf;
mapping(address => Staker) public stakers;

event Staked(address indexed staker, uint256 amount);
event Unstaked(address indexed staker, uint256 amount);

constructor(IERC20 _token) {
require(
Expand Down Expand Up @@ -51,6 +57,23 @@ contract TokenStaking is IReceiveApproval {
_stake(msg.sender, amount);
}

/// @notice Reduces stake amount by the provided amount and
/// withdraws tokens to the owner.
/// @param amount Amount to unstake and withdraw.
function unstake(uint256 amount) external {
require((amount > 0), "Amount can not be zero");

Staker storage staker = stakers[msg.sender];

require(staker.balance > 0, "Nothing to unstake");
require(staker.balance >= amount, "Insufficient funds");

staker.balance -= amount;

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

/// @notice Returns minimum amount of staking tokens to participate in
/// protocol.
function minimumStake() public pure returns (uint256) {
Expand All @@ -71,7 +94,10 @@ contract TokenStaking is IReceiveApproval {
require(amount <= maximumStake(), "Amount is greater than maxium");
require(staker != address(0), "Can not be the zero address");

balanceOf[staker] += amount;
Staker storage stakerStruct = stakers[staker];
stakerStruct.balance += amount;
/* solhint-disable-next-line not-rely-on-time */
stakerStruct.startStakingTimestamp = block.timestamp;

// TODO: Mint stBTC token.
emit Staked(staker, amount);
Expand Down
2 changes: 1 addition & 1 deletion core/contracts/test/TestToken.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity 0.8.20;
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../shared/IReceiveApproval.sol";
Expand Down
53 changes: 47 additions & 6 deletions core/test/staking/TokenStaking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ describe("TokenStaking", () => {
await expect(tokenStaking.connect(tokenHolder).stake(amountToStake))
.to.emit(tokenStaking, "Staked")
.withArgs(tokenHolderAddress, amountToStake)
expect(await tokenStaking.balanceOf(tokenHolderAddress)).to.be.eq(
amountToStake,
)
expect(
(await tokenStaking.stakers(tokenHolderAddress)).balance,
).to.be.eq(amountToStake)
expect(await token.balanceOf(tokenHolderAddress)).to.be.eq(
tokenBalanceBeforeStake - amountToStake,
)
Expand Down Expand Up @@ -86,13 +86,54 @@ describe("TokenStaking", () => {
)
.to.emit(tokenStaking, "Staked")
.withArgs(tokenHolderAddress, amountToStake)
expect(await tokenStaking.balanceOf(tokenHolderAddress)).to.be.eq(
amountToStake,
)
expect(
(await tokenStaking.stakers(tokenHolderAddress)).balance,
).to.be.eq(amountToStake)
expect(await token.balanceOf(tokenHolderAddress)).to.be.eq(
tokenBalanceBeforeStake - amountToStake,
)
})
})
})

describe("unstaking", () => {
const amountToStake = WeiPerEther * 10n

beforeEach(async () => {
// Stake tokens.
await token
.connect(tokenHolder)
.approveAndCall(await tokenStaking.getAddress(), amountToStake, "0x")
})

it("should unstake tokens", async () => {
const staker = await tokenHolder.getAddress()
const stakingBalance = (await tokenStaking.stakers(staker)).balance
const balanceBeforeUnstaking = await token.balanceOf(staker)

await expect(tokenStaking.connect(tokenHolder).unstake(stakingBalance))
.to.emit(tokenStaking, "Unstaked")
.withArgs(staker, stakingBalance)

expect(await token.balanceOf(staker)).to.be.equal(
balanceBeforeUnstaking + stakingBalance,
)
expect((await tokenStaking.stakers(staker)).balance).to.be.eq(0)
})

it("should revert if the unstaked amount is equal 0", async () => {
await expect(
tokenStaking.connect(tokenHolder).unstake(0),
).to.be.revertedWith("Amount can not be zero")
})

it("should revert if the user wants to unstake more tokens than currently staked", async () => {
const staker = await tokenHolder.getAddress()
const stakingBalance = (await tokenStaking.stakers(staker)).balance

await expect(
tokenStaking.connect(tokenHolder).unstake(stakingBalance + 10n),
).to.be.revertedWith("Insufficient funds")
})
})
})