Skip to content

Commit

Permalink
Add Staking.sol
Browse files Browse the repository at this point in the history
  • Loading branch information
anajuliabit committed Jun 10, 2024
1 parent e0951bf commit 23a4d2a
Show file tree
Hide file tree
Showing 4 changed files with 1,485 additions and 91 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ out/

# Dotenv file
.env

node_modules/
180 changes: 89 additions & 91 deletions docs/simplified-staking-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
## Overview

Enables keypers to stake SHU tokens for a minimum period. In exchange, keypers receive rewards in the form of
any ERC20 token that the DAO chooses to distribute, such as SHU or WETH. SHU rewards are automatically compounded when the contract state is updated and can be withdraw at any time.
any ERC20 token that the DAO chooses to distribute, such as SHU or WETH. SHU rewards are automatically compounded when the contract state is updated and can be withdraw at any time.

The architecture consists of two contracts:

1. Staking Contract: The main contract where keypers can stake SHU tokens and claim rewards.
2. Rewards Distribution Contract: A contract that distributes rewards to the
staking contract.

The contracts are designed to be customizable, with adjustable parameters such as the lock period, minimum stake, and reward emission. Additionally, the contracts uses the Transparent Proxy pattern, where only the DAO has the permission to upgrade the contract and call the owner functions defined below.

## Requirements
Expand All @@ -28,7 +28,7 @@ The contracts are designed to be customizable, with adjustable parameters such a
below.
2. The staking contracts follows the checks-effects-interactions pattern to
prevent reentrancy attacks.
3. The staking contract has 100% unit test coverage
3. The staking contract has 100% unit test coverage
4. The staking contract has been deployed to the testnet and integration tests
have been run against the testnet.
5. The staking contract has integration tests running against the mainnet fork
Expand All @@ -42,26 +42,24 @@ The contracts are designed to be customizable, with adjustable parameters such a

### Immutable Variables

* `stakingToken`: the SHU token address
- `stakingToken`: the SHU token address

The staking token must be immutable. If the DAO changes the staking token, the
keypers will not be able to redeem their old stakes.
keypers will not be able to redeem their old stakes.
If the DAO upgrades the SHU token to a new contract, it must also redeploy the staking contract and ask the keypers to migrate their stakes to the new contract.

### Mutable Variables

* `uint256 public lockPeriod`: the minimum amount of time a keyper must stake their SHU tokens
before they can unstake

* `uint256 public minimumStake`: the minimum amount of SHU tokens that must be
staked at the first stake

* `uint256 public totalSupply`: the amount of shares in circulation.
- `uint256 public lockPeriod`: the minimum amount of time a keyper must stake their SHU tokens
before they can unstake
- `uint256 public minimumStake`: the minimum amount of SHU tokens that must be
staked at the first stake
- `uint256 public totalSupply`: the amount of shares in circulation.

- `uint256 public lastUpdateTimestamp`: the last time the contract rewards were
compounded, i.e the rewards were withdrawn from the rewards distribution contract.

* `uint256 public lastUpdateTimestamp`: the last time the contract rewards were
compounded, i.e the rewards were withdrawn from the rewards distribution contract.

### Mappings
### Mappings

```solidity
struct Stake {
Expand All @@ -72,89 +70,91 @@ struct Stake {
}
```

* `mapping(address keyper => bool) public keypers`: a mapping from keypers to
their status. If the keyper is true, they are allowed to stake. If the keyper
is false, they are not allowed to stake.
* `mapping(address keyper => Stake[]) public stakes`: a mapping from keypers to
their stakes. Each keyper can have multiple stakes with different lock periods.
* `mapping(address keyper => uint256 balance) public balances`: a mapping from
keypers to their balance of shares.
- `mapping(address keyper => bool) public keypers`: a mapping from keypers to
their status. If the keyper is true, they are allowed to stake. If the keyper
is false, they are not allowed to stake.
- `mapping(address keyper => Stake[]) public stakes`: a mapping from keypers to
their stakes. Each keyper can have multiple stakes with different lock periods.
- `mapping(address keyper => uint256 balance) public balances`: a mapping from
keypers to their balance of shares.

### Rewards Calculation Mechanismm

