Skip to content

Commit

Permalink
Implement functions requested by staking portal
Browse files Browse the repository at this point in the history
  • Loading branch information
DrZoltanFazekas committed Dec 12, 2024
1 parent 50ca3e9 commit 6b7841c
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 34 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ event Unstaked(address indexed delegator, uint256 amount, bytes data);
event Claimed(address indexed delegator, uint256 amount, bytes data);
function stake() external payable;
function unstake(uint256) external;
function unstake(uint256) external returns(uint256 unstakedZil);
function claim() external;
function getClaimable() external virtual view returns(uint256 total);
function getPendingClaims() external virtual view returns(uint256[2][] memory blockNumbersAndAmounts);
function getMinDelegation() external view returns(uint256 amount);
function getCommission() external view returns(uint256 numerator, uint256 denominator);
function getStake() external view returns(uint256 validatorStake);
```
as well as the additional events and methods applicable to a specific staking variant only such as
```solidity
Expand Down
30 changes: 29 additions & 1 deletion src/BaseDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ library WithdrawalQueue {
return index < fifo.last && fifo.items[index].blockNumber <= block.number;
}

function notReady(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 ready(fifo, fifo.first);
}
Expand Down Expand Up @@ -181,9 +185,19 @@ abstract contract BaseDelegation is Delegation, PausableUpgradeable, Ownable2Ste
$.commissionNumerator = _commissionNumerator;
}

function getCommission() public virtual view returns(uint256 numerator, uint256 denominator) {
BaseDelegationStorage storage $ = _getBaseDelegationStorage();
numerator = $.commissionNumerator;
denominator = DENOMINATOR;
}

function getMinDelegation() public virtual view returns(uint256) {
return MIN_DELEGATION;
}

function stake() external virtual payable;

function unstake(uint256) external virtual;
function unstake(uint256) external virtual returns(uint256);

function claim() external virtual;

Expand All @@ -201,6 +215,20 @@ abstract contract BaseDelegation is Delegation, PausableUpgradeable, Ownable2Ste
}
}

function getPendingClaims() public virtual view returns(uint256[2][] memory claims) {
BaseDelegationStorage storage $ = _getBaseDelegationStorage();
WithdrawalQueue.Fifo storage fifo = $.withdrawals[_msgSender()];
uint256 index = fifo.first;
while (fifo.ready(index))
index++;
uint256 firstPending = index;
claims = new uint256[2][](fifo.last - index);
while (fifo.notReady(index)) {
claims[index - firstPending] = [fifo.items[index].blockNumber, fifo.items[index].amount];
index++;
}
}

