Skip to content

Commit

Permalink
Merge master to main (#1)
Browse files Browse the repository at this point in the history
* initial version without unbonding period

* Delete test/Counter.t.sol

* Delete script/Counter.s.sol

* commented out forge fmt check

* deleted broadcast files

* excluded broadcast from tracking
  • Loading branch information
DrZoltanFazekas authored Oct 8, 2024
1 parent ae14e67 commit 4712ae1
Show file tree
Hide file tree
Showing 15 changed files with 886 additions and 1 deletion.
45 changes: 45 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Show Forge version
run: |
forge --version
# - name: Run Forge fmt
# run: |
# forge fmt --check
# id: fmt

- name: Run Forge build
run: |
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Compiler files
cache/
out/
lib/
broadcast/

# Ignores development broadcast logs
#!/broadcast
#/broadcast/*/31337/
#/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
121 changes: 120 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,120 @@
# liquid_staking
# Liquid Staking

This repo contains the contracts and scripts needed to activate a validator that users can stake ZIL with. When delegating stake, 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.

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
```

## 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. `DelegationV1` is the initial implementation of the delegation contract is upgradeable: `DelegationV2` deploys a `NonRebasingLST` contract when it is initialized and `DelegationV3` adds the newest features.

The delegation contract shall be deployed and upgraded by the account with the private key that was used to run the validator node and was used to generate its BLS keypair and peer id. Make sure the `PRIVATE_KEY` environment variable is set accordingly.

To deploy `DelegationV1` run
```
forge script script/deploy_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy
```
You will see an output like this:
```
Signer is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
Proxy deployed: 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2
Implementation deployed: 0x7C623e01c5ce2e313C223ef2aEc1Ae5C6d12D9DD
Deployed version: 1
Owner is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
```

You will need the proxy address from the above output in all commands below.

To upgrade the contract to `DelegationV2`, run
```
forge script script/upgrade_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2
```

The output will look like this:
```
Signer is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
Upgrading from version: 1
Owner is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
New implementation deployed: 0x64Fa96a67910956141cc481a43f242C045c10165
Upgraded to version: 2
```

To upgrade the contract to `DelegationV3`, replace line 33 in `upgrade_Delegation.s.sol` with
```solidity
new DelegationV3()
```
and run
```
forge script script/upgrade_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2
```
again.

The output will look like this:
```
Signer is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
Upgrading from version: 2
Owner is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77
New implementation deployed: 0x90A65311b6C7246FFD1F212C123cfE351a6d65A9
Upgraded to version: 3
```

## Validator Activation
Now you are ready to use the contract to activate your node as a validator with a deposit of e.g. 10 million ZIL. Run
```
cast send --legacy --value 10000000ether --rpc-url https://api.zq2-devnet.zilliqa.com --private-key $PRIVATE_KEY \
0x7a0b7e6d24ede78260c9ddbd98e828b0e11a8ea2 "deposit(bytes,bytes,bytes)" \
0x92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c \
0x002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f \
0xb14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a
```
with the BLS public key, the peer id and the BLS signature of your node. Note that the peer id must be converted from base58 to hex.

Make sure your node's account has the 10 million ZIL and your node is fully synced before you run the above command.

Note that the reward address registered for your validator node will be the address of the delegation contract (the proxy contract to be more precise).

## Staking and Unstaking
If the above transaction was successful and the node became a validator, it can accept delegations. In order to stake e.g. 200 ZIL, run
```
forge script script/stake_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --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.

The output will look like this:
```
Running version: 3
Current stake: 10000000000000000000000000
Current rewards: 110314207650273223687
LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83
Owner balance: 10000000000000000000000000
Staker balance: 0
Staker balance: 199993793908430833324
```

Note that the staker LST balance in the output will be different from the actual LST balance which you can query by running
```
cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --rpc-url https://api.zq2-devnet.zilliqa.com
```
This is due to the fact that the above output was generated based on the local script execution before the transaction got submitted to the network.

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.

Last but not least, to unstake, run
```
forge script script/unstake_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x...
```
with the private key of an account that holds some LST.

The output will look like this:
```
Running version: 3
Current stake: 10000000000000000000000000
Current rewards: 331912568306010928520
LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83
Owner balance: 10000000000000000000000000
Staker balance: 199993784619390291653
Staker balance: 0
```
7 changes: 7 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
evm_version = 'shanghai'

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
56 changes: 56 additions & 0 deletions script/deploy_Delegation.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;

import {Script} from "forge-std/Script.sol";
import {Delegation} from "src/Delegation.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "forge-std/console.sol";

contract Deploy is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address owner = vm.addr(deployerPrivateKey);
console.log("Signer is %s", owner);

vm.startBroadcast(deployerPrivateKey);

address implementation = address(
//new Delegation{salt: "zilliqa"}()
new Delegation()
);

bytes memory initializerCall = abi.encodeWithSelector(
Delegation.initialize.selector,
owner
);

address payable proxy = payable(
//new ERC1967Proxy{salt: "zilliqa"}(implementation, initializerCall)
new ERC1967Proxy(implementation, initializerCall)
);

console.log(
"Proxy deployed: %s \r\n Implementation deployed: %s",
proxy,
implementation
);

Delegation delegation = Delegation(
proxy
);

delegation.stake();
delegation.unstake();
delegation.claim();

console.log("Deployed version: %s",
delegation.version()
);

console.log("Owner is %s",
delegation.owner()
);

vm.stopBroadcast();
}
}
78 changes: 78 additions & 0 deletions script/deposit_Delegation.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;

