diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..64a46a5 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,13 @@ +{ + "extends": "solhint:recommended", + "plugins": [], + "rules": { + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "reason-string": ["warn", {"maxLength": 70}], + "func-visibility": ["warn", {"ignoreConstructors": true}], + "gas-custom-errors": "off", + "no-empty-blocks": "off", + "no-complex-fallback": "off" + } +} diff --git a/README.md b/README.md index 58d0f8d..a66d362 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,13 @@ Before running the deployment script, set the `PRIVATE_KEY` environment variable To deploy `LiquidDelegation` run ```bash -forge script script/deploy_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(string,string,string)" LiquidDelegation Name Symbol +forge script script/Deploy.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(string,string,string)" LiquidDelegation Name Symbol ``` using the `Name` and the `Symbol` of your LST. To deploy ``NonLiquidDelegation` run ```bash -forge script script/deploy_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(string,string,string)" NonLiquidDelegation "" "" +forge script script/Deploy.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(string,string,string)" NonLiquidDelegation "" "" ``` You will see an output like this: @@ -77,13 +77,13 @@ You will see an output like this: You and your delegators will need the proxy address from the above output in all commands below. If you know the address of a proxy contract but don't know which variant of staking it supports, run ```bash -forge script script/variant_Delegation.s.sol --rpc-url http://localhost:4201 --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 +forge script script/CheckVariant.s.sol --rpc-url http://localhost:4201 --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 ``` The output will be `ILiquidStaking`, `INonLiquidStaking` or none of them if the address is not a valid delegation contract. To use the delegation contract, upgrade it to the latest version of `LiquidDelegationV2` or `NonLiquidDelegationV2` depending on the staking model it implements, by running ```bash -forge script script/upgrade_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 +forge script script/Upgrade.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 ``` The output will look like this: @@ -102,7 +102,7 @@ To adapt the contract to your needs, create your own copy of `LiquidDelegationV2 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, string, bool)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000 false +forge script script/ManageCommission.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, string, bool)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000 false ``` The output will contain the following information: @@ -119,7 +119,7 @@ cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "DENOMINATOR()(uint256)" -- Once the validator is activated and starts earning rewards, commissions are transferred automatically to the validator node's account. Commissions of a non-liquid staking validator are deducted when delegators withdraw rewards. In case of the liquid staking variant, commissions are deducted each time delegators stake, unstake or claim what they unstaked, or when the node requests the outstanding commissions that haven't been transferred yet. To collect them, run ```bash -forge script script/commission_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, string, bool)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 same true +forge script script/ManageCommission.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, string, bool)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 same true ``` using `same` for the second argument to leave the commission percentage unchanged and `true` for the third argument. Replacing the second argument with `same` and the third argument with `false` only displays the current commission rate. @@ -168,7 +168,7 @@ Note that the deposit will not take effect and the node will not start earning r ## Staking and Unstaking Once the delegation contract has been deployed and upgraded to the latest version, your node can accept delegations. In order to stake e.g. 200 ZIL, your delegators must run ```bash -forge script script/stake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 200000000000000000000 --private-key 0x... +forge script script/Stake.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 200000000000000000000 --private-key 0x... ``` with the private key of their account. It's important to make sure the account's balance can cover the transaction fees plus the 200 ZIL to be delegated. @@ -204,7 +204,7 @@ cast to-unit $(cast call 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 "getPrice()( To unstake e.g. 100 LST (liquid variant) or 100 ZIL (non-liquid variant), run ```bash -forge script script/unstake_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 100000000000000000000 --private-key 0x... +forge script script/Unstake.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, uint256)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 100000000000000000000 --private-key 0x... ``` using the private key of an account that holds some LST in case of the liquid variant or using the private key of the delegator account in case of the non-liquid variant. @@ -228,7 +228,7 @@ and like this for the non-liquid variant: The ZIL balance hasn't increased because the unstaked amount can not be transferred immediately. 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... +forge script script/Claim.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... ``` with the private key of the account that unstaked in the previous step above. @@ -249,7 +249,7 @@ with the user's address as an argument. ## Staking and Withdrawing Rewards In the liquid staking variant, only you as the node operator can stake the rewards accrued by the node. To do so, run ```bash -forge script script/stakeRewards_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... +forge script script/StakeRewards.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... ``` In the non-liquid variant of staking, your delegators can stake or withdraw their share of the rewards. To query the amount of rewards available, run @@ -265,13 +265,13 @@ Note that `n` actually denotes the number of additional (un)stakings so that at To withdraw e.g. 1 ZIL of rewards using `n = 100`, run ```bash -forge script script/withdrawRewards_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, string, string)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000000000000000000 100 --private-key 0x... +forge script script/WithdrawRewards.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable, string, string)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 1000000000000000000 100 --private-key 0x... ``` with the private key of a delegator account. To withdraw as much as possible with the given value of `n` set the amount to `all`. To withdraw the chosen amount without setting `n` replace `n` with `all`. To withdraw all rewards replace both the amount and `n` with `all`. Last but not least, in order to stake rewards instead of withdrawing them, your delegators can run ```bash -forge script script/stakeRewards_Delegation.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... +forge script script/StakeRewards.s.sol --rpc-url http://localhost:4201 --broadcast --legacy --sig "run(address payable)" 0x7A0b7e6D24eDe78260c9ddBD98e828B0e11A8EA2 --private-key 0x... ``` using the private key of their account. diff --git a/claim.sh b/claim.sh index 6129d1e..bbd826a 100755 --- a/claim.sh +++ b/claim.sh @@ -9,14 +9,14 @@ fi staker=$(cast wallet address $2) -temp=$(forge script script/variant_Delegation.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) +temp=$(forge script script/CheckVariant.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) variant=$(sed -E 's/\s\s([a-zA-Z0-9]+)/\1/' <<< "$temp") if [[ "$variant" == "$temp" ]]; then echo Incompatible delegation contract at $1 exit 1 fi -forge script script/claim_Delegation.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 -vvvv +forge script script/Claim.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 -vvvv block=$(cast rpc eth_blockNumber --rpc-url $url) block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) diff --git a/script/variant_Delegation.s.sol b/script/CheckVariant.s.sol similarity index 85% rename from script/variant_Delegation.s.sol rename to script/CheckVariant.s.sol index 4d13f8c..63c86f6 100644 --- a/script/variant_Delegation.s.sol +++ b/script/CheckVariant.s.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {ILiquidDelegation} from "src/LiquidDelegation.sol"; import {INonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; -contract Upgrade is Script { +contract CheckVariant is Script { using ERC165Checker for address; function run(address proxy) external { diff --git a/script/claim_Delegation.s.sol b/script/Claim.s.sol similarity index 89% rename from script/claim_Delegation.s.sol rename to script/Claim.s.sol index e367bfa..c6c1e16 100644 --- a/script/claim_Delegation.s.sol +++ b/script/Claim.s.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract Claim is Script { function run(address payable proxy) external { diff --git a/script/deploy_Delegation.s.sol b/script/Deploy.s.sol similarity index 96% rename from script/deploy_Delegation.s.sol rename to script/Deploy.s.sol index fe7cda6..aea663c 100644 --- a/script/deploy_Delegation.s.sol +++ b/script/Deploy.s.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; import {LiquidDelegation} from "src/LiquidDelegation.sol"; import {NonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract Deploy is Script { using Strings for string; diff --git a/script/commission_Delegation.s.sol b/script/ManageCommission.s.sol similarity index 91% rename from script/commission_Delegation.s.sol rename to script/ManageCommission.s.sol index 349639d..b32d033 100644 --- a/script/commission_Delegation.s.sol +++ b/script/ManageCommission.s.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; import {Console} from "src/Console.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; -contract Commission is Script { +contract ManageCommission is Script { using Strings for string; function run(address payable proxy, string calldata commissionNumerator, bool collectCommission) external { diff --git a/script/stake_Delegation.s.sol b/script/Stake.s.sol similarity index 96% rename from script/stake_Delegation.s.sol rename to script/Stake.s.sol index 6165ad1..2cc8e7b 100644 --- a/script/stake_Delegation.s.sol +++ b/script/Stake.s.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {NonRebasingLST} from "src/NonRebasingLST.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; import {ILiquidDelegation} from "src/LiquidDelegation.sol"; import {LiquidDelegationV2} from "src/LiquidDelegationV2.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract Stake is Script { using ERC165Checker for address; diff --git a/script/stakeRewards_Delegation.s.sol b/script/StakeRewards.s.sol similarity index 91% rename from script/stakeRewards_Delegation.s.sol rename to script/StakeRewards.s.sol index 097a12f..6add143 100644 --- a/script/stakeRewards_Delegation.s.sol +++ b/script/StakeRewards.s.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract StakeRewards is Script { diff --git a/script/unstake_Delegation.s.sol b/script/Unstake.s.sol similarity index 96% rename from script/unstake_Delegation.s.sol rename to script/Unstake.s.sol index 62195df..617eee0 100644 --- a/script/unstake_Delegation.s.sol +++ b/script/Unstake.s.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {NonRebasingLST} from "src/NonRebasingLST.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; import {ILiquidDelegation} from "src/LiquidDelegation.sol"; import {INonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract Unstake is Script { using ERC165Checker for address; diff --git a/script/upgrade_Delegation.s.sol b/script/Upgrade.s.sol similarity index 96% rename from script/upgrade_Delegation.s.sol rename to script/Upgrade.s.sol index de11ef2..62d69fd 100644 --- a/script/upgrade_Delegation.s.sol +++ b/script/Upgrade.s.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {BaseDelegation} from "src/BaseDelegation.sol"; import {ILiquidDelegation} from "src/LiquidDelegation.sol"; @@ -8,7 +9,7 @@ import {INonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {LiquidDelegationV2} from "src/LiquidDelegationV2.sol"; import {NonLiquidDelegationV2} from "src/NonLiquidDelegationV2.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract Upgrade is Script { using ERC165Checker for address; diff --git a/script/withdrawRewards_Delegation.s.sol b/script/WithdrawRewards.s.sol similarity index 94% rename from script/withdrawRewards_Delegation.s.sol rename to script/WithdrawRewards.s.sol index 77ec220..fd284b3 100644 --- a/script/withdrawRewards_Delegation.s.sol +++ b/script/WithdrawRewards.s.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Script} from "forge-std/Script.sol"; import {NonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract WithdrawRewards is Script { using Strings for string; diff --git a/src/BaseDelegation.sol b/src/BaseDelegation.sol index 1ce1825..cf26cef 100644 --- a/src/BaseDelegation.sol +++ b/src/BaseDelegation.sol @@ -1,60 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 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 "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; -import "src/Delegation.sol"; - -library WithdrawalQueue { - - address public constant DEPOSIT_CONTRACT = address(0x5A494C4445504F53495450524F5859); - - struct Item { - uint256 blockNumber; - uint256 amount; - } - - struct Fifo { - uint256 first; - uint256 last; - mapping(uint256 => Item) items; - } - - function unbondingPeriod() view internal returns(uint256) { - (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( - abi.encodeWithSignature("withdrawalPeriod()") - ); - require(success, "unbonding period unknown"); - return abi.decode(data, (uint256)); - } - - function enqueue(Fifo storage fifo, uint256 amount) internal { - fifo.items[fifo.last] = Item(block.number + unbondingPeriod(), 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, uint256 index) internal view returns(bool) { - 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); - } -} +import {Delegation} from "src/Delegation.sol"; +import {WithdrawalQueue} from "src/WithdrawalQueue.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; abstract contract BaseDelegation is Delegation, PausableUpgradeable, Ownable2StepUpgradeable, UUPSUpgradeable, ERC165Upgradeable { @@ -70,6 +22,7 @@ abstract contract BaseDelegation is Delegation, PausableUpgradeable, Ownable2Ste } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.BaseDelegation")) - 1)) & ~bytes32(uint256(0xff)) + // solhint-disable const-name-snakecase bytes32 private constant BaseDelegationStorageLocation = 0xc8ff0e571ef581b660c1651f85bbac921a40f9489bd04631c07fa723c13c6000; function _getBaseDelegationStorage() private pure returns (BaseDelegationStorage storage $) { @@ -86,6 +39,7 @@ abstract contract BaseDelegation is Delegation, PausableUpgradeable, Ownable2Ste return _getInitializedVersion(); } + // solhint-disable func-name-mixedcase function __BaseDelegation_init(address initialOwner) internal onlyInitializing { __Pausable_init_unchained(); __Ownable2Step_init_unchained(); diff --git a/src/Console.sol b/src/Console.sol index 639da8d..27f6262 100644 --- a/src/Console.sol +++ b/src/Console.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; +/* solhint-disable no-console */ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; library Console { - function convert(int256 amount, uint8 precision) pure internal returns ( + function convert(int256 amount, uint8 precision) internal pure returns ( int256 predecimal, string memory zeros, uint256 postdecimal @@ -24,35 +25,35 @@ library Console { postdecimal /= 10; } - function toString(uint256 amount, uint8 precision) pure internal returns (string memory result) { + function toString(uint256 amount, uint8 precision) internal pure returns (string memory result) { return toString(int256(amount), precision); } - function toString(int256 amount, uint8 precision) pure internal returns (string memory result) { + function toString(int256 amount, uint8 precision) internal pure returns (string memory result) { (int256 predecimal, string memory zeros, uint256 postdecimal) = convert(amount, precision); result = string.concat(Strings.toStringSigned(predecimal), "."); result = string.concat(result, zeros); result = string.concat(result, Strings.toString(postdecimal)); } - function log(string memory format, uint256 amount, uint8 precision) pure internal { + function log(string memory format, uint256 amount, uint8 precision) internal pure { log(format, int256(amount), precision); } - function log(string memory format, int256 amount, uint8 precision) pure internal { + function log(string memory format, int256 amount, uint8 precision) internal pure { (int256 predecimal, string memory zeros, uint256 postdecimal) = convert(amount, precision); console.log(format, Strings.toStringSigned(predecimal), zeros, postdecimal); } - function log(string memory format, uint256 amount) pure internal { + function log(string memory format, uint256 amount) internal pure { return log(format, amount, 18); } - function log(string memory format, int256 amount) pure internal { + function log(string memory format, int256 amount) internal pure { return log(format, amount, 18); } - function log(string memory format, uint64[] memory array) pure internal { + function log(string memory format, uint64[] memory array) internal pure { string memory s; for (uint256 i = 0; i < array.length; i++) { s = string.concat(s, Strings.toString(array[i])); diff --git a/src/LiquidDelegation.sol b/src/LiquidDelegation.sol index 2dca66c..b6d64cc 100644 --- a/src/LiquidDelegation.sol +++ b/src/LiquidDelegation.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import "src/BaseDelegation.sol"; -import "src/NonRebasingLST.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; +import {NonRebasingLST} from "src/NonRebasingLST.sol"; // do not change this interface, it will break the detection of // the staking variant of an already deployed delegation contract @@ -20,6 +20,7 @@ contract LiquidDelegation is BaseDelegation, ILiquidDelegation { } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.LiquidDelegation")) - 1)) & ~bytes32(uint256(0xff)) + // solhint-disable const-name-snakecase bytes32 private constant LiquidDelegationStorageLocation = 0xfa57cbed4b267d0bc9f2cbdae86b4d1d23ca818308f873af9c968a23afadfd00; function _getLiquidDelegationStorage() private pure returns (LiquidDelegationStorage storage $) { @@ -33,7 +34,7 @@ contract LiquidDelegation is BaseDelegation, ILiquidDelegation { _disableInitializers(); } - function initialize(address initialOwner, string calldata name, string calldata symbol) initializer public { + function initialize(address initialOwner, string calldata name, string calldata symbol) public initializer { __BaseDelegation_init(initialOwner); LiquidDelegationStorage storage $ = _getLiquidDelegationStorage(); $.lst = address(new NonRebasingLST(address(this), name, symbol)); diff --git a/src/LiquidDelegationV2.sol b/src/LiquidDelegationV2.sol index 7bdb919..eda0e6e 100644 --- a/src/LiquidDelegationV2.sol +++ b/src/LiquidDelegationV2.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import "src/BaseDelegation.sol"; -import "src/LiquidDelegation.sol"; -import "src/NonRebasingLST.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; +import {ILiquidDelegation} from "src/LiquidDelegation.sol"; +import {NonRebasingLST} from "src/NonRebasingLST.sol"; // the contract is supposed to be deployed with the node's signer account contract LiquidDelegationV2 is BaseDelegation, ILiquidDelegation { @@ -15,6 +15,7 @@ contract LiquidDelegationV2 is BaseDelegation, ILiquidDelegation { } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.LiquidDelegation")) - 1)) & ~bytes32(uint256(0xff)) + // solhint-disable const-name-snakecase bytes32 private constant LiquidDelegationStorageLocation = 0xfa57cbed4b267d0bc9f2cbdae86b4d1d23ca818308f873af9c968a23afadfd00; function _getLiquidDelegationStorage() private pure returns (LiquidDelegationStorage storage $) { @@ -34,12 +35,12 @@ contract LiquidDelegationV2 is BaseDelegation, ILiquidDelegation { // it won't be possible to identify the actual version of the // source file without a hardcoded version number, but storing // the file versions in separate folders would help - function reinitialize() reinitializer(version() + 1) public { + function reinitialize() public reinitializer(version() + 1) { } // called when stake withdrawn from the deposit contract is claimed // but not called when rewards are assigned to the reward address - receive() payable external { + receive() external payable { LiquidDelegationStorage storage $ = _getLiquidDelegationStorage(); // do not deduct commission from the withdrawn stake $.taxedRewards += msg.value; diff --git a/src/NonLiquidDelegation.sol b/src/NonLiquidDelegation.sol index 71017bf..7778488 100644 --- a/src/NonLiquidDelegation.sol +++ b/src/NonLiquidDelegation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import "src/BaseDelegation.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; // do not change this interface, it will break the detection of // the staking variant of an already deployed delegation contract @@ -20,6 +20,7 @@ contract NonLiquidDelegation is BaseDelegation, INonLiquidDelegation { */ // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.NonLiquidDelegation")) - 1)) & ~bytes32(uint256(0xff)) + // solhint-disable const-name-snakecase bytes32 private constant NonLiquidDelegationStorageLocation = 0x66c8dc4f9c8663296597cb1e39500488e05713d82a9122d4f548b19a70fc2000; /* commented out because defining empty structs is disallowed @@ -35,7 +36,7 @@ contract NonLiquidDelegation is BaseDelegation, INonLiquidDelegation { _disableInitializers(); } - function initialize(address initialOwner) initializer public { + function initialize(address initialOwner) public initializer { __BaseDelegation_init(initialOwner); } diff --git a/src/NonLiquidDelegationV2.sol b/src/NonLiquidDelegationV2.sol index 9eff4d7..88e0995 100644 --- a/src/NonLiquidDelegationV2.sol +++ b/src/NonLiquidDelegationV2.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import "src/BaseDelegation.sol"; -import "src/NonLiquidDelegation.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; +import {INonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; contract NonLiquidDelegationV2 is BaseDelegation, INonLiquidDelegation { @@ -50,6 +50,7 @@ contract NonLiquidDelegationV2 is BaseDelegation, INonLiquidDelegation { } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.NonLiquidDelegation")) - 1)) & ~bytes32(uint256(0xff)) + // solhint-disable const-name-snakecase bytes32 private constant NonLiquidDelegationStorageLocation = 0x66c8dc4f9c8663296597cb1e39500488e05713d82a9122d4f548b19a70fc2000; function _getNonLiquidDelegationStorage() private pure returns (NonLiquidDelegationStorage storage $) { @@ -69,12 +70,12 @@ contract NonLiquidDelegationV2 is BaseDelegation, INonLiquidDelegation { // it won't be possible to identify the actual version of the // source file without a hardcoded version number, but storing // the file versions in separate folders would help - function reinitialize() reinitializer(version() + 1) public { + function reinitialize() public reinitializer(version() + 1) { } // called when stake withdrawn from the deposit contract is claimed // but not called when rewards are assigned to the reward address - receive() payable external { + receive() external payable { NonLiquidDelegationStorage storage $ = _getNonLiquidDelegationStorage(); // add the stake withdrawn from the deposit to the reward balance $.totalRewards += int256(msg.value); diff --git a/src/NonRebasingLST.sol b/src/NonRebasingLST.sol index e2ef923..4675fd6 100644 --- a/src/NonRebasingLST.sol +++ b/src/NonRebasingLST.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract NonRebasingLST is ERC20, Ownable { constructor(address initialOwner, string memory name, string memory symbol) diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol new file mode 100644 index 0000000..53f0bd8 --- /dev/null +++ b/src/WithdrawalQueue.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.26; + +library WithdrawalQueue { + + address public constant DEPOSIT_CONTRACT = address(0x5A494C4445504F53495450524F5859); + + struct Item { + uint256 blockNumber; + uint256 amount; + } + + struct Fifo { + uint256 first; + uint256 last; + mapping(uint256 => Item) items; + } + + function unbondingPeriod() internal view returns(uint256) { + (bool success, bytes memory data) = DEPOSIT_CONTRACT.staticcall( + abi.encodeWithSignature("withdrawalPeriod()") + ); + require(success, "unbonding period unknown"); + return abi.decode(data, (uint256)); + } + + function enqueue(Fifo storage fifo, uint256 amount) internal { + fifo.items[fifo.last] = Item(block.number + unbondingPeriod(), 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, uint256 index) internal view returns(bool) { + 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); + } +} diff --git a/stake.sh b/stake.sh index 21ad08f..90841f7 100755 --- a/stake.sh +++ b/stake.sh @@ -9,14 +9,14 @@ fi staker=$(cast wallet address $2) -temp=$(forge script script/variant_Delegation.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) +temp=$(forge script script/CheckVariant.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) variant=$(sed -E 's/\s\s([a-zA-Z0-9]+)/\1/' <<< "$temp") if [[ "$variant" == "$temp" ]]; then echo Incompatible delegation contract at $1 exit 1 fi -forge script script/stake_Delegation.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable, uint256)" $1 $3 --private-key $2 +forge script script/Stake.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable, uint256)" $1 $3 --private-key $2 block=$(cast rpc eth_blockNumber --rpc-url $url) block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) diff --git a/stakeRewards.sh b/stakeRewards.sh index 8ee0361..89d4f4a 100755 --- a/stakeRewards.sh +++ b/stakeRewards.sh @@ -9,7 +9,7 @@ fi staker=$(cast wallet address $2) -temp=$(forge script script/variant_Delegation.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) +temp=$(forge script script/CheckVariant.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) variant=$(sed -E 's/\s\s([a-zA-Z0-9]+)/\1/' <<< "$temp") if [[ "$variant" == "$temp" ]]; then echo Incompatible delegation contract at $1 @@ -23,7 +23,7 @@ if [ "$variant" == "ILiquidDelegation" ] && [ "$staker" != "$owner" ]; then exit 1 fi -forge script script/stakeRewards_Delegation.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 +forge script script/StakeRewards.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable)" $1 --private-key $2 block=$(cast rpc eth_blockNumber --rpc-url $url) block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) diff --git a/state.sh b/state.sh index c834ec1..3926d22 100755 --- a/state.sh +++ b/state.sh @@ -7,7 +7,7 @@ if [ $# -lt 2 ]; then exit 1 fi -temp=$(forge script script/variant_Delegation.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) +temp=$(forge script script/CheckVariant.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) variant=$(sed -E 's/\s\s([a-zA-Z0-9]+)/\1/' <<< "$temp") if [[ "$variant" == "$temp" ]]; then echo Incompatible delegation contract at $1 diff --git a/test/BaseDelegation.t.sol b/test/BaseDelegation.t.sol index b142f0f..d3ac499 100644 --- a/test/BaseDelegation.t.sol +++ b/test/BaseDelegation.t.sol @@ -1,28 +1,25 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import {BaseDelegation, WithdrawalQueue} from "src/BaseDelegation.sol"; +/* solhint-disable no-console */ +import {PopVerifyPrecompile} from "test/PopVerifyPrecompile.t.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; +import {WithdrawalQueue} from "src/WithdrawalQueue.sol"; import {Delegation} from "src/Delegation.sol"; -import {Deposit, InitialStaker} from "@zilliqa/zq2/deposit_v2.sol"; -import {Console} from "src/Console.sol"; +import {Deposit} from "@zilliqa/zq2/deposit_v2.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Test, Vm} from "forge-std/Test.sol"; -import "forge-std/console.sol"; - -contract PopVerifyPrecompile { - function popVerify(bytes memory, bytes memory) public pure returns(bool) { - return true; - } -} +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; +/* solhint-disable one-contract-per-file */ abstract contract BaseDelegationTest is Test { - address payable proxy; - address oldImplementation; - bytes initializerCall; - address payable newImplementation; - bytes reinitializerCall; - address owner; - address[4] stakers = [ + address payable internal proxy; + address internal oldImplementation; + bytes internal initializerCall; + address payable internal newImplementation; + bytes internal reinitializerCall; + address internal owner; + address[4] internal stakers = [ 0xd819fFcE7A58b1E835c25617Db7b46a00888B013, 0x092E5E57955437876dA9Df998C96e2BE19341670, 0xeA78aAE5Be606D2D152F00760662ac321aB8F017, @@ -104,7 +101,6 @@ abstract contract BaseDelegationTest is Test { ); //*/ - InitialStaker[] memory initialStakers = new InitialStaker[](0); //vm.deployCodeTo("Deposit.sol", delegation.DEPOSIT_CONTRACT()); vm.etch( delegation.DEPOSIT_CONTRACT(), diff --git a/test/LiquidDelegation.t.sol b/test/LiquidDelegation.t.sol index 8b84b3a..011bdff 100644 --- a/test/LiquidDelegation.t.sol +++ b/test/LiquidDelegation.t.sol @@ -1,20 +1,22 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import {BaseDelegationTest, PopVerifyPrecompile} from "test/BaseDelegation.t.sol"; +/* solhint-disable no-console */ +import {BaseDelegationTest} from "test/BaseDelegation.t.sol"; import {LiquidDelegation} from "src/LiquidDelegation.sol"; import {LiquidDelegationV2} from "src/LiquidDelegationV2.sol"; import {NonRebasingLST} from "src/NonRebasingLST.sol"; -import {BaseDelegation, WithdrawalQueue} from "src/BaseDelegation.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; +import {WithdrawalQueue} from "src/WithdrawalQueue.sol"; import {Delegation} from "src/Delegation.sol"; import {Deposit} from "@zilliqa/zq2/deposit_v2.sol"; import {Console} from "src/Console.sol"; import {Vm} from "forge-std/Test.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; contract LiquidDelegationTest is BaseDelegationTest { - LiquidDelegationV2 delegation; - NonRebasingLST lst; + LiquidDelegationV2 internal delegation; + NonRebasingLST internal lst; constructor() BaseDelegationTest() { oldImplementation = address(new LiquidDelegation()); diff --git a/test/NonLiquidDelegation.t.sol b/test/NonLiquidDelegation.t.sol index 2ef4ec2..61a6279 100644 --- a/test/NonLiquidDelegation.t.sol +++ b/test/NonLiquidDelegation.t.sol @@ -1,22 +1,23 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; -import {BaseDelegationTest, PopVerifyPrecompile} from "test/BaseDelegation.t.sol"; +/* solhint-disable no-console */ +import {BaseDelegationTest} from "test/BaseDelegation.t.sol"; import {NonLiquidDelegation} from "src/NonLiquidDelegation.sol"; import {NonLiquidDelegationV2} from "src/NonLiquidDelegationV2.sol"; -import {BaseDelegation, WithdrawalQueue} from "src/BaseDelegation.sol"; +import {BaseDelegation} from "src/BaseDelegation.sol"; +import {WithdrawalQueue} from "src/WithdrawalQueue.sol"; import {Delegation} from "src/Delegation.sol"; import {Deposit} from "@zilliqa/zq2/deposit_v2.sol"; import {Console} from "src/Console.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Vm} from "forge-std/Test.sol"; -import "forge-std/console.sol"; +import {console} from "forge-std/console.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; contract NonLiquidDelegationTest is BaseDelegationTest { using SafeCast for int256; - NonLiquidDelegationV2 delegation; + NonLiquidDelegationV2 internal delegation; constructor() BaseDelegationTest() { oldImplementation = address(new NonLiquidDelegation()); @@ -94,7 +95,7 @@ contract NonLiquidDelegationTest is BaseDelegationTest { uint256 withdrawnAfterLastStaking ) = delegation.getStakingData(); Console.log("stakingIndices = [ %s]", stakingIndices); - console.log("firstStakingIndex = %s lastWithdrawnRewardIndex = %s", uint(firstStakingIndex), uint(lastWithdrawnRewardIndex)); + console.log("firstStakingIndex = %s lastWithdrawnRewardIndex = %s", uint256(firstStakingIndex), uint256(lastWithdrawnRewardIndex)); console.log("allWithdrawnRewards = %s withdrawnAfterLastStaking = %s", allWithdrawnRewards, withdrawnAfterLastStaking); } @@ -645,7 +646,7 @@ contract NonLiquidDelegationTest is BaseDelegationTest { uint256 withdrawnAfterLastStaking ) = delegation.getStakingData(); Console.log("stakingIndices = [ %s]", stakingIndices); - console.log("firstStakingIndex = %s lastWithdrawnRewardIndex = %s", uint(firstStakingIndex), uint(lastWithdrawnRewardIndex)); + console.log("firstStakingIndex = %s lastWithdrawnRewardIndex = %s", uint256(firstStakingIndex), uint256(lastWithdrawnRewardIndex)); console.log("allWithdrawnRewards = %s withdrawnAfterLastStaking = %s", allWithdrawnRewards, withdrawnAfterLastStaking); vm.recordLogs(); @@ -670,7 +671,7 @@ contract NonLiquidDelegationTest is BaseDelegationTest { withdrawnAfterLastStaking ) = delegation.getStakingData(); Console.log("stakingIndices = [ %s]", stakingIndices); - console.log("firstStakingIndex = %s lastWithdrawnRewardIndex = %s", uint(firstStakingIndex), uint(lastWithdrawnRewardIndex)); + console.log("firstStakingIndex = %s lastWithdrawnRewardIndex = %s", uint256(firstStakingIndex), uint256(lastWithdrawnRewardIndex)); console.log("allWithdrawnRewards = %s withdrawnAfterLastStaking = %s", allWithdrawnRewards, withdrawnAfterLastStaking); Console.log("contract balance: %s.%s%s", address(delegation).balance); diff --git a/test/PopVerifyPrecompile.t.sol b/test/PopVerifyPrecompile.t.sol new file mode 100644 index 0000000..7b3f22f --- /dev/null +++ b/test/PopVerifyPrecompile.t.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.26; + +contract PopVerifyPrecompile { + function popVerify(bytes memory, bytes memory) public pure returns(bool) { + return true; + } +} \ No newline at end of file diff --git a/unstake.sh b/unstake.sh index af64f3c..47ca435 100755 --- a/unstake.sh +++ b/unstake.sh @@ -15,14 +15,14 @@ fi staker=$(cast wallet address $2) -temp=$(forge script script/variant_Delegation.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) +temp=$(forge script script/CheckVariant.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) variant=$(sed -E 's/\s\s([a-zA-Z0-9]+)/\1/' <<< "$temp") if [[ "$variant" == "$temp" ]]; then echo Incompatible delegation contract at $1 exit 1 fi -forge script script/unstake_Delegation.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable, uint256)" $1 $shares --private-key $2 +forge script script/Unstake.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable, uint256)" $1 $shares --private-key $2 block=$(cast rpc eth_blockNumber --rpc-url $url) block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16) diff --git a/withdrawRewards.sh b/withdrawRewards.sh index 201781b..a1f68f7 100755 --- a/withdrawRewards.sh +++ b/withdrawRewards.sh @@ -21,7 +21,7 @@ fi staker=$(cast wallet address $2) -temp=$(forge script script/variant_Delegation.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) +temp=$(forge script script/CheckVariant.s.sol --rpc-url $url --sig "run(address payable)" $1 | tail -n 1) variant=$(sed -E 's/\s\s([a-zA-Z0-9]+)/\1/' <<< "$temp") if [[ "$variant" == "$temp" ]]; then echo Incompatible delegation contract at $1 @@ -32,7 +32,7 @@ if [[ "$variant" != "INonLiquidDelegation" ]]; then exit 1 fi -forge script script/withdrawRewards_Delegation.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable, string, string)" $1 $amount $steps --private-key $2 +forge script script/WithdrawRewards.s.sol --rpc-url $url --broadcast --legacy --sig "run(address payable, string, string)" $1 $amount $steps --private-key $2 block=$(cast rpc eth_blockNumber --rpc-url $url) block_num=$(echo $block | tr -d '"' | cast to-dec --base-in 16)