Add contract for keeping track of stakes in ERC20 tokens
This commit is contained in:
parent
23887f9190
commit
c013a37229
|
@ -0,0 +1,38 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
contract Stakes {
|
||||
|
||||
IERC20 private token;
|
||||
mapping(address=>uint) private stakes;
|
||||
mapping(address=>uint) private locks;
|
||||
|
||||
constructor(IERC20 _token) {
|
||||
token = _token;
|
||||
}
|
||||
|
||||
function stake(address account) public view returns (uint) {
|
||||
return stakes[account];
|
||||
}
|
||||
|
||||
function increase(uint amount) public {
|
||||
token.transferFrom(msg.sender, address(this), amount);
|
||||
stakes[msg.sender] += amount;
|
||||
}
|
||||
|
||||
function withdraw() public {
|
||||
require(locks[msg.sender] == 0, "Stake locked");
|
||||
token.transfer(msg.sender, stakes[msg.sender]);
|
||||
}
|
||||
|
||||
function _lock(address account) internal {
|
||||
locks[account] += 1;
|
||||
}
|
||||
|
||||
function _unlock(address account) internal {
|
||||
require(locks[account] > 0, "Stake already unlocked");
|
||||
locks[account] -= 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./Stakes.sol";
|
||||
|
||||
// exposes internal functions of Stakes for testing
|
||||
contract TestStakes is Stakes {
|
||||
|
||||
constructor(IERC20 token) Stakes(token) {}
|
||||
|
||||
function lock(address account) public {
|
||||
_lock(account);
|
||||
}
|
||||
|
||||
function unlock(address account) public {
|
||||
_unlock(account);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract TestToken is ERC20 {
|
||||
constructor() ERC20("TestToken", "TST") {
|
||||
_mint(msg.sender, 1000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
const { expect } = require("chai")
|
||||
const { ethers } = require("hardhat")
|
||||
|
||||
describe("Stakes", function () {
|
||||
|
||||
var stakes
|
||||
var token
|
||||
var host
|
||||
|
||||
beforeEach(async function() {
|
||||
[host] = await ethers.getSigners()
|
||||
const Stakes = await ethers.getContractFactory("TestStakes")
|
||||
const TestToken = await ethers.getContractFactory("TestToken")
|
||||
token = await TestToken.deploy()
|
||||
stakes = await Stakes.deploy(token.address)
|
||||
})
|
||||
|
||||
it("has zero stakes initially", async function () {
|
||||
const address = await host.getAddress()
|
||||
const stake = await stakes.stake(address)
|
||||
expect(stake).to.equal(0)
|
||||
})
|
||||
|
||||
it("increases stakes by transferring tokens", async function () {
|
||||
await token.approve(stakes.address, 20)
|
||||
await stakes.increase(20)
|
||||
let stake = await stakes.stake(host.address)
|
||||
expect(stake).to.equal(20)
|
||||
})
|
||||
|
||||
it("does not increase stake when token transfer fails", async function () {
|
||||
await expect(
|
||||
stakes.increase(20)
|
||||
).to.be.revertedWith("ERC20: transfer amount exceeds allowance")
|
||||
})
|
||||
|
||||
it("allows withdrawal of stake", async function () {
|
||||
await token.approve(stakes.address, 20)
|
||||
await stakes.increase(20)
|
||||
let balanceBefore = await token.balanceOf(host.address)
|
||||
await stakes.withdraw()
|
||||
let balanceAfter = await token.balanceOf(host.address)
|
||||
expect(balanceAfter - balanceBefore).to.equal(20)
|
||||
})
|
||||
|
||||
it("locks stake", async function () {
|
||||
await token.approve(stakes.address, 20)
|
||||
await stakes.increase(20)
|
||||
await stakes.lock(host.address)
|
||||
await expect(stakes.withdraw()).to.be.revertedWith("Stake locked")
|
||||
await stakes.unlock(host.address)
|
||||
await expect(stakes.withdraw()).not.to.be.reverted
|
||||
})
|
||||
|
||||
it("fails to unlock when already unlocked", async function () {
|
||||
await expect(
|
||||
stakes.unlock(host.address)
|
||||
).to.be.revertedWith("Stake already unlocked")
|
||||
})
|
||||
|
||||
it("requires an equal amount of locks and unlocks", async function () {
|
||||
await token.approve(stakes.address, 20)
|
||||
await stakes.increase(20)
|
||||
await stakes.lock(host.address)
|
||||
await stakes.lock(host.address)
|
||||
await stakes.unlock(host.address)
|
||||
await expect(stakes.withdraw()).to.be.revertedWith("Stake locked")
|
||||
await stakes.unlock(host.address)
|
||||
await expect(stakes.withdraw()).not.to.be.reverted
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue