From 518247ed2d4a31d4b62b25897e55679a816b5dc9 Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Fri, 18 Oct 2024 15:35:20 +0200 Subject: [PATCH] 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