Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tests and and compatibility with deposit contract changes #21

Merged
merged 5 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
# Delegated Staking

This repo contains the contracts and scripts needed to activate a validator that users can stake ZIL with. Currently, there are two variants of the contracts. When delegating stake to the liquid variant, users receive a non-rebasing **liquid staking token** (LST) that anyone can send to the validator's delegation contract later on to withdraw the staked ZIL plus the corresponding share of the validator rewards. When delegating stake to the non-liquid variant, the delegator can withdraw rewards.
This repository contains the contracts and scripts needed to activate a validator that users can delegate stake to. Currently, there are two variants of the contracts:
1. When delegating stake to the liquid variant, users receive a non-rebasing **liquid staking token** (LST) that anyone can send to the validator's contract later on to withdraw the stake plus the corresponding share of the validator rewards.
1. When delegating stake to the non-liquid variant, the users can regularly withdraw their share of the rewards without withdrawing their stake.

## Prerequisites
Install Foundry (https://book.getfoundry.sh/getting-started/installation) and the OpenZeppelin contracts before proceeding with the deployment:
```
forge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit
forge install OpenZeppelin/openzeppelin-contracts --no-commit
```
The Zilliqa 2.0 deposit contract must be compiled for the tests in this repository to work. Specify the folder containing the `deposit.sol` file in `remappings.txt`:
The Zilliqa 2.0 deposit contract must be compiled for the tests included in this repository to work. Specify the folder containing the `deposit.sol` file in `remappings.txt`:
```
@zilliqa/zq2/=/home/user/zq2/zilliqa/src/contracts/
```

## Contract Deployment
The delegation contract is used by delegators to stake and unstake ZIL with the respective validator. It acts as the validator node's control address and interacts with the `Deposit` system contract.
The delegation contract is used by delegators to stake and unstake ZIL with the respective validator. It acts as the validator node's control address and interacts with the deposit contract.

`BaseDelegation` is an abstract contract that concrete implementations inherit from.
`LiquidDelegation` is the initial version of the liquid staking variant of the delegation contract that creates a `NonRebasingLST` contract when it is initialized. `LiquidDelegationV2` contains the full implementation including the LST price calculation and other features. `NonLiquidDelegation` is the initial version of the non-liquid staking variant of the delegation contract. `NonLiquidDelegationV2` contains the full implementation that allows delegators to withdraw rewards.

`LiquidDelegation` is the initial implementation of the liquid staking variant of the delegation contract that creates a `NonRebasingLST` contract when it is initialized. `LiquidDelegationV2` implements all other features. `NonLiquidDelegation` is the initial implementation of the non-liquid staking variant of the delegation contract that allows delegators to withdraw rewards.

Before running the deployment script, set the `PRIVATE_KEY` environment variable to the private key of the contract owner. Note that only the contract owner will be able to upgrade the contract, change the commission and activate the node as a validator.
Before running the deployment script, set the `PRIVATE_KEY` environment variable to the private key of the contract owner. Note that only the contract owner will be able to upgrade the contract, change the commission rate and activate the node as a validator.

To deploy `LiquidDelegation` run
```bash
Expand All @@ -40,13 +42,13 @@ You will see an output like this:
Owner is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
```

You will need the proxy address from the above output in all commands below. If you have the address of a proxy contract but don't know which variant of staking it supports, run
You and your delegators will need the proxy address from the above output in all commands below. If you know the address of a proxy contract but don't know which variant of staking it supports, run
```bash
forge script script/variant_Delegation.s.sol --rpc-url http://localhost:4201 --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2
```
The output will be `ILiquidStaking`, `INonLiquidStaking` or none of them if the address is not a valid delegation contract.

To upgrade the contract to `LiquidDelegationV2` or `NonLiquidDelegationV2` depending on the staking model it implements, run
To use the delegation contract, upgrade it to the latest version of `LiquidDelegationV2` or `NonLiquidDelegationV2` depending on the staking model it implements, by running
```bash
forge script script/upgrade_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2
```
Expand All @@ -60,6 +62,9 @@ The output will look like this:
Upgraded to version: 2
```

To adapt the contract to your needs, create your own copy of `LiquidDelegationV2` or `NonLiquidDelegationV2` and run the above upgrade script again.


## Contract Configuration

Now or at a later time you can set the commission on the rewards the validator earns to e.g. 10% as follows:
Expand All @@ -85,6 +90,7 @@ forge script script/commission_Delegation.s.sol --rpc-url http://localhost:4201
```
using `same` for the second argument to leave the commission percentage unchanged and `true` for the third argument. Replacing the second argument with `same` and the third argument with `false` only displays the current commission rate.


## Validator Activation
If your node's account has enough ZIL for the minimum stake required, you can activate your node as a validator with a deposit of e.g. 10 million ZIL. Run
```bash
Expand All @@ -110,12 +116,13 @@ to deposit all of it.

Note that the deposit will not take effect and the node will not start earning rewards until the epoch after next.


## Staking and Unstaking
If the delegation contract has been deployed and upgraded to the latest version, the validator can accept delegations. In order to stake e.g. 200 ZIL, run
Once the delegation contract has been deployed and upgraded to the latest version, your node can accept delegations. In order to stake e.g. 200 ZIL, your delegators must run
```bash
forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 200000000000000000000 --private-key 0x...
```
with the private key of the delegator account. Make sure the account's balance can cover the transaction fees plus the 200 ZIL to be delegated.
with the private key of their account. It's important to make sure the account's balance can cover the transaction fees plus the 200 ZIL to be delegated.

The output will look like this for liquid staking:
```
Expand All @@ -140,18 +147,18 @@ Due to the fact that the above output was generated based on the local script ex
cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g'
```

You can copy the LST address from the above output and add it to your wallet to transfer your liquid staking tokens to another account if you want to.
Your delegators can copy the LST address from the above output and add it to their wallet to transfer their liquid staking tokens to another account if they want to.

To query the current price of an LST, run
```bash
cast to-unit $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getPrice()(uint256)" --block latest --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') ether
```

To unstake e.g. 100 LST, run
To unstake e.g. 100 LST (liquid variant) or 100 ZIL (non-liquid variant), run
```bash
forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 100000000000000000000 --private-key 0x...
```
with the private key of an account that holds some LST.
using the private key of an account that holds some LST in case of the liquid variant or using the private key of the delegator account in case of the non-liquid variant.

The output will look like this for liquid staking:
```
Expand All @@ -175,7 +182,7 @@ The ZIL balance hasn't increased because the unstaked amount can not be transfer
```bash
forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x...
```
with the private key of an account that unstaked some LST.
with the private key of the account that unstaked in the previous step above.

The output will look like this:
```
Expand All @@ -184,36 +191,38 @@ The output will look like this:
Staker balance after: 99798095485861371162343 wei
```

To query how much ZIL you can already claim, run
To query how much ZIL a user can already claim, run
```bash
cast to-unit $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getClaimable()(uint256)" --from 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --block latest --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') ether
```
with the user's address as an argument.


## Staking and Withdrawing Rewards
In the liquid staking variant, only you as the node operator can stake the rewards accrued by the node. To do so, run
```bash
forge script script/stakeRewards_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x...
```

In the non-liquid variant of staking, delegators can stake or withdraw their share of the rewards earned by the validator. To query the amount of rewards available, run
In the non-liquid variant of staking, your delegators can stake or withdraw their share of the rewards. To query the amount of rewards available, run
```bash
cast to-unit $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "rewards()(uint256)" --from 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --block latest --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') ether
```

In case you haven't withdrawn rewards for a long time during which many delegators staked or unstaked, the gas used by the above function might hit the block limit. In this case you can withdraw rewards from the period between the (un)staking until which you withdrew rewards last time and the `n`th subsequent (un)staking. Choose a number `0 <= n <= 11000` e.g. `100` and run
In case users haven't withdrawn rewards for a long time during which many delegators staked or unstaked, the gas used by the above function might hit the block limit. In this case rewards can be withdrawn from the period between the (un)staking until which they were withdrawn last time and the `n`th subsequent (un)staking. This can be repeated several times to withdraw all rewards using multiple transactions. To calculate the rewards that can be withdrawn in the next transaction, choose a number `0 <= n <= 11000` e.g. `100` and run
```bash
cast to-unit $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "rewards(uint64)(uint256)" 100 --from 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --block latest --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') ether
```
Note that `n` actually denotes the number of additional (un)stakings so that at least one is always reflected in the result, even if you specified `n = 0`.

To withdraw 1 ZIL of rewards using `n = 100`, run
To withdraw e.g. 1 ZIL of rewards using `n = 100`, run
```bash
forge script script/withdrawRewards_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, string, string)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000000000000000000 100 --private-key 0x...
```
with the private key of a staked account. To withdrawn as much as possible with the given value of `n` set the amount to `all`. To withdraw the chosen amount without setting `n` replace `n` with `all`. To withdraw all rewards replace both the amount and `n` with `all`.
with the private key of a delegator account. To withdraw as much as possible with the given value of `n` set the amount to `all`. To withdraw the chosen amount without setting `n` replace `n` with `all`. To withdraw all rewards replace both the amount and `n` with `all`.

Last but not least, in order to stake rewards instead of withdrawing them, run
Last but not least, in order to stake rewards instead of withdrawing them, your delegators can run
```bash
forge script script/stakeRewards_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x...
```
with the private key of a staked account.
using the private key of their account.
12 changes: 12 additions & 0 deletions src/BaseDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ abstract contract BaseDelegation is Delegation, PausableUpgradeable, Ownable2Ste
require(success, "deposit failed");
}

