Skip to content

Commit

Permalink
Merge pull request #42 from blockful-io/docs
Browse files Browse the repository at this point in the history
Review
  • Loading branch information
anajuliabit authored Aug 20, 2024
2 parents 9b5d234 + cf67cb1 commit 1dc2c30
Show file tree
Hide file tree
Showing 23 changed files with 1,046 additions and 806 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ The architecture consists of two contracts:
through the Staking contract, keypers trust that the DAO will not set the
minimum stake amount to an unreasonable value.

## Protocol Invariants [TBD]
## Protocol Invariants

1. On unstake, `keyperStake.timestamp + lockPeriod <= block.timestamp` if global `lockPeriod` is greater or equal to the stake lock period, otherwise `keyperStake.timestamp + keyperStake.lockPeriod <= block.timestamp`.
2. If `some(keyperStakes(keyper).length()) > 0` then `nextStakeId` != 0;
Expand Down
11 changes: 6 additions & 5 deletions docs/delegate-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
inherits from OpenZeppelin's ERC20VotesUpgradeable and
OwnableUpgradeable.
- The contract overrides the `transfer` and
`transferFrom` functions to prevent the sSHU token from being transferred. All
`transferFrom` functions to prevent the dSHU token from being transferred. All
the other inherited functions follow the OpenZeppelin implementation.
- To avoid rounding errors, the contract uses the FixedPointMathLib from Solmate
library.
- The contract uses SafeTransferLib from solmate to interact with the SHU token.
- The choosen mechanism for the rewards distribution is a ERC4626 vault implementation.

## Variables
Expand Down Expand Up @@ -108,7 +107,9 @@ Set the new staking contract address.

Get a list of stake ids belonging to a user.

### `maxWithdraw(address user)`
## Security Considerations