import {Script} from "forge-std/Script.sol";
import {NonRebasingLST} from "src/NonRebasingLST.sol";
import {DelegationV3} from "src/DelegationV3.sol";
import "forge-std/console.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract Deposit is Script {
function run(address payable proxy) external {

uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address owner = vm.addr(deployerPrivateKey);

//address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2);

DelegationV3 delegation = DelegationV3(
proxy
);
/*
console.log("Running version: %s",
delegation.version()
);
*/
//TODO: output the arguments to use with cast send since forge script will fail when it tries to execute the script locally and can't call the BLS signature verification precompile
/*vm.broadcast(deployerPrivateKey);
delegation.deposit{
value: 10_000_000 ether
}(
bytes(hex"92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c"),
bytes(hex"002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f"), //"12D3KooWQDT1rcThrxoSmnCt9n35jrhy5wo4BHsM5JuVz8LstQpN"
bytes(hex"b14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a")
);
console.log("Current stake: %s \r\n Current rewards: %s",
delegation.getStake(),
delegation.getRewards()
);
*/
bytes memory input = abi.encodeWithSignature(
"deposit(bytes,bytes,bytes)",
bytes(hex"92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c"),
bytes(hex"002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f"), //"12D3KooWQDT1rcThrxoSmnCt9n35jrhy5wo4BHsM5JuVz8LstQpN"
bytes(hex"b14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a")
);
string memory output = 'cast send';
output = string.concat(output, ' --legacy --value 10000000ether --rpc-url https://api.zq2-devnet.zilliqa.com --private-key ');
output = string.concat(output, Strings.toHexString(deployerPrivateKey));
output = string.concat(output, ' ');
output = string.concat(output, Strings.toHexString(address(delegation)));
/*console.log("%s \\", output);
console.logBytes(input);*/
output = string.concat(output, ' "deposit(bytes,bytes,bytes)"');
output = string.concat(output, ' 0x92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c');
output = string.concat(output, ' 0x002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f');
output = string.concat(output, ' 0xb14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a');
console.log(output);

// use this only for testing if deposit transaction not possible (e.g. no fully synced node available)
/*delegation.setup(
bytes(hex"b0447d886f8499bc0fd4aa21da63d71a0175ddd005d217a00c5304e1272e4a79a7df0ecb878a343582c9f2ca78c8c17f"),
bytes(hex"0024080112203f260505ee97570cbc034831097eddf177c4a49151dffb129abdc209329cc7e0")
);
*/
/*
NonRebasingLST lst = NonRebasingLST(delegation.getLST());
console.log("LST address: %s",
address(lst)
);
console.log("Owner LST balance: %s",
lst.balanceOf(owner)
);
*/
}
}
Loading

0 comments on commit 4712ae1

Please sign in to comment.