function deposit(
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public virtual payable;

function deposit2(
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public virtual;

function _increaseDeposit(uint256 amount) internal virtual {
// topup the deposit only if already activated as a validator
if (_isActivated()) {
Expand Down
16 changes: 16 additions & 0 deletions src/LiquidDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ contract LiquidDelegation is BaseDelegation, ILiquidDelegation {
return $.lst;
}

function deposit(
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public override payable {
revert("not implemented");
}

function deposit2(
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public override {
revert("not implemented");
}

function stake() external override payable {
revert("not implemented");
}
Expand Down
4 changes: 2 additions & 2 deletions src/LiquidDelegationV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ contract LiquidDelegationV2 is BaseDelegation, ILiquidDelegation {
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public onlyOwner {
) public override onlyOwner {
_deposit(
blsPubKey,
peerId,
Expand All @@ -67,7 +67,7 @@ contract LiquidDelegationV2 is BaseDelegation, ILiquidDelegation {
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public payable onlyOwner {
) public override payable onlyOwner {
_deposit(
blsPubKey,
peerId,
Expand Down
16 changes: 16 additions & 0 deletions src/NonLiquidDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ contract NonLiquidDelegation is BaseDelegation, INonLiquidDelegation {
__BaseDelegation_init(initialOwner);
}

function deposit(
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public override payable {
revert("not implemented");
}

function deposit2(
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public override {
revert("not implemented");
}

function stake() external payable override {
revert("not implemented");
}
Expand Down
4 changes: 2 additions & 2 deletions src/NonLiquidDelegationV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ contract NonLiquidDelegationV2 is BaseDelegation, INonLiquidDelegation {
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public onlyOwner {
) public override onlyOwner {
_deposit(
blsPubKey,
peerId,
Expand All @@ -136,7 +136,7 @@ contract NonLiquidDelegationV2 is BaseDelegation, INonLiquidDelegation {
bytes calldata blsPubKey,
bytes calldata peerId,
bytes calldata signature
) public payable onlyOwner {
) public payable override onlyOwner {
_deposit(
blsPubKey,
peerId,
Expand Down
28 changes: 21 additions & 7 deletions state.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

if [ $# -ne 2 ]; then
echo "Provide the delegation contract address and a staker address as arguments."
if [ $# -lt 2 ]; then
echo "Provide the delegation contract address, a staker address and optionally, a block number as arguments."
exit 1
fi

Expand All @@ -12,8 +12,13 @@ if [[ "$variant" == "$temp" ]]; then
exit 1
fi

block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201)
block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16)
if [ $# -eq 3 ]; then
block_num=$3
block=$(echo $block_num | cast to-hex --base-in 10)
else
block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201)
block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16)
fi
echo $(date +"%T,%3N") $block_num

owner=$(cast call $1 "owner()(address)" --block $block_num --rpc-url http://localhost:4201)
Expand Down Expand Up @@ -71,6 +76,15 @@ claimable=$(cast call $1 "getClaimable()(uint256)" --from $2 --block $block_num
echo staker claimable: $(cast to-unit $claimable ether) ZIL

echo validator deposit: $(cast to-unit $stake ether) ZIL
balance=$(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16)
echo validator balance: $(cast to-unit $balance ether) ZIL
echo pending withdrawals: $(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') wei

validatorBalance=$(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16)
echo validator balance: $(cast to-unit $validatorBalance ether) ZIL

pendingWithdrawals=$(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g')
echo pending withdrawals: $(cast to-unit $pendingWithdrawals ether) ZIL

totalStake=$(cast call 0x000000000000000000005a494C4445504F534954 "getFutureTotalStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g')
echo total stake: $(cast to-unit $totalStake ether) ZIL

depositBalance=$(cast rpc eth_getBalance 0x000000000000000000005a494C4445504F534954 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16)
echo deposit balance: $(cast to-unit $depositBalance ether) ZIL
Loading
Loading