function _dequeueWithdrawals() internal virtual returns (uint256 total) {
BaseDelegationStorage storage $ = _getBaseDelegationStorage();
while ($.withdrawals[_msgSender()].ready())
Expand Down
7 changes: 6 additions & 1 deletion src/Delegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ interface Delegation {
event CommissionPaid(address indexed owner, uint256 commission);

function stake() external payable;
function unstake(uint256) external;
function unstake(uint256) external returns(uint256);
function claim() external;
function collectCommission() external;
function stakeRewards() external;
function getMinDelegation() external view returns(uint256);
function getCommission() external view returns(uint256, uint256);
function getStake() external view returns(uint256);
function getClaimable() external view returns(uint256);
function getPendingClaims() external view returns(uint256[2][] memory);
}
2 changes: 1 addition & 1 deletion src/LiquidDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ contract LiquidDelegation is BaseDelegation, ILiquidDelegation {
revert("not implemented");
}

function unstake(uint256) external override {
function unstake(uint256) external override returns(uint256) {
revert("not implemented");
}

Expand Down
3 changes: 1 addition & 2 deletions src/LiquidDelegationV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ contract LiquidDelegationV2 is BaseDelegation, ILiquidDelegation {
emit Staked(_msgSender(), msg.value, abi.encode(shares));
}

function unstake(uint256 shares) public override whenNotPaused {
uint256 amount;
function unstake(uint256 shares) public override whenNotPaused returns(uint256 amount) {
LiquidDelegationStorage storage $ = _getLiquidDelegationStorage();
// before calculating the amount deduct the commission from the yet untaxed rewards
taxRewards();
Expand Down
2 changes: 1 addition & 1 deletion src/NonLiquidDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ contract NonLiquidDelegation is BaseDelegation, INonLiquidDelegation {
revert("not implemented");
}

function unstake(uint256) external override {
function unstake(uint256) external override returns(uint256) {
revert("not implemented");
}

Expand Down
3 changes: 2 additions & 1 deletion src/NonLiquidDelegationV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,12 @@ contract NonLiquidDelegationV2 is BaseDelegation, INonLiquidDelegation {
emit Staked(_msgSender(), msg.value, "");
}

function unstake(uint256 value) public override whenNotPaused {
function unstake(uint256 value) public override whenNotPaused returns(uint256 amount) {
_append(-int256(value));
_decreaseDeposit(uint256(value));
_enqueueWithdrawal(value);
emit Unstaked(_msgSender(), value, "");
return value;
}

function _append(int256 value) internal {
Expand Down
116 changes: 115 additions & 1 deletion test/BaseDelegation.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;

import {BaseDelegation} from "src/BaseDelegation.sol";
import {BaseDelegation, WithdrawalQueue} from "src/BaseDelegation.sol";
import {Delegation} from "src/Delegation.sol";
import {Deposit, InitialStaker} from "@zilliqa/zq2/deposit_v2.sol";
import {Console} from "src/Console.sol";
Expand Down Expand Up @@ -181,4 +181,118 @@ abstract contract BaseDelegationTest is Test {
// wait 2 epochs for the change to the deposit to take affect
vm.roll(block.number + Deposit(delegation.DEPOSIT_CONTRACT()).blocksPerEpoch() * 2);
}

function claimsAfterManyUnstakings(BaseDelegation delegation, uint64 steps) public {
uint256 i;
uint256 x;

deposit(BaseDelegation(delegation), 10_000_000 ether, true);

// wait 2 epochs for the change to the deposit to take affect
vm.roll(block.number + Deposit(delegation.DEPOSIT_CONTRACT()).blocksPerEpoch() * 2);

for (i = 0; i < 4; i++) {
vm.deal(stakers[i], 100_000 ether);
console.log("staker %s: %s", i+1, stakers[i]);
}

// rewards accrued so far
vm.deal(address(delegation), 50_000 ether);
x = 50;
i = 1;

vm.startPrank(stakers[i-1]);
vm.recordLogs();
vm.expectEmit(
true,
false,
false,
false,
address(delegation)
);
emit Delegation.Staked(
stakers[i-1],
steps * x * 1 ether,
""
);
delegation.stake{value: 2 * steps * x * 1 ether}();
// wait 2 epochs for the change to the deposit to take affect
vm.roll(block.number + Deposit(delegation.DEPOSIT_CONTRACT()).blocksPerEpoch() * 2);
vm.stopPrank();
vm.deal(address(delegation), address(delegation).balance + 10_000 ether);

uint256 totalUnstaked;
for (uint256 j = 0; j < steps; j++) {
console.log("--------------------------------------------------------------------");
vm.startPrank(stakers[i-1]);

uint256 amount = delegation.unstake(x * 1 ether);
console.log("%s unstaked %s in block %s", stakers[i-1], amount, block.number);
totalUnstaked += amount;

//console.log("block number: %s", block.number);
console.log("claimable: %s", delegation.getClaimable());
uint256[2][] memory claims = delegation.getPendingClaims();
console.log("%s pending claims:", claims.length);
uint256 totalPending;
for (uint256 j = 0; j < claims.length; j++) {
console.log("%s can claim %s in block %s", stakers[i-1], claims[j][1], claims[j][0]);
totalPending += claims[j][1];
}
assertEq(delegation.getClaimable() + totalPending, totalUnstaked, "claims must match unstaked amount");

// wait 2 epochs for the change to the deposit to take affect
vm.roll(block.number + Deposit(delegation.DEPOSIT_CONTRACT()).blocksPerEpoch() * 2);
vm.stopPrank();
vm.deal(address(delegation), address(delegation).balance + 10_000 ether);
}

vm.startPrank(stakers[i-1]);

console.log("--------------------------------------------------------------------");
console.log("block number: %s", block.number);
console.log("claimable: %s", delegation.getClaimable());
uint256[2][] memory claims = delegation.getPendingClaims();
console.log("%s pending claims:", claims.length);
uint256 totalPending;
for (uint256 j = 0; j < claims.length; j++) {
console.log("%s can claim %s in block %s", stakers[i-1], claims[j][1], claims[j][0]);
totalPending += claims[j][1];
}
assertEq(delegation.getClaimable() + totalPending, totalUnstaked, "claims must match unstaked amount");

vm.roll(block.number + 100);
//TODO: remove the next line once https://github.com/Zilliqa/zq2/issues/1761 is fixed
vm.warp(block.timestamp + 100);

console.log("--------------------------------------------------------------------");
console.log("block number: %s", block.number);
console.log("claimable: %s", delegation.getClaimable());
claims = delegation.getPendingClaims();
console.log("%s pending claims:", claims.length);
totalPending = 0;
for (uint256 j = 0; j < claims.length; j++) {
console.log("%s can claim %s in block %s", stakers[i-1], claims[j][1], claims[j][0]);
totalPending += claims[j][1];
}
assertEq(delegation.getClaimable() + totalPending, totalUnstaked, "claims must match unstaked amount");

vm.roll(block.number + WithdrawalQueue.unbondingPeriod());
//TODO: remove the next line once https://github.com/Zilliqa/zq2/issues/1761 is fixed
vm.warp(block.timestamp + WithdrawalQueue.unbondingPeriod());

console.log("--------------------------------------------------------------------");
console.log("block number: %s", block.number);
console.log("claimable: %s", delegation.getClaimable());
claims = delegation.getPendingClaims();
console.log("%s pending claims:", claims.length);
totalPending = 0;
for (uint256 j = 0; j < claims.length; j++) {
console.log("%s can claim %s in block %s", stakers[i-1], claims[j][1], claims[j][0]);
totalPending += claims[j][1];
}
assertEq(delegation.getClaimable() + totalPending, totalUnstaked, "claims must match unstaked amount");

vm.stopPrank();
}
}
53 changes: 30 additions & 23 deletions test/LiquidDelegation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -332,29 +332,6 @@ contract LiquidDelegationTest is BaseDelegationTest {

}

function test_1a_LargeStake_Late_NoRewards_UnstakeAll() public {
stakers[0] = 0x092E5E57955437876dA9Df998C96e2BE19341670;
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(
depositAmount,
rewardsBeforeStaking,
taxedRewardsBeforeStaking,
delegatedAmount,
1, // numberOfDelegations
0, // rewardsAccruedAfterEach
taxedRewardsAfterStaking, // rewardsBeforeUnstaking
WithdrawalQueue.unbondingPeriod(), // after unstaking wait blocksUntil claiming
true // initialDeposit using funds held by the node, otherwise delegated by a staker
);
}

//TODO: remove the test once https://github.com/Zilliqa/zq2/issues/1761 is fixed
function test_DepositContract() public {
vm.deal(owner, 10_000_000 ether + 1_000_000 ether + 0 ether);
Expand Down Expand Up @@ -411,6 +388,36 @@ contract LiquidDelegationTest is BaseDelegationTest {
vm.stopPrank();
}

function test_claimsAfterManyUnstakings() public {
claimsAfterManyUnstakings(
LiquidDelegationV2(proxy), //delegation
20 //steps
);
}

function test_1a_LargeStake_Late_NoRewards_UnstakeAll() public {
stakers[0] = 0x092E5E57955437876dA9Df998C96e2BE19341670;
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(
depositAmount,
rewardsBeforeStaking,
taxedRewardsBeforeStaking,
delegatedAmount,
1, // numberOfDelegations
0, // rewardsAccruedAfterEach
taxedRewardsAfterStaking, // rewardsBeforeUnstaking
WithdrawalQueue.unbondingPeriod(), // after unstaking wait blocksUntil claiming
true // initialDeposit using funds held by the node, otherwise delegated by a staker
);
}

function test_1b_LargeStake_Early_NoRewards_UnstakeAll() public {
stakers[0] = 0x092E5E57955437876dA9Df998C96e2BE19341670;
uint256 depositAmount = 10_000_000 ether;
Expand Down
9 changes: 7 additions & 2 deletions test/NonLiquidDelegation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ contract NonLiquidDelegationTest is BaseDelegationTest {
console.log("allWithdrawnRewards = %s withdrawnAfterLastStaking = %s", allWithdrawnRewards, withdrawnAfterLastStaking);
}

//TODO: add assertions
function run (
bytes memory _stakerIndicesBeforeWithdrawals,
// each element in the interval (-100, 100)
Expand Down Expand Up @@ -477,7 +476,6 @@ contract NonLiquidDelegationTest is BaseDelegationTest {
//TODO: remove the next line once https://github.com/Zilliqa/zq2/issues/1761 is fixed
vm.warp(block.timestamp + WithdrawalQueue.unbondingPeriod());


i = 1;
vm.startPrank(stakers[i-1]);
vm.recordLogs();
Expand All @@ -497,6 +495,13 @@ contract NonLiquidDelegationTest is BaseDelegationTest {
vm.stopPrank();
}

function test_claimsAfterManyUnstakings() public {
claimsAfterManyUnstakings(
NonLiquidDelegationV2(proxy), //delegation
20 //steps
);
}

function test_rewardsAfterWithdrawalLessThanBeforeWithdrawal() public {
uint256 i;
uint256 x;
Expand Down

0 comments on commit 6b7841c

Please sign in to comment.