diff --git a/README.md b/README.md index 85227398..27071fd9 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Additionally, it also includes various deployment scripts that are integral for - [Deployment Directory Folder](#deployment-directory-folder) - [Deployment of L2 Lisk Token](#deployment-of-l2-lisk-token) - [Transferring Lisk Tokens After Smart Contracts Deployment](#transferring-lisk-tokens-after-smart-contracts-deployment) + - [Lisk L2 Staking](#lisk-l2-staking) - [Smart Contract Ownership](#smart-contract-ownership) - [Contributing](#contributing) - [Security](#security) @@ -32,10 +33,15 @@ Additionally, it also includes various deployment scripts that are integral for ### Contracts deployed to L2 -| Name | Description | -| --------------------------------------- | ------------------------------------------------------------------- | -| [`L2LiskToken`](src/L2/L2LiskToken.sol) | Bridged Lisk token (LSK) deployed on Lisk L2 network | -| [`L2Claim`](src/L2/L2Claim.sol) | Smart contract responsible for a claiming process of the LSK tokens | +| Name | Description | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| [`L2LiskToken`](src/L2/L2LiskToken.sol) | Bridged Lisk token (LSK) deployed on Lisk L2 network | +| [`L2Claim`](src/L2/L2Claim.sol) | Smart contract responsible for a claiming process of the LSK tokens | +| [`L2Governor`](src/L2/L2Governor.sol) | The [Governor](https://docs.openzeppelin.com/contracts/5.x/governance) contract for the Lisk DAO which manages proposals | +| [`L2VotingPower`](src/L2/L2VotingPower.sol) | The [token](https://docs.openzeppelin.com/contracts/5.x/governance#token) contract for the Lisk DAO which manages the voting power of accounts | +| [`L2Staking`](src/L2/L2Staking.sol) | Responsible for locking, unlocking and editing locking positions | +| [`L2LockingPosition`](src/L2/L2LockingPosition.sol) | Stores all information of locking positions | +| [`L2Reward`](src/L2/L2Reward.sol) | Responsible for staking rewards | ## Installation @@ -61,27 +67,29 @@ git submodule update --init --recursive **NOTE**: On a private test network, the deployment of smart contracts is feasible on both L1 and L2 networks. However, the transfer of tokens between these networks is not possible as it requires the operation of the Sequencer. -**NOTE**: To successfully deploy all smart contracts and execute the required transactions, the deployer (specified by `PRIVATE_KEY` in the `.env` file) must have funds available in its address on the respective networks. For a private test network, you can use a any private key from the list provided by `anvil` when the network is created, or choose another private key with sufficient funds on both forked networks. +**NOTE**: To successfully deploy all smart contracts and execute the required transactions, the deployer (specified by `PRIVATE_KEY` in the `.env` file) must have funds available in its address on the respective networks. For a private test network, you can use any private key from the list provided by `anvil` when the network is created, or choose another private key with sufficient funds on both forked networks. Private L1 and L2 test networks are established using the `anvil` tool, and the smart contracts are deployed using the `forge script` tool. To run private networks and deploy the smart contracts, follow these steps: -1. Create `.env` file and set the vars `PRIVATE_KEY`, `NETWORK`, `L1_TOKEN_OWNER_ADDRESS`, `L2_CLAIM_OWNER_ADDRESS`, `DAO_ADDRESS`, `DETERMINISTIC_ADDRESS_SALT`, `L1_STANDARD_BRIDGE_ADDR`, `L1_RPC_URL`, `L2_RPC_URL`, `L1_FORK_RPC_URL`, `L2_FORK_RPC_URL` and `TEST_NETWORK_MNEMONIC`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `L1_RPC_URL` should be set to `http://127.0.0.1:8545` and `L2_RPC_URL` should be set to `http://127.0.0.1:8546` if no changes are made in the `./runL1TestNetwork.sh` or `./runL2TestNetwork.sh` script files. + +1. Create `.env` file and set the vars `PRIVATE_KEY`, `NETWORK`, `L1_TOKEN_OWNER_ADDRESS`, `L2_CLAIM_OWNER_ADDRESS`, `L2_STAKING_OWNER_ADDRESS`, `L2_LOCKING_POSITION_OWNER_ADDRESS`, `L2_REWARD_OWNER_ADDRESS`, `L2_GOVERNOR_OWNER_ADDRESS`, `L2_VOTING_POWER_OWNER_ADDRESS`, `DETERMINISTIC_ADDRESS_SALT`, `L1_STANDARD_BRIDGE_ADDR`, `L1_RPC_URL`, `L2_RPC_URL`, `L1_FORK_RPC_URL`, `L2_FORK_RPC_URL` and `TEST_NETWORK_MNEMONIC`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `L1_RPC_URL` should be set to `http://127.0.0.1:8545` and `L2_RPC_URL` should be set to `http://127.0.0.1:8546` if no changes are made in the `./runL1TestNetwork.sh` or `./runL2TestNetwork.sh` script files. 2. Navigate to the `script` directory. 3. Place the `accounts.json` and `merkle-root.json` files in the correct folder (`data/devnet`, `data/testnet`, or `data/mainnet`) corresponding to the previously set `NETWORK` environment variable. Example files for `accounts.json` and `merkle-root.json` may be found inside `data/devnet` directory. 4. To create and launch a private test L1 network, execute the script: `./runL1TestNetwork.sh` 5. To create and launch a private test L2 network, execute the script: `./runL2TestNetwork.sh` -6. To deploy all smart contracts, execute the script: `./deployContracts.sh` +6. To deploy all smart contracts, execute the scripts: `./1_deployTokenContracts.sh`, `./2_deployStakingAndGovernance.sh`, `./3_deployVestingWallets.sh` and `./4_deployClaimContract`. ## Deployment on Public Test Network **NOTE**: To successfully deploy all smart contracts and execute the required transactions, the deployer (specified by `PRIVATE_KEY` in the `.env` file) must have funds available in its address. This implies that a private key with a sufficient balance on both public test networks is required. To deploy smart contracts on both L1 and L2 public networks, you will need to provide for each network an URL for a public node from a RPC provider, such as Alchemy or Infura. Additionally, in order to verify smart contracts on Blockscout or Etherscan Block Explorers during the deployment process, it is necessary to provide verifier name along with additional information (URL and API key). Follow these steps to deploy the smart contracts: -1. Create `.env` file and set the vars `PRIVATE_KEY`, `NETWORK`, `L1_TOKEN_OWNER_ADDRESS`, `L2_CLAIM_OWNER_ADDRESS`, `DAO_ADDRESS`, `DETERMINISTIC_ADDRESS_SALT`, `L1_STANDARD_BRIDGE_ADDR`, `L1_RPC_URL`, `L2_RPC_URL` and `CONTRACT_VERIFIER`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `CONTRACT_VERIFIER` may be empty to skip smart contracts verification process on Blockscout or Etherscan Block Explorers. -2. When `CONTRACT_VERIFIER` is configured as either `blockscout` or `etherscan`, there are specific additional variables that must be defined. For `blockscout`, it is necessary to set `L1_VERIFIER_URL` and `L2_VERIFIER_URL`. Conversely, for `etherscan`, it is necessary to set `L1_ETHERSCAN_API_KEY` and `L2_ETHERSCAN_API_KEY`. + +1. Create `.env` file and set the vars `PRIVATE_KEY`, `NETWORK`, `L1_TOKEN_OWNER_ADDRESS`, `L2_CLAIM_OWNER_ADDRESS`, `L2_STAKING_OWNER_ADDRESS`, `L2_LOCKING_POSITION_OWNER_ADDRESS`, `L2_REWARD_OWNER_ADDRESS`, `L2_GOVERNOR_OWNER_ADDRESS`, `L2_VOTING_POWER_OWNER_ADDRESS`, `DETERMINISTIC_ADDRESS_SALT`, `L1_STANDARD_BRIDGE_ADDR`, `L1_RPC_URL`, `L2_RPC_URL` and `CONTRACT_VERIFIER`. You can copy and rename the `.env.example` file if the default values provided in `.env.example` are satisfactory. `CONTRACT_VERIFIER` may be empty to skip smart contracts verification process on Blockscout or Etherscan Block Explorers. +2. When `CONTRACT_VERIFIER` is configured as either `blockscout` or `etherscan`, there are specific additional variables that must be defined. For `blockscout`, it is necessary to set `L1_VERIFIER_URL` and `L2_VERIFIER_URL`. Conversely, for `etherscan`, it is necessary to set `L1_ETHERSCAN_API_KEY` and `L2_ETHERSCAN_API_KEY`. 3. Navigate to the `script` directory. 4. Place the `accounts.json` and `merkle-root.json` files in the correct folder (`data/devnet`, `data/testnet`, or `data/mainnet`) corresponding to the previously set `NETWORK` environment variable. Example files for `accounts.json` and `merkle-root.json` may be found inside `data/devnet` directory. -5. To deploy all smart contracts, execute the script: `./deployContracts.sh` - +5. To deploy all smart contracts, execute the scripts: `./1_deployTokenContracts.sh`, `./2_deployStakingAndGovernance.sh`, `./3_deployVestingWallets.sh` and `./4_deployClaimContract`. + ## Tips & Tricks **WARNING**: Foundry installs the latest versions of `openzeppelin-contracts` and `openzeppelin-contracts-upgradeable` initially, but subsequent `forge update` commands will use the `master` branch which is a development branch that should be avoided in favor of tagged releases. The release process involves security measures that the `master` branch does not guarantee. @@ -96,9 +104,17 @@ L2 Lisk Token is deployed using `CREATE2` opcode to ensure deterministic smart c ### Transferring Lisk Tokens After Smart Contracts Deployment -After the successful deployment of all smart contracts using the `deployContracts.sh` script, the distribution of newly minted Lisk tokens takes place in accordance with the instructions specified in the `accounts.json` file. This file contains a list of addresses and the respective amounts of tokens that need to be sent to various accounts on both the L1 and L2 networks. +The distribution of newly minted Lisk tokens takes place in accordance with the instructions specified in the files `accounts_1.json`, `accounts_2.json` and `vestingWallets.json`. The two files `accounts_1.json` and `accounts_2.json` contain a list of addresses and the respective amounts of tokens that need to be sent to various accounts on both the L1 and L2 networks. The funds specified in `accounts_1.json` will be transferred as part of `1_deployTokenContracts.sh` and the funds of `accounts_2.json` as part of `4_deployClaimContract.sh`. The funds specified in `vestingWallets.json` are distributed to vesting wallets as part of `3_deployVestingWallets.sh`. Moreover, some initial amount is transferred to the DAO treasury within the same script. + +The process ensures that each address specified in the json files receives the designated amount of tokens accurately. Any remaining Lisk Tokens, those not allocated to the addresses listed in the files, are then transferred to the [Claim smart contract](src/L2/L2Claim.sol). This systematic distribution is critical for ensuring that the tokens are correctly assigned to their intended recipients across the different network layers as part of the project's requirements. + +### Lisk L2 Staking +Staking L2 tokens creates additional utility of L2 Lisk token by allowing user to stake an amount of token for a certain period of time allowing them to earn daily rewards and contribute to Governance. -The process ensures that each address specified in the `accounts.json` file receives the designated amount of tokens accurately. Any remaining Lisk Tokens, those not allocated to the addresses listed in the file, are then transferred to the [Claim smart contract](src/L2/L2Claim.sol). This systematic distribution is critical for ensuring that the tokens are correctly assigned to their intended recipients across the different network layers as part of the project's requirements. +Implementation of L2 staking functionality is separated into; +- `L2LockingPosition` contract represents staking positions as an implementation of ERC721 (NFT) interface. +- `L2Staking` contract manages interactions with `L2LockingPosition` contract. It restricts manipulation of staking positions represented by `LockingPostion` structure to the creator role. +- `L2Reward` contract exposes the public API to stakers, enabling them to lock funds, manipulate existing position they own and claim rewards. ### Smart Contract Ownership diff --git a/documentation/diagrams/contracts_overview.png b/documentation/diagrams/contracts_overview.png new file mode 100644 index 00000000..3832e7c5 Binary files /dev/null and b/documentation/diagrams/contracts_overview.png differ diff --git a/documentation/diagrams/create_proposal.png b/documentation/diagrams/create_proposal.png new file mode 100644 index 00000000..214d3c7c Binary files /dev/null and b/documentation/diagrams/create_proposal.png differ diff --git a/documentation/diagrams/delegating.png b/documentation/diagrams/delegating.png new file mode 100644 index 00000000..7cb6efb9 Binary files /dev/null and b/documentation/diagrams/delegating.png differ diff --git a/documentation/diagrams/lock_unlock.png b/documentation/diagrams/lock_unlock.png new file mode 100644 index 00000000..0068a44d Binary files /dev/null and b/documentation/diagrams/lock_unlock.png differ diff --git a/documentation/diagrams/queue.png b/documentation/diagrams/queue.png new file mode 100644 index 00000000..ff7afce2 Binary files /dev/null and b/documentation/diagrams/queue.png differ diff --git a/documentation/staking-governance.md b/documentation/staking-governance.md new file mode 100644 index 00000000..b096563a --- /dev/null +++ b/documentation/staking-governance.md @@ -0,0 +1,85 @@ +# Staking and Lisk DAO + +## Overview + +In general, staking and governance in Lisk is inspired by the veToken Model and [this staking proposal for the Arbitrum DAO](https://snapshot.org/#/arbitrumfoundation.eth/proposal/0xf22530295daee96dffd7f70854475c06216a4d3594929672f71c12bf638bb0c8). The basic idea is that token holders have the option to lock their tokens in Lisk for a duration between 2 weeks and 2 years in return for: + +- **voting power** depending on the locked amount with the option of a boost for longer paused unlocking countdown +- **staking rewards** depending on the locked amount and locking duration + +This design with selectable locking duration helps to align token holders with the long term success of the Lisk project. + +## Locking Mechanism + +The mechanism for locking tokens works as follows: + +- Users can lock tokens for a specific locking duration (between 2 weeks and 2 years). After the locking duration ends, the users can redeem their tokens. +- Users can modify their locking positions (increase amount, extend locking duration) at any time. +- Users have the option to pause their locking period countdown. That means, the remaining locking duration remains fixed until the user decides to resume it. Note that this results in higher voting power and staking rewards as described below. +- Each user may have multiple locking positions. +- Locking positions are represented using NFTs for composability into DeFi and potential future uses. +- In case a user wants to unlock earlier than the end of the locking duration, there is a fast unlock option. The fast unlock implies a penalty, i.e., an amount that is deducted from the locked LSK tokens. The penalty is set to `0.5 * lockedAmount * (remainingLockingDurationInDays / maximumLockingDuration)`, where `maximumLockingDuration` is set to 2 years. Users can then redeem their tokens after a 3-day emergency locking period. + - Confiscated tokens are immediately redirected to the staking rewards pool to be distributed over the next 14 days period on top of the guaranteed rewards. + - Users do not have the option to cancel the fast un-stake during this 3 day window. + +## Voting Power + +The voting power for a locked amount of tokens is computed as follows: + +- Generally, locked tokens provide a voting power proportional to the amount of locked tokens. Concretely, 1 locked LSK provides one unit of voting power. +- In case a user pauses their locking period countdown, they receives a boost of the voting power meaning the voting power is set to `lockedAmount * (1 + remainingLockingDurationInDays/365)`. Hence, the voting power can be increased by up to 200%. + +## Staking Rewards + +Users receive rewards for their locking tokens as follows: + +- Rewards are calculated on a daily basis, based on the amount locked and the remaining locking duration. Concretely, the weight for a locked amount is given by `lockedAmount * (remainingLockingDurationInDays + 150)`. The total daily amount of staking rewards is then shared by all users proportional to their weight. +- Users can claim their rewards (and restake them immediately if they want) at any time. + +## Onchain Governance + +The main aspects of our onchain governance system are planned as follows: + +- We use OpenZeppelin’s Governor contract framework and also manage the Lisk DAO treasury with it. +- The following parameters are used: + - **Proposal threshold**: For creating a proposal, the voting power of the proposer must be at least 300,000, i.e., the value corresponding to 100,000 LSK locked for 2 years where the countdown is paused. + - **Quorum**: For a proposal to pass, the “yes” and “abstain” votes must sum up at least to 22,500,000, i.e., the value that corresponds to 7,500,000 LSK locked for 2 years where the countdown is paused. +- A proposal is accepted if the quorum, as defined above, is reached, and if there are strictly more “yes” than “no” votes. +- The onchain governance allows the following two proposal types: + - **Funding proposals**: A proposal for receiving a certain amount of funds to an address. If passed, the recipient receives the amount given in the proposal. + - **General proposal**: Generic proposals about protocol parameters or the project direction in general. +- For the UI, we integrate with Tally. In particular, users will be able to delegate their tokens to delegates who can vote on their behalf. + +## Contracts Overview + +![Contracts Overview](diagrams/contracts_overview.png) + +The graphic above shows all contracts involved in the staking and governance system and their responsibilities. Additionally, Tally is show which serves as the front end for governance. + +### User Interaction + +### Locking/Unlocking + +![Locking/Unlocking/Modifying](diagrams/lock_unlock.png) + +There are two ways how the user can lock/unlock/modify a staking position. In the first one, the user calls the Staking contract. The Staking contract will create/delete/modify a locking position in the Locking Position Contract which in turn forwards a call to the Voting Power Contract to adjust the voting power of the owner of the locking position. The Voting Power contract will emit events which will be used by Tally for indexing. For locking positions created like this, the user will not receive any rewards. + +In the second way, the user calls the Reward contract. The Reward contract will call lock/unlock/modify in the Staking contract, which will trigger the same forwarded calls as in the first way. The difference is that the user will receive rewards for locking positions create this way. + +### Delegating + +![Delegating](diagrams/delegating.png) + +For delegating, the user is interacting with the Voting Power contract directly. The Voting Power contract will emit events which will be used by Tally for indexing. + +### Creating Proposals and Voting + +![Creating Proposals](diagrams/create_proposal.png) + +For creating a proposal or voting on a proposal, the user is interacting with the Governor contract. The Governor contract will request the voting power of the proposer/voter in order to see if the user has enough voting power to create a proposal or for counting the voting results. Moreover, the Governor contract will emit events which will be used by Tally for indexing. + +### Queueing and Executing Proposals + +![Queueing](diagrams/queue.png) + +If a proposal has an attached execution, e.g. a transfer of some treasury funds, and the proposal passed, then the proposal must be queued and then executed. For this, a user (this can be any user) must interact with the Governor contract. This one is forwarding the queue/execute operation to the Timelock Controller contract, and the Governor contract additionally emits events which Tally uses for indexing. The Governor contract is the only account that is allowed to queue proposals at the Timelock Controller. As all executions are eventually executed by the Timelock Controller, contracts owned by the Lisk DAO must be owned by the Timelock Controller, and the DAO treasury must be held by it as well.