From bd4e53e0e002b432d569e7df91ecd37e2aeae210 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Thu, 10 Oct 2024 22:47:26 +0200 Subject: [PATCH 01/15] removed logging --- src/DelegationV3.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/DelegationV3.sol b/src/DelegationV3.sol index df4a635..6383f30 100644 --- a/src/DelegationV3.sol +++ b/src/DelegationV3.sol @@ -82,18 +82,18 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade require(success, "deposit failed"); } - event Log(uint256 _totalSupply, uint256 _msgValue, uint256 _getStake, uint256 _getReward, uint256 shares); + //event Log(uint256 _totalSupply, uint256 _msgValue, uint256 _getStake, uint256 _getReward, uint256 shares); function stake() public payable whenNotPaused { require(msg.value >= MIN_DELEGATION, "delegated amount too low"); //TODO: topup deposit by msg.value so that msg.value becomes part of getStake() Storage storage $ = _getStorage(); - uint256 _totalSupply = NonRebasingLST($.lst).totalSupply(); - uint256 _msgValue = msg.value; - uint256 _getRewards = getRewards(); - uint256 _getStake = getStake(); - //uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); - uint256 shares = _totalSupply * _msgValue / (_getStake + _getRewards); - emit Log(_totalSupply, _msgValue, _getStake, _getRewards, shares); + //uint256 _totalSupply = NonRebasingLST($.lst).totalSupply(); + //uint256 _msgValue = msg.value; + //uint256 _getRewards = getRewards(); + //uint256 _getStake = getStake(); + uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); + //uint256 shares = _totalSupply * _msgValue / (_getStake + _getRewards); + //emit Log(_totalSupply, _msgValue, _getStake, _getRewards, shares); NonRebasingLST($.lst).mint(msg.sender, shares); emit Staked(msg.sender, msg.value, shares); } From 182bbde2c6f6f8db777d8384bc2a5f580ecdd361 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Thu, 10 Oct 2024 22:47:26 +0200 Subject: [PATCH 02/15] removed logging --- src/DelegationV3.sol | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/DelegationV3.sol b/src/DelegationV3.sol index df4a635..a3c3172 100644 --- a/src/DelegationV3.sol +++ b/src/DelegationV3.sol @@ -82,18 +82,12 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade require(success, "deposit failed"); } - event Log(uint256 _totalSupply, uint256 _msgValue, uint256 _getStake, uint256 _getReward, uint256 shares); function stake() public payable whenNotPaused { require(msg.value >= MIN_DELEGATION, "delegated amount too low"); - //TODO: topup deposit by msg.value so that msg.value becomes part of getStake() + //TODO: topup the deposit by msg.value so that msg.value becomes part of getStake(), + // currently it's part of getRewards() since this contrac is the reward address Storage storage $ = _getStorage(); - uint256 _totalSupply = NonRebasingLST($.lst).totalSupply(); - uint256 _msgValue = msg.value; - uint256 _getRewards = getRewards(); - uint256 _getStake = getStake(); - //uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); - uint256 shares = _totalSupply * _msgValue / (_getStake + _getRewards); - emit Log(_totalSupply, _msgValue, _getStake, _getRewards, shares); + uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); NonRebasingLST($.lst).mint(msg.sender, shares); emit Staked(msg.sender, msg.value, shares); } @@ -102,8 +96,8 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade Storage storage $ = _getStorage(); NonRebasingLST($.lst).burn(msg.sender, shares); uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply(); - //TODO: deduct the commission - //TODO: don't transfer the amount, msg.sender can claim it after the unbonding period + //TODO: deduct the commission from the rewards but not from the deposit + //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period (bool success, bytes memory data) = msg.sender.call{ value: amount }(""); From e947a22f93a679569d1dd41c9547a30e536c4a76 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Fri, 11 Oct 2024 18:45:49 +0200 Subject: [PATCH 03/15] implemented commission --- src/DelegationV3.sol | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/DelegationV3.sol b/src/DelegationV3.sol index a3c3172..ce4f803 100644 --- a/src/DelegationV3.sol +++ b/src/DelegationV3.sol @@ -16,6 +16,7 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade bytes blsPubKey; bytes peerId; address lst; + uint16 commission; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Delegation")) - 1)) & ~bytes32(uint256(0xff)) @@ -29,6 +30,7 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade uint256 public constant MIN_DELEGATION = 100 ether; address public constant DEPOSIT_CONTRACT = 0x000000000000000000005a494C4445504F534954; + uint16 public constant DIVISOR = 10_000; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -95,8 +97,8 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade function unstake(uint256 shares) public whenNotPaused { Storage storage $ = _getStorage(); NonRebasingLST($.lst).burn(msg.sender, shares); - uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply(); - //TODO: deduct the commission from the rewards but not from the deposit + uint256 commission = (getRewards() * $.commission / DIVISOR) * shares / NonRebasingLST($.lst).totalSupply(); + uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply() - commission; //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period (bool success, bytes memory data) = msg.sender.call{ value: amount @@ -105,6 +107,12 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade emit UnStaked(msg.sender, amount, shares); } + function setCommission(uint16 _commission) public onlyOwner { + require(_commission < DIVISOR, "invalid commission"); + Storage storage $ = _getStorage(); + $.commission = _commission; + } + function claim() public whenNotPaused { // } From 4ce6913b947e4162159ed320f917113a73793596 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Fri, 11 Oct 2024 19:54:26 +0200 Subject: [PATCH 04/15] Implement getCommission() and extend readme --- README.md | 22 +++++++++------ script/commission_Delegation.s.sol | 44 ++++++++++++++++++++++++++++++ src/DelegationV3.sol | 6 ++++ 3 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 script/commission_Delegation.s.sol diff --git a/README.md b/README.md index 05d8e55..823197a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The delegation contract is used by delegators to stake and unstake ZIL with the 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 -``` +```bash forge script script/deploy_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy ``` You will see an output like this: @@ -29,7 +29,7 @@ You will see an output like this: You will need the proxy address from the above output in all commands below. To upgrade the contract to `DelegationV2`, run -``` +```bash forge script script/upgrade_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 ``` @@ -46,11 +46,10 @@ To upgrade the contract to `DelegationV3`, replace line 33 in `upgrade_Delegatio ```solidity new DelegationV3() ``` -and run -``` +and run once again +```bash 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: ``` @@ -61,9 +60,14 @@ The output will look like this: Upgraded to version: 3 ``` +Now (or at a later time) you can set the commission on the rewards the validator earns to e.g. 10%: +```bash +forge script script/commission_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 +``` + ## 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 -``` +```bash cast send --legacy --value 10000000ether --rpc-url https://api.zq2-devnet.zilliqa.com --private-key $PRIVATE_KEY \ 0x7a0b7e6d24ede78260c9ddbd98e828b0e11a8ea2 "deposit(bytes,bytes,bytes)" \ 0x92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c \ @@ -78,7 +82,7 @@ Note that the reward address registered for your validator node will be the addr ## 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 -``` +```bash 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. @@ -95,7 +99,7 @@ The output will look like this: ``` Note that the staker LST balance in the output will be different from the actual LST balance which you can query by running -``` +```bash 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. @@ -103,7 +107,7 @@ This is due to the fact that the above output was generated based on the local s 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 -``` +```bash 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. diff --git a/script/commission_Delegation.s.sol b/script/commission_Delegation.s.sol new file mode 100644 index 0000000..78ca3ad --- /dev/null +++ b/script/commission_Delegation.s.sol @@ -0,0 +1,44 @@ +// 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"; + +contract Stake is Script { + function run(address payable proxy) external { + + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + //address owner = vm.addr(deployerPrivateKey); + + DelegationV3 delegation = DelegationV3( + proxy + ); + + console.log("Running version: %s", + delegation.version() + ); + + console.log("Current stake: %s \r\n Current rewards: %s", + delegation.getStake(), + delegation.getRewards() + ); + + NonRebasingLST lst = NonRebasingLST(delegation.getLST()); + console.log("LST address: %s", + address(lst) + ); + + console.log("Current commission is: %s", + delegation.getCommission() + ); + + vm.broadcast(deployerPrivateKey); + delegation.setCommission(1000); + + console.log("New commission is: %s", + delegation.getCommission() + ); + } +} \ No newline at end of file diff --git a/src/DelegationV3.sol b/src/DelegationV3.sol index ce4f803..3740318 100644 --- a/src/DelegationV3.sol +++ b/src/DelegationV3.sol @@ -98,6 +98,7 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade Storage storage $ = _getStorage(); NonRebasingLST($.lst).burn(msg.sender, shares); uint256 commission = (getRewards() * $.commission / DIVISOR) * shares / NonRebasingLST($.lst).totalSupply(); + //TODO: transfer the commission to another wallet otherwise it remains part of the rewards uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply() - commission; //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period (bool success, bytes memory data) = msg.sender.call{ @@ -107,6 +108,11 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade emit UnStaked(msg.sender, amount, shares); } + function getCommission() public view returns(uint16) { + Storage storage $ = _getStorage(); + return $.commission; + } + function setCommission(uint16 _commission) public onlyOwner { require(_commission < DIVISOR, "invalid commission"); Storage storage $ = _getStorage(); From 9b603ec8d3cccff5e0d9e59b550a80041d01aeb9 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Fri, 11 Oct 2024 23:42:13 +0200 Subject: [PATCH 05/15] Implement getCommissionAddress() --- README.md | 12 ++++++++++- script/commission_Delegation.s.sol | 27 +++++++++++++----------- src/DelegationV3.sol | 34 +++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 823197a..d19e788 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,21 @@ The output will look like this: Upgraded to version: 3 ``` -Now (or at a later time) you can set the commission on the rewards the validator earns to e.g. 10%: +## Contract Configuration + +Now or at a later time you can set the commission on the rewards the validator earns to e.g. 10% and the wallet address the commission will be sent to e.g. the validator node's address: ```bash forge script script/commission_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 ``` +The output will contain the following information: +``` + Running version: 3 + LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 + Current commission rate and commission address is: 0.0% 0x0000000000000000000000000000000000000000 + New commission rate and commission address is: 10.0% 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 +``` + ## 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 ```bash diff --git a/script/commission_Delegation.s.sol b/script/commission_Delegation.s.sol index 78ca3ad..f451360 100644 --- a/script/commission_Delegation.s.sol +++ b/script/commission_Delegation.s.sol @@ -10,7 +10,7 @@ contract Stake is Script { function run(address payable proxy) external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - //address owner = vm.addr(deployerPrivateKey); + address owner = vm.addr(deployerPrivateKey); DelegationV3 delegation = DelegationV3( proxy @@ -20,25 +20,28 @@ contract Stake is Script { delegation.version() ); - console.log("Current stake: %s \r\n Current rewards: %s", - delegation.getStake(), - delegation.getRewards() - ); - NonRebasingLST lst = NonRebasingLST(delegation.getLST()); console.log("LST address: %s", address(lst) ); - console.log("Current commission is: %s", - delegation.getCommission() + console.log("Current commission rate and commission address: %s.%s%% %s", + uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), + uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100), + delegation.getCommissionAddress() ); - vm.broadcast(deployerPrivateKey); - delegation.setCommission(1000); + vm.startBroadcast(deployerPrivateKey); + + delegation.setCommissionNumerator(1000); + delegation.setCommissionAddress(owner); + + vm.stopBroadcast(); - console.log("New commission is: %s", - delegation.getCommission() + console.log("New commission rate and commission address: %s.%s%% %s", + uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), + uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100), + delegation.getCommissionAddress() ); } } \ No newline at end of file diff --git a/src/DelegationV3.sol b/src/DelegationV3.sol index 3740318..a19c19a 100644 --- a/src/DelegationV3.sol +++ b/src/DelegationV3.sol @@ -16,7 +16,8 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade bytes blsPubKey; bytes peerId; address lst; - uint16 commission; + uint16 commissionNumerator; + address commissionAddress; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Delegation")) - 1)) & ~bytes32(uint256(0xff)) @@ -30,7 +31,7 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade uint256 public constant MIN_DELEGATION = 100 ether; address public constant DEPOSIT_CONTRACT = 0x000000000000000000005a494C4445504F534954; - uint16 public constant DIVISOR = 10_000; + uint16 public constant DENOMINATOR = 10_000; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -97,26 +98,39 @@ contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgrade function unstake(uint256 shares) public whenNotPaused { Storage storage $ = _getStorage(); NonRebasingLST($.lst).burn(msg.sender, shares); - uint256 commission = (getRewards() * $.commission / DIVISOR) * shares / NonRebasingLST($.lst).totalSupply(); - //TODO: transfer the commission to another wallet otherwise it remains part of the rewards + uint256 commission = (getRewards() * $.commissionNumerator / DENOMINATOR) * shares / NonRebasingLST($.lst).totalSupply(); + (bool success, bytes memory data) = $.commissionAddress.call{ + value: commission + }(""); + require(success, "transfer of commission failed"); uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply() - commission; //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period - (bool success, bytes memory data) = msg.sender.call{ + (success, data) = msg.sender.call{ value: amount }(""); require(success, "transfer of funds failed"); emit UnStaked(msg.sender, amount, shares); } - function getCommission() public view returns(uint16) { + function getCommissionNumerator() public view returns(uint16) { + Storage storage $ = _getStorage(); + return $.commissionNumerator; + } + + function setCommissionNumerator(uint16 _commissionNumerator) public onlyOwner { + require(_commissionNumerator < DENOMINATOR, "invalid commission"); + Storage storage $ = _getStorage(); + $.commissionNumerator = _commissionNumerator; + } + + function getCommissionAddress() public view returns(address) { Storage storage $ = _getStorage(); - return $.commission; + return $.commissionAddress; } - function setCommission(uint16 _commission) public onlyOwner { - require(_commission < DIVISOR, "invalid commission"); + function setCommissionAddress(address _commissionAddress) public onlyOwner { Storage storage $ = _getStorage(); - $.commission = _commission; + $.commissionAddress = _commissionAddress; } function claim() public whenNotPaused { From 02db1dceec748d9219565c8550474562c29cdb19 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Mon, 14 Oct 2024 08:52:14 +0200 Subject: [PATCH 06/15] Introduce script params --- README.md | 46 +++---- script/commission_Delegation.s.sol | 10 +- script/deploy_Delegation.s.sol | 4 - script/deposit_Delegation.s.sol | 4 +- script/stake_Delegation.s.sol | 8 +- script/unstake_Delegation.s.sol | 11 +- script/upgrade_Delegation.s.sol | 1 - src/Delegation.sol | 25 ++-- src/DelegationV2.sol | 77 ++++++++++-- src/DelegationV3.sol | 189 ----------------------------- 10 files changed, 104 insertions(+), 271 deletions(-) delete mode 100644 src/DelegationV3.sol diff --git a/README.md b/README.md index d19e788..46558f3 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ 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 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. `Delegation` is the initial implementation of the delegation contract that creates a `NonRebasingLST` contract when it is initialized. `DelegationV2` implements staking, unstaking and other 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 +To deploy `Delegation` run ```bash -forge script script/deploy_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy +forge script script/deploy_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy ``` You will see an output like this: ``` @@ -30,7 +30,7 @@ You will need the proxy address from the above output in all commands below. To upgrade the contract to `DelegationV2`, run ```bash -forge script script/upgrade_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 +forge script script/upgrade_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 ``` The output will look like this: @@ -42,34 +42,16 @@ The output will look like this: Upgraded to version: 2 ``` -To upgrade the contract to `DelegationV3`, replace line 33 in `upgrade_Delegation.s.sol` with -```solidity -new DelegationV3() -``` -and run once again -```bash -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: 2 - Owner is 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 - New implementation deployed: 0x90A65311b6C7246FFD1F212C123cfE351a6d65A9 - Upgraded to version: 3 -``` - ## Contract Configuration Now or at a later time you can set the commission on the rewards the validator earns to e.g. 10% and the wallet address the commission will be sent to e.g. the validator node's address: ```bash -forge script script/commission_Delegation.s.sol --rpc-url https://api.zq2-devnet.zilliqa.com --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 +forge script script/commission_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint16, address)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 ``` The output will contain the following information: ``` - Running version: 3 + Running version: 2 LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 Current commission rate and commission address is: 0.0% 0x0000000000000000000000000000000000000000 New commission rate and commission address is: 10.0% 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 @@ -78,7 +60,7 @@ The output will contain the following information: ## 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 ```bash -cast send --legacy --value 10000000ether --rpc-url https://api.zq2-devnet.zilliqa.com --private-key $PRIVATE_KEY \ +cast send --legacy --value 10000000ether --rpc-url http://localhost:4201 --private-key $PRIVATE_KEY \ 0x7a0b7e6d24ede78260c9ddbd98e828b0e11a8ea2 "deposit(bytes,bytes,bytes)" \ 0x92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c \ 0x002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f \ @@ -93,13 +75,13 @@ Note that the reward address registered for your validator node will be the addr ## 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 ```bash -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... +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. The output will look like this: ``` - Running version: 3 + Running version: 2 Current stake: 10000000000000000000000000 Current rewards: 110314207650273223687 LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 @@ -110,25 +92,25 @@ The output will look like this: Note that the staker LST balance in the output will be different from the actual LST balance which you can query by running ```bash -cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --rpc-url https://api.zq2-devnet.zilliqa.com +cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g' ``` 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 +Last but not least, to unstake e.g. 100 LST, run ```bash -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... +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. The output will look like this: ``` - Running version: 3 + Running version: 2 Current stake: 10000000000000000000000000 Current rewards: 331912568306010928520 LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 Owner balance: 10000000000000000000000000 Staker balance: 199993784619390291653 - Staker balance: 0 + Staker balance: 99993784619390291653 ``` diff --git a/script/commission_Delegation.s.sol b/script/commission_Delegation.s.sol index f451360..800daf0 100644 --- a/script/commission_Delegation.s.sol +++ b/script/commission_Delegation.s.sol @@ -3,16 +3,16 @@ 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 {DelegationV2} from "src/DelegationV2.sol"; import "forge-std/console.sol"; contract Stake is Script { - function run(address payable proxy) external { + function run(address payable proxy, uint16 commissionNumerator, address commissionAddress) external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); - DelegationV3 delegation = DelegationV3( + DelegationV2 delegation = DelegationV2( proxy ); @@ -33,8 +33,8 @@ contract Stake is Script { vm.startBroadcast(deployerPrivateKey); - delegation.setCommissionNumerator(1000); - delegation.setCommissionAddress(owner); + delegation.setCommissionNumerator(commissionNumerator); + delegation.setCommissionAddress(commissionAddress); vm.stopBroadcast(); diff --git a/script/deploy_Delegation.s.sol b/script/deploy_Delegation.s.sol index 833f0fe..9ad67ad 100644 --- a/script/deploy_Delegation.s.sol +++ b/script/deploy_Delegation.s.sol @@ -39,10 +39,6 @@ contract Deploy is Script { proxy ); - delegation.stake(); - delegation.unstake(); - delegation.claim(); - console.log("Deployed version: %s", delegation.version() ); diff --git a/script/deposit_Delegation.s.sol b/script/deposit_Delegation.s.sol index 9bfa664..fa60f49 100644 --- a/script/deposit_Delegation.s.sol +++ b/script/deposit_Delegation.s.sol @@ -3,7 +3,7 @@ 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 {DelegationV2} from "src/DelegationV2.sol"; import "forge-std/console.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; @@ -15,7 +15,7 @@ contract Deposit is Script { //address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2); - DelegationV3 delegation = DelegationV3( + DelegationV2 delegation = DelegationV2( proxy ); /* diff --git a/script/stake_Delegation.s.sol b/script/stake_Delegation.s.sol index 8910a33..7a1c400 100644 --- a/script/stake_Delegation.s.sol +++ b/script/stake_Delegation.s.sol @@ -3,11 +3,11 @@ 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 {DelegationV2} from "src/DelegationV2.sol"; import "forge-std/console.sol"; contract Stake is Script { - function run(address payable proxy) external { + function run(address payable proxy, uint256 amount) external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); @@ -17,7 +17,7 @@ contract Stake is Script { address staker = msg.sender; //address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2); - DelegationV3 delegation = DelegationV3( + DelegationV2 delegation = DelegationV2( proxy ); @@ -47,7 +47,7 @@ contract Stake is Script { vm.broadcast(); delegation.stake{ - value: 200 ether + value: amount }(); console.log("Staker balance: %s", diff --git a/script/unstake_Delegation.s.sol b/script/unstake_Delegation.s.sol index 7fbc5aa..64cc109 100644 --- a/script/unstake_Delegation.s.sol +++ b/script/unstake_Delegation.s.sol @@ -3,11 +3,11 @@ 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 {DelegationV2} from "src/DelegationV2.sol"; import "forge-std/console.sol"; contract Unstake is Script { - function run(address payable proxy) external { + function run(address payable proxy, uint256 amount) external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); @@ -17,7 +17,7 @@ contract Unstake is Script { address staker = msg.sender; //address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2); - DelegationV3 delegation = DelegationV3( + DelegationV2 delegation = DelegationV2( proxy ); @@ -39,16 +39,15 @@ contract Unstake is Script { lst.balanceOf(owner) ); - uint256 stakerBalance = lst.balanceOf(staker); console.log("Staker balance: %s", - stakerBalance + lst.balanceOf(staker) ); //vm.broadcast(staker); vm.broadcast(); delegation.unstake( - stakerBalance + amount ); console.log("Staker balance: %s", diff --git a/script/upgrade_Delegation.s.sol b/script/upgrade_Delegation.s.sol index ac9027e..e0e886f 100644 --- a/script/upgrade_Delegation.s.sol +++ b/script/upgrade_Delegation.s.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.26; import {Script} from "forge-std/Script.sol"; import {Delegation} from "src/Delegation.sol"; import {DelegationV2} from "src/DelegationV2.sol"; -import {DelegationV3} from "src/DelegationV3.sol"; import "forge-std/console.sol"; contract Upgrade is Script { diff --git a/src/Delegation.sol b/src/Delegation.sol index ce55d4c..50d190c 100644 --- a/src/Delegation.sol +++ b/src/Delegation.sol @@ -5,14 +5,14 @@ import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "src/NonRebasingLST.sol"; // the contract is supposed to be deployed with the node's signer account contract Delegation is Initializable, PausableUpgradeable, Ownable2StepUpgradeable, UUPSUpgradeable { /// @custom:storage-location erc7201:zilliqa.storage.Delegation struct Storage { - bytes blsPubKey; - bytes peerId; + address lst; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Delegation")) - 1)) & ~bytes32(uint256(0xff)) @@ -40,29 +40,18 @@ contract Delegation is Initializable, PausableUpgradeable, Ownable2StepUpgradeab __Ownable_init(initialOwner); __Ownable2Step_init(); __UUPSUpgradeable_init(); + Storage storage $ = _getStorage(); + $.lst = address(new NonRebasingLST(address(this))); } function _authorizeUpgrade(address newImplementation) internal onlyOwner override {} - // this is to receive rewards receive() payable external { } - // called by the node's account that deployed this contract and is its owner - function deposit( - bytes calldata blsPubKey, - bytes calldata peerId, - bytes calldata signature - ) public payable onlyOwner { + function getLST() public view returns(address) { Storage storage $ = _getStorage(); - $.blsPubKey = blsPubKey; - $.peerId = peerId; - } - - function stake() public payable {} - - function unstake() public {} - - function claim() public{} + return $.lst; + } } diff --git a/src/DelegationV2.sol b/src/DelegationV2.sol index 59fd839..251f693 100644 --- a/src/DelegationV2.sol +++ b/src/DelegationV2.sol @@ -13,13 +13,15 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade /// @custom:storage-location erc7201:zilliqa.storage.Delegation struct Storage { + address lst; bytes blsPubKey; bytes peerId; - address lst; + uint16 commissionNumerator; + address commissionAddress; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Delegation")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant STORAGE_POSITION = 0x4432bdf0e567007e5ad3c8ad839a7f885ef69723eaa659dd9f06e98a97274300; + bytes32 private constant STORAGE_POSITION = 0x669e9cfa685336547bc6d91346afdd259f6cd8c0cb6d0b16603b5fa60cb48800; function _getStorage() private pure returns (Storage storage $) { assembly { @@ -29,6 +31,7 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade uint256 public constant MIN_DELEGATION = 100 ether; address public constant DEPOSIT_CONTRACT = 0x000000000000000000005a494C4445504F534954; + uint16 public constant DENOMINATOR = 10_000; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -40,8 +43,6 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade } function reinitialize() reinitializer(version() + 1) public { - Storage storage $ = _getStorage(); - $.lst = address(new NonRebasingLST(address(this))); } function _authorizeUpgrade(address newImplementation) internal onlyOwner override {} @@ -49,8 +50,12 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade event Staked(address indexed delegator, uint256 amount, uint256 shares); event UnStaked(address indexed delegator, uint256 amount, uint256 shares); - // only for test purposes - receive() payable external {} + // currently not called as there is no transaction for issuing rewards + receive() payable external { + require (msg.sender == 0x0000000000000000000000000000000000000000, "rewards must be issues by zero address"); + // topup deposit by msg.value to restake the rewards + // or use them for instant stake withdrawals + } // called by the node's account that deployed this contract and is its owner // with at least the minimum stake to request activation as a validator @@ -82,7 +87,8 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade function stake() public payable whenNotPaused { require(msg.value >= MIN_DELEGATION, "delegated amount too low"); - //TODO: topup deposit by msg.value so that msg.value becomes part of getStake() + //TODO: topup the deposit by msg.value so that msg.value becomes part of getStake(), + // currently it's part of getRewards() since this contrac is the reward address Storage storage $ = _getStorage(); uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); NonRebasingLST($.lst).mint(msg.sender, shares); @@ -92,20 +98,53 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade function unstake(uint256 shares) public whenNotPaused { Storage storage $ = _getStorage(); NonRebasingLST($.lst).burn(msg.sender, shares); - uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply(); - //TODO: don't the transfer the amount, msg.sender can claim it after the unbonding period - msg.sender.call{ + uint256 commission = (getRewards() * $.commissionNumerator / DENOMINATOR) * shares / NonRebasingLST($.lst).totalSupply(); + (bool success, bytes memory data) = $.commissionAddress.call{ + value: commission + }(""); + require(success, "transfer of commission failed"); + uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply() - commission; + //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period + (success, data) = msg.sender.call{ value: amount }(""); + require(success, "transfer of funds failed"); emit UnStaked(msg.sender, amount, shares); } + function getCommissionNumerator() public view returns(uint16) { + Storage storage $ = _getStorage(); + return $.commissionNumerator; + } + + function setCommissionNumerator(uint16 _commissionNumerator) public onlyOwner { + require(_commissionNumerator < DENOMINATOR, "invalid commission"); + Storage storage $ = _getStorage(); + $.commissionNumerator = _commissionNumerator; + } + + function getCommissionAddress() public view returns(address) { + Storage storage $ = _getStorage(); + return $.commissionAddress; + } + + function setCommissionAddress(address _commissionAddress) public onlyOwner { + Storage storage $ = _getStorage(); + $.commissionAddress = _commissionAddress; + } + function claim() public whenNotPaused { + // } function restake() public onlyOwner{ + // } +/* function getRewards() public view returns(uint256){ + return 24391829365079365070369; + } +*/ function getRewards() public view returns(uint256) { Storage storage $ = _getStorage(); (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( @@ -116,6 +155,11 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade return rewardAddress.balance; } +/* //TODO: replace with the below getStake2() function once stake() tops up the deposit + function getStake() public view returns(uint256) { + return getStake2() + address(this).balance; + } +*/ function getStake() public view returns(uint256) { Storage storage $ = _getStorage(); (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( @@ -130,4 +174,17 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade return $.lst; } + // only for testing purposes, will be removed later + function setup(bytes calldata blsPubKey, bytes calldata peerId) public onlyOwner { + Storage storage $ = _getStorage(); + $.blsPubKey = blsPubKey; + $.peerId = peerId; + (bool success, bytes memory data) = owner().call{ + value: address(this).balance + }(""); + require(success, "transfer failed"); + $.lst = address(new NonRebasingLST(address(this))); + NonRebasingLST($.lst).mint(owner(), getStake()); + } + } \ No newline at end of file diff --git a/src/DelegationV3.sol b/src/DelegationV3.sol deleted file mode 100644 index a19c19a..0000000 --- a/src/DelegationV3.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.26; - -import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "src/NonRebasingLST.sol"; - -// the contract is supposed to be deployed with the node's signer account -// TODO: add events -contract DelegationV3 is Initializable, PausableUpgradeable, Ownable2StepUpgradeable, UUPSUpgradeable { - - /// @custom:storage-location erc7201:zilliqa.storage.Delegation - struct Storage { - bytes blsPubKey; - bytes peerId; - address lst; - uint16 commissionNumerator; - address commissionAddress; - } - - // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Delegation")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant STORAGE_POSITION = 0x4432bdf0e567007e5ad3c8ad839a7f885ef69723eaa659dd9f06e98a97274300; - - function _getStorage() private pure returns (Storage storage $) { - assembly { - $.slot := STORAGE_POSITION - } - } - - uint256 public constant MIN_DELEGATION = 100 ether; - address public constant DEPOSIT_CONTRACT = 0x000000000000000000005a494C4445504F534954; - uint16 public constant DENOMINATOR = 10_000; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - function version() public view returns(uint64) { - return _getInitializedVersion(); - } - - function reinitialize() reinitializer(version() + 1) public { - } - - function _authorizeUpgrade(address newImplementation) internal onlyOwner override {} - - event Staked(address indexed delegator, uint256 amount, uint256 shares); - event UnStaked(address indexed delegator, uint256 amount, uint256 shares); - - // currently not called as there is no transaction for issuing rewards - receive() payable external { - require (msg.sender == 0x0000000000000000000000000000000000000000, "rewards must be issues by zero address"); - // topup deposit by msg.value to restake the rewards - // or use them for instant stake withdrawals - } - - // called by the node's account that deployed this contract and is its owner - // with at least the minimum stake to request activation as a validator - function deposit( - bytes calldata blsPubKey, - bytes calldata peerId, - bytes calldata signature - ) public payable onlyOwner { - Storage storage $ = _getStorage(); - $.blsPubKey = blsPubKey; - $.peerId = peerId; - (bool success, bytes memory data) = DEPOSIT_CONTRACT.call{ - value: msg.value - }( - //abi.encodeWithSignature("deposit(bytes,bytes,bytes,address,address)", - //TODO: replace next line with the previous one once the signer address is implemented - abi.encodeWithSignature("deposit(bytes,bytes,bytes,address)", - blsPubKey, - peerId, - signature, - address(this) - //TODO: enable next line once the signer address is implemented - //owner() - ) - ); - NonRebasingLST($.lst).mint(owner(), msg.value); - require(success, "deposit failed"); - } - - function stake() public payable whenNotPaused { - require(msg.value >= MIN_DELEGATION, "delegated amount too low"); - //TODO: topup the deposit by msg.value so that msg.value becomes part of getStake(), - // currently it's part of getRewards() since this contrac is the reward address - Storage storage $ = _getStorage(); - uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); - NonRebasingLST($.lst).mint(msg.sender, shares); - emit Staked(msg.sender, msg.value, shares); - } - - function unstake(uint256 shares) public whenNotPaused { - Storage storage $ = _getStorage(); - NonRebasingLST($.lst).burn(msg.sender, shares); - uint256 commission = (getRewards() * $.commissionNumerator / DENOMINATOR) * shares / NonRebasingLST($.lst).totalSupply(); - (bool success, bytes memory data) = $.commissionAddress.call{ - value: commission - }(""); - require(success, "transfer of commission failed"); - uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply() - commission; - //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period - (success, data) = msg.sender.call{ - value: amount - }(""); - require(success, "transfer of funds failed"); - emit UnStaked(msg.sender, amount, shares); - } - - function getCommissionNumerator() public view returns(uint16) { - Storage storage $ = _getStorage(); - return $.commissionNumerator; - } - - function setCommissionNumerator(uint16 _commissionNumerator) public onlyOwner { - require(_commissionNumerator < DENOMINATOR, "invalid commission"); - Storage storage $ = _getStorage(); - $.commissionNumerator = _commissionNumerator; - } - - function getCommissionAddress() public view returns(address) { - Storage storage $ = _getStorage(); - return $.commissionAddress; - } - - function setCommissionAddress(address _commissionAddress) public onlyOwner { - Storage storage $ = _getStorage(); - $.commissionAddress = _commissionAddress; - } - - function claim() public whenNotPaused { - // - } - - function restake() public onlyOwner{ - // - } - -/* function getRewards() public view returns(uint256){ - return 24391829365079365070369; - } -*/ - function getRewards() public view returns(uint256) { - Storage storage $ = _getStorage(); - (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( - abi.encodeWithSignature("getRewardAddress(bytes)", $.blsPubKey) - ); - require(success, "could not retrieve reward address"); - address rewardAddress = abi.decode(data, (address)); - return rewardAddress.balance; - } - -/* //TODO: replace with the below getStake2() function once stake() tops up the deposit - function getStake() public view returns(uint256) { - return getStake2() + address(this).balance; - } -*/ - function getStake() public view returns(uint256) { - Storage storage $ = _getStorage(); - (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( - abi.encodeWithSignature("getStake(bytes)", $.blsPubKey) - ); - require(success, "could not retrieve staked amount"); - return abi.decode(data, (uint256)); - } - - function getLST() public view returns(address) { - Storage storage $ = _getStorage(); - return $.lst; - } - - // only for testing purposes, will be removed later - function setup(bytes calldata blsPubKey, bytes calldata peerId) public onlyOwner { - Storage storage $ = _getStorage(); - $.blsPubKey = blsPubKey; - $.peerId = peerId; - owner().call{ - value: address(this).balance - }(""); - $.lst = address(new NonRebasingLST(address(this))); - NonRebasingLST($.lst).mint(owner(), getStake()); - } - -} \ No newline at end of file From 680aa2fba6c7ffee92af2fb60d52ea19cab3ac2f Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Wed, 16 Oct 2024 17:49:18 +0200 Subject: [PATCH 07/15] Implement claiming after unbonding period --- README.md | 48 ++- script/claim_Delegation.s.sol | 33 ++ script/commission_Delegation.s.sol | 20 +- script/deploy_Delegation.s.sol | 2 - script/deposit_Delegation.s.sol | 78 ----- script/stake_Delegation.s.sol | 15 +- script/unstake_Delegation.s.sol | 19 +- script/upgrade_Delegation.s.sol | 2 - src/Delegation.sol | 1 - src/DelegationV2.sol | 207 +++++++---- src/Deposit.sol | 223 ++++++++++++ test/Delegation.t.sol | 545 +++++++++++++++++++++++++++++ 12 files changed, 1004 insertions(+), 189 deletions(-) create mode 100644 script/claim_Delegation.s.sol delete mode 100644 script/deposit_Delegation.s.sol create mode 100644 src/Deposit.sol create mode 100644 test/Delegation.t.sol diff --git a/README.md b/README.md index 46558f3..48e5cbe 100644 --- a/README.md +++ b/README.md @@ -44,17 +44,22 @@ The output will look like this: ## Contract Configuration -Now or at a later time you can set the commission on the rewards the validator earns to e.g. 10% and the wallet address the commission will be sent to e.g. the validator node's address: +Now or at a later time you can set the commission on the rewards the validator earns to e.g. 10% as follows: ```bash -forge script script/commission_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint16, address)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 +forge script script/commission_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint16)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000 ``` The output will contain the following information: ``` Running version: 2 LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 - Current commission rate and commission address is: 0.0% 0x0000000000000000000000000000000000000000 - New commission rate and commission address is: 10.0% 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 + Old commission rate: 0.0% + New commission rate: 10.0% +``` + +Note that the commission rate is specified as an integer to be devided by the `DENOMINATOR` which can be retrieved from the delegation contract: +```bash +cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "DENOMINATOR()(uint256)" --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g' ``` ## Validator Activation @@ -82,12 +87,12 @@ with the private key of the delegator account. Make sure the account's balance c The output will look like this: ``` Running version: 2 - Current stake: 10000000000000000000000000 - Current rewards: 110314207650273223687 + Current stake: 10000000000000000000000000 ZIL + Current rewards: 110314207650273223687 ZIL LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 - Owner balance: 10000000000000000000000000 - Staker balance: 0 - Staker balance: 199993793908430833324 + Owner balance: 10000000000000000000000000 LST + Staker balance before: 99899145245801454561224 ZIL 0 LST + Staker balance after: 99699145245801454561224 ZIL 199993793908430833324 LST ``` Note that the staker LST balance in the output will be different from the actual LST balance which you can query by running @@ -98,7 +103,7 @@ This is due to the fact that the above output was generated based on the local s 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 e.g. 100 LST, run +To unstake e.g. 100 LST, 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... ``` @@ -107,10 +112,23 @@ with the private key of an account that holds some LST. The output will look like this: ``` Running version: 2 - Current stake: 10000000000000000000000000 - Current rewards: 331912568306010928520 + Current stake: 10000000000000000000000000 ZIL + Current rewards: 331912568306010928520 ZIL LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 - Owner balance: 10000000000000000000000000 - Staker balance: 199993784619390291653 - Staker balance: 99993784619390291653 + Owner balance: 10000000000000000000000000 LST + Staker balance before: 99698814298179759361224 ZIL 199993784619390291653 LST + Staker balance after: 99698814298179759361224 ZIL 99993784619390291653 LST +``` + +Last but not least, to claim the amount that is available after the unbonding period, run +```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. + +The output will look like this: +``` + Running version: 2 + Staker balance before: 99698086421983460161224 ZIL + Staker balance after: 99798095485861371162343 ZIL +``` \ No newline at end of file diff --git a/script/claim_Delegation.s.sol b/script/claim_Delegation.s.sol new file mode 100644 index 0000000..78a75fc --- /dev/null +++ b/script/claim_Delegation.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.26; + +import {Script} from "forge-std/Script.sol"; +import {DelegationV2} from "src/DelegationV2.sol"; +import "forge-std/console.sol"; + +contract Claim is Script { + function run(address payable proxy) external { + + address staker = msg.sender; + + DelegationV2 delegation = DelegationV2( + proxy + ); + + console.log("Running version: %s", + delegation.version() + ); + + console.log("Staker balance before: %s ZIL", + staker.balance + ); + + vm.broadcast(); + + delegation.claim(); + + console.log("Staker balance after: %s ZIL", + staker.balance + ); + } +} \ No newline at end of file diff --git a/script/commission_Delegation.s.sol b/script/commission_Delegation.s.sol index 800daf0..fad63f1 100644 --- a/script/commission_Delegation.s.sol +++ b/script/commission_Delegation.s.sol @@ -7,10 +7,9 @@ import {DelegationV2} from "src/DelegationV2.sol"; import "forge-std/console.sol"; contract Stake is Script { - function run(address payable proxy, uint16 commissionNumerator, address commissionAddress) external { + function run(address payable proxy, uint16 commissionNumerator) external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address owner = vm.addr(deployerPrivateKey); DelegationV2 delegation = DelegationV2( proxy @@ -25,23 +24,20 @@ contract Stake is Script { address(lst) ); - console.log("Current commission rate and commission address: %s.%s%% %s", + console.log("Old commission rate: %s.%s%%", uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), - uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100), - delegation.getCommissionAddress() + //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% + uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) ); - vm.startBroadcast(deployerPrivateKey); + vm.broadcast(deployerPrivateKey); delegation.setCommissionNumerator(commissionNumerator); - delegation.setCommissionAddress(commissionAddress); - vm.stopBroadcast(); - - console.log("New commission rate and commission address: %s.%s%% %s", + console.log("New commission rate: %s.%s%%", uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), - uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100), - delegation.getCommissionAddress() + //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% + uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) ); } } \ No newline at end of file diff --git a/script/deploy_Delegation.s.sol b/script/deploy_Delegation.s.sol index 9ad67ad..fde9d04 100644 --- a/script/deploy_Delegation.s.sol +++ b/script/deploy_Delegation.s.sol @@ -15,7 +15,6 @@ contract Deploy is Script { vm.startBroadcast(deployerPrivateKey); address implementation = address( - //new Delegation{salt: "zilliqa"}() new Delegation() ); @@ -25,7 +24,6 @@ contract Deploy is Script { ); address payable proxy = payable( - //new ERC1967Proxy{salt: "zilliqa"}(implementation, initializerCall) new ERC1967Proxy(implementation, initializerCall) ); diff --git a/script/deposit_Delegation.s.sol b/script/deposit_Delegation.s.sol deleted file mode 100644 index fa60f49..0000000 --- a/script/deposit_Delegation.s.sol +++ /dev/null @@ -1,78 +0,0 @@ -// 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 {DelegationV2} from "src/DelegationV2.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); - - DelegationV2 delegation = DelegationV2( - 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) - ); -*/ - } -} \ No newline at end of file diff --git a/script/stake_Delegation.s.sol b/script/stake_Delegation.s.sol index 7a1c400..79f2385 100644 --- a/script/stake_Delegation.s.sol +++ b/script/stake_Delegation.s.sol @@ -11,11 +11,7 @@ contract Stake is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); - //console.log("Owner is %s", owner); - - //address staker = 0xd819fFcE7A58b1E835c25617Db7b46a00888B013; address staker = msg.sender; - //address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2); DelegationV2 delegation = DelegationV2( proxy @@ -25,7 +21,7 @@ contract Stake is Script { delegation.version() ); - console.log("Current stake: %s \r\n Current rewards: %s", + console.log("Current stake: %s ZIL \r\n Current rewards: %s ZIL", delegation.getStake(), delegation.getRewards() ); @@ -35,22 +31,23 @@ contract Stake is Script { address(lst) ); - console.log("Owner balance: %s", + console.log("Owner balance: %s LST", lst.balanceOf(owner) ); - console.log("Staker balance: %s", + console.log("Staker balance before: %s ZIL %s LST", + staker.balance, lst.balanceOf(staker) ); - //vm.broadcast(staker); vm.broadcast(); delegation.stake{ value: amount }(); - console.log("Staker balance: %s", + console.log("Staker balance after: %s ZIL %s LST", + staker.balance, lst.balanceOf(staker) ); } diff --git a/script/unstake_Delegation.s.sol b/script/unstake_Delegation.s.sol index 64cc109..f9fab0b 100644 --- a/script/unstake_Delegation.s.sol +++ b/script/unstake_Delegation.s.sol @@ -11,11 +11,7 @@ contract Unstake is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); - //console.log("Owner is %s", owner); - - //address staker = 0xd819fFcE7A58b1E835c25617Db7b46a00888B013; address staker = msg.sender; - //address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2); DelegationV2 delegation = DelegationV2( proxy @@ -25,7 +21,7 @@ contract Unstake is Script { delegation.version() ); - console.log("Current stake: %s \r\n Current rewards: %s", + console.log("Current stake: %s ZIL \r\n Current rewards: %s ZIL", delegation.getStake(), delegation.getRewards() ); @@ -35,22 +31,27 @@ contract Unstake is Script { address(lst) ); - console.log("Owner balance: %s", + console.log("Owner LST balance: %s LST", lst.balanceOf(owner) ); - console.log("Staker balance: %s", + console.log("Staker balance before: %s ZIL %s LST", + staker.balance, lst.balanceOf(staker) ); - //vm.broadcast(staker); + if (amount == 0) { + amount = lst.balanceOf(staker); + } + vm.broadcast(); delegation.unstake( amount ); - console.log("Staker balance: %s", + console.log("Staker balance after: %s ZIL %s LST", + staker.balance, lst.balanceOf(staker) ); } diff --git a/script/upgrade_Delegation.s.sol b/script/upgrade_Delegation.s.sol index e0e886f..0ad8281 100644 --- a/script/upgrade_Delegation.s.sol +++ b/script/upgrade_Delegation.s.sol @@ -12,8 +12,6 @@ contract Upgrade is Script { address owner = vm.addr(deployerPrivateKey); console.log("Signer is %s", owner); - //address payable proxy = payable(0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2); - Delegation oldDelegation = Delegation( proxy ); diff --git a/src/Delegation.sol b/src/Delegation.sol index 50d190c..aff774c 100644 --- a/src/Delegation.sol +++ b/src/Delegation.sol @@ -7,7 +7,6 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "src/NonRebasingLST.sol"; -// the contract is supposed to be deployed with the node's signer account contract Delegation is Initializable, PausableUpgradeable, Ownable2StepUpgradeable, UUPSUpgradeable { /// @custom:storage-location erc7201:zilliqa.storage.Delegation diff --git a/src/DelegationV2.sol b/src/DelegationV2.sol index 251f693..df0748e 100644 --- a/src/DelegationV2.sol +++ b/src/DelegationV2.sol @@ -7,17 +7,52 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "src/NonRebasingLST.sol"; +library WithdrawalQueue { + + uint256 public constant UNBONDING_PERIOD = 30; //approx. 30s, used only for testing + + struct Item { + uint256 blockNumber; + uint256 amount; + } + + struct Fifo { + uint256 first; + uint256 last; + mapping(uint256 => Item) items; + } + + function queue(Fifo storage fifo, uint256 amount) internal { + fifo.items[fifo.last] = Item(block.number + UNBONDING_PERIOD, amount); + fifo.last++; + } + + function dequeue(Fifo storage fifo) internal returns(Item memory result) { + require(fifo.first < fifo.last, "queue empty"); + result = fifo.items[fifo.first]; + delete fifo.items[fifo.first]; + fifo.first++; + } + + function ready(Fifo storage fifo) internal view returns(bool) { + return fifo.first < fifo.last && fifo.items[fifo.first].blockNumber <= block.number; + } +} + // the contract is supposed to be deployed with the node's signer account -// TODO: add events contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgradeable, UUPSUpgradeable { + using WithdrawalQueue for WithdrawalQueue.Fifo; + /// @custom:storage-location erc7201:zilliqa.storage.Delegation struct Storage { address lst; bytes blsPubKey; bytes peerId; - uint16 commissionNumerator; - address commissionAddress; + uint256 commissionNumerator; + uint256 taxedRewards; + mapping(address => WithdrawalQueue.Fifo) withdrawals; + uint256 totalWithdrawals; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Delegation")) - 1)) & ~bytes32(uint256(0xff)) @@ -31,7 +66,7 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade uint256 public constant MIN_DELEGATION = 100 ether; address public constant DEPOSIT_CONTRACT = 0x000000000000000000005a494C4445504F534954; - uint16 public constant DENOMINATOR = 10_000; + uint256 public constant DENOMINATOR = 10_000; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -48,27 +83,29 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade function _authorizeUpgrade(address newImplementation) internal onlyOwner override {} event Staked(address indexed delegator, uint256 amount, uint256 shares); - event UnStaked(address indexed delegator, uint256 amount, uint256 shares); + event Unstaked(address indexed delegator, uint256 amount, uint256 shares); + event Claimed(address indexed delegator, uint256 amount); - // currently not called as there is no transaction for issuing rewards + // not called as there is no transaction for issuing rewards receive() payable external { require (msg.sender == 0x0000000000000000000000000000000000000000, "rewards must be issues by zero address"); - // topup deposit by msg.value to restake the rewards + // we could deduct the commission from msg.value and + // topup the deposit to restake the rewards // or use them for instant stake withdrawals } - // called by the node's account that deployed this contract and is its owner - // with at least the minimum stake to request activation as a validator - function deposit( + function _deposit( bytes calldata blsPubKey, bytes calldata peerId, - bytes calldata signature - ) public payable onlyOwner { + bytes calldata signature, + uint256 depositAmount + ) internal { Storage storage $ = _getStorage(); + require($.blsPubKey.length == 0, "deposit already performed"); $.blsPubKey = blsPubKey; $.peerId = peerId; (bool success, bytes memory data) = DEPOSIT_CONTRACT.call{ - value: msg.value + value: depositAmount }( //abi.encodeWithSignature("deposit(bytes,bytes,bytes,address,address)", //TODO: replace next line with the previous one once the signer address is implemented @@ -81,72 +118,136 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade //owner() ) ); - NonRebasingLST($.lst).mint(owner(), msg.value); require(success, "deposit failed"); + } + + // called by the node's account that deployed this contract and is its owner + // to request the node's activation as a validator using the delegated stake + function deposit2( + bytes calldata blsPubKey, + bytes calldata peerId, + bytes calldata signature + ) public onlyOwner { + _deposit( + blsPubKey, + peerId, + signature, + address(this).balance + ); + } + + // called by the node's account that deployed this contract and is its owner + // with at least the minimum stake to request the node's activation as a validator + // before any stake is delegated to it + function deposit( + bytes calldata blsPubKey, + bytes calldata peerId, + bytes calldata signature + ) public payable onlyOwner { + _deposit( + blsPubKey, + peerId, + signature, + msg.value + ); + Storage storage $ = _getStorage(); + require(NonRebasingLST($.lst).totalSupply() == 0, "stake already delegated"); + NonRebasingLST($.lst).mint(owner(), msg.value); } function stake() public payable whenNotPaused { require(msg.value >= MIN_DELEGATION, "delegated amount too low"); - //TODO: topup the deposit by msg.value so that msg.value becomes part of getStake(), - // currently it's part of getRewards() since this contrac is the reward address + uint256 shares; Storage storage $ = _getStorage(); - uint256 shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + getRewards()); + if ($.blsPubKey.length > 0) { + //TODO: topup the deposit by msg.value so that msg.value becomes part of getStake(), + // currently it's part of getRewards() since this contract is the reward address + } + taxRewards(); // before calculating the shares we must deduct the commission from the yet untaxed rewards + if (NonRebasingLST($.lst).totalSupply() == 0) + shares = msg.value; + else + shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + $.taxedRewards); NonRebasingLST($.lst).mint(msg.sender, shares); emit Staked(msg.sender, msg.value, shares); } function unstake(uint256 shares) public whenNotPaused { + uint256 amount; Storage storage $ = _getStorage(); NonRebasingLST($.lst).burn(msg.sender, shares); - uint256 commission = (getRewards() * $.commissionNumerator / DENOMINATOR) * shares / NonRebasingLST($.lst).totalSupply(); - (bool success, bytes memory data) = $.commissionAddress.call{ - value: commission - }(""); - require(success, "transfer of commission failed"); - uint256 amount = (getStake() + getRewards()) * shares / NonRebasingLST($.lst).totalSupply() - commission; - //TODO: store but don't transfer the amount, msg.sender can claim it after the unbonding period - (success, data) = msg.sender.call{ - value: amount - }(""); - require(success, "transfer of funds failed"); - emit UnStaked(msg.sender, amount, shares); + taxRewards(); // before calculating the amount we must deduct the commission from the yet untaxed rewards + if (NonRebasingLST($.lst).totalSupply() == 0) + amount = shares; + else + amount = (getStake() + $.taxedRewards) * shares / NonRebasingLST($.lst).totalSupply(); + $.withdrawals[msg.sender].queue(amount); + $.totalWithdrawals += amount; + if ($.blsPubKey.length > 0) { + //TODO: if the contract's balance is smaller than totalWithdrawals + // then withdraw the difference from the deposit contract + } + emit Unstaked(msg.sender, amount, shares); } - function getCommissionNumerator() public view returns(uint16) { + function getCommissionNumerator() public view returns(uint256) { Storage storage $ = _getStorage(); return $.commissionNumerator; } - function setCommissionNumerator(uint16 _commissionNumerator) public onlyOwner { + function setCommissionNumerator(uint256 _commissionNumerator) public onlyOwner { require(_commissionNumerator < DENOMINATOR, "invalid commission"); Storage storage $ = _getStorage(); $.commissionNumerator = _commissionNumerator; } - function getCommissionAddress() public view returns(address) { + function taxRewards() internal { Storage storage $ = _getStorage(); - return $.commissionAddress; + uint256 rewards = getRewards(); + uint256 commission = (rewards - $.taxedRewards) * $.commissionNumerator / DENOMINATOR; + $.taxedRewards = rewards - commission; + if (commission == 0) + return; + // commissions are not subject to the unbonding period + (bool success, bytes memory data) = owner().call{ + value: commission + }(""); + require(success, "transfer of commission failed"); } - function setCommissionAddress(address _commissionAddress) public onlyOwner { + function claim() public whenNotPaused { Storage storage $ = _getStorage(); - $.commissionAddress = _commissionAddress; + uint256 total; + while ($.withdrawals[msg.sender].ready()) + total += $.withdrawals[msg.sender].dequeue().amount; + /*if (total == 0) + return;*/ + taxRewards(); // before the balance changes we must deduct the commission from the yet untaxed rewards + //TODO: claim funds withdrawn from the deposit contract + (bool success, bytes memory data) = msg.sender.call{ + value: total + }(""); + require(success, "transfer of funds failed"); + $.totalWithdrawals -= total; + emit Claimed(msg.sender, total); } - function claim() public whenNotPaused { - // + //TODO: call restake every time someone wants to stake, unstake or claim? + function restake() public onlyOwner { + taxRewards(); // before the balance changes, we must deduct the commission from the yet untaxed rewards + //TODO: topup the deposit by address(this).balance - $.totalWithdrawals + // i.e. the rewards accrued minus the amount needed for the pending withdrawals } - function restake() public onlyOwner{ - // + function getTaxedRewards() public view returns(uint256) { + Storage storage $ = _getStorage(); + return $.taxedRewards; } -/* function getRewards() public view returns(uint256){ - return 24391829365079365070369; - } -*/ function getRewards() public view returns(uint256) { Storage storage $ = _getStorage(); + if ($.blsPubKey.length == 0) + return 0; (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( abi.encodeWithSignature("getRewardAddress(bytes)", $.blsPubKey) ); @@ -155,13 +256,10 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade return rewardAddress.balance; } -/* //TODO: replace with the below getStake2() function once stake() tops up the deposit - function getStake() public view returns(uint256) { - return getStake2() + address(this).balance; - } -*/ function getStake() public view returns(uint256) { Storage storage $ = _getStorage(); + if ($.blsPubKey.length == 0) + return address(this).balance; (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( abi.encodeWithSignature("getStake(bytes)", $.blsPubKey) ); @@ -174,17 +272,4 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade return $.lst; } - // only for testing purposes, will be removed later - function setup(bytes calldata blsPubKey, bytes calldata peerId) public onlyOwner { - Storage storage $ = _getStorage(); - $.blsPubKey = blsPubKey; - $.peerId = peerId; - (bool success, bytes memory data) = owner().call{ - value: address(this).balance - }(""); - require(success, "transfer failed"); - $.lst = address(new NonRebasingLST(address(this))); - NonRebasingLST($.lst).mint(owner(), getStake()); - } - } \ No newline at end of file diff --git a/src/Deposit.sol b/src/Deposit.sol new file mode 100644 index 0000000..48f2f6e --- /dev/null +++ b/src/Deposit.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +struct Staker { + // The index of this staker's `blsPubKey` in the `_stakerKeys` array, plus 1. 0 is used for non-existing entries. + uint256 keyIndex; + // Invariant: `balance >= minimumStake` + uint256 balance; + address rewardAddress; + bytes peerId; +} + +contract Deposit { + bytes[] _stakerKeys; + mapping(bytes => Staker) _stakersMap; + uint256 public totalStake; + + uint256 public _minimumStake; + uint256 public _maximumStakers; + + constructor(uint256 minimumStake, uint256 maximumStakers) { + _minimumStake = minimumStake; + _maximumStakers = maximumStakers; + } + + function leaderFromRandomness( + uint256 randomness + ) private view returns (bytes memory) { + // Get a random number in the inclusive range of 0 to (totalStake - 1) + uint256 position = randomness % totalStake; + uint256 cummulative_stake = 0; + + for (uint256 i = 0; i < _stakerKeys.length; i++) { + bytes storage stakerKey = _stakerKeys[i]; + Staker storage staker = _stakersMap[stakerKey]; + + cummulative_stake += staker.balance; + + if (position < cummulative_stake) { + return stakerKey; + } + } + + revert("Unable to select next leader"); + } + + function leader() public view returns (bytes memory) { + return leaderFromRandomness(uint256(block.prevrandao)); + } + + function leaderAtView( + uint256 viewNumber + ) public view returns (bytes memory) { + uint256 randomness = uint256( + keccak256(bytes.concat(bytes32(viewNumber))) + ); + return leaderFromRandomness(randomness); + } + + // Temporary function to manually remove a staker. Can be called by the reward address of any staker with more than + // 10% stake. Will be removed later in development. + function tempRemoveStaker(bytes calldata blsPubKey) public { + require(blsPubKey.length == 48); + + // Inefficient, but its fine because this is temporary. + for (uint256 i = 0; i < _stakerKeys.length; i++) { + bytes storage stakerKey = _stakerKeys[i]; + Staker storage staker = _stakersMap[stakerKey]; + + // Check if the call is authorised. + if ( + msg.sender == staker.rewardAddress && + staker.balance > (totalStake / 10) + ) { + // The call is authorised, so we can delete the specified staker. + Staker storage stakerToDelete = _stakersMap[blsPubKey]; + + // Delete this staker's key from `_stakerKeys`. Swap the last element in the array into the deleted position. + bytes storage swappedStakerKey = _stakerKeys[ + _stakerKeys.length - 1 + ]; + Staker storage swappedStaker = _stakersMap[swappedStakerKey]; + _stakerKeys[stakerToDelete.keyIndex - 1] = swappedStakerKey; + swappedStaker.keyIndex = stakerToDelete.keyIndex; + + // The last element is now the element we want to delete. + _stakerKeys.pop(); + + // Reduce the total stake, but don't refund to the removed staker + totalStake -= stakerToDelete.balance; + + // Delete the staker from `_stakersMap` too. + delete _stakersMap[blsPubKey]; + + return; + } + } + revert( + "call must come from a reward address corresponding to a staker with more than 10% stake" + ); + } + + // keep in-sync with zilliqa/src/precompiles.rs + function _popVerify( + bytes memory pubkey, + bytes memory signature + ) private view returns (bool) { + // mocked to make tests work + return true; + bytes memory input = abi.encodeWithSelector( + hex"bfd24965", // bytes4(keccak256("popVerify(bytes,bytes)")) + signature, + pubkey + ); + uint inputLength = input.length; + bytes memory output = new bytes(32); + bool success; + assembly { + success := staticcall( + gas(), + 0x5a494c80, // "ZIL\x80" + add(input, 0x20), + inputLength, + add(output, 0x20), + 32 + ) + } + require(success, "popVerify"); + bool result = abi.decode(output, (bool)); + return result; + } + + function deposit( + bytes calldata blsPubKey, + bytes calldata peerId, + bytes calldata signature, + address rewardAddress + ) public payable { + require(blsPubKey.length == 48); + require(peerId.length == 38); + require(signature.length == 96); + + require(_stakerKeys.length < _maximumStakers, "too many stakers"); + + // Verify signature as a proof-of-possession of the private key. + bool pop = _popVerify(blsPubKey, signature); + require(pop, "rogue key check"); + + uint256 keyIndex = _stakersMap[blsPubKey].keyIndex; + if (keyIndex == 0) { + // The staker will be at index `_stakerKeys.length`. We also need to add 1 to avoid the 0 sentinel value. + _stakersMap[blsPubKey].keyIndex = _stakerKeys.length + 1; + _stakerKeys.push(blsPubKey); + } + + _stakersMap[blsPubKey].balance += msg.value; + totalStake += msg.value; + + if (_stakersMap[blsPubKey].balance < _minimumStake) { + revert("stake less than minimum stake"); + } + + _stakersMap[blsPubKey].rewardAddress = rewardAddress; + _stakersMap[blsPubKey].peerId = peerId; + } + + function setStake( + bytes calldata blsPubKey, + bytes calldata peerId, + address rewardAddress, + uint256 amount + ) public { + require(msg.sender == address(0)); + require(blsPubKey.length == 48); + require(peerId.length == 38); + + if (amount < _minimumStake) { + revert("stake less than minimum stake"); + } + + totalStake -= _stakersMap[blsPubKey].balance; + _stakersMap[blsPubKey].balance = amount; + totalStake += amount; + _stakersMap[blsPubKey].rewardAddress = rewardAddress; + _stakersMap[blsPubKey].peerId = peerId; + uint256 keyIndex = _stakersMap[blsPubKey].keyIndex; + if (keyIndex == 0) { + // The staker will be at index `_stakerKeys.length`. We also need to add 1 to avoid the 0 sentinel value. + _stakersMap[blsPubKey].keyIndex = _stakerKeys.length + 1; + _stakerKeys.push(blsPubKey); + } + } + + function getStake(bytes calldata blsPubKey) public view returns (uint256) { + require(blsPubKey.length == 48); + + return _stakersMap[blsPubKey].balance; + } + + function getRewardAddress( + bytes calldata blsPubKey + ) public view returns (address) { + require(blsPubKey.length == 48); + if (_stakersMap[blsPubKey].rewardAddress == address(0)) { + revert("not staked"); + } + return _stakersMap[blsPubKey].rewardAddress; + } + + function getStakers() public view returns (bytes[] memory) { + return _stakerKeys; + } + + function getPeerId( + bytes calldata blsPubKey + ) public view returns (bytes memory) { + require(blsPubKey.length == 48); + if (_stakersMap[blsPubKey].rewardAddress == address(0)) { + revert("not staked"); + } + return _stakersMap[blsPubKey].peerId; + } +} \ No newline at end of file diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol new file mode 100644 index 0000000..966c766 --- /dev/null +++ b/test/Delegation.t.sol @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.26; + +import {Delegation} from "src/Delegation.sol"; +import {DelegationV2} from "src/DelegationV2.sol"; +import {NonRebasingLST} from "src/NonRebasingLST.sol"; +import {Deposit} from "src/Deposit.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Test, Vm} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +library Console { + function log(string memory format, uint256 amount) external { + string memory zeros = ""; + uint256 decimals = amount % 10**18; + while (decimals > 0 && decimals < 10**17) { + //console.log("%s %s", zeros, decimals); + zeros = string.concat(zeros, "0"); + decimals *= 10; + } + console.log( + format, + amount / 10**18, + zeros, + amount % 10**18 + ); + } +} + +contract DelegationTest is Test { + address payable proxy; + address owner; + address staker = 0xd819fFcE7A58b1E835c25617Db7b46a00888B013; + + function setUp() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + owner = vm.addr(deployerPrivateKey); + //console.log("Signer is %s", owner); + vm.deal(owner, 100_000 ether); + vm.startPrank(owner); + + address oldImplementation = address( + new Delegation() + ); + + bytes memory initializerCall = abi.encodeWithSelector( + Delegation.initialize.selector, + owner + ); + + proxy = payable( + new ERC1967Proxy(oldImplementation, initializerCall) + ); + /* + console.log( + "Proxy deployed: %s \r\n Implementation deployed: %s", + proxy, + oldImplementation + ); + //*/ + Delegation oldDelegation = Delegation( + proxy + ); + /* + console.log("Deployed version: %s", + oldDelegation.version() + ); + + console.log("Owner is %s", + oldDelegation.owner() + ); + //*/ + address payable newImplementation = payable( + new DelegationV2() + ); + /* + console.log("New implementation deployed: %s", + newImplementation + ); + //*/ + bytes memory reinitializerCall = abi.encodeWithSelector( + DelegationV2.reinitialize.selector + ); + + oldDelegation.upgradeToAndCall( + newImplementation, + reinitializerCall + ); + + DelegationV2 delegation = DelegationV2( + proxy + ); + /* + console.log("Upgraded to version: %s", + delegation.version() + ); + //*/ + NonRebasingLST lst = NonRebasingLST(delegation.getLST()); + /* + console.log("LST address: %s", + address(lst) + ); + + console.log("Old commission rate: %s.%s%%", + uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), + //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% + uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) + ); + //*/ + uint256 commissionNumerator = 1_000; + delegation.setCommissionNumerator(commissionNumerator); + /* + console.log("New commission rate: %s.%s%%", + uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), + //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% + uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) + ); + //*/ + + //vm.deployCodeTo("Deposit.sol", delegation.DEPOSIT_CONTRACT()); + vm.etch( + delegation.DEPOSIT_CONTRACT(), //0x000000000000000000005a494C4445504F534954, + address(new Deposit(10_000_000 ether, 256)).code + ); + vm.store(delegation.DEPOSIT_CONTRACT(), bytes32(uint256(3)), bytes32(uint256(10_000_000 ether))); + vm.store(delegation.DEPOSIT_CONTRACT(), bytes32(uint256(4)), bytes32(uint256(256))); + /* + console.log("Deposit._minimimStake() =", Deposit(delegation.DEPOSIT_CONTRACT())._minimumStake()); + console.log("Deposit._maximumStakers() =", Deposit(delegation.DEPOSIT_CONTRACT())._maximumStakers()); + //*/ + } + + function run( + uint256 depositAmount, + uint256 rewardsBefore, + uint256 delegatedAmount, + uint256 rewardsAfter, + uint256 blocksUntil, + bool initialDeposit + ) public { + DelegationV2 delegation = DelegationV2(proxy); + NonRebasingLST lst = NonRebasingLST(delegation.getLST()); + + if (initialDeposit) { + vm.deal(owner, owner.balance + depositAmount); + vm.startPrank(owner); + + delegation.deposit{ + value: depositAmount + }( + bytes(hex"92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c"), + bytes(hex"002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f"), + bytes(hex"b14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a") + ); + } else { + vm.deal(staker, staker.balance + depositAmount); + vm.startPrank(staker); + + vm.expectEmit( + true, + false, + false, + true, + address(delegation) + ); + emit DelegationV2.Staked( + staker, + depositAmount, + depositAmount + ); + + delegation.stake{ + value: depositAmount + }(); + + vm.startPrank(owner); + + delegation.deposit2( + bytes(hex"92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c"), + bytes(hex"002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f"), + bytes(hex"b14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a") + ); + } + + vm.deal(address(delegation), rewardsBefore); + vm.deal(staker, 100_000 ether); + vm.startPrank(staker); + + Console.log("Stake deposited before staking: %s.%s%s ZIL", + delegation.getStake() + ); + + Console.log("Rewards before staking: %s.%s%s ZIL", + delegation.getRewards() + ); + + Console.log("Staker balance before staking: %s.%s%s ZIL", + staker.balance + ); + + Console.log("Staker balance before staking: %s.%s%s LST", + lst.balanceOf(staker) + ); + + Console.log("Total supply before staking: %s.%s%s LST", + lst.totalSupply() + ); + + vm.recordLogs(); + + vm.expectEmit( + true, + false, + false, + false, + address(delegation) + ); + emit DelegationV2.Staked( + staker, + delegatedAmount, + lst.totalSupply() * delegatedAmount / (delegation.getStake() + delegation.getRewards()) + ); + + uint256 ownerZILBefore = delegation.owner().balance; + + delegation.stake{ + value: delegatedAmount + }(); + + uint256 ownerZILAfter = delegation.owner().balance; + + Vm.Log[] memory entries = vm.getRecordedLogs(); + uint256 loggedAmount; + uint256 loggedShares; + for (uint256 i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == keccak256("Staked(address,uint256,uint256)")) { + (loggedAmount, loggedShares) = abi.decode(entries[i].data, (uint256, uint256)); + //console.log(loggedAmount, loggedShares); + } + } + //console.log(delegatedAmount, (lst.totalSupply() - lst.balanceOf(staker)) * delegatedAmount / (delegation.getStake() + delegation.getTaxedRewards())); + //console.log(delegatedAmount, lst.balanceOf(staker)); + + Console.log("Owner commission after staking: %s.%s%s ZIL", + ownerZILAfter - ownerZILBefore + ); + + Console.log("Stake deposited after staking: %s.%s%s ZIL", + delegation.getStake() + ); + + Console.log("Rewards after staking: %s.%s%s ZIL", + delegation.getRewards() + ); + + Console.log("Staker balance after staking: %s.%s%s ZIL", + staker.balance + ); + + Console.log("Staker balance after staking: %s.%s%s LST", + lst.balanceOf(staker) + ); + + Console.log("Total supply after staking: %s.%s%s LST", + lst.totalSupply() + ); + + vm.deal(address(delegation), address(delegation).balance + rewardsAfter); + + Console.log("LST price: %s.%s%s", + 10**18 * (delegation.getStake() + delegation.getRewards()) / lst.totalSupply() + ); + + Console.log("LST value: %s.%s%s", + lst.balanceOf(staker) * (delegation.getStake() + delegation.getRewards()) / lst.totalSupply() + ); + + vm.recordLogs(); + + vm.expectEmit( + true, + false, + false, + false, + address(delegation) + ); + emit DelegationV2.Unstaked( + staker, + (delegation.getStake() + delegation.getRewards()) * lst.balanceOf(staker) / lst.totalSupply(), + lst.balanceOf(staker) + ); + + uint256 stakerLSTBefore = lst.balanceOf(staker); + ownerZILBefore = delegation.owner().balance; + + delegation.unstake( + initialDeposit ? lst.balanceOf(staker) : lst.balanceOf(staker) - depositAmount + ); + + uint256 stakerLSTAfter = lst.balanceOf(staker); + ownerZILAfter = delegation.owner().balance; + + entries = vm.getRecordedLogs(); + + for (uint256 i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == keccak256("Unstaked(address,uint256,uint256)")) { + (loggedAmount, loggedShares) = abi.decode(entries[i].data, (uint256, uint256)); + //console.log(loggedAmount, loggedShares); + } + } + //TODO: why is loggedAmount equal to the value below without adding back lst.totalSupply() + stakerLSTBefore although unstake() burns stakerLSTBefore before computing the amount? + //console.log((delegation.getStake() + delegation.getTaxedRewards()) * stakerLSTBefore / lst.totalSupply(), stakerLSTBefore - stakerLSTAfter); + + Console.log("Owner commission after unstaking: %s.%s%s ZIL", + ownerZILAfter - ownerZILBefore + ); + + Console.log("Stake deposited after unstaking: %s.%s%s ZIL", + delegation.getStake() + ); + + Console.log("Rewards after unstaking: %s.%s%s ZIL", + delegation.getRewards() + ); + + Console.log("Staker balance after unstaking: %s.%s%s ZIL", + staker.balance + ); + + Console.log("Staker balance after unstaking: %s.%s%s LST", + lst.balanceOf(staker) + ); + + Console.log("Total supply after unstaking: %s.%s%s LST", + lst.totalSupply() + ); + + vm.roll(block.number + blocksUntil); + + vm.recordLogs(); + + uint256 unstakedAmount = loggedAmount; // the amount we logged on unstaking + vm.expectEmit( + true, + false, + false, + false, + address(delegation) + ); + emit DelegationV2.Claimed( + staker, + unstakedAmount + ); + + uint256 stakerZILBefore = staker.balance; + ownerZILBefore = delegation.owner().balance; + + delegation.claim(); + + uint256 stakerZILAfter = staker.balance; + ownerZILAfter = delegation.owner().balance; + + entries = vm.getRecordedLogs(); + + for (uint256 i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == keccak256("Claimed(address,uint256)")) { + loggedAmount = abi.decode(entries[i].data, (uint256)); + //console.log(loggedAmount); + } + } + //console.log(stakerZILAfter - stakerZILBefore); + //console.log(unstakedAmount); + + Console.log("Owner commission after claiming: %s.%s%s ZIL", + ownerZILAfter - ownerZILBefore + ); + + Console.log("Stake deposited after claiming: %s.%s%s ZIL", + delegation.getStake() + ); + + Console.log("Rewards after claiming: %s.%s%s ZIL", + delegation.getRewards() + ); + + Console.log("Staker balance after claiming: %s.%s%s ZIL", + staker.balance + ); + + Console.log("Staker balance after claiming: %s.%s%s LST", + lst.balanceOf(staker) + ); + + Console.log("Total supply after claiming: %s.%s%s LST", + lst.totalSupply() + ); + + } + + function test_Real() public { + //TODO: how could the price fall below 1.00 when rewardsAfter was based on 9969126831808605271675? + // supply + rewards + 10k - tax < supply where tax = (rewards + 10k) / 10 + // supply + (rewards + 10k) * 9 / 10 < supply + // because we deducted 10% of 10k as commission and it reduced the left hand side + uint256 rewardsBefore = 9961644437442408088600; + uint256 rewardsAfter = (10003845141667760201143 - rewardsBefore) * uint256(10) / 9; + rewardsBefore = rewardsBefore * uint256(10) / 9 - 10_000 ether; + run( + 10_000_000 ether, + rewardsBefore, + 10_000 ether, // delegatedAmount + rewardsAfter, + 30, // blocksUntil claiming + true // initialDeposit + ); + // staker's ZIL after claiming minus before claiming plus 18-digit claiming transaction fee + Console.log("%s.%s%s", 99994156053341800951925 - 99894533133440243560633 + 377395241114400000); + } + + function test_ReproduceDevnet() public { + uint256 rewardsBefore = 500790859951588622934; + uint256 rewardsAfter = (532306705022011158106 - rewardsBefore) * uint256(10) / 9; + rewardsBefore = rewardsBefore * uint256(10) / 9 - 100 ether; + run( + 10_000_000 ether, + rewardsBefore, + 100 ether, // delegatedAmount + rewardsAfter, + 30, // blocksUntil claiming + true // initialDeposit + ); + // staker's ZIL after claiming minus before claiming plus 18-digit claiming transaction fee + Console.log("%s.%s%s", 99994156053341800951925 - 99894533133440243560633 + 377395241114400000); + } + + function test_NoRewardsUnstakeAll() public { + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + run( + 10_000_000 ether, // depositAmount + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // set rewardsBefore staking + 10_000 ether, // delegatedAmount + 0, // add rewardsAfter staking + 30, // wait blocksUntil claiming + true // initialDeposit + ); + } + + function test_SmallStakeSmallRewardsUnstakeAll() public { + run( + 10_000_000 ether, // depositAmount + 690 ether, // set rewardsBefore staking + 100 ether, // delegatedAmount + 100 ether, // add rewardsAfter staking + 30, // wait blocksUntil claiming + true // initialDeposit + ); + } + + function test_SmallStakeMediumRewardsUnstakeAll() public { + run( + 10_000_000 ether, // depositAmount + 690 ether, // set rewardsBefore staking + 100 ether, // delegatedAmount + 800 ether, // add rewardsAfter staking + 30, // wait blocksUntil claiming + true // initialDeposit + ); + } + + function test_SmallStakeOneYearUnstakeAll() public { + // 7.7318% APY + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 rewardsBefore = 690 ether; + uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + run( + depositAmount, + rewardsBefore, + 100 ether, // delegatedAmount + rewardsAfter, + 30, // blocksUntil claiming + true // initialDeposit + ); + } + + function test_LargeStakeOneYearUnstakeAll() public { + // 7.6629% APY is lower than in SmallStakeOneYearUnstakeAll + // because the delegated amount is added to the rewards + // and the owner receives a commission on it + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 rewardsBefore = 690 ether; + uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + run( + depositAmount, + rewardsBefore, + 100_000 ether, // delegatedAmount + rewardsAfter, + 30, // blocksUntil claiming + true // initialDeposit + ); + } + + function test_SmallStakeLaggardOneYearUnstakeAll() public { + // 7.1773% APY is lower than in SmallStakeOneYearUnstakeAll + // because ?????????? + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 rewardsBefore = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + run( + depositAmount, + rewardsBefore, + 100 ether, // delegatedAmount + rewardsAfter, + 30, // blocksUntil claiming + true // initialDeposit + ); + } + + function test_SmallStakeMediumDepositOneYearUnstakeAll() public { + // 7.7323% APY is higher than in SmallStakeOneYearUnstakeAll + // because the delegated amount is not added to the deposit + // i.e. it doesn't earn rewards, but the missing rewards are + // more significant in case of a smaller deposit + uint256 depositAmount = 100_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 rewardsBefore = 690 ether; + uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + run( + depositAmount, + rewardsBefore, + 100 ether, // delegatedAmount + rewardsAfter, + 30, // blocksUntil claiming + true // initialDeposit + ); + } + +} \ No newline at end of file From f9787290eb93d77f1036d490f6bbf379e27797e7 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Thu, 17 Oct 2024 20:16:03 +0200 Subject: [PATCH 08/15] Implement deposit increase and decrease --- src/DelegationV2.sol | 39 +++-- src/Deposit.sol | 43 ++++- test/Delegation.t.sol | 390 ++++++++++++++++++++++++++++++++---------- 3 files changed, 365 insertions(+), 107 deletions(-) diff --git a/src/DelegationV2.sol b/src/DelegationV2.sol index df0748e..ce7759b 100644 --- a/src/DelegationV2.sol +++ b/src/DelegationV2.sol @@ -85,13 +85,14 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade event Staked(address indexed delegator, uint256 amount, uint256 shares); event Unstaked(address indexed delegator, uint256 amount, uint256 shares); event Claimed(address indexed delegator, uint256 amount); + event CommissionPaid(address indexed owner, uint256 rewardsBefore, uint256 committion); - // not called as there is no transaction for issuing rewards + // called when stake withdrawn from the deposit contract is claimed + // but not called when rewards are assigned to the reward address receive() payable external { - require (msg.sender == 0x0000000000000000000000000000000000000000, "rewards must be issues by zero address"); - // we could deduct the commission from msg.value and - // topup the deposit to restake the rewards - // or use them for instant stake withdrawals + Storage storage $ = _getStorage(); + // do not deduct commission from the withdrawn stake + $.taxedRewards += msg.value; } function _deposit( @@ -160,8 +161,16 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade uint256 shares; Storage storage $ = _getStorage(); if ($.blsPubKey.length > 0) { - //TODO: topup the deposit by msg.value so that msg.value becomes part of getStake(), - // currently it's part of getRewards() since this contract is the reward address + // topup the deposit before deducting the commission or calculating the shares + // otherwise the delegated amount will be treated as part of the rewards + (bool success, bytes memory data) = DEPOSIT_CONTRACT.call{ + value: msg.value + }( + abi.encodeWithSignature("tempIncreaseDeposit(bytes)", + $.blsPubKey + ) + ); + require(success, "deposit increase failed"); } taxRewards(); // before calculating the shares we must deduct the commission from the yet untaxed rewards if (NonRebasingLST($.lst).totalSupply() == 0) @@ -184,8 +193,16 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade $.withdrawals[msg.sender].queue(amount); $.totalWithdrawals += amount; if ($.blsPubKey.length > 0) { - //TODO: if the contract's balance is smaller than totalWithdrawals - // then withdraw the difference from the deposit contract + // we shall maintain a balance that is always sufficient to cover the claims + if (address(this).balance < $.totalWithdrawals) { + (bool success, bytes memory data) = DEPOSIT_CONTRACT.call( + abi.encodeWithSignature("tempDecreaseDeposit(bytes,uint256)", + $.blsPubKey, + $.totalWithdrawals - address(this).balance + ) + ); + require(success, "deposit increase failed"); + } } emit Unstaked(msg.sender, amount, shares); } @@ -213,6 +230,7 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade value: commission }(""); require(success, "transfer of commission failed"); + emit CommissionPaid(owner(), rewards, commission); } function claim() public whenNotPaused { @@ -223,12 +241,13 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade /*if (total == 0) return;*/ taxRewards(); // before the balance changes we must deduct the commission from the yet untaxed rewards - //TODO: claim funds withdrawn from the deposit contract + //TODO: claim the withdrawals requested from the deposit contract (bool success, bytes memory data) = msg.sender.call{ value: total }(""); require(success, "transfer of funds failed"); $.totalWithdrawals -= total; + $.taxedRewards -= total; emit Claimed(msg.sender, total); } diff --git a/src/Deposit.sol b/src/Deposit.sol index 48f2f6e..53eb810 100644 --- a/src/Deposit.sol +++ b/src/Deposit.sol @@ -105,13 +105,13 @@ contract Deposit { bytes memory pubkey, bytes memory signature ) private view returns (bool) { - // mocked to make tests work - return true; bytes memory input = abi.encodeWithSelector( hex"bfd24965", // bytes4(keccak256("popVerify(bytes,bytes)")) signature, pubkey ); + // mocked to make tests work + return true; uint inputLength = input.length; bytes memory output = new bytes(32); bool success; @@ -164,6 +164,33 @@ contract Deposit { _stakersMap[blsPubKey].peerId = peerId; } + // temporary function to test liquid staking + function tempIncreaseDeposit(bytes calldata blsPubKey) public payable { + Staker storage staker = _stakersMap[blsPubKey]; + require(staker.keyIndex != 0, "unknown staker"); + require(staker.rewardAddress == msg.sender, "invalid sender"); + staker.balance += msg.value; + totalStake += msg.value; + } + + // temporary function to test liquid staking + function tempDecreaseDeposit( + bytes calldata blsPubKey, + uint256 amount + ) public { + Staker storage staker = _stakersMap[blsPubKey]; + require(staker.keyIndex != 0, "unknown staker"); + require(staker.rewardAddress == msg.sender, "invalid sender"); + staker.balance -= amount; + require( + staker.balance == 0 || staker.balance >= _minimumStake, + "stake too low" + ); + totalStake -= amount; + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "withdrawal failed"); + } + function setStake( bytes calldata blsPubKey, bytes calldata peerId, @@ -211,6 +238,18 @@ contract Deposit { return _stakerKeys; } + function getStakersData() + public + view + returns (bytes[] memory stakerKeys, Staker[] memory stakers) + { + stakerKeys = _stakerKeys; + stakers = new Staker[](stakerKeys.length); + for (uint256 i = 0; i < stakerKeys.length; i++) { + stakers[i] = _stakersMap[stakerKeys[i]]; + } + } + function getPeerId( bytes calldata blsPubKey ) public view returns (bytes memory) { diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol index 966c766..529778d 100644 --- a/test/Delegation.t.sol +++ b/test/Delegation.t.sol @@ -132,9 +132,10 @@ contract DelegationTest is Test { function run( uint256 depositAmount, - uint256 rewardsBefore, + uint256 rewardsBeforeStaking, + uint256 taxedRewardsBeforeStaking, uint256 delegatedAmount, - uint256 rewardsAfter, + uint256 rewardsBeforeUnstaking, uint256 blocksUntil, bool initialDeposit ) public { @@ -182,7 +183,8 @@ contract DelegationTest is Test { ); } - vm.deal(address(delegation), rewardsBefore); + vm.store(address(delegation), 0x669e9cfa685336547bc6d91346afdd259f6cd8c0cb6d0b16603b5fa60cb48804, bytes32(taxedRewardsBeforeStaking)); + vm.deal(address(delegation), rewardsBeforeStaking); vm.deal(staker, 100_000 ether); vm.startPrank(staker); @@ -265,7 +267,8 @@ contract DelegationTest is Test { lst.totalSupply() ); - vm.deal(address(delegation), address(delegation).balance + rewardsAfter); + //vm.deal(address(delegation), address(delegation).balance + rewardsEarnedUntilUnstaking); + vm.deal(address(delegation), rewardsBeforeUnstaking); Console.log("LST price: %s.%s%s", 10**18 * (delegation.getStake() + delegation.getRewards()) / lst.totalSupply() @@ -397,148 +400,345 @@ contract DelegationTest is Test { } - function test_Real() public { + function test_0_RealCaseOnDevnet() public { //TODO: how could the price fall below 1.00 when rewardsAfter was based on 9969126831808605271675? - // supply + rewards + 10k - tax < supply where tax = (rewards + 10k) / 10 - // supply + (rewards + 10k) * 9 / 10 < supply - // because we deducted 10% of 10k as commission and it reduced the left hand side - uint256 rewardsBefore = 9961644437442408088600; - uint256 rewardsAfter = (10003845141667760201143 - rewardsBefore) * uint256(10) / 9; - rewardsBefore = rewardsBefore * uint256(10) / 9 - 10_000 ether; + uint256 delegatedAmount = 10_000 ether; + // We need to retrieve the following values + // from the block before the staking transaction: + // rewardsBeforeStaking + // taxedRewardsBeforeStaking + // from block that includes the staking transaction: + // rewardsAfterStaking <- can include unknown rewards accrued by the validator at the end of the block + // taxedRewardsAfterStaking <- just to compare with the value calculated below + uint256 rewardsBeforeStaking = 3927570941165246990673; + uint256 taxedRewardsBeforeStaking = 3201207304801610627105; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking + delegatedAmount + - (rewardsBeforeStaking + delegatedAmount - taxedRewardsBeforeStaking) / uint256(10); + Console.log("Expected taxed rewards after staking: %s.%s%s ZIL", taxedRewardsAfterStaking); + // We also need the following value from the block after which we (would or did) unstake: + // rewardsBeforeUnstaking + uint256 rewardsBeforeUnstaking = 12932207304801610627037; run( 10_000_000 ether, - rewardsBefore, - 10_000 ether, // delegatedAmount - rewardsAfter, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + rewardsBeforeUnstaking, 30, // blocksUntil claiming true // initialDeposit ); - // staker's ZIL after claiming minus before claiming plus 18-digit claiming transaction fee - Console.log("%s.%s%s", 99994156053341800951925 - 99894533133440243560633 + 377395241114400000); + // Last but not least, we need + // the staker's ZIL balance in wei after claiming + // the staker's ZIL balance in wei before claiming + // the claiming transaction fee in wei + Console.log("Expected staker balance after claiming: %s.%s%s ZIL", + 100_000 ether - delegatedAmount + + 99993.342518411621164599 ether - 89993.662605102785881591 ether + 0.3895428602592 ether + ); } - function test_ReproduceDevnet() public { - uint256 rewardsBefore = 500790859951588622934; - uint256 rewardsAfter = (532306705022011158106 - rewardsBefore) * uint256(10) / 9; - rewardsBefore = rewardsBefore * uint256(10) / 9 - 100 ether; + function test_1a_LargeStakeLateNoRewardsUnstakeAll() public { + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( - 10_000_000 ether, - rewardsBefore, - 100 ether, // delegatedAmount - rewardsAfter, - 30, // blocksUntil claiming - true // initialDeposit + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); - // staker's ZIL after claiming minus before claiming plus 18-digit claiming transaction fee - Console.log("%s.%s%s", 99994156053341800951925 - 99894533133440243560633 + 377395241114400000); } - function test_NoRewardsUnstakeAll() public { + function test_1a_LargeStakeEarlyNoRewardsUnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 1 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( - 10_000_000 ether, // depositAmount - 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // set rewardsBefore staking - 10_000 ether, // delegatedAmount - 0, // add rewardsAfter staking - 30, // wait blocksUntil claiming - true // initialDeposit + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_SmallStakeSmallRewardsUnstakeAll() public { + function test_2a_LargeStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1707% APR is plausible + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( - 10_000_000 ether, // depositAmount - 690 ether, // set rewardsBefore staking - 100 ether, // delegatedAmount - 100 ether, // add rewardsAfter staking - 30, // wait blocksUntil claiming - true // initialDeposit + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_SmallStakeMediumRewardsUnstakeAll() public { + function test_3a_SmallStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1773% APR is more than large stake earns + //TODO: why? + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 100 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( - 10_000_000 ether, // depositAmount - 690 ether, // set rewardsBefore staking - 100 ether, // delegatedAmount - 800 ether, // add rewardsAfter staking - 30, // wait blocksUntil claiming - true // initialDeposit + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_SmallStakeOneYearUnstakeAll() public { - // 7.7318% APY + function test_4a_LargeStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1767% APR is less than small stake + //TODO: why? + // but more than small validator + //TODO: why? + uint256 depositAmount = 100_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_5a_SmallStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1774% APR is more than large stake, same as small validator + uint256 depositAmount = 100_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 100 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_2b_LargeStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1707% APR is plausible uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; - uint256 rewardsBefore = 690 ether; - uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; - Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( depositAmount, - rewardsBefore, - 100 ether, // delegatedAmount - rewardsAfter, - 30, // blocksUntil claiming - true // initialDeposit + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_LargeStakeOneYearUnstakeAll() public { - // 7.6629% APY is lower than in SmallStakeOneYearUnstakeAll - // because the delegated amount is added to the rewards - // and the owner receives a commission on it + function test_3b_SmallStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1773% APR is more than large stake earns + //TODO: why? uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; - uint256 rewardsBefore = 690 ether; - uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; - Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + uint256 delegatedAmount = 100 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( depositAmount, - rewardsBefore, - 100_000 ether, // delegatedAmount - rewardsAfter, - 30, // blocksUntil claiming - true // initialDeposit + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_SmallStakeLaggardOneYearUnstakeAll() public { - // 7.1773% APY is lower than in SmallStakeOneYearUnstakeAll - // because ?????????? + function test_4b_LargeStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1767% APR is less than small stake + //TODO: why? + // but more than small validator + //TODO: why? + uint256 depositAmount = 100_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + false // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_5b_SmallStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1774% APR is more than large stake, same as small validator + uint256 depositAmount = 100_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 100 ether; + uint256 rewardsBeforeStaking = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + false // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_2b_LargeStakeEarlySmallValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1707% APR is plausible uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; - uint256 rewardsBefore = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; - uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; - Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 1 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( depositAmount, - rewardsBefore, - 100 ether, // delegatedAmount - rewardsAfter, - 30, // blocksUntil claiming - true // initialDeposit + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_3b_SmallStakeEarlySmallValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1773% APR is more than large stake earns + //TODO: why? + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 100 ether; + uint256 rewardsBeforeStaking = 1 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_SmallStakeMediumDepositOneYearUnstakeAll() public { - // 7.7323% APY is higher than in SmallStakeOneYearUnstakeAll - // because the delegated amount is not added to the deposit - // i.e. it doesn't earn rewards, but the missing rewards are - // more significant in case of a smaller deposit + function test_4b_LargeStakeEarlyLargeValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1767% APR is less than small stake + //TODO: why? + // but more than small validator + //TODO: why? uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; - uint256 rewardsBefore = 690 ether; - uint256 rewardsAfter = 365 * 24 * 51_000 ether * depositAmount / totalDeposit; - Console.log("Rewards for 1 year: %s.%s%s", rewardsAfter); + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 1 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); run( depositAmount, - rewardsBefore, - 100 ether, // delegatedAmount - rewardsAfter, - 30, // blocksUntil claiming - true // initialDeposit + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + false // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_5b_SmallStakeEarlyLargeValidatorOneYearOfRewardsUnstakeAll() public { + // 7.1774% APR is more than large stake, same as small validator + uint256 depositAmount = 100_000_000 ether; + uint256 totalDeposit = 5_200_000_000 ether; + uint256 delegatedAmount = 100 ether; + uint256 rewardsBeforeStaking = 1 * 51_000 ether * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } From 518247ed2d4a31d4b62b25897e55679a816b5dc9 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Fri, 18 Oct 2024 15:35:20 +0200 Subject: [PATCH 09/15] Modify order of staking and unstaking steps --- README.md | 16 +- src/DelegationV2.sol | 71 +++++--- test/Delegation.t.sol | 386 ++++++++++++++++++++++++++++-------------- 3 files changed, 326 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 48e5cbe..7373624 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "DENOMINATOR()(uint256)" -- ``` ## 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 +If you 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 cast send --legacy --value 10000000ether --rpc-url http://localhost:4201 --private-key $PRIVATE_KEY \ 0x7a0b7e6d24ede78260c9ddbd98e828b0e11a8ea2 "deposit(bytes,bytes,bytes)" \ @@ -71,12 +71,20 @@ cast send --legacy --value 10000000ether --rpc-url http://localhost:4201 --priva 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. +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 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). +Alternatively, you can proceed to the next section and delegate stake until the contract's balance reaches the 10 million ZIL minimum stake required for the activation, and then run +```bash +cast send --legacy --rpc-url http://localhost:4201 --private-key $PRIVATE_KEY \ +0x7a0b7e6d24ede78260c9ddbd98e828b0e11a8ea2 "deposit2(bytes,bytes,bytes)" \ +0x92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c \ +0x002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f \ +0xb14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a +``` +to deposit all of it. + ## 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 ```bash diff --git a/src/DelegationV2.sol b/src/DelegationV2.sol index ce7759b..965e82f 100644 --- a/src/DelegationV2.sol +++ b/src/DelegationV2.sol @@ -9,6 +9,8 @@ import "src/NonRebasingLST.sol"; library WithdrawalQueue { + //TODO: add it to the variables and implement a getter and an onlyOwner setter + // since a governance vote can change the unbonding period anytime uint256 public constant UNBONDING_PERIOD = 30; //approx. 30s, used only for testing struct Item { @@ -160,9 +162,23 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade require(msg.value >= MIN_DELEGATION, "delegated amount too low"); uint256 shares; Storage storage $ = _getStorage(); + // deduct commission from the rewards only if already activated as a validator + // otherwise getRewards() returns 0 but taxedRewards would be greater than 0 + if ($.blsPubKey.length > 0) { + // the delegated amount is temporarily part of the rewards as it's in the balance + // add to the taxed rewards to avoid commission and remove it again after taxing + $.taxedRewards += msg.value; + // before calculating the shares deduct the commission from the yet untaxed rewards + taxRewards(); + $.taxedRewards -= msg.value; + } + if (NonRebasingLST($.lst).totalSupply() == 0) + shares = msg.value; + else + shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + $.taxedRewards); + NonRebasingLST($.lst).mint(msg.sender, shares); + // increase the deposit only if already activated as a validator if ($.blsPubKey.length > 0) { - // topup the deposit before deducting the commission or calculating the shares - // otherwise the delegated amount will be treated as part of the rewards (bool success, bytes memory data) = DEPOSIT_CONTRACT.call{ value: msg.value }( @@ -172,20 +188,14 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade ); require(success, "deposit increase failed"); } - taxRewards(); // before calculating the shares we must deduct the commission from the yet untaxed rewards - if (NonRebasingLST($.lst).totalSupply() == 0) - shares = msg.value; - else - shares = NonRebasingLST($.lst).totalSupply() * msg.value / (getStake() + $.taxedRewards); - NonRebasingLST($.lst).mint(msg.sender, shares); emit Staked(msg.sender, msg.value, shares); } function unstake(uint256 shares) public whenNotPaused { uint256 amount; Storage storage $ = _getStorage(); - NonRebasingLST($.lst).burn(msg.sender, shares); - taxRewards(); // before calculating the amount we must deduct the commission from the yet untaxed rewards + // before calculating the amount deduct the commission from the yet untaxed rewards + taxRewards(); if (NonRebasingLST($.lst).totalSupply() == 0) amount = shares; else @@ -193,7 +203,7 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade $.withdrawals[msg.sender].queue(amount); $.totalWithdrawals += amount; if ($.blsPubKey.length > 0) { - // we shall maintain a balance that is always sufficient to cover the claims + // maintain a balance that is always sufficient to cover the claims if (address(this).balance < $.totalWithdrawals) { (bool success, bytes memory data) = DEPOSIT_CONTRACT.call( abi.encodeWithSignature("tempDecreaseDeposit(bytes,uint256)", @@ -201,9 +211,10 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade $.totalWithdrawals - address(this).balance ) ); - require(success, "deposit increase failed"); + require(success, "deposit decrease failed"); } } + NonRebasingLST($.lst).burn(msg.sender, shares); emit Unstaked(msg.sender, amount, shares); } @@ -240,8 +251,9 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade total += $.withdrawals[msg.sender].dequeue().amount; /*if (total == 0) return;*/ - taxRewards(); // before the balance changes we must deduct the commission from the yet untaxed rewards - //TODO: claim the withdrawals requested from the deposit contract + // before the balance changes deduct the commission from the yet untaxed rewards + taxRewards(); + //TODO: claim all deposit withdrawals requested whose unbonding period is over (bool success, bytes memory data) = msg.sender.call{ value: total }(""); @@ -251,18 +263,37 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade emit Claimed(msg.sender, total); } - //TODO: call restake every time someone wants to stake, unstake or claim? - function restake() public onlyOwner { - taxRewards(); // before the balance changes, we must deduct the commission from the yet untaxed rewards - //TODO: topup the deposit by address(this).balance - $.totalWithdrawals - // i.e. the rewards accrued minus the amount needed for the pending withdrawals - } + //TODO: make it onlyOwnerOrContract and call it every time someone stakes, unstakes or claims? + function restakeRewards() public onlyOwner { + Storage storage $ = _getStorage(); + // before the balance changes deduct the commission from the yet untaxed rewards + taxRewards(); + if ($.blsPubKey.length > 0) { + (bool success, bytes memory data) = DEPOSIT_CONTRACT.call{ + value: address(this).balance - $.totalWithdrawals + }( + abi.encodeWithSignature("tempIncreaseDeposit(bytes)", + $.blsPubKey + ) + ); + require(success, "deposit increase failed"); + } + } + + function collectCommission() public onlyOwner { + taxRewards(); + } function getTaxedRewards() public view returns(uint256) { Storage storage $ = _getStorage(); return $.taxedRewards; } + function getTotalWithdrawals() public view returns(uint256) { + Storage storage $ = _getStorage(); + return $.totalWithdrawals; + } + function getRewards() public view returns(uint256) { Storage storage $ = _getStorage(); if ($.blsPubKey.length == 0) diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol index 529778d..0b05b77 100644 --- a/test/Delegation.t.sol +++ b/test/Delegation.t.sol @@ -135,6 +135,8 @@ contract DelegationTest is Test { uint256 rewardsBeforeStaking, uint256 taxedRewardsBeforeStaking, uint256 delegatedAmount, + uint8 numberOfDelegations, + uint256 rewardsAccruedAfterEach, uint256 rewardsBeforeUnstaking, uint256 blocksUntil, bool initialDeposit @@ -208,64 +210,74 @@ contract DelegationTest is Test { lst.totalSupply() ); - vm.recordLogs(); + uint256 ownerZILBefore; + uint256 ownerZILAfter; + uint256 loggedAmount; + uint256 loggedShares; + Vm.Log[] memory entries; - vm.expectEmit( - true, - false, - false, - false, - address(delegation) - ); - emit DelegationV2.Staked( - staker, - delegatedAmount, - lst.totalSupply() * delegatedAmount / (delegation.getStake() + delegation.getRewards()) - ); + for (uint8 j = 0; j < numberOfDelegations; j++) { + console.log("staking %s --------------------------------", j + 1); - uint256 ownerZILBefore = delegation.owner().balance; + vm.recordLogs(); - delegation.stake{ - value: delegatedAmount - }(); + vm.expectEmit( + true, + false, + false, + false, + address(delegation) + ); + emit DelegationV2.Staked( + staker, + delegatedAmount, + lst.totalSupply() * delegatedAmount / (delegation.getStake() + delegation.getRewards()) + ); - uint256 ownerZILAfter = delegation.owner().balance; + ownerZILBefore = delegation.owner().balance; - Vm.Log[] memory entries = vm.getRecordedLogs(); - uint256 loggedAmount; - uint256 loggedShares; - for (uint256 i = 0; i < entries.length; i++) { - if (entries[i].topics[0] == keccak256("Staked(address,uint256,uint256)")) { - (loggedAmount, loggedShares) = abi.decode(entries[i].data, (uint256, uint256)); - //console.log(loggedAmount, loggedShares); - } - } - //console.log(delegatedAmount, (lst.totalSupply() - lst.balanceOf(staker)) * delegatedAmount / (delegation.getStake() + delegation.getTaxedRewards())); - //console.log(delegatedAmount, lst.balanceOf(staker)); + delegation.stake{ + value: delegatedAmount + }(); - Console.log("Owner commission after staking: %s.%s%s ZIL", - ownerZILAfter - ownerZILBefore - ); + ownerZILAfter = delegation.owner().balance; - Console.log("Stake deposited after staking: %s.%s%s ZIL", - delegation.getStake() - ); + entries = vm.getRecordedLogs(); + for (uint256 i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == keccak256("Staked(address,uint256,uint256)")) { + (loggedAmount, loggedShares) = abi.decode(entries[i].data, (uint256, uint256)); + //console.log(loggedAmount, loggedShares); + } + } + //console.log(delegatedAmount, (lst.totalSupply() - lst.balanceOf(staker)) * delegatedAmount / (delegation.getStake() + delegation.getTaxedRewards())); + //console.log(delegatedAmount, lst.balanceOf(staker)); - Console.log("Rewards after staking: %s.%s%s ZIL", - delegation.getRewards() - ); + Console.log("Owner commission after staking: %s.%s%s ZIL", + ownerZILAfter - ownerZILBefore + ); - Console.log("Staker balance after staking: %s.%s%s ZIL", - staker.balance - ); + Console.log("Stake deposited after staking: %s.%s%s ZIL", + delegation.getStake() + ); - Console.log("Staker balance after staking: %s.%s%s LST", - lst.balanceOf(staker) - ); + Console.log("Rewards after staking: %s.%s%s ZIL", + delegation.getRewards() + ); - Console.log("Total supply after staking: %s.%s%s LST", - lst.totalSupply() - ); + Console.log("Staker balance after staking: %s.%s%s ZIL", + staker.balance + ); + + Console.log("Staker balance after staking: %s.%s%s LST", + lst.balanceOf(staker) + ); + + Console.log("Total supply after staking: %s.%s%s LST", + lst.totalSupply() + ); + + vm.deal(address(delegation), address(delegation).balance + rewardsAccruedAfterEach); + } //vm.deal(address(delegation), address(delegation).balance + rewardsEarnedUntilUnstaking); vm.deal(address(delegation), rewardsBeforeUnstaking); @@ -400,45 +412,7 @@ contract DelegationTest is Test { } - function test_0_RealCaseOnDevnet() public { - //TODO: how could the price fall below 1.00 when rewardsAfter was based on 9969126831808605271675? - uint256 delegatedAmount = 10_000 ether; - // We need to retrieve the following values - // from the block before the staking transaction: - // rewardsBeforeStaking - // taxedRewardsBeforeStaking - // from block that includes the staking transaction: - // rewardsAfterStaking <- can include unknown rewards accrued by the validator at the end of the block - // taxedRewardsAfterStaking <- just to compare with the value calculated below - uint256 rewardsBeforeStaking = 3927570941165246990673; - uint256 taxedRewardsBeforeStaking = 3201207304801610627105; - uint256 taxedRewardsAfterStaking = - rewardsBeforeStaking + delegatedAmount - - (rewardsBeforeStaking + delegatedAmount - taxedRewardsBeforeStaking) / uint256(10); - Console.log("Expected taxed rewards after staking: %s.%s%s ZIL", taxedRewardsAfterStaking); - // We also need the following value from the block after which we (would or did) unstake: - // rewardsBeforeUnstaking - uint256 rewardsBeforeUnstaking = 12932207304801610627037; - run( - 10_000_000 ether, - rewardsBeforeStaking, - taxedRewardsBeforeStaking, - delegatedAmount, - rewardsBeforeUnstaking, - 30, // blocksUntil claiming - true // initialDeposit - ); - // Last but not least, we need - // the staker's ZIL balance in wei after claiming - // the staker's ZIL balance in wei before claiming - // the claiming transaction fee in wei - Console.log("Expected staker balance after claiming: %s.%s%s ZIL", - 100_000 ether - delegatedAmount - + 99993.342518411621164599 ether - 89993.662605102785881591 ether + 0.3895428602592 ether - ); - } - - function test_1a_LargeStakeLateNoRewardsUnstakeAll() public { + function test_1a_LargeStake_Late_NoRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -452,13 +426,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_1a_LargeStakeEarlyNoRewardsUnstakeAll() public { + function test_1b_LargeStake_Early_NoRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -472,14 +448,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_2a_LargeStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1707% APR is plausible + function test_2a_LargeStake_Late_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -493,15 +470,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_3a_SmallStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1773% APR is more than large stake earns - //TODO: why? + function test_3a_SmallStake_Late_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -515,17 +492,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_4a_LargeStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1767% APR is less than small stake - //TODO: why? - // but more than small validator - //TODO: why? + function test_4a_LargeStake_Late_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -539,14 +514,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_5a_SmallStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1774% APR is more than large stake, same as small validator + function test_5a_SmallStake_Late_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -560,14 +536,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_2b_LargeStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1707% APR is plausible + function test_2b_LargeStake_Late_SmallValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -581,15 +558,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_3b_SmallStakeLateSmallValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1773% APR is more than large stake earns - //TODO: why? + function test_3b_SmallStake_Late_SmallValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -603,17 +580,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_4b_LargeStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1767% APR is less than small stake - //TODO: why? - // but more than small validator - //TODO: why? + function test_4b_LargeStake_Late_LargeValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -627,14 +602,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_5b_SmallStakeLateLargeValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1774% APR is more than large stake, same as small validator + function test_5b_SmallStake_Late_LargeValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -648,14 +624,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_2b_LargeStakeEarlySmallValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1707% APR is plausible + function test_2c_LargeStake_Early_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -669,15 +646,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming true // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_3b_SmallStakeEarlySmallValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1773% APR is more than large stake earns - //TODO: why? + function test_3c_SmallStake_Early_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -691,17 +668,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_4b_LargeStakeEarlyLargeValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1767% APR is less than small stake - //TODO: why? - // but more than small validator - //TODO: why? + function test_4c_LargeStake_Early_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -715,14 +690,15 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } - function test_5b_SmallStakeEarlyLargeValidatorOneYearOfRewardsUnstakeAll() public { - // 7.1774% APR is more than large stake, same as small validator + function test_5c_SmallStake_Early_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -736,10 +712,174 @@ contract DelegationTest is Test { rewardsBeforeStaking, taxedRewardsBeforeStaking, delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming false // initialDeposit using the node owner' funds, otherwise delegated by a staker ); } + function test_6a_ManyVsOneStake_UnstakeAll() public { + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 110_000_000 ether; + uint256 delegatedAmount = 10_000 ether; + uint256 rewardsBeforeStaking = 51_000 ether / uint256(60) * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + 9, // numberOfDelegations + // 5s of rewards between the delegations; always check if + // (numberOfDelegations - 1) * rewardsAccruedAfterEach <= rewardsBeforeUnstaking + 5 * 51_000 ether / uint256(3600) * depositAmount / totalDeposit, // rewardsAccruedAfterEach + taxedRewardsAfterStaking + 51_000 ether / uint256(60) * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + function test_6b_OneVsManyStakes_UnstakeAll() public { + uint256 depositAmount = 10_000_000 ether; + uint256 totalDeposit = 110_000_000 ether; + uint256 delegatedAmount = 90_000 ether; + uint256 rewardsBeforeStaking = 51_000 ether / uint256(60) * depositAmount / totalDeposit; + uint256 taxedRewardsBeforeStaking = 0; + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("taxedRewardsAfterStaking = %s.%s%s", taxedRewardsAfterStaking); + run( + depositAmount, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach + taxedRewardsAfterStaking + 51_000 ether / uint256(60) * depositAmount / totalDeposit, // rewardsBeforeUnstaking + 30, // after unstaking wait blocksUntil claiming + true // initialDeposit using the node owner' funds, otherwise delegated by a staker + ); + } + + /* + To compare the results of Foundry tests and a real network, use the bash scripts below + to stake, unstake and claim on the network your local node is connected to. + + Before and after running the STAKING, UNSTAKING and CLAIMING scripts presented below, + always execute the following bash script to capture the values needed in the Foundry test below. + + STATE: + + block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ + block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ + echo $(date +"%T,%3N") $block_num && \ + echo rewardsBeforeUnstaking = $(cast rpc eth_getBalance 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ + x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + owner_lst=$(cast to-unit $x ether) && \ + x=$(cast rpc eth_getBalance 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ + owner_zil=$(cast to-unit $x ether) && \ + echo owner: $owner_lst LST && echo owner: $owner_zil ZIL && \ + x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + staker_lst=$(cast to-unit $x ether) && \ + x=$(cast rpc eth_getBalance 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ + staker_zil=$(cast to-unit $x ether) && \ + echo staker: $staker_lst LST && echo staker: $staker_zil ZIL && \ + x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + y=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + z=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && price=$(bc -l <<< \($y+$z\)/$x) && \ + echo LST price: $price && \ + echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL && \ + echo getStake = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo getRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo getTaxedRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo getTotalWithdrawals = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + + STAKING: insert the private key at the end of line 1 before running the script + + forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 10000000000000000000000 --private-key 0x... && \ + block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ + echo rewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo taxedRewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo $(date +"%T,%3N") $block && \ + block=$((block-1)) && \ + echo rewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo taxedRewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo $(date +"%T,%3N") $block + + UNSTAKING: insert the private key at the end of line 1 before running the script + + forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 0 --private-key 0x... && \ + block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ + echo rewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo taxedRewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo $(date +"%T,%3N") $block && \ + block=$((block-1)) && \ + echo rewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo taxedRewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo $(date +"%T,%3N") $block + + CLAIMING: insert the private key at the end of line 1 before running the script + + forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... -vvvv && \ + block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ + echo rewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo taxedRewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo $(date +"%T,%3N") $block && \ + block=$((block-1)) && \ + echo rewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo taxedRewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ + echo $(date +"%T,%3N") $block + */ + function test_0_ReproduceRealNetwork() public { + uint256 delegatedAmount = 10_000 ether; + // Insert the following values output by the STATE script below + uint256 rewardsBeforeStaking = 197818620596390326580; + uint256 taxedRewardsBeforeStaking = 166909461128204338052; + // Compare the taxedRewardsAfterStaking output by the STATE script + // with the value logged by the test below + uint256 taxedRewardsAfterStaking = + rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); + Console.log("Expected taxed rewards after staking: %s.%s%s ZIL", taxedRewardsAfterStaking); + // Insert the following value output by the UNSTAKE script + uint256 rewardsBeforeUnstaking = 233367080700403454378; + run( + 10_000_000 ether, + rewardsBeforeStaking, + taxedRewardsBeforeStaking, + delegatedAmount, + 1, // numberOfDelegations + 0, // rewardsAccruedAfterEach + rewardsBeforeUnstaking, + 30, // blocksUntil claiming + true // initialDeposit + ); + // Replace the values below in the same order with the values output by the STATE script + // run after the CLAIM script or logged by the CLAIM script itself + // the staker's ZIL balance in wei according to the STATE script after claiming + // the staker's ZIL balance in wei according to the STATE script before claiming + // the claiming transaction fee in wei output by the CLAIM script + Console.log("Expected staker balance after claiming: %s.%s%s ZIL", + 100_000 ether - delegatedAmount + + 100013.464887553198739807 ether - 90013.819919979031083499 ether + 0.3897714316896 ether + ); + // Replace the values below in the same order with values output by the STATE script + // run before the STAKE and after the UNSTAKE scripts or logged by those script themselves + // the owner's ZIL balance in wei according to the STATE script after unstaking + // the owner's ZIL balance in wei according to the STATE script before staking + // the transaction fees in wei output by the STAKING and UNSTAKING scripts + Console.log("Actual owner commission: %s.%s%s ZIL", + 100032.696802178975738911 ether - 100025.741948627073967394 ether + + 0.6143714334864 ether + 0.8724381022176 ether + ); + // Compare the value logged above with the sum of the following values + // you will see after running the test: + // Owner commission after staking + // Owner commission after unstaking + } + } \ No newline at end of file From b22883893f786937ca0e90c93eb12df40d9e5586 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Fri, 18 Oct 2024 19:39:42 +0200 Subject: [PATCH 10/15] Create bash scripts for staking, unstaking, claiming tests --- claim.sh | 16 ++++++++++ stake.sh | 16 ++++++++++ state.sh | 32 +++++++++++++++++++ test/Delegation.t.sol | 71 ++++++++----------------------------------- unstake.sh | 22 ++++++++++++++ 5 files changed, 98 insertions(+), 59 deletions(-) create mode 100755 claim.sh create mode 100755 stake.sh create mode 100755 state.sh create mode 100755 unstake.sh diff --git a/claim.sh b/claim.sh new file mode 100755 index 0000000..c44bada --- /dev/null +++ b/claim.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [ $# -ne 1 ]; then + echo "Provide a staker private key as an arguments." + exit 1 +fi + +forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key $1 -vvvv && \ +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo rewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block && \ +block=$((block-1)) && \ +echo rewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block diff --git a/stake.sh b/stake.sh new file mode 100755 index 0000000..e528b81 --- /dev/null +++ b/stake.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [ $# -ne 2 ]; then + echo "Provide a staker private key and the amount in wei as arguments." + exit 1 +fi + +forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $2 --private-key $1 && \ +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo rewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block && \ +block=$((block-1)) && \ +echo rewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block \ No newline at end of file diff --git a/state.sh b/state.sh new file mode 100755 index 0000000..a2baad6 --- /dev/null +++ b/state.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +if [ $# -ne 1 ]; then + echo "Provide a staker address as an argument." + 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) && \ +echo $(date +"%T,%3N") $block_num && \ +echo rewardsBeforeUnstaking = $(cast rpc eth_getBalance 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +owner_lst=$(cast to-unit $x ether) && \ +x=$(cast rpc eth_getBalance 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +owner_zil=$(cast to-unit $x ether) && \ +echo owner: $owner_lst LST && echo owner: $owner_zil ZIL && \ +x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" $1 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +staker_lst=$(cast to-unit $x ether) && \ +x=$(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +staker_zil=$(cast to-unit $x ether) && \ +echo staker: $staker_lst LST && echo staker: $staker_zil ZIL && \ +x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +y=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +z=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +price=$(bc -l <<< \($y+$z\)/$x) && \ +echo LST supply: $(cast to-unit $x ether) && \ +echo LST price: $price && \ +echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL && \ +echo getStake = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo getRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo getTaxedRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo getTotalWithdrawals = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') \ No newline at end of file diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol index 0b05b77..7817d28 100644 --- a/test/Delegation.t.sol +++ b/test/Delegation.t.sol @@ -774,68 +774,21 @@ contract DelegationTest is Test { always execute the following bash script to capture the values needed in the Foundry test below. STATE: + chmod +x state.sh && ./state.sh - block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ - block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ - echo $(date +"%T,%3N") $block_num && \ - echo rewardsBeforeUnstaking = $(cast rpc eth_getBalance 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ - x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - owner_lst=$(cast to-unit $x ether) && \ - x=$(cast rpc eth_getBalance 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ - owner_zil=$(cast to-unit $x ether) && \ - echo owner: $owner_lst LST && echo owner: $owner_zil ZIL && \ - x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - staker_lst=$(cast to-unit $x ether) && \ - x=$(cast rpc eth_getBalance 0xd819fFcE7A58b1E835c25617Db7b46a00888B013 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ - staker_zil=$(cast to-unit $x ether) && \ - echo staker: $staker_lst LST && echo staker: $staker_zil ZIL && \ - x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - y=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - z=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && price=$(bc -l <<< \($y+$z\)/$x) && \ - echo LST price: $price && \ - echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL && \ - echo getStake = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo getRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo getTaxedRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo getTotalWithdrawals = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') - - STAKING: insert the private key at the end of line 1 before running the script - - forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 10000000000000000000000 --private-key 0x... && \ - block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ - echo rewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo taxedRewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo $(date +"%T,%3N") $block && \ - block=$((block-1)) && \ - echo rewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo taxedRewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo $(date +"%T,%3N") $block - - UNSTAKING: insert the private key at the end of line 1 before running the script - - forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 0 --private-key 0x... && \ - block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ - echo rewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo taxedRewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo $(date +"%T,%3N") $block && \ - block=$((block-1)) && \ - echo rewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo taxedRewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo $(date +"%T,%3N") $block - - CLAIMING: insert the private key at the end of line 1 before running the script - - forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... -vvvv && \ - block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ - echo rewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo taxedRewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo $(date +"%T,%3N") $block && \ - block=$((block-1)) && \ - echo rewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo taxedRewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ - echo $(date +"%T,%3N") $block + STAKING: + chmod +x stake.sh && ./stake.sh 10000000000000000000000 + + UNSTAKING: + chmod +x unstake.sh && ./unstake.sh + + CLAIMING: + chmod +x claim.sh && ./claim.sh + + Before running the test, replace the address on the first line with */ function test_0_ReproduceRealNetwork() public { + staker = 0xd819fFcE7A58b1E835c25617Db7b46a00888B013; uint256 delegatedAmount = 10_000 ether; // Insert the following values output by the STATE script below uint256 rewardsBeforeStaking = 197818620596390326580; diff --git a/unstake.sh b/unstake.sh new file mode 100755 index 0000000..4d0d597 --- /dev/null +++ b/unstake.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if [ $# -lt 1 ]; then + echo "Provide a staker private key and optionally the number of shares as arguments." + exit 1 +fi + +if [ $# -eq 2 ]; then + amount="$2" +else + amount=0 +fi + +forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $amount --private-key $1 && \ +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo rewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block && \ +block=$((block-1)) && \ +echo rewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block \ No newline at end of file From 41ac789ae193f359dfbd0939264bf5c8d737e2c6 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Mon, 21 Oct 2024 09:58:43 +0200 Subject: [PATCH 11/15] Improve bash and foundry scripts --- README.md | 20 +++++++++--------- claim.sh | 28 +++++++++++++++---------- script/claim_Delegation.s.sol | 4 ++-- script/stake_Delegation.s.sol | 6 +++--- script/unstake_Delegation.s.sol | 8 +++---- stake.sh | 28 +++++++++++++++---------- state.sh | 30 +++++++++++++------------- test/Delegation.t.sol | 18 ++++++++-------- unstake.sh | 37 +++++++++++++++++++++------------ 9 files changed, 102 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 7373624..2e32538 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,12 @@ with the private key of the delegator account. Make sure the account's balance c The output will look like this: ``` Running version: 2 - Current stake: 10000000000000000000000000 ZIL - Current rewards: 110314207650273223687 ZIL + Current stake: 10000000000000000000000000 wei + Current rewards: 110314207650273223687 wei LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 Owner balance: 10000000000000000000000000 LST - Staker balance before: 99899145245801454561224 ZIL 0 LST - Staker balance after: 99699145245801454561224 ZIL 199993793908430833324 LST + Staker balance before: 99899145245801454561224 wei 0 LST + Staker balance after: 99699145245801454561224 wei 199993793908430833324 LST ``` Note that the staker LST balance in the output will be different from the actual LST balance which you can query by running @@ -120,12 +120,12 @@ with the private key of an account that holds some LST. The output will look like this: ``` Running version: 2 - Current stake: 10000000000000000000000000 ZIL - Current rewards: 331912568306010928520 ZIL + Current stake: 10000000000000000000000000 wei + Current rewards: 331912568306010928520 wei LST address: 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 Owner balance: 10000000000000000000000000 LST - Staker balance before: 99698814298179759361224 ZIL 199993784619390291653 LST - Staker balance after: 99698814298179759361224 ZIL 99993784619390291653 LST + Staker balance before: 99698814298179759361224 wei 199993784619390291653 LST + Staker balance after: 99698814298179759361224 wei 99993784619390291653 LST ``` Last but not least, to claim the amount that is available after the unbonding period, run @@ -137,6 +137,6 @@ with the private key of an account that unstaked some LST. The output will look like this: ``` Running version: 2 - Staker balance before: 99698086421983460161224 ZIL - Staker balance after: 99798095485861371162343 ZIL + Staker balance before: 99698086421983460161224 wei + Staker balance after: 99798095485861371162343 wei ``` \ No newline at end of file diff --git a/claim.sh b/claim.sh index c44bada..d0f9021 100755 --- a/claim.sh +++ b/claim.sh @@ -1,16 +1,22 @@ #!/bin/bash -if [ $# -ne 1 ]; then - echo "Provide a staker private key as an arguments." +if [ $# -ne 2 ]; then + echo "Provide the delegation contract address and a staker private key as arguments." exit 1 fi -forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key $1 -vvvv && \ -block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -echo rewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsAfterClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block && \ -block=$((block-1)) && \ -echo rewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsBeforeClaiming = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block +staker=$(cast wallet address $2) && \ +forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 -vvvv && \ +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ +echo rewardsAfterClaiming = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsAfterClaiming = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +staker_wei_after=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo $(date +"%T,%3N") $block_num && \ +block_num=$((block_num-1)) && \ +block=$(echo $block_num | cast to-hex --base-in 10) && \ +echo rewardsBeforeClaiming = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsBeforeClaiming = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo claimed amount - gas fee = $(bc -l <<< $staker_wei_after-$staker_wei_before) wei +echo $(date +"%T,%3N") $block_num diff --git a/script/claim_Delegation.s.sol b/script/claim_Delegation.s.sol index 78a75fc..eabb46c 100644 --- a/script/claim_Delegation.s.sol +++ b/script/claim_Delegation.s.sol @@ -18,7 +18,7 @@ contract Claim is Script { delegation.version() ); - console.log("Staker balance before: %s ZIL", + console.log("Staker balance before: %s wei", staker.balance ); @@ -26,7 +26,7 @@ contract Claim is Script { delegation.claim(); - console.log("Staker balance after: %s ZIL", + console.log("Staker balance after: %s wei", staker.balance ); } diff --git a/script/stake_Delegation.s.sol b/script/stake_Delegation.s.sol index 79f2385..82077d8 100644 --- a/script/stake_Delegation.s.sol +++ b/script/stake_Delegation.s.sol @@ -21,7 +21,7 @@ contract Stake is Script { delegation.version() ); - console.log("Current stake: %s ZIL \r\n Current rewards: %s ZIL", + console.log("Current stake: %s wei \r\n Current rewards: %s wei", delegation.getStake(), delegation.getRewards() ); @@ -35,7 +35,7 @@ contract Stake is Script { lst.balanceOf(owner) ); - console.log("Staker balance before: %s ZIL %s LST", + console.log("Staker balance before: %s wei %s LST", staker.balance, lst.balanceOf(staker) ); @@ -46,7 +46,7 @@ contract Stake is Script { value: amount }(); - console.log("Staker balance after: %s ZIL %s LST", + console.log("Staker balance after: %s wei %s LST", staker.balance, lst.balanceOf(staker) ); diff --git a/script/unstake_Delegation.s.sol b/script/unstake_Delegation.s.sol index f9fab0b..06e29a8 100644 --- a/script/unstake_Delegation.s.sol +++ b/script/unstake_Delegation.s.sol @@ -21,7 +21,7 @@ contract Unstake is Script { delegation.version() ); - console.log("Current stake: %s ZIL \r\n Current rewards: %s ZIL", + console.log("Current stake: %s wei \r\n Current rewards: %s wei", delegation.getStake(), delegation.getRewards() ); @@ -31,11 +31,11 @@ contract Unstake is Script { address(lst) ); - console.log("Owner LST balance: %s LST", + console.log("Owner balance: %s LST", lst.balanceOf(owner) ); - console.log("Staker balance before: %s ZIL %s LST", + console.log("Staker balance before: %s wei %s LST", staker.balance, lst.balanceOf(staker) ); @@ -50,7 +50,7 @@ contract Unstake is Script { amount ); - console.log("Staker balance after: %s ZIL %s LST", + console.log("Staker balance after: %s wei %s LST", staker.balance, lst.balanceOf(staker) ); diff --git a/stake.sh b/stake.sh index e528b81..1b0139f 100755 --- a/stake.sh +++ b/stake.sh @@ -1,16 +1,22 @@ #!/bin/bash -if [ $# -ne 2 ]; then - echo "Provide a staker private key and the amount in wei as arguments." +if [ $# -ne 3 ]; then + echo "Provide the delegation contract address, a staker private key and an amount in wei as arguments." exit 1 fi -forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $2 --private-key $1 && \ -block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -echo rewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsAfterStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block && \ -block=$((block-1)) && \ -echo rewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsBeforeStaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block \ No newline at end of file +staker=$(cast wallet address $2) && \ +forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" $1 $3 --private-key $2 && \ +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ +echo rewardsAfterStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsAfterStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +staker_wei_after=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo $(date +"%T,%3N") $block_num && \ +block_num=$((block_num-1)) && \ +block=$(echo $block_num | cast to-hex --base-in 10) && \ +echo rewardsBeforeStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsBeforeStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +echo staked amount + gas fee = $(bc -l <<< $staker_wei_before-$staker_wei_after) wei +echo $(date +"%T,%3N") $block_num \ No newline at end of file diff --git a/state.sh b/state.sh index a2baad6..1eaf673 100755 --- a/state.sh +++ b/state.sh @@ -1,32 +1,34 @@ #!/bin/bash -if [ $# -ne 1 ]; then - echo "Provide a staker address as an argument." +if [ $# -ne 2 ]; then + echo "Provide the delegation contract address and a staker address as arguments." 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) && \ echo $(date +"%T,%3N") $block_num && \ -echo rewardsBeforeUnstaking = $(cast rpc eth_getBalance 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +owner=$(cast call $1 "owner()(address)" --block $block_num --rpc-url http://localhost:4201) && \ +lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) && \ +echo rewardsBeforeUnstaking = $(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +x=$(cast call $lst "balanceOf(address)(uint256)" $owner --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ owner_lst=$(cast to-unit $x ether) && \ -x=$(cast rpc eth_getBalance 0x15fc323DFE5D5DCfbeEdc25CEcbf57f676634d77 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +x=$(cast rpc eth_getBalance $owner $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ owner_zil=$(cast to-unit $x ether) && \ echo owner: $owner_lst LST && echo owner: $owner_zil ZIL && \ -x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "balanceOf(address)(uint256)" $1 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +x=$(cast call $lst "balanceOf(address)(uint256)" $2 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ staker_lst=$(cast to-unit $x ether) && \ -x=$(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +x=$(cast rpc eth_getBalance $2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ staker_zil=$(cast to-unit $x ether) && \ echo staker: $staker_lst LST && echo staker: $staker_zil ZIL && \ -x=$(cast call 0x9e5c257D1c6dF74EaA54e58CdccaCb924669dc83 "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -y=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -z=$(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +x=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +y=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +z=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ price=$(bc -l <<< \($y+$z\)/$x) && \ echo LST supply: $(cast to-unit $x ether) && \ echo LST price: $price && \ echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL && \ -echo getStake = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo getRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo getTaxedRewards = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo getTotalWithdrawals = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') \ No newline at end of file +echo getStake = $z && \ +echo getRewards = $y && \ +echo getTaxedRewards = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo getTotalWithdrawals = $(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') \ No newline at end of file diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol index 7817d28..60c6c44 100644 --- a/test/Delegation.t.sol +++ b/test/Delegation.t.sol @@ -771,19 +771,19 @@ contract DelegationTest is Test { to stake, unstake and claim on the network your local node is connected to. Before and after running the STAKING, UNSTAKING and CLAIMING scripts presented below, - always execute the following bash script to capture the values needed in the Foundry test below. + always execute the STATE script to capture the values needed in the Foundry test below. STATE: - chmod +x state.sh && ./state.sh + chmod +x state.sh && ./state.sh STAKING: - chmod +x stake.sh && ./stake.sh 10000000000000000000000 + chmod +x stake.sh && ./stake.sh 10000000000000000000000 UNSTAKING: - chmod +x unstake.sh && ./unstake.sh + chmod +x unstake.sh && ./unstake.sh CLAIMING: - chmod +x claim.sh && ./claim.sh + chmod +x claim.sh && ./claim.sh Before running the test, replace the address on the first line with */ @@ -798,7 +798,7 @@ contract DelegationTest is Test { uint256 taxedRewardsAfterStaking = rewardsBeforeStaking - (rewardsBeforeStaking - taxedRewardsBeforeStaking) / uint256(10); Console.log("Expected taxed rewards after staking: %s.%s%s ZIL", taxedRewardsAfterStaking); - // Insert the following value output by the UNSTAKE script + // Insert the following value output by the UNSTAKING script uint256 rewardsBeforeUnstaking = 233367080700403454378; run( 10_000_000 ether, @@ -812,16 +812,16 @@ contract DelegationTest is Test { true // initialDeposit ); // Replace the values below in the same order with the values output by the STATE script - // run after the CLAIM script or logged by the CLAIM script itself + // run after the CLAIMING script or logged by the CLAIMING script itself // the staker's ZIL balance in wei according to the STATE script after claiming // the staker's ZIL balance in wei according to the STATE script before claiming - // the claiming transaction fee in wei output by the CLAIM script + // the claiming transaction fee in wei output by the CLAIMING script Console.log("Expected staker balance after claiming: %s.%s%s ZIL", 100_000 ether - delegatedAmount + 100013.464887553198739807 ether - 90013.819919979031083499 ether + 0.3897714316896 ether ); // Replace the values below in the same order with values output by the STATE script - // run before the STAKE and after the UNSTAKE scripts or logged by those script themselves + // run before the STAKING and after the UNSTAKE scripts or logged by those script themselves // the owner's ZIL balance in wei according to the STATE script after unstaking // the owner's ZIL balance in wei according to the STATE script before staking // the transaction fees in wei output by the STAKING and UNSTAKING scripts diff --git a/unstake.sh b/unstake.sh index 4d0d597..78740b9 100755 --- a/unstake.sh +++ b/unstake.sh @@ -1,22 +1,33 @@ #!/bin/bash -if [ $# -lt 1 ]; then - echo "Provide a staker private key and optionally the number of shares as arguments." +if [ $# -lt 2 ]; then + echo "Provide the delegation contract address, a staker private key and optionally the number of shares as arguments." exit 1 fi -if [ $# -eq 2 ]; then - amount="$2" +if [ $# -eq 3 ]; then + amount="$3" else amount=0 fi -forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 $amount --private-key $1 && \ -block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -echo rewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsAfterUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block && \ -block=$((block-1)) && \ -echo rewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsBeforeUnstaking = $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getTaxedRewards()(uint256)" --block $block --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block \ No newline at end of file +staker=$(cast wallet address $2) && \ +forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" $1 $amount --private-key $2 && \ +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ +echo rewardsAfterUnstaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsAfterUnstaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo $(date +"%T,%3N") $block_num && \ +block_num=$((block_num-1)) && \ +block=$(echo $block_num | cast to-hex --base-in 10) && \ +echo rewardsBeforeUnstaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +echo taxedRewardsBeforeUnstaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) && \ +if [[ "$amount" == "0" ]]; then amount=$(cast call $lst "balanceOf(address)(uint256)" $staker --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g'); fi && \ +x=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +y=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +z=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ +price=$(bc -l <<< \($y+$z\)/$x) && \ +echo LST price: $price && \ +echo unstaked LST value: $(bc -l <<< $amount*$price/10^18) ZIL && \ +echo $(date +"%T,%3N") $block_num \ No newline at end of file From 087f58dd8d5e90253cb96351ccb91f803ed698a0 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Mon, 21 Oct 2024 19:37:51 +0200 Subject: [PATCH 12/15] Fix price calculation and commission formatting --- claim.sh | 45 +++++++++++++------ script/commission_Delegation.s.sol | 15 +++---- src/Console.sol | 26 +++++++++++ stake.sh | 47 ++++++++++++++------ state.sh | 64 +++++++++++++++------------ test/Delegation.t.sol | 25 ++--------- unstake.sh | 69 +++++++++++++++++++++--------- 7 files changed, 188 insertions(+), 103 deletions(-) create mode 100644 src/Console.sol diff --git a/claim.sh b/claim.sh index d0f9021..d1306ba 100755 --- a/claim.sh +++ b/claim.sh @@ -5,18 +5,37 @@ if [ $# -ne 2 ]; then exit 1 fi -staker=$(cast wallet address $2) && \ -forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 -vvvv && \ -block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ -block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ -echo rewardsAfterClaiming = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsAfterClaiming = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -staker_wei_after=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -echo $(date +"%T,%3N") $block_num && \ -block_num=$((block_num-1)) && \ -block=$(echo $block_num | cast to-hex --base-in 10) && \ -echo rewardsBeforeClaiming = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsBeforeClaiming = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +staker=$(cast wallet address $2) + +forge script script/claim_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 -vvvv + +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) + +echo rewardsAfterClaiming = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo taxedRewardsAfterClaiming = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + +staker_wei_after=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) + +tmp=$(cast logs --from-block $block_num --to-block $block_num --address $1 "Claimed(address,uint256)" --rpc-url http://localhost:4201 | grep "data") +if [[ "$tmp" != "" ]]; then + tmp=${tmp#*: } + tmp=$(cast abi-decode --input "x(uint256)" $tmp | sed 's/\[[^]]*\]//g') + tmp=(${tmp}) + d1=${tmp[0]} + #d1=$(echo $tmp | sed -n -e 1p | sed 's/\[[^]]*\]//g') +fi + +echo $(date +"%T,%3N") $block_num + +block_num=$((block_num-1)) +block=$(echo $block_num | cast to-hex --base-in 10) + +echo rewardsBeforeClaiming = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo taxedRewardsBeforeClaiming = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + +staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) + echo claimed amount - gas fee = $(bc -l <<< $staker_wei_after-$staker_wei_before) wei +if [[ "$tmp" != "" ]]; then echo event Claimed\($staker, $d1\) emitted; fi echo $(date +"%T,%3N") $block_num diff --git a/script/commission_Delegation.s.sol b/script/commission_Delegation.s.sol index fad63f1..0d14727 100644 --- a/script/commission_Delegation.s.sol +++ b/script/commission_Delegation.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.26; import {Script} from "forge-std/Script.sol"; import {NonRebasingLST} from "src/NonRebasingLST.sol"; import {DelegationV2} from "src/DelegationV2.sol"; +import {Console} from "src/Console.sol"; import "forge-std/console.sol"; contract Stake is Script { @@ -24,20 +25,18 @@ contract Stake is Script { address(lst) ); - console.log("Old commission rate: %s.%s%%", - uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), - //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% - uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) + Console.log("Old commission rate: %s.%s%s%%", + delegation.getCommissionNumerator(), + 2 ); vm.broadcast(deployerPrivateKey); delegation.setCommissionNumerator(commissionNumerator); - console.log("New commission rate: %s.%s%%", - uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), - //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% - uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) + Console.log("New commission rate: %s.%s%s%%", + delegation.getCommissionNumerator(), + 2 ); } } \ No newline at end of file diff --git a/src/Console.sol b/src/Console.sol new file mode 100644 index 0000000..ab394a0 --- /dev/null +++ b/src/Console.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.26; + +import "forge-std/console.sol"; + +library Console { + function log(string memory format, uint256 amount, uint8 precision) pure internal { + string memory zeros = ""; + uint256 decimals = amount % 10**precision; + while (decimals > 0 && decimals < 10**(precision - 1)) { + //console.log("%s %s", zeros, decimals); + zeros = string.concat(zeros, "0"); + decimals *= 10; + } + console.log( + format, + amount / 10**precision, + zeros, + amount % 10**precision + ); + } + + function log(string memory format, uint256 amount) pure internal { + return log(format, amount, 18); + } +} \ No newline at end of file diff --git a/stake.sh b/stake.sh index 1b0139f..678498b 100755 --- a/stake.sh +++ b/stake.sh @@ -5,18 +5,39 @@ if [ $# -ne 3 ]; then exit 1 fi -staker=$(cast wallet address $2) && \ -forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" $1 $3 --private-key $2 && \ -block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ -block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ -echo rewardsAfterStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsAfterStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -staker_wei_after=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -echo $(date +"%T,%3N") $block_num && \ -block_num=$((block_num-1)) && \ -block=$(echo $block_num | cast to-hex --base-in 10) && \ -echo rewardsBeforeStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsBeforeStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ +staker=$(cast wallet address $2) + +forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" $1 $3 --private-key $2 + +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) + +echo rewardsAfterStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo taxedRewardsAfterStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + +staker_wei_after=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) + +tmp=$(cast logs --from-block $block_num --to-block $block_num --address $1 "Staked(address,uint256,uint256)" --rpc-url http://localhost:4201 | grep "data") +if [[ "$tmp" != "" ]]; then + tmp=${tmp#*: } + tmp=$(cast abi-decode --input "x(uint256,uint256)" $tmp | sed 's/\[[^]]*\]//g') + tmp=(${tmp}) + d1=${tmp[0]} + d2=${tmp[1]} + #d1=$(echo $tmp | sed -n -e 1p | sed 's/\[[^]]*\]//g') + #d2=$(echo $tmp | sed -n -e 2p | sed 's/\[[^]]*\]//g') +fi + +echo $(date +"%T,%3N") $block_num + +block_num=$((block_num-1)) +block=$(echo $block_num | cast to-hex --base-in 10) + +echo rewardsBeforeStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo taxedRewardsBeforeStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + +staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) + echo staked amount + gas fee = $(bc -l <<< $staker_wei_before-$staker_wei_after) wei +if [[ "$tmp" != "" ]]; then echo event Staked\($staker, $d1, $d2\) emitted; fi echo $(date +"%T,%3N") $block_num \ No newline at end of file diff --git a/state.sh b/state.sh index 1eaf673..99b028a 100755 --- a/state.sh +++ b/state.sh @@ -5,30 +5,40 @@ if [ $# -ne 2 ]; 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) && \ -echo $(date +"%T,%3N") $block_num && \ -owner=$(cast call $1 "owner()(address)" --block $block_num --rpc-url http://localhost:4201) && \ -lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) && \ -echo rewardsBeforeUnstaking = $(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -x=$(cast call $lst "balanceOf(address)(uint256)" $owner --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -owner_lst=$(cast to-unit $x ether) && \ -x=$(cast rpc eth_getBalance $owner $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -owner_zil=$(cast to-unit $x ether) && \ -echo owner: $owner_lst LST && echo owner: $owner_zil ZIL && \ -x=$(cast call $lst "balanceOf(address)(uint256)" $2 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -staker_lst=$(cast to-unit $x ether) && \ -x=$(cast rpc eth_getBalance $2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) && \ -staker_zil=$(cast to-unit $x ether) && \ -echo staker: $staker_lst LST && echo staker: $staker_zil ZIL && \ -x=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -y=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -z=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -price=$(bc -l <<< \($y+$z\)/$x) && \ -echo LST supply: $(cast to-unit $x ether) && \ -echo LST price: $price && \ -echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL && \ -echo getStake = $z && \ -echo getRewards = $y && \ -echo getTaxedRewards = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo getTotalWithdrawals = $(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') \ No newline at end of file +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) +echo $(date +"%T,%3N") $block_num + +owner=$(cast call $1 "owner()(address)" --block $block_num --rpc-url http://localhost:4201) +lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) + +rewardsBeforeUnstaking=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +#rewardsBeforeUnstaking=$(cast rpc eth_getBalance $1 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) +taxedRewardsBeforeUnstaking=$(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo rewardsBeforeUnstaking = $rewardsBeforeUnstaking +echo taxedRewardsBeforeUnstaking = $taxedRewardsBeforeUnstaking + +x=$(cast call $lst "balanceOf(address)(uint256)" $owner --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +owner_lst=$(cast to-unit $x ether) +x=$(cast rpc eth_getBalance $owner $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) +owner_zil=$(cast to-unit $x ether) +echo owner: $owner_lst LST && echo owner: $owner_zil ZIL + +x=$(cast call $lst "balanceOf(address)(uint256)" $2 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +staker_lst=$(cast to-unit $x ether) +x=$(cast rpc eth_getBalance $2 $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) +staker_zil=$(cast to-unit $x ether) +echo staker: $staker_lst LST && echo staker: $staker_zil ZIL + +totalSupply=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +price=$(bc -l <<< \($stake+$rewardsBeforeUnstaking-\($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking\)*$commissionNumerator/$denominator\)/$totalSupply) + +echo LST supply: $(cast to-unit $totalSupply ether) ZIL +echo LST price: $price +echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL + +echo validator stake: $(cast to-unit $stake ether) ZIL +echo pending withdrawals: $(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') wei \ No newline at end of file diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol index 60c6c44..5959f8d 100644 --- a/test/Delegation.t.sol +++ b/test/Delegation.t.sol @@ -1,32 +1,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import {Delegation} from "src/Delegation.sol"; -import {DelegationV2} from "src/DelegationV2.sol"; +import {Delegation} from "src/Delegation.sol"; +import {DelegationV2} from "src/DelegationV2.sol"; import {NonRebasingLST} from "src/NonRebasingLST.sol"; -import {Deposit} from "src/Deposit.sol"; +import {Deposit} from "src/Deposit.sol"; +import {Console} from "src/Console.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Test, Vm} from "forge-std/Test.sol"; import "forge-std/console.sol"; -library Console { - function log(string memory format, uint256 amount) external { - string memory zeros = ""; - uint256 decimals = amount % 10**18; - while (decimals > 0 && decimals < 10**17) { - //console.log("%s %s", zeros, decimals); - zeros = string.concat(zeros, "0"); - decimals *= 10; - } - console.log( - format, - amount / 10**18, - zeros, - amount % 10**18 - ); - } -} - contract DelegationTest is Test { address payable proxy; address owner; diff --git a/unstake.sh b/unstake.sh index 78740b9..383d00b 100755 --- a/unstake.sh +++ b/unstake.sh @@ -6,28 +6,55 @@ if [ $# -lt 2 ]; then fi if [ $# -eq 3 ]; then - amount="$3" + shares="$3" else - amount=0 + shares=0 fi -staker=$(cast wallet address $2) && \ -forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" $1 $amount --private-key $2 && \ -block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) && \ -block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) && \ -echo rewardsAfterUnstaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsAfterUnstaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo $(date +"%T,%3N") $block_num && \ -block_num=$((block_num-1)) && \ -block=$(echo $block_num | cast to-hex --base-in 10) && \ -echo rewardsBeforeUnstaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -echo taxedRewardsBeforeUnstaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) && \ -if [[ "$amount" == "0" ]]; then amount=$(cast call $lst "balanceOf(address)(uint256)" $staker --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g'); fi && \ -x=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -y=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -z=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') && \ -price=$(bc -l <<< \($y+$z\)/$x) && \ -echo LST price: $price && \ -echo unstaked LST value: $(bc -l <<< $amount*$price/10^18) ZIL && \ +staker=$(cast wallet address $2) + +forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" $1 $shares --private-key $2 + +block=$(cast rpc eth_blockNumber --rpc-url http://localhost:4201) +block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) + +echo rewardsAfterUnstaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo taxedRewardsAfterUnstaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + +tmp=$(cast logs --from-block $block_num --to-block $block_num --address $1 "Unstaked(address,uint256,uint256)" --rpc-url http://localhost:4201 | grep "data") +if [[ "$tmp" != "" ]]; then + tmp=${tmp#*: } + tmp=$(cast abi-decode --input "x(uint256,uint256)" $tmp | sed 's/\[[^]]*\]//g') + tmp=(${tmp}) + d1=${tmp[0]} + d2=${tmp[1]} + #d1=$(echo $tmp | sed -n -e 1p | sed 's/\[[^]]*\]//g') + #d2=$(echo $tmp | sed -n -e 2p | sed 's/\[[^]]*\]//g') +fi + +echo $(date +"%T,%3N") $block_num + +block_num=$((block_num-1)) +block=$(echo $block_num | cast to-hex --base-in 10) + +rewardsBeforeUnstaking=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +taxedRewardsBeforeUnstaking=$(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo rewardsBeforeUnstaking = $rewardsBeforeUnstaking +echo taxedRewardsBeforeUnstaking = $taxedRewardsBeforeUnstaking + +lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) + +if [[ "$shares" == "0" ]]; then shares=$(cast call $lst "balanceOf(address)(uint256)" $staker --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g'); fi + +totalSupply=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +price=$(bc -l <<< \($stake+$rewardsBeforeUnstaking-\($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking\)*$commissionNumerator/$denominator\)/$totalSupply) + +echo LST price: $price +echo unstaked LST value: $(bc -l <<< $shares*$price/10^18) ZIL + +if [[ "$tmp" != "" ]]; then echo event Unstaked\($staker, $d1, $d2\) emitted; fi + echo $(date +"%T,%3N") $block_num \ No newline at end of file From 7389500e298546d1b85707b680d7e410aac96978 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Mon, 21 Oct 2024 21:25:01 +0200 Subject: [PATCH 13/15] Fix bc scale in bash scripts --- claim.sh | 2 +- stake.sh | 2 +- state.sh | 4 ++-- unstake.sh | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/claim.sh b/claim.sh index d1306ba..9795232 100755 --- a/claim.sh +++ b/claim.sh @@ -36,6 +36,6 @@ echo taxedRewardsBeforeClaiming = $(cast call $1 "getTaxedRewards()(uint256)" -- staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) -echo claimed amount - gas fee = $(bc -l <<< $staker_wei_after-$staker_wei_before) wei +echo claimed amount - gas fee = $(bc -l <<< "scale=18; $staker_wei_after-$staker_wei_before") wei if [[ "$tmp" != "" ]]; then echo event Claimed\($staker, $d1\) emitted; fi echo $(date +"%T,%3N") $block_num diff --git a/stake.sh b/stake.sh index 678498b..b851b47 100755 --- a/stake.sh +++ b/stake.sh @@ -38,6 +38,6 @@ echo taxedRewardsBeforeStaking = $(cast call $1 "getTaxedRewards()(uint256)" --b staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) -echo staked amount + gas fee = $(bc -l <<< $staker_wei_before-$staker_wei_after) wei +echo staked amount + gas fee = $(bc -l <<< "scale=18; $staker_wei_before-$staker_wei_after") wei if [[ "$tmp" != "" ]]; then echo event Staked\($staker, $d1, $d2\) emitted; fi echo $(date +"%T,%3N") $block_num \ No newline at end of file diff --git a/state.sh b/state.sh index 99b028a..0a30ee4 100755 --- a/state.sh +++ b/state.sh @@ -34,11 +34,11 @@ totalSupply=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-u stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') -price=$(bc -l <<< \($stake+$rewardsBeforeUnstaking-\($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking\)*$commissionNumerator/$denominator\)/$totalSupply) +price=$(bc -l <<< "scale=36; ($stake+$rewardsBeforeUnstaking-($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking)*$commissionNumerator/$denominator)/$totalSupply") echo LST supply: $(cast to-unit $totalSupply ether) ZIL echo LST price: $price -echo staker LST value: $(bc -l <<< $staker_lst*$price) ZIL +echo staker LST value: $(bc -l <<< "scale=18; $staker_lst*$price") ZIL echo validator stake: $(cast to-unit $stake ether) ZIL echo pending withdrawals: $(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') wei \ No newline at end of file diff --git a/unstake.sh b/unstake.sh index 383d00b..1d3d1c1 100755 --- a/unstake.sh +++ b/unstake.sh @@ -50,10 +50,10 @@ totalSupply=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-u stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') -price=$(bc -l <<< \($stake+$rewardsBeforeUnstaking-\($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking\)*$commissionNumerator/$denominator\)/$totalSupply) +price=$(bc -l <<< "scale=36; ($stake+$rewardsBeforeUnstaking-($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking)*$commissionNumerator/$denominator)/$totalSupply") echo LST price: $price -echo unstaked LST value: $(bc -l <<< $shares*$price/10^18) ZIL +echo unstaked LST value: $(bc -l <<< "scale=18; $shares*$price/10^18") ZIL if [[ "$tmp" != "" ]]; then echo event Unstaked\($staker, $d1, $d2\) emitted; fi From dcbaece7bcde83fa9197b92d4c70f6c15e3b9552 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Tue, 22 Oct 2024 18:03:08 +0200 Subject: [PATCH 14/15] Fix tests, add getPrice() and getClaimable() --- README.md | 10 +++ src/Delegation.sol | 2 +- src/DelegationV2.sol | 32 +++++++++- src/NonRebasingLST.sol | 2 +- state.sh | 5 +- test/Delegation.t.sol | 139 ++++++++++++++++++++++++++++------------- unstake.sh | 3 +- 7 files changed, 141 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 2e32538..ebe334c 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,11 @@ This is due to the fact that the above output was generated based on the local s 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. +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 ```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... @@ -139,4 +144,9 @@ The output will look like this: Running version: 2 Staker balance before: 99698086421983460161224 wei Staker balance after: 99798095485861371162343 wei +``` + +To query how much ZIL you 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 ``` \ No newline at end of file diff --git a/src/Delegation.sol b/src/Delegation.sol index aff774c..9d59a9f 100644 --- a/src/Delegation.sol +++ b/src/Delegation.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; diff --git a/src/DelegationV2.sol b/src/DelegationV2.sol index 965e82f..f58e1fe 100644 --- a/src/DelegationV2.sol +++ b/src/DelegationV2.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; @@ -10,7 +10,8 @@ import "src/NonRebasingLST.sol"; library WithdrawalQueue { //TODO: add it to the variables and implement a getter and an onlyOwner setter - // since a governance vote can change the unbonding period anytime + // since a governance vote can change the unbonding period anytime or fetch + // it from the deposit contract uint256 public constant UNBONDING_PERIOD = 30; //approx. 30s, used only for testing struct Item { @@ -36,8 +37,12 @@ library WithdrawalQueue { fifo.first++; } + function ready(Fifo storage fifo, uint256 index) internal view returns(bool) { + return index < fifo.last && fifo.items[index].blockNumber <= block.number; + } + function ready(Fifo storage fifo) internal view returns(bool) { - return fifo.first < fifo.last && fifo.items[fifo.first].blockNumber <= block.number; + return ready(fifo, fifo.first); } } @@ -229,6 +234,17 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade $.commissionNumerator = _commissionNumerator; } + // return the amount of ZIL equivalent to 1 LST (share) + function getPrice() public view returns(uint256 amount) { + Storage storage $ = _getStorage(); + uint256 rewards = getRewards(); + uint256 commission = (rewards - $.taxedRewards) * $.commissionNumerator / DENOMINATOR; + if (NonRebasingLST($.lst).totalSupply() == 0) + amount = 1 ether; + else + amount = (getStake() + rewards - commission) * 1 ether / NonRebasingLST($.lst).totalSupply(); + } + function taxRewards() internal { Storage storage $ = _getStorage(); uint256 rewards = getRewards(); @@ -244,6 +260,16 @@ contract DelegationV2 is Initializable, PausableUpgradeable, Ownable2StepUpgrade emit CommissionPaid(owner(), rewards, commission); } + function getClaimable() public view returns(uint256 total) { + Storage storage $ = _getStorage(); + WithdrawalQueue.Fifo storage fifo = $.withdrawals[msg.sender]; + uint256 index = fifo.first; + while (fifo.ready(index)) { + total += fifo.items[index].amount; + index++; + } + } + function claim() public whenNotPaused { Storage storage $ = _getStorage(); uint256 total; diff --git a/src/NonRebasingLST.sol b/src/NonRebasingLST.sol index c9bd82a..55e9496 100644 --- a/src/NonRebasingLST.sol +++ b/src/NonRebasingLST.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/state.sh b/state.sh index 0a30ee4..7553506 100755 --- a/state.sh +++ b/state.sh @@ -35,10 +35,13 @@ stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://l commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') price=$(bc -l <<< "scale=36; ($stake+$rewardsBeforeUnstaking-($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking)*$commissionNumerator/$denominator)/$totalSupply") +price0=$(cast call $1 "getPrice()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +claimable=$(cast call $1 "getClaimable()(uint256)" --from $2 --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') echo LST supply: $(cast to-unit $totalSupply ether) ZIL -echo LST price: $price +echo LST price: $price \~ $(cast to-unit $price0 ether) echo staker LST value: $(bc -l <<< "scale=18; $staker_lst*$price") ZIL +echo staker claimable: $(cast to-unit $claimable ether) ZIL echo validator stake: $(cast to-unit $stake ether) ZIL echo pending withdrawals: $(cast call $1 "getTotalWithdrawals()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') wei \ No newline at end of file diff --git a/test/Delegation.t.sol b/test/Delegation.t.sol index 5959f8d..9fd2b3e 100644 --- a/test/Delegation.t.sol +++ b/test/Delegation.t.sol @@ -84,19 +84,17 @@ contract DelegationTest is Test { address(lst) ); - console.log("Old commission rate: %s.%s%%", - uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), - //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% - uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) + Console.log("Old commission rate: %s.%s%s%%", + delegation.getCommissionNumerator(), + 2 ); //*/ uint256 commissionNumerator = 1_000; delegation.setCommissionNumerator(commissionNumerator); /* - console.log("New commission rate: %s.%s%%", - uint256(delegation.getCommissionNumerator()) * 100 / uint256(delegation.DENOMINATOR()), - //TODO: check if the decimals are printed correctly e.g. 12.01% vs 12.1% - uint256(delegation.getCommissionNumerator()) % (uint256(delegation.DENOMINATOR()) / 100) + Console.log("New commission rate: %s.%s%s%%", + delegation.getCommissionNumerator(), + 2 ); //*/ @@ -173,7 +171,7 @@ contract DelegationTest is Test { vm.deal(staker, 100_000 ether); vm.startPrank(staker); - Console.log("Stake deposited before staking: %s.%s%s ZIL", + Console.log("Deposit before staking: %s.%s%s ZIL", delegation.getStake() ); @@ -181,6 +179,10 @@ contract DelegationTest is Test { delegation.getRewards() ); + Console.log("Taxed rewards before staking: %s.%s%s ZIL", + delegation.getTaxedRewards() + ); + Console.log("Staker balance before staking: %s.%s%s ZIL", staker.balance ); @@ -197,6 +199,10 @@ contract DelegationTest is Test { uint256 ownerZILAfter; uint256 loggedAmount; uint256 loggedShares; + uint256 totalShares; + uint256 rewardsAfterStaking; + uint256 taxedRewardsAfterStaking; + uint256 rewardsDelta = rewardsBeforeStaking - taxedRewardsBeforeStaking; Vm.Log[] memory entries; for (uint8 j = 0; j < numberOfDelegations; j++) { @@ -229,22 +235,28 @@ contract DelegationTest is Test { for (uint256 i = 0; i < entries.length; i++) { if (entries[i].topics[0] == keccak256("Staked(address,uint256,uint256)")) { (loggedAmount, loggedShares) = abi.decode(entries[i].data, (uint256, uint256)); - //console.log(loggedAmount, loggedShares); + assertEq(loggedAmount, delegatedAmount, "staked amount mismatch"); } } - //console.log(delegatedAmount, (lst.totalSupply() - lst.balanceOf(staker)) * delegatedAmount / (delegation.getStake() + delegation.getTaxedRewards())); - //console.log(delegatedAmount, lst.balanceOf(staker)); + totalShares += loggedShares; Console.log("Owner commission after staking: %s.%s%s ZIL", ownerZILAfter - ownerZILBefore ); + assertEq(rewardsDelta * delegation.getCommissionNumerator() / delegation.DENOMINATOR(), ownerZILAfter - ownerZILBefore, "commission mismatch after staking"); - Console.log("Stake deposited after staking: %s.%s%s ZIL", + Console.log("Deposit after staking: %s.%s%s ZIL", delegation.getStake() ); + rewardsAfterStaking = delegation.getRewards(); Console.log("Rewards after staking: %s.%s%s ZIL", - delegation.getRewards() + rewardsAfterStaking + ); + + taxedRewardsAfterStaking = delegation.getTaxedRewards(); + Console.log("Taxed rewards after staking: %s.%s%s ZIL", + taxedRewardsAfterStaking ); Console.log("Staker balance after staking: %s.%s%s ZIL", @@ -260,17 +272,19 @@ contract DelegationTest is Test { ); vm.deal(address(delegation), address(delegation).balance + rewardsAccruedAfterEach); + rewardsDelta = delegation.getRewards() - taxedRewardsAfterStaking; } - //vm.deal(address(delegation), address(delegation).balance + rewardsEarnedUntilUnstaking); vm.deal(address(delegation), rewardsBeforeUnstaking); + uint256 lstPrice = 10**18 * 1 ether * ((delegation.getStake() + delegation.getRewards() - (delegation.getRewards() - delegation.getTaxedRewards()) * delegation.getCommissionNumerator() / delegation.DENOMINATOR())) / lst.totalSupply(); Console.log("LST price: %s.%s%s", - 10**18 * (delegation.getStake() + delegation.getRewards()) / lst.totalSupply() + lstPrice ); + assertEq(lstPrice / 10**18, delegation.getPrice(), "price mismatch"); Console.log("LST value: %s.%s%s", - lst.balanceOf(staker) * (delegation.getStake() + delegation.getRewards()) / lst.totalSupply() + totalShares * lstPrice / 10**18 / 1 ether ); vm.recordLogs(); @@ -291,8 +305,11 @@ contract DelegationTest is Test { uint256 stakerLSTBefore = lst.balanceOf(staker); ownerZILBefore = delegation.owner().balance; + uint256 shares = initialDeposit ? lst.balanceOf(staker) : lst.balanceOf(staker) - depositAmount; + assertEq(totalShares, shares, "staked shares balance mismatch"); + delegation.unstake( - initialDeposit ? lst.balanceOf(staker) : lst.balanceOf(staker) - depositAmount + shares ); uint256 stakerLSTAfter = lst.balanceOf(staker); @@ -303,26 +320,35 @@ contract DelegationTest is Test { for (uint256 i = 0; i < entries.length; i++) { if (entries[i].topics[0] == keccak256("Unstaked(address,uint256,uint256)")) { (loggedAmount, loggedShares) = abi.decode(entries[i].data, (uint256, uint256)); - //console.log(loggedAmount, loggedShares); } } - //TODO: why is loggedAmount equal to the value below without adding back lst.totalSupply() + stakerLSTBefore although unstake() burns stakerLSTBefore before computing the amount? - //console.log((delegation.getStake() + delegation.getTaxedRewards()) * stakerLSTBefore / lst.totalSupply(), stakerLSTBefore - stakerLSTAfter); + assertEq(totalShares * lstPrice / 10**18 / 1 ether, loggedAmount, "unstaked amount mismatch"); + assertEq(shares, loggedShares, "unstaked shares mismatch"); + assertEq(shares, stakerLSTBefore - stakerLSTAfter, "shares balance mismatch"); Console.log("Owner commission after unstaking: %s.%s%s ZIL", ownerZILAfter - ownerZILBefore ); - Console.log("Stake deposited after unstaking: %s.%s%s ZIL", + assertEq((rewardsBeforeUnstaking - taxedRewardsAfterStaking) * delegation.getCommissionNumerator() / delegation.DENOMINATOR(), ownerZILAfter - ownerZILBefore, "commission mismatch after unstaking"); + + Console.log("Deposit after unstaking: %s.%s%s ZIL", delegation.getStake() ); + uint256 rewardsAfterUnstaking = delegation.getRewards(); Console.log("Rewards after unstaking: %s.%s%s ZIL", - delegation.getRewards() + rewardsAfterUnstaking ); + uint256 taxedRewardsAfterUnstaking = delegation.getTaxedRewards(); + Console.log("Taxed rewards after unstaking: %s.%s%s ZIL", + taxedRewardsAfterUnstaking + ); + + uint256 stakerBalanceAfterUnstaking = staker.balance; Console.log("Staker balance after unstaking: %s.%s%s ZIL", - staker.balance + stakerBalanceAfterUnstaking ); Console.log("Staker balance after unstaking: %s.%s%s LST", @@ -338,6 +364,8 @@ contract DelegationTest is Test { vm.recordLogs(); uint256 unstakedAmount = loggedAmount; // the amount we logged on unstaking + Console.log("Unstaked amount: %s.%s%s ZIL", unstakedAmount); + vm.expectEmit( true, false, @@ -363,17 +391,17 @@ contract DelegationTest is Test { for (uint256 i = 0; i < entries.length; i++) { if (entries[i].topics[0] == keccak256("Claimed(address,uint256)")) { loggedAmount = abi.decode(entries[i].data, (uint256)); - //console.log(loggedAmount); } - } - //console.log(stakerZILAfter - stakerZILBefore); - //console.log(unstakedAmount); + } + assertEq(loggedAmount, unstakedAmount, "unstaked vs claimed amount mismatch"); + assertEq(loggedAmount, stakerZILAfter - stakerZILBefore, "claimed amount vs staker balance mismatch"); Console.log("Owner commission after claiming: %s.%s%s ZIL", ownerZILAfter - ownerZILBefore ); + assertEq((rewardsAfterUnstaking - taxedRewardsAfterUnstaking) * delegation.getCommissionNumerator() / delegation.DENOMINATOR(), ownerZILAfter - ownerZILBefore, "commission mismatch after claiming"); - Console.log("Stake deposited after claiming: %s.%s%s ZIL", + Console.log("Deposit after claiming: %s.%s%s ZIL", delegation.getStake() ); @@ -381,9 +409,14 @@ contract DelegationTest is Test { delegation.getRewards() ); + Console.log("Taxed rewards after claiming: %s.%s%s ZIL", + delegation.getTaxedRewards() + ); + Console.log("Staker balance after claiming: %s.%s%s ZIL", staker.balance ); + assertEq(staker.balance, stakerBalanceAfterUnstaking + unstakedAmount, "final staker balance mismatch"); Console.log("Staker balance after claiming: %s.%s%s LST", lst.balanceOf(staker) @@ -396,6 +429,7 @@ contract DelegationTest is Test { } function test_1a_LargeStake_Late_NoRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -413,11 +447,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_1b_LargeStake_Early_NoRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -435,11 +470,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_2a_LargeStake_Late_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -457,11 +493,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_3a_SmallStake_Late_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -479,11 +516,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_4a_LargeStake_Late_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -501,11 +539,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_5a_SmallStake_Late_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -523,11 +562,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_2b_LargeStake_Late_SmallValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -545,11 +585,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_3b_SmallStake_Late_SmallValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -567,11 +608,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - false // initialDeposit using the node owner' funds, otherwise delegated by a staker + false // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_4b_LargeStake_Late_LargeValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -589,11 +631,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - false // initialDeposit using the node owner' funds, otherwise delegated by a staker + false // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_5b_SmallStake_Late_LargeValidator_DelegatedDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -611,11 +654,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - false // initialDeposit using the node owner' funds, otherwise delegated by a staker + false // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_2c_LargeStake_Early_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -633,11 +677,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_3c_SmallStake_Early_SmallValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -655,11 +700,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - false // initialDeposit using the node owner' funds, otherwise delegated by a staker + false // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_4c_LargeStake_Early_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -677,11 +723,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - false // initialDeposit using the node owner' funds, otherwise delegated by a staker + false // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_5c_SmallStake_Early_LargeValidator_OwnDeposit_OneYearOfRewards_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 100_000_000 ether; uint256 totalDeposit = 5_200_000_000 ether; uint256 delegatedAmount = 100 ether; @@ -699,11 +746,12 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 365 * 24 * 51_000 ether * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - false // initialDeposit using the node owner' funds, otherwise delegated by a staker + false // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_6a_ManyVsOneStake_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 110_000_000 ether; uint256 delegatedAmount = 10_000 ether; @@ -723,11 +771,12 @@ contract DelegationTest is Test { 5 * 51_000 ether / uint256(3600) * depositAmount / totalDeposit, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 51_000 ether / uint256(60) * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } function test_6b_OneVsManyStakes_UnstakeAll() public { + staker = 0x092E5E57955437876dA9Df998C96e2BE19341670; uint256 depositAmount = 10_000_000 ether; uint256 totalDeposit = 110_000_000 ether; uint256 delegatedAmount = 90_000 ether; @@ -745,7 +794,7 @@ contract DelegationTest is Test { 0, // rewardsAccruedAfterEach taxedRewardsAfterStaking + 51_000 ether / uint256(60) * depositAmount / totalDeposit, // rewardsBeforeUnstaking 30, // after unstaking wait blocksUntil claiming - true // initialDeposit using the node owner' funds, otherwise delegated by a staker + true // initialDeposit using funds held by the node, otherwise delegated by a staker ); } diff --git a/unstake.sh b/unstake.sh index 1d3d1c1..4b555fb 100755 --- a/unstake.sh +++ b/unstake.sh @@ -51,8 +51,9 @@ stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://l commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') price=$(bc -l <<< "scale=36; ($stake+$rewardsBeforeUnstaking-($rewardsBeforeUnstaking-$taxedRewardsBeforeUnstaking)*$commissionNumerator/$denominator)/$totalSupply") +price0=$(cast call $1 "getPrice()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') -echo LST price: $price +echo LST price: $price \~ $(cast to-unit $price0 ether) echo unstaked LST value: $(bc -l <<< "scale=18; $shares*$price/10^18") ZIL if [[ "$tmp" != "" ]]; then echo event Unstaked\($staker, $d1, $d2\) emitted; fi From 4ecb98aa1fd754b5f49367e31b996cef67bfc0b0 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Wed, 23 Oct 2024 08:06:55 +0200 Subject: [PATCH 15/15] Improve staking script --- stake.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/stake.sh b/stake.sh index b851b47..24193c6 100755 --- a/stake.sh +++ b/stake.sh @@ -33,11 +33,26 @@ echo $(date +"%T,%3N") $block_num block_num=$((block_num-1)) block=$(echo $block_num | cast to-hex --base-in 10) -echo rewardsBeforeStaking = $(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') -echo taxedRewardsBeforeStaking = $(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +rewardsBeforeStaking=$(cast call $1 "getRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +taxedRewardsBeforeStaking=$(cast call $1 "getTaxedRewards()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +echo rewardsBeforeStaking = $rewardsBeforeStaking +echo taxedRewardsBeforeStaking = $taxedRewardsBeforeStaking -staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) +lst=$(cast call $1 "getLST()(address)" --block $block_num --rpc-url http://localhost:4201) + +totalSupply=$(cast call $lst "totalSupply()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +stake=$(cast call $1 "getStake()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +commissionNumerator=$(cast call $1 "getCommissionNumerator()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +denominator=$(cast call $1 "DENOMINATOR()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') +price=$(bc -l <<< "scale=36; ($stake+$rewardsBeforeStaking-($rewardsBeforeStaking-$taxedRewardsBeforeStaking)*$commissionNumerator/$denominator)/$totalSupply") +price0=$(cast call $1 "getPrice()(uint256)" --block $block_num --rpc-url http://localhost:4201 | sed 's/\[[^]]*\]//g') + +echo LST price: $price \~ $(cast to-unit $price0 ether) +echo staked ZIL shares: $(bc -l <<< "scale=18; $3/$price/10^18") LST +staker_wei_before=$(cast rpc eth_getBalance $staker $block --rpc-url http://localhost:4201 | tr -d '"' | cast to-dec --base-in 16) echo staked amount + gas fee = $(bc -l <<< "scale=18; $staker_wei_before-$staker_wei_after") wei + if [[ "$tmp" != "" ]]; then echo event Staked\($staker, $d1, $d2\) emitted; fi + echo $(date +"%T,%3N") $block_num \ No newline at end of file