Calculates the maximum amount of assets that a keyper can withdraw, which
represents the rewards accumulated and not claimed yet. This funciton will revert if the user has no shares.
- The contract doesn't use the Ownable2Step pattern due to the 24KB contract
size limit.
- The contract doesn't use safe transfer as the only token that can be
transferred is the SHU token, which is a trusted token.
34 changes: 28 additions & 6 deletions docs/rewards-distributor.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,41 @@ struct RewardConfiguration {

### `setRewardConfiguration(address receiver, uint256 emissionRate)`

Add, update or stop distributing rewards to a receiver. The emission rate is
Add, or update the distributing rewards to a receiver. The emission rate is
the number of reward tokens distributed per second. This function can only be
called by the Owner (DAO). If the emission rate for the specified receiver is not 0,
the function will update the `emissionRate`. If the owner wants to stop
distributing rewards, they should set the emission rate to 0.
distributing rewards, they should call `removeRewardConfiguration`.

### `removeRewardConfiguration(address receiver)`

Remove the reward configuration for a specific receiver. This function can only
be called by the Owner.

### `setRewardToken(address rewardToken)`

This function can only be called by the Owner.
This function will first withdraw all the rewards from the previous reward
token and send them to the owner. Then it will set the new reward token
address.

### `withdrawFunds(address token, address to, uint256 amount)`

This function is useful in case someone transfer tokens to the contract by
mistake. The owner can withdraw any ERC20 token from the contract.

## Permissionless Functions

### `distributionRewards()`
### `collectRewards()`

Distribute all the rewards to the receiver contract (msg.sender) accumulated until from the
`lastUpdate` timestamp to the current timestamp.

### `collectRewardsTo(address receiver)`

Distribute all the rewards to the receiver contract accumulated until from the
`lastUpdate` timestamp to the current timestamp. If the msg.sender is not one of
the receivers, the function will revert.
Distribute all the rewards to the specified receiver contract accumulated until
from the `lastUpdate` timestamp to the current timestamp. If the receiver is
not a valid receiver, the function will revert.

## View Functions

Expand Down
12 changes: 2 additions & 10 deletions docs/staking-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
the other inherited functions follow the OpenZeppelin implementation.
- To avoid rounding errors, the contract uses the FixedPointMathLib from Solmate
library.
- The contract uses SafeTransferLib from solmate to interact with the SHU token.
- The choosen mechanism for the rewards distribution is a ERC4626 vault implementation.

## Variables
Expand Down Expand Up @@ -147,18 +146,11 @@ Set the new minimum amount of SHU tokens that must be staked by keypers.

Get a list of stake ids belonging to a keyper.

### `maxWithdraw(address keyper)`

Calculates the maximum amount of assets that a keyper can withdraw, which
represents the rewards accumulated and not claimed yet. This doesn't include
unlocked stakes.

- if the keyper has no shares, the function will revert.
- if the keyper sSHU balance is less or equal than the minimum stake or the total locked amount, the function will return 0.

## Security Considerations

- The contract doesn't use the Ownable2Step pattern due to the 24KB contract
size limit.
- If the Owner address gets compromised, the attacker can increase the minimum
stake to a very high value, preventing keypers from unstaking their SHU tokens.
- The contract doesn't use safe transfer as the only token that can be
transferred is the SHU token, which is a trusted token.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
via_ir = true
solc_version = "0.8.26"
verbosity = 3
gas_reports = ["Staking", "DelegateStaking", "RewardsDistributor"]

[profile.ci]
fuzz = { runs = 5000 }
Expand Down
1 change: 1 addition & 0 deletions script/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ address constant CONTRACT_OWNER = 0x36bD3044ab68f600f6d3e081056F34f2a58432c4; //
uint256 constant MIN_STAKE = 50_000e18;
uint256 constant REWARD_RATE = 0.1333333333e18;
uint256 constant LOCK_PERIOD = 182 days;
uint256 constant INITIAL_MINT = 10_000e18;
37 changes: 36 additions & 1 deletion script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ pragma solidity 0.8.26;

import "@forge-std/Script.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {RewardsDistributor} from "src/RewardsDistributor.sol";
import {DelegateStaking} from "src/DelegateStaking.sol";
import {Staking} from "src/Staking.sol";
import "./Constants.sol";

contract Deploy is Script {
function run()
public
returns (Staking stakingProxy, RewardsDistributor rewardsDistributor)
returns (
Staking stakingProxy,
RewardsDistributor rewardsDistributor,
DelegateStaking delegateProxy
)
{
vm.startBroadcast();

Expand All @@ -29,6 +35,11 @@ contract Deploy is Script {
)
);

IERC20Metadata(STAKING_TOKEN).approve(
address(stakingProxy),
INITIAL_MINT
);

stakingProxy.initialize(
CONTRACT_OWNER,
STAKING_TOKEN,
Expand All @@ -37,6 +48,30 @@ contract Deploy is Script {
MIN_STAKE
);

DelegateStaking delegate = new DelegateStaking();
delegateProxy = DelegateStaking(
address(
new TransparentUpgradeableProxy(
address(delegate),
address(CONTRACT_OWNER),
""
)
)
);

IERC20Metadata(STAKING_TOKEN).approve(
address(delegateProxy),
INITIAL_MINT
);

delegateProxy.initialize(
CONTRACT_OWNER,
STAKING_TOKEN,
address(rewardsDistributor),
address(stakingProxy),
LOCK_PERIOD
);

vm.stopBroadcast();
}
}
14 changes: 8 additions & 6 deletions script/testnet/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ uint256 constant MIN_STAKE = 50_000e18;
uint256 constant REWARD_RATE = 0.1333333333e18;
uint256 constant LOCK_PERIOD = 182 days;

address constant STAKING_CONTRACT_IMPL = 0x966aea71f391D044017143ab1D7e5DEd9a950e7e;
address constant STAKING_CONTRACT_PROXY = 0xe53a0850fDd90af0be3d4fDE02bD36C5EdFfc437;
address constant MOCKED_SHU = 0xF2215e7eDfc4782D85BAfA06114f22A0654cA8aC;
address constant REWARDS_DISTRIBUTOR = 0x8aA01CcdEec887f0a6AF127b094702F283d244DE;
address constant DELEGATE_CONTRACT_IMPL = 0x82957f2a4270BCb3A544133c5A41F76ac4862CC3;
address constant DELEGATE_CONTRACT_PROXY = 0x46707609373E016D6F72fAA4c13cbFC9BF3AFF7c;
address constant STAKING_CONTRACT_IMPL = 0xFaD109819176Ded391B663ceB621D24EF5E921d6;
address constant STAKING_CONTRACT_PROXY = 0x04c34f9c83A108153153a63CF2012761350B6667;
address constant STAKING_TOKEN = 0xF2215e7eDfc4782D85BAfA06114f22A0654cA8aC;
address constant REWARDS_DISTRIBUTOR = 0x2061c38E4F168294CcD989ecf427F44a77d9cC34;
address constant DELEGATE_CONTRACT_IMPL = 0x266ea1Ea3d1482cCd17dFb17E102dD8Ff2B26882;
address constant DELEGATE_CONTRACT_PROXY = 0x7F51584f23B61e4d3E4D1C8A4D5f8C39Acb53251;

uint256 constant INITIAL_MINT = 10_000e18;
35 changes: 0 additions & 35 deletions script/testnet/DeployDelegateTestnet.s.sol

This file was deleted.

46 changes: 40 additions & 6 deletions script/testnet/DeployTestnet.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,30 @@
pragma solidity 0.8.26;

import "@forge-std/Script.sol";

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {RewardsDistributor} from "src/RewardsDistributor.sol";
import {Staking} from "src/Staking.sol";
import {DelegateStaking} from "src/DelegateStaking.sol";
import {MockGovToken} from "test/mocks/MockGovToken.sol";
import "./Constants.sol";

// forge script script/testnet/DeployTestnet.s.sol --rpc-url testnet -vvvvv --slow --always-use-create-2-factory --account test --etherscan-api-key testnet --verify --chain 11155111
// forge script script/testnet/DeployTestnet.s.sol --rpc-url testnet -vvvvv --slow --always-use-create-2-factory --account test --etherscan-api-key testnet --verify --chain 11155111 --code-size-limit 40000 --broadcast
contract DeployTestnet is Script {
function run()
public
returns (Staking stakingProxy, RewardsDistributor rewardsDistributor)
returns (
Staking stakingProxy,
RewardsDistributor rewardsDistributor,
DelegateStaking delegateProxy
)
{
vm.startBroadcast();

MockGovToken govToken = new MockGovToken();

rewardsDistributor = new RewardsDistributor(
CONTRACT_OWNER,
address(govToken)
address(STAKING_TOKEN)
);

Staking stake = new Staking();
Expand All @@ -34,14 +39,43 @@ contract DeployTestnet is Script {
)
);

IERC20Metadata(STAKING_TOKEN).approve(
address(stakingProxy),
INITIAL_MINT
);

stakingProxy.initialize(
CONTRACT_OWNER,
address(govToken),
address(STAKING_TOKEN),
address(rewardsDistributor),
LOCK_PERIOD,
MIN_STAKE
);

DelegateStaking delegate = new DelegateStaking();
delegateProxy = DelegateStaking(
address(
new TransparentUpgradeableProxy(
address(delegate),
address(CONTRACT_OWNER),
""
)
)
);

IERC20Metadata(STAKING_TOKEN).approve(
address(delegateProxy),
INITIAL_MINT
);

delegateProxy.initialize(
CONTRACT_OWNER,
STAKING_TOKEN,
address(rewardsDistributor),
address(stakingProxy),
LOCK_PERIOD
);

vm.stopBroadcast();
}
}
Loading

0 comments on commit 1dc2c30

Please sign in to comment.