This repo implements a basic staking contract with some added functionality for stake-time based rewards.
This staking contract shall eventually be the basis for an improved incentive mechanism for a rewards-based lottery ticket allocation.
At time of deployment, the contract address of the token which can be staked needs to be provided as well as the time (in seconds) the staked token shall be locked.
After deployment, (optionally) a ERC20 rewards token can be set and "reward tokens" provided to the contract, which can be claimed later by the users.
Deposit the specified amount of POLS token into the staking contract. In our context POLS is the "staking token".
The user has to approve POLS token first by calling the approve()
function on the POLS ERC20 token contract, before he can call the stake function.
Every time a user stakes token, either for the first time or adding tokens later, a new lockTimePeriod starts.
The unlockTime
will be calculcalted at the time of staking (current time + lockTimePeriod
) and stored for every user individually.
Returns the amount of staked token (POLS) for msg.sender
Returns the unix epoch time (in seconds) when the user executed a transaction (stake or unstake) the last time.
Returns the time when the user's token will be unlocked and can be withdrawn.
If lockTimePeriod
had been set, this time period has to be expired since the last stake
transaction, before the staked tokens can be withdrawn.
There is no need for the user to explicitly 'unlock' the staked token, they will 'automatically' be unlocked after the lockTimePeriod
expired.
withdraw(amount)
, return amount
staked tokens to the user's account.
The lock period will not be extended, unlock time will stay unchanged.
All rewards will stay within the contract.
As withdraw(amount)
, but all staked tokens will be returned to the user's account.
While the user has staked token, 'internal rewards' are being earned.
stakeRewardEndTime
defines the time when reward scheme ends and no more 'internal rewards' are being earned for staking token.
Over time the user earns 'internal rewards' which are (to begin with) only tracked internally within the contract.
userClaimableRewards
is the ongoing reward allocation = amount of staked token * the time since the last stake/unstake transaction was executed.
Whenever the staking amount changes, the past earned rewards (= userClaimableRewards
) are being added to userAccumulatedRewards
. Then the stakeTime
is reset to the current time, and userClaimableRewards
are being calculated anew based on the new time period * new staked token amount.
userTotalRewards
is just the sum of userAccumulatedRewards
and userClaimableRewards
Calculates the amount of reward tokens for msg.sender
based on userTotalRewards / stakeRewardFactor
.
If enough reward tokens are within the contract, the 'reward tokens' are being transferred to the account of msg.sender
.
After claim
all 'internal rewards' have been converted to reward tokens and userAccumulatedRewards
as well as userClaimableRewards
will be 0 thereafter.
The deployer account is being assigned the DEFAULT_ADMIN_ROLE
which is allowed to execute various administrative functions.
Sets the time (in seconds) a user has to wait after the last stake transaction until he can withdraw the staked tokens.
lockTimePeriod
can be changed any time and there are no restrictions to the value.
As unlockTime
will be calculcalted at the time of staking (current time + lockTimePeriod
) and stored for every user individually, lockTimePeriod
can be changed while users have staked token, but it will only affect stakes after lockTimePeriod
has been changed.
Specify the contract address of a ERC20 reward token.
Setting it to address(0)
(obviously) prevents user from claiming reward tokens.
If the contract holds an amoun of a previous rewards token, that amount will be transferred to the msg.sender
who has to own the DEFAULT_ADMIN_ROLE
.
If the previous rewards token is identical to the staking token, then only the difference between the contract balance and the total staked amount is returned.
The 'internal rewards' are just accumulated stakeAmount
* stakeTime
.
Example 1000 POLS token staked for 1 day : 1000 * 24 * 60 * 60 = 604800000
(This example assumes that stake token uses the same decimals as reward token, otherwise it has to be accounted for when setting stakeRewardFactor
.)
If this value is being set as setStakeRewardFactor
then a user will able to claim/mint 1 reward token after staking 1000 staking token for 1 week.
A user would also be able to claim/mint 1 reward token after staking 7000 staking token for 1 day.
Set the time when the reward scheme ends and no more 'internal rewards' are being earned for staking token.
burnRewards()
allows an external contract which has been assigned the BURNER_ROLE
to subtract a certain amount of 'internal rewards' of a specified account.
This would allow the token sale contract to reduce the amount of 'internal rewards' of a user who was successful to claim a token allocation. If the probability to win the token lottery is based on the 'internal rewards', burning internal rewards can be used to setup a mechnism to decrease the chance of a user to win again who just won and received a token allocation.
===============================================================================
The Solidity template from @paulrberg was used to initialize this project.
https://github.com/paulrberg/solidity-template
- Hardhat: compile and run the smart contracts on a local development network
- TypeChain: generate TypeScript types for smart contracts
- Ethers: renowned Ethereum library and wallet implementation
- Waffle: tooling for writing comprehensive smart contract tests
- Solhint: linter
- Solcover: code coverage
- Prettier Plugin Solidity: code formatter
Before running any command, make sure to install dependencies:
$ yarn install
Compile the smart contracts with Hardhat:
$ yarn compile
Compile the smart contracts and generate TypeChain artifacts:
$ yarn typechain
Lint the Solidity code:
$ yarn lint:sol
Lint the TypeScript code:
$ yarn lint:ts
Run the Mocha tests on hardhat:
$ yarn test
Run the Mocha tests on a test blockchain:
Currently configured (in hardhat.config.ts
) :
- mainnet (Ethereum)
- ganache
- goerli
- kovan
- rinkeby
- ropsten
- moonDev
- moonAlpha
$ yarn test --network <network>
Generate the code coverage report:
$ yarn coverage
See the gas usage per unit test and average gas per method call:
$ REPORT_GAS=true yarn test
Delete the smart contract artifacts, the coverage reports and the Hardhat cache:
$ yarn clean
Deploy the contracts to Hardhat Network:
$ yarn deploy
Deploy the contracts to a specific network, such as the Ropsten testnet:
$ yarn deploy --network kovan
Deploy the contracts to a specific main network, providing a private key:
$ MAINNET_PRIVATE_KEY=0xe15.... yarn deploy --network ethMain
$ yarn flatten
Will flatten PolsStake.sol
and write the result file contracts_flat/PolsStake_flat.sol
$ yarn audit
Will execute slither
contract audit.
If you use VSCode, you can enjoy syntax highlighting for your Solidity code via the vscode-solidity extension. The recommended approach to set the compiler version is to add the following fields to your VSCode user settings:
{
"solidity.compileUsingRemoteVersion": "v0.8.4+commit.c7e474f2",
"solidity.defaultCompiler": "remote"
}
Where of course v0.8.4+commit.c7e474f2
can be replaced with any other version.