* The rewards are withdrawn from the rewards distribution contract every time
anyone interacts with state changes functions. This includes staking, unstaking, and claiming rewards.
As the contract balance of SHU and other reward tokens increases, when the
keyper decides to claim the rewards, they will get a better conversion rate from
shares (xSHU) to the reward token. As the staking token is SHU, when the rewards
are claimed, the SHU balance of the contract increases, causing a coumpound effect.
* For unstaking, the keyper also gets the SHU rewards accumulated.
* The reward earned by a user is proportional to the amount they have
staked. The more tokens a user stakes, the larger their share of the rewards.
* As more users stake tokens, the total supply increases. Since the reward rate
per second is constant, the reward per token decreases. This means each user earns a smaller share of the rewards if more tokens are staked by others. This creates a balance where the total rewards distributed per second remains steady, but the individual rewards depend on the user's share of the total staked amount and for how long they have staked. This way, early stakers are rewarded more than late stakers, incentivizing users to stake early.
- The rewards are withdrawn from the rewards distribution contract every time
anyone interacts with state changes functions. This includes staking, unstaking, and claiming rewards.
As the contract balance of SHU and other reward tokens increases, when the
keyper decides to claim the rewards, they will get a better conversion rate from
shares (xSHU) to the reward token. As the staking token is SHU, when the rewards
are claimed, the SHU balance of the contract increases, causing a coumpound effect.
- For unstaking, the keyper also gets the SHU rewards accumulated.
- The reward earned by a user is proportional to the amount they have
staked. The more tokens a user stakes, the larger their share of the rewards.
- As more users stake tokens, the total supply increases. Since the reward rate
per second is constant, the reward per token decreases. This means each user earns a smaller share of the rewards if more tokens are staked by others. This creates a balance where the total rewards distributed per second remains steady, but the individual rewards depend on the user's share of the total staked amount and for how long they have staked. This way, early stakers are rewarded more than late stakers, incentivizing users to stake early.

### Keyper Functions

#### `stake(uint256 amount)`

* When staking, the keyper receives shares in exchange for the SHU tokens they stake. The shares represent the keyper's ownership of the total staked amount and are used to calculate the rewards the keyper earns. The more shares a keyper has, the larger their share of the rewards.
* The caller must have approved the staking contract to transfer `amount` of SHU tokens on their behalf.
* Only keypers can call this function.
* A minimum amount of SHU tokens defined by the DAO must be staked.
* Each stake has an individual lock period that must be respected before the
keyper can unstake.
- When staking, the keyper receives shares in exchange for the SHU tokens they stake. The shares represent the keyper's ownership of the total staked amount and are used to calculate the rewards the keyper earns. The more shares a keyper has, the larger their share of the rewards.
- The caller must have approved the staking contract to transfer `amount` of SHU tokens on their behalf.
- Only keypers can call this function.
- A minimum amount of SHU tokens defined by the DAO must be staked.
- Each stake has an individual lock period that must be respected before the
keyper can unstake.

#### `unstake(uint256 amount, uint256 stakeId)`

* When unstaking, the keyper receives the SHU tokens they staked plus any rewards
they have earned. The rewards are automatically compounded into the staked
amount. The shares are burned when the keyper unstakes.
* The caller must have staked for at least `lockPeriod` for the specific stake.
- When unstaking, the keyper receives the SHU tokens they staked plus any rewards
they have earned. The SHU rewards are automatically compounded into the staked
amount. The shares are burned when the keyper unstakes.
- The caller must have staked for at least `lockPeriod` for the specific stake.
- If amount is greater than the user balance, the contract will unstake the
maximum amount possible.

#### `claimRewards(address rewardToken, uint256 amount)`

* Claim rewards for a specific reward token.
* The amount must be less than or equal to the rewards accumulated until the
last update timestamp.
* Only the keyper can claim their rewards.
* The maximum amount of rewards that can be claimed can be calculated by calling
the `getRewards` function.
* If caller pass 0 in the `amount` paramater, the contract will claim all the
caller rewards accumulated until the current timestamp for the specific reward
token.
* This function will call the `distributeRewards` function before claiming the
rewards.
* The rewardToken must exist in the rewards distribution contract
- Claim rewards for a specific reward token.
- The amount must be less than or equal to the rewards accumulated until the
last update timestamp.
- Only the keyper can claim their rewards.
- The maximum amount of rewards that can be claimed can be calculated by calling
the `getRewards` function.
- If caller pass 0 in the `amount` paramater, the contract will claim all the
caller rewards accumulated until the current timestamp for the specific reward
token.
- This function will call the `distributeRewards` function before claiming the
rewards.
- The rewardToken must exist in the rewards distribution contract

#### `claimAllRewards()`

* Claim all the rewards accumulated until the last update timestamp for all the
reward tokens.
* This function will call the `distributeRewards` function before claiming the
rewards.
- Claim all the rewards accumulated until the last update timestamp for all the
reward tokens.
- This function will call the `distributeRewards` function before claiming the
rewards.

### Permissioneless Functions

#### `distributeRewards()`

* Withdraw the rewards from the rewards distribution contract and compound the
SHU rewards into the staked amount. As this is beneficial for all keypers,
this function can be called by anyone.
- Withdraw the rewards from the rewards distribution contract and compound the
SHU rewards into the staked amount. As this is beneficial for all keypers,
this function can be called by anyone.

### Owner Functions (DAO)

#### `setLockPeriod(uint256 newLockPeriod)`

* The minimum staking period for SHU tokens before they can be unstaked.
* Measured in seconds.
* New stakes will have the new lock period.
* For existing stakes, the lock period will be considered only if the new lock
period is lower than the current one for that stake. This ensures that the
keyper can trust that their tokens will never be locked for longer than the
agreed-upon period when they staked, while also allowing keyper to unstake
their SHU tokens in emergency situations.
**TODO: Validade this statement with the DAO**
- The minimum staking period for SHU tokens before they can be unstaked.
- Measured in seconds.
- New stakes will have the new lock period.
- For existing stakes, the lock period will be considered only if the new lock
period is lower than the current one for that stake. This ensures that the
keyper can trust that their tokens will never be locked for longer than the
agreed-upon period when they staked, while also allowing keyper to unstake
their SHU tokens in emergency situations.
**TODO: Validade this statement with the DAO**

#### `setRewardsDistribution(address newRewardsDistribution)`

Expand Down Expand Up @@ -226,8 +226,8 @@ contains the rewards configuration for each reward token.

### Storage Layout

* `mapping(address rewardToken => RewardConfiguration[] rewardsConfiguration) public rewards`: a
mapping from reward tokens to the reward configuration.
- `mapping(address rewardToken => RewardConfiguration[] rewardsConfiguration) public rewards`: a
mapping from reward tokens to the reward configuration.

```solidity
struct RewardConfiguration {
Expand All @@ -237,25 +237,25 @@ struct RewardConfiguration {
```

1. The `emissionRate` defines the number of rewards tokens distributed per
second. This is a fixed rate and determines how many reward tokens the contract
allocates every second to be distributed to all the keypers.
second. This is a fixed rate and determines how many reward tokens the contract
allocates every second to be distributed to all the keypers.

2. The `finishTimestamp` defines the timestamp when the rewards distribution will stop.

* `uint256[] public rewardsTokenIndex`: an array of reward tokens index to be
used to iterate over the reward tokens.
- `uint256[] public rewardsTokenIndex`: an array of reward tokens index to be
used to iterate over the reward tokens.

### Owner Functions (DAO)

#### `configureReward(address rewardToken,uint256 emissionRate, uint256 finishTimestamp)`

* Configure a reward token and the respective emission rate.
* The reward token must be ERC20 compliant. No native rewards are allowed.
* If the reward token already exists, the emission rate will be updated.
* If the reward token does not exist, a new reward token will be added.
* This function calls the `setRewardToken` function in the staking contract to
add the reward token to the list of reward tokens.
- Configure a reward token and the respective emission rate.
- The reward token must be ERC20 compliant. No native rewards are allowed.
- If the reward token already exists, the emission rate will be updated.
- If the reward token does not exist, a new reward token will be added.
- This function calls the `setRewardToken` function in the staking contract to
add the reward token to the list of reward tokens.

### Only callable by the Staking Contract

#### `distributionRewards()`
Expand All @@ -279,14 +279,12 @@ Get the reward configuration for a specific reward token.
## Protocol Invariants

1. The total amount of SHU tokens staked in the contract must be equal to the
total amount of SHU tokens staked by each keyper: `totalStaked = sum(stakes[keyper].amount)`.
total amount of SHU tokens staked by each keyper: `totalStaked = sum(stakes[keyper].amount)`.
2. On unstake, `block.timestamp >= stakes[msg.sender].stakedTimestamp +
stakes[msg.sender].lockPeriod` if global `lockPeriod` is greater or equal to
the stake lock period, otherwise `block.timestamp >=
stakes[msg.sender].stakedTimestamp + lockPeriod`.
stakes[msg.sender].lockPeriod` if global `lockPeriod` is greater or equal to
the stake lock period, otherwise `block.timestamp >=
stakes[msg.sender].stakedTimestamp + lockPeriod`.
3. On unstake, the withdrawn amount must be less than or equal to `stakes[msg.sender].amount`.
4. `stakes[keyper].amount >= minimumStake` for any keyper who has staked tokens.
5. Functions with access control (onlyOwner) should be callable only by the owner address.
6. `rewardToken` addresses in `rewardEmissionRate` mapping must be valid ERC20 tokens.


Loading

0 comments on commit 23a4d2a

Please sign in to comment.