diff --git a/core/contracts/staking/TokenStaking.sol b/core/contracts/staking/TokenStaking.sol index aa61fc59d..d1b9d2ccd 100644 --- a/core/contracts/staking/TokenStaking.sol +++ b/core/contracts/staking/TokenStaking.sol @@ -11,9 +11,13 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// and recover the stake after undelegation period is over. contract TokenStaking { using SafeERC20 for IERC20; - + + event Staked(address indexed account, uint256 amount); + IERC20 internal immutable token; + mapping(address => uint256) public balanceOf; + constructor(IERC20 _token) { require( address(_token) != address(0), @@ -22,4 +26,15 @@ contract TokenStaking { token = _token; } + + /// @notice Stakes the owner's tokens in the staking contract. + /// @param amount Approved amount for the transfer and stake. + function stake(uint256 amount) external { + require(amount > 0, "Amount is less than minimum"); + + balanceOf[msg.sender] += amount; + + emit Staked(msg.sender, amount); + token.safeTransferFrom(msg.sender, address(this), amount); + } } diff --git a/core/contracts/test/TestToken.sol b/core/contracts/test/TestToken.sol new file mode 100644 index 000000000..749165b4c --- /dev/null +++ b/core/contracts/test/TestToken.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity 0.8.20; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract Token is ERC20 { + constructor() ERC20("Test Token", "TEST") {} + + function mint(address account, uint256 value) external { + _mint(account, value); + } +} diff --git a/core/test/staking/TokenStaking.test.ts b/core/test/staking/TokenStaking.test.ts new file mode 100644 index 000000000..fb2e8655a --- /dev/null +++ b/core/test/staking/TokenStaking.test.ts @@ -0,0 +1,67 @@ +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { ethers } from "hardhat" +import { expect } from "chai" +import { Token, TokenStaking } from "../../typechain" +import { WeiPerEther } from "ethers" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import { before } from "mocha" + +async function tokenStakingFixture() { + const [deployer, tokenHolder] = await ethers.getSigners() + const StakingToken = await ethers.getContractFactory("Token") + const token = await StakingToken.deploy() + + const amountToMint = WeiPerEther * 10000n + + token.mint(tokenHolder, amountToMint) + + const TokenStaking = await ethers.getContractFactory("TokenStaking") + const tokenStaking = await TokenStaking.deploy(await token.getAddress()) + + return { tokenStaking, token, tokenHolder } +} + +describe("TokenStaking", () => { + let tokenStaking: TokenStaking + let token: Token + let tokenHolder: HardhatEthersSigner + + beforeEach(async () => { + const { + tokenStaking: _tokenStaking, + token: _token, + tokenHolder: _tokenHolder, + } = await loadFixture(tokenStakingFixture) + + tokenStaking = _tokenStaking + token = _token + tokenHolder = _tokenHolder + }) + + describe("staking", () => { + beforeEach(async () => { + // Infinite approval for staking contract. + await token + .connect(tokenHolder) + .approve(await tokenStaking.getAddress(), ethers.MaxUint256) + }) + + it("should stake tokens", async () => { + const tokenHolderAddress = await tokenHolder.getAddress() + const tokenBalance = await token.balanceOf(tokenHolderAddress) + + await expect(tokenStaking.connect(tokenHolder).stake(tokenBalance)) + .to.emit(tokenStaking, "Staked") + .withArgs(tokenHolderAddress, tokenBalance) + expect(await tokenStaking.balanceOf(tokenHolderAddress)).to.be.eq( + tokenBalance, + ) + expect(await token.balanceOf(tokenHolderAddress)).to.be.eq(0) + }) + + it("should revert if the staked amount is less than required minimum", async () => { + await expect(tokenStaking.connect(tokenHolder).stake(0)) + .to.be.revertedWith("Amount is less than minimum") + }) + }) +})