diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index a706a78f..60b88207 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -44,9 +44,14 @@ jobs: run: | forge build - - name: Set RPC Env - run: echo "RPC_BSC=https://bsc-dataseed.bnbchain.org" >> $GITHUB_ENV + - name: Start Local Chain + run: | + npm install pm2 -g + pm2 start --name local-chain "anvil -f https://bsc-dataseed.bnbchain.org" + sleep 5 + env: + PORT: 8545 - name: Unit Test run: | - forge test + forge test --rpc-url http://127.0.0.1:8545 diff --git a/abi/bscvalidatorset.abi b/abi/bscvalidatorset.abi index 9c9c265e..c9659acc 100644 --- a/abi/bscvalidatorset.abi +++ b/abi/bscvalidatorset.abi @@ -912,6 +912,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getTurnLength", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getValidators", @@ -1239,7 +1252,20 @@ }, { "type": "function", - "name": "systemRewardRatio", + "name": "systemRewardAntiMEVRatio", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "systemRewardBaseRatio", "inputs": [], "outputs": [ { @@ -1263,6 +1289,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "turnLength", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "updateParam", @@ -1684,4 +1723,4 @@ "inputs": [], "anonymous": false } -] \ No newline at end of file +] diff --git a/abi/stakehub.abi b/abi/stakehub.abi index 1bae4809..6f9de9f2 100644 --- a/abi/stakehub.abi +++ b/abi/stakehub.abi @@ -94,6 +94,25 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "agentToOperator", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "blackList", @@ -447,6 +466,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getValidatorAgent", + "inputs": [ + { + "name": "operatorAddress", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getValidatorBasicInfo", @@ -1032,6 +1070,19 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "updateAgent", + "inputs": [ + { + "name": "newAgent", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "updateParam", @@ -1088,6 +1139,31 @@ ], "stateMutability": "view" }, + { + "type": "event", + "name": "AgentChanged", + "inputs": [ + { + "name": "operatorAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "oldAgent", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newAgent", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, { "type": "event", "name": "BlackListed", @@ -1656,6 +1732,11 @@ "name": "InBlackList", "inputs": [] }, + { + "type": "error", + "name": "InvalidAgent", + "inputs": [] + }, { "type": "error", "name": "InvalidCommission", @@ -1681,6 +1762,11 @@ "name": "InvalidSynPackage", "inputs": [] }, + { + "type": "error", + "name": "InvalidValidator", + "inputs": [] + }, { "type": "error", "name": "InvalidValue", diff --git a/contracts/BC_fusion/StakeHub.sol b/contracts/BC_fusion/StakeHub.sol index 46316c52..587b6749 100644 --- a/contracts/BC_fusion/StakeHub.sol +++ b/contracts/BC_fusion/StakeHub.sol @@ -89,6 +89,10 @@ contract StakeHub is System, Initializable, Protectable { error ConsensusAddressExpired(); // @notice signature: 0x0d7b78d4 error InvalidSynPackage(); + // @notice signature: 0xbebdc757 + error InvalidAgent(); + // @notice signature: 0x682a6e7c + error InvalidValidator(); /*----------------- storage -----------------*/ uint8 private _receiveFundStatus; @@ -135,6 +139,9 @@ contract StakeHub is System, Initializable, Protectable { // slash key => slash jail time mapping(bytes32 => uint256) private _felonyRecords; + // agent => validator operator address + mapping(address => address) public agentToOperator; + /*----------------- structs and events -----------------*/ struct StakeMigrationPackage { address operatorAddress; // the operator address of the target validator to delegate to @@ -162,7 +169,9 @@ contract StakeHub is System, Initializable, Protectable { bool jailed; uint256 jailUntil; uint256 updateTime; - uint256[20] __reservedSlots; + // The agent can perform transactions on behalf of the operatorAddress in certain scenarios. + address agent; + uint256[19] __reservedSlots; } struct Description { @@ -219,6 +228,7 @@ contract StakeHub is System, Initializable, Protectable { address indexed operatorAddress, address indexed delegator, uint256 bnbAmount, StakeMigrationRespCode respCode ); event UnexpectedPackage(uint8 channelId, bytes msgBytes); + event AgentChanged(address indexed operatorAddress, address indexed oldAgent, address indexed newAgent); /*----------------- modifiers -----------------*/ modifier validatorExist(address operatorAddress) { @@ -315,6 +325,27 @@ contract StakeHub is System, Initializable, Protectable { } /*----------------- external functions -----------------*/ + /** + * @param newAgent the new agent address of the validator, updating to address(0) means remove the old agent. + */ + function updateAgent(address newAgent) external validatorExist(msg.sender) whenNotPaused notInBlackList { + if (agentToOperator[newAgent] != address(0)) revert InvalidAgent(); + if (_validatorSet.contains(newAgent)) revert InvalidAgent(); + + address operatorAddress = msg.sender; + address oldAgent = _validators[operatorAddress].agent; + if (oldAgent == newAgent) revert InvalidAgent(); + + if (oldAgent != address(0)) { + delete agentToOperator[oldAgent]; + } + + _validators[operatorAddress].agent = newAgent; + agentToOperator[newAgent] = operatorAddress; + + emit AgentChanged(operatorAddress, oldAgent, newAgent); + } + /** * @param consensusAddress the consensus address of the validator * @param voteAddress the vote address of the validator @@ -332,6 +363,8 @@ contract StakeHub is System, Initializable, Protectable { // basic check address operatorAddress = msg.sender; if (_validatorSet.contains(operatorAddress)) revert ValidatorExisted(); + if (agentToOperator[operatorAddress] != address(0)) revert InvalidValidator(); + if (consensusToOperator[consensusAddress] != address(0) || _legacyConsensusAddress[consensusAddress]) { revert DuplicateConsensusAddress(); } @@ -384,14 +417,14 @@ contract StakeHub is System, Initializable, Protectable { external whenNotPaused notInBlackList - validatorExist(msg.sender) + validatorExist(_bep410MsgSender()) { if (newConsensusAddress == address(0)) revert InvalidConsensusAddress(); if (consensusToOperator[newConsensusAddress] != address(0) || _legacyConsensusAddress[newConsensusAddress]) { revert DuplicateConsensusAddress(); } - address operatorAddress = msg.sender; + address operatorAddress = _bep410MsgSender(); Validator storage valInfo = _validators[operatorAddress]; if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); @@ -410,9 +443,9 @@ contract StakeHub is System, Initializable, Protectable { external whenNotPaused notInBlackList - validatorExist(msg.sender) + validatorExist(_bep410MsgSender()) { - address operatorAddress = msg.sender; + address operatorAddress = _bep410MsgSender(); Validator storage valInfo = _validators[operatorAddress]; if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); @@ -436,9 +469,9 @@ contract StakeHub is System, Initializable, Protectable { external whenNotPaused notInBlackList - validatorExist(msg.sender) + validatorExist(_bep410MsgSender()) { - address operatorAddress = msg.sender; + address operatorAddress = _bep410MsgSender(); Validator storage valInfo = _validators[operatorAddress]; if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); @@ -456,9 +489,9 @@ contract StakeHub is System, Initializable, Protectable { function editVoteAddress( bytes calldata newVoteAddress, bytes calldata blsProof - ) external whenNotPaused notInBlackList validatorExist(msg.sender) { + ) external whenNotPaused notInBlackList validatorExist(_bep410MsgSender()) { // proof-of-possession verify - address operatorAddress = msg.sender; + address operatorAddress = _bep410MsgSender(); if (!_checkVoteAddress(operatorAddress, newVoteAddress, blsProof)) revert InvalidVoteAddress(); if (voteToOperator[newVoteAddress] != address(0) || _legacyVoteAddress[newVoteAddress]) { revert DuplicateVoteAddress(); @@ -955,6 +988,20 @@ contract StakeHub is System, Initializable, Protectable { return _validators[operatorAddress].commission; } + /** + * @param operatorAddress the operator address of the validator + * + * @return the agent of a validator + */ + function getValidatorAgent(address operatorAddress) + external + view + validatorExist(operatorAddress) + returns (address) + { + return _validators[operatorAddress].agent; + } + /** * @dev this function will be used by Parlia consensus engine. * @@ -1179,4 +1226,12 @@ contract StakeHub is System, Initializable, Protectable { uint256 bnbAmount = IStakeCredit(_validators[operatorAddress].creditContract).claim(msg.sender, requestNumber); emit Claimed(operatorAddress, msg.sender, bnbAmount); } + + function _bep410MsgSender() internal view returns (address) { + if (agentToOperator[msg.sender] != address(0)) { + return agentToOperator[msg.sender]; + } + + return msg.sender; + } } diff --git a/contracts/BC_fusion/lib/RLPDecode.sol b/contracts/BC_fusion/lib/RLPDecode.sol index 6b250f63..49718255 100644 --- a/contracts/BC_fusion/lib/RLPDecode.sol +++ b/contracts/BC_fusion/lib/RLPDecode.sol @@ -144,7 +144,7 @@ library RLPDecode { assembly { result := mload(memPtr) - // shfit to the correct location if neccesary + // shift to the correct location if necessary if lt(len, 32) { result := div(result, exp(256, sub(32, len))) } } diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index b2cacc63..fbc92601 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -84,7 +84,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 public constant INIT_SYSTEM_REWARD_RATIO = 625; // 625/10000 is 1/16 uint256 public constant MAX_SYSTEM_REWARD_BALANCE = 100 ether; - uint256 public systemRewardRatio; + uint256 public systemRewardBaseRatio; uint256 public previousHeight; uint256 public previousBalanceOfSystemReward; // deprecated bytes[] public previousVoteAddrFullSet; @@ -95,6 +95,10 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica Validator[] private _tmpMigratedValidatorSet; bytes[] private _tmpMigratedVoteAddrs; + // BEP-341 Validators can produce consecutive blocks + uint256 public turnLength; // Consecutive number of blocks a validator receives priority for block production + uint256 public systemRewardAntiMEVRatio; + struct Validator { address consensusAddress; address payable feeAddress; @@ -331,11 +335,16 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 index = currentValidatorSetMap[valAddr]; if (isSystemRewardIncluded == false) { - systemRewardRatio = INIT_SYSTEM_REWARD_RATIO; + systemRewardBaseRatio = INIT_SYSTEM_REWARD_RATIO; burnRatio = INIT_BURN_RATIO; isSystemRewardIncluded = true; } + uint256 systemRewardRatio = systemRewardBaseRatio; + if (turnLength > 1 && systemRewardAntiMEVRatio > 0) { + systemRewardRatio += systemRewardAntiMEVRatio * (block.number % turnLength) / (turnLength - 1); + } + if (value > 0 && systemRewardRatio > 0) { uint256 toSystemReward = msg.value.mul(systemRewardRatio).div(BLOCK_FEES_RATIO_SCALE); if (toSystemReward > 0) { @@ -697,8 +706,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica require(value.length == 32, "length of burnRatio mismatch"); uint256 newBurnRatio = BytesToTypes.bytesToUint256(32, value); require( - newBurnRatio.add(systemRewardRatio) <= BLOCK_FEES_RATIO_SCALE, - "the burnRatio plus systemRewardRatio must be no greater than 10000" + newBurnRatio.add(systemRewardBaseRatio).add(systemRewardAntiMEVRatio) <= BLOCK_FEES_RATIO_SCALE, + "the burnRatio plus systemRewardBaseRatio and systemRewardAntiMEVRatio must be no greater than 10000" ); burnRatio = newBurnRatio; } else if (Memory.compareStrings(key, "maxNumOfMaintaining")) { @@ -741,14 +750,30 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica newNumOfCabinets <= MAX_NUM_OF_VALIDATORS, "the numOfCabinets must be less than MAX_NUM_OF_VALIDATORS" ); numOfCabinets = newNumOfCabinets; - } else if (Memory.compareStrings(key, "systemRewardRatio")) { - require(value.length == 32, "length of systemRewardRatio mismatch"); - uint256 newSystemRewardRatio = BytesToTypes.bytesToUint256(32, value); + } else if (Memory.compareStrings(key, "systemRewardBaseRatio")) { + require(value.length == 32, "length of systemRewardBaseRatio mismatch"); + uint256 newSystemRewardBaseRatio = BytesToTypes.bytesToUint256(32, value); + require( + newSystemRewardBaseRatio.add(burnRatio).add(systemRewardAntiMEVRatio) <= BLOCK_FEES_RATIO_SCALE, + "the systemRewardBaseRatio plus burnRatio and systemRewardAntiMEVRatio must be no greater than 10000" + ); + systemRewardBaseRatio = newSystemRewardBaseRatio; + } else if (Memory.compareStrings(key, "systemRewardAntiMEVRatio")) { + require(value.length == 32, "length of systemRewardAntiMEVRatio mismatch"); + uint256 newSystemRewardAntiMEVRatio = BytesToTypes.bytesToUint256(32, value); require( - newSystemRewardRatio.add(burnRatio) <= BLOCK_FEES_RATIO_SCALE, - "the systemRewardRatio plus burnRatio must be no greater than 10000" + newSystemRewardAntiMEVRatio.add(burnRatio).add(systemRewardBaseRatio) <= BLOCK_FEES_RATIO_SCALE, + "the systemRewardAntiMEVRatio plus burnRatio and systemRewardBaseRatio must be no greater than 10000" ); - systemRewardRatio = newSystemRewardRatio; + systemRewardAntiMEVRatio = newSystemRewardAntiMEVRatio; + } else if (Memory.compareStrings(key, "turnLength")) { + require(value.length == 32, "length of turnLength mismatch"); + uint256 newTurnLength = BytesToTypes.bytesToUint256(32, value); + require( + newTurnLength >= 3 && newTurnLength <= 9 || newTurnLength == 1, + "the turnLength should be in [3,9] or equal to 1" + ); + turnLength = newTurnLength; } else { require(false, "unknown param"); } @@ -1047,6 +1072,13 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return voteAddrs; } + function getTurnLength() external view returns (uint256) { + if (turnLength == 0) { + return 1; + } + return turnLength; + } + function setPreviousVoteAddrFullSet() private { uint256 n = previousVoteAddrFullSet.length; uint256 m = currentVoteAddrFullSet.length; diff --git a/contracts/lib/RLPDecode.sol b/contracts/lib/RLPDecode.sol index acbf519b..3db833ab 100644 --- a/contracts/lib/RLPDecode.sol +++ b/contracts/lib/RLPDecode.sol @@ -136,7 +136,7 @@ library RLPDecode { assembly { result := mload(memPtr) - // shfit to the correct location if neccesary + // shift to the correct location if necessary if lt(len, 32) { result := div(result, exp(256, sub(32, len))) } } diff --git a/pyproject.toml b/pyproject.toml index 8f93c144..8d8a88f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,5 @@ [tool.poetry] name = "bsc-genesis-contract" -version = "0.1.0" -description = "" authors = ["BNB Chain"] license = "Apache2.0" readme = "README.md" @@ -16,5 +14,5 @@ typing-extensions = "4.8.0" setuptools = "^69.1.1" [build-system] -requires = ["poetry-core"] +requires = ["poetry-core>=1.8.0"] build-backend = "poetry.core.masonry.api" diff --git a/scripts/generate.py b/scripts/generate.py index d4336b70..0ea738d7 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -242,14 +242,14 @@ def generate_token_recover_portal(source_chain_id, token_recover_portal_protecto ) -def generate_validator_set(init_validatorset_bytes, init_burn_ratio, epoch): +def generate_validator_set(init_validator_set_bytes, init_burn_ratio, epoch): contract = "BSCValidatorSet.sol" backup_file( os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") ) replace_parameter(contract, "uint256 public constant INIT_BURN_RATIO", f"{init_burn_ratio}") - replace_parameter(contract, "bytes public constant INIT_VALIDATORSET_BYTES", f"hex\"{init_validatorset_bytes}\"") + replace_parameter(contract, "bytes public constant INIT_VALIDATORSET_BYTES", f"hex\"{init_validator_set_bytes}\"") replace_parameter(contract, "uint256 public constant EPOCH", f"{epoch}") if network == "dev": @@ -286,8 +286,8 @@ def generate_gov_hub(): def generate_genesis(): - subprocess.run(["forge", "build"], cwd=work_dir) - subprocess.run(["node", "scripts/generate-genesis.js", "--chainId", f"{chain_id}"], cwd=work_dir) + subprocess.run(["forge", "build"], cwd=work_dir, check=True) + subprocess.run(["node", "scripts/generate-genesis.js", "--chainId", f"{chain_id}"], cwd=work_dir, check=True) @main.command(help="Generate contracts for BSC mainnet") @@ -300,7 +300,7 @@ def mainnet(): # mainnet init data init_consensus_bytes = "42696e616e63652d436861696e2d5469677269730000000000000000000000000000000006915167cedaf7bbf7df47d932fdda630527ee648562cf3e52c5e5f46156a3a971a4ceb443c53a50d8653ef8cf1e5716da68120fb51b636dc6d111ec3277b098ecd42d49d3769d8a1f78b4c17a965f7a30d4181fabbd1f969f46d3c8e83b5ad4845421d8000000e8d4a510002ba4e81542f437b7ae1f8a35ddb233c789a8dc22734377d9b6d63af1ca403b61000000e8d4a51000df8da8c5abfdb38595391308bb71e5a1e0aabdc1d0cf38315d50d6be939b2606000000e8d4a51000b6619edca4143484800281d698b70c935e9152ad57b31d85c05f2f79f64b39f3000000e8d4a510009446d14ad86c8d2d74780b0847110001a1c2e252eedfea4753ebbbfce3a22f52000000e8d4a510000353c639f80cc8015944436dab1032245d44f912edc31ef668ff9f4a45cd0599000000e8d4a51000e81d3797e0544c3a718e1f05f0fb782212e248e784c1a851be87e77ae0db230e000000e8d4a510005e3fcda30bd19d45c4b73688da35e7da1fce7c6859b2c1f20ed5202d24144e3e000000e8d4a51000b06a59a2d75bf5d014fce7c999b5e71e7a960870f725847d4ba3235baeaa08ef000000e8d4a510000c910e2fe650e4e01406b3310b489fb60a84bc3ff5c5bee3a56d5898b6a8af32000000e8d4a5100071f2d7b8ec1c8b99a653429b0118cd201f794f409d0fea4d65b1b662f2b00063000000e8d4a51000" init_burn_ratio = "1000" - init_validatorset_bytes = "f905ec80f905e8f846942a7cdd959bfe8d9487b2a43b33565295a698f7e294b6a7edd747c0554875d3fc531d19ba1497992c5e941ff80f3f7f110ffd8920a3ac38fdef318fe94a3f86048c27395000f846946488aa4d1955ee33403f8ccb1d4de5fb97c7ade294220f003d8bdfaadf52aa1e55ae4cc485e6794875941a87e90e440a39c99aa9cb5cea0ad6a3f0b2407b86048c27395000f846949ef9f4360c606c7ab4db26b016007d3ad0ab86a0946103af86a874b705854033438383c82575f25bc29418e2db06cbff3e3c5f856410a1838649e760175786048c27395000f84694ee01c3b1283aa067c58eab4709f85e99d46de5fe94ee4b9bfb1871c64e2bcabb1dc382dc8b7c4218a29415904ab26ab0e99d70b51c220ccdcccabee6e29786048c27395000f84694685b1ded8013785d6623cc18d214320b6bb6475994a20ef4e5e4e7e36258dbf51f4d905114cb1b34bc9413e39085dc88704f4394d35209a02b1a9520320c86048c27395000f8469478f3adfc719c99674c072166708589033e2d9afe9448a30d5eaa7b64492a160f139e2da2800ec3834e94055838358c29edf4dcc1ba1985ad58aedbb6be2b86048c27395000f84694c2be4ec20253b8642161bc3f444f53679c1f3d479466f50c616d737e60d7ca6311ff0d9c434197898a94d1d678a2506eeaa365056fe565df8bc8659f28b086048c27395000f846942f7be8361c80a4c1e7e9aaf001d0877f1cfde218945f93992ac37f3e61db2ef8a587a436a161fd210b94ecbc4fb1a97861344dad0867ca3cba2b860411f086048c27395000f84694ce2fd7544e0b2cc94692d4a704debef7bcb613289444abc67b4b2fba283c582387f54c9cba7c34bafa948acc2ab395ded08bb75ce85bf0f95ad2abc51ad586048c27395000f84694b8f7166496996a7da21cf1f1b04d9b3e26a3d077946770572763289aac606e4f327c2f6cc1aa3b3e3b94882d745ed97d4422ca8da1c22ec49d880c4c097286048c27395000f846942d4c407bbe49438ed859fe965b140dcf1aab71a9943ad0939e120f33518fbba04631afe7a3ed6327b194b2bbb170ca4e499a2b0f3cc85ebfa6e8c4dfcbea86048c27395000f846946bbad7cf34b5fa511d8e963dbba288b1960e75d694853b0f6c324d1f4e76c8266942337ac1b0af1a229442498946a51ca5924552ead6fc2af08b94fcba648601d1a94a2000f846944430b3230294d12c6ab2aac5c2cd68e80b16b581947b107f4976a252a6939b771202c28e64e03f52d694795811a7f214084116949fc4f53cedbf189eeab28601d1a94a2000f84694ea0a6e3c511bbd10f4519ece37dc24887e11b55d946811ca77acfb221a49393c193f3a22db829fcc8e9464feb7c04830dd9ace164fc5c52b3f5a29e5018a8601d1a94a2000f846947ae2f5b9e386cd1b50a4550696d957cb4900f03a94e83bcc5077e6b873995c24bac871b5ad856047e19464e48d4057a90b233e026c1041e6012ada897fe88601d1a94a2000f8469482012708dafc9e1b880fd083b32182b869be8e09948e5adc73a2d233a1b496ed3115464dd6c7b887509428b383d324bc9a37f4e276190796ba5a8947f5ed8601d1a94a2000f8469422b81f8e175ffde54d797fe11eb03f9e3bf75f1d94a1c3ef7ca38d8ba80cce3bfc53ebd2903ed21658942767f7447f7b9b70313d4147b795414aecea54718601d1a94a2000f8469468bf0b8b6fb4e317a0f9d6f03eaf8ce6675bc60d94675cfe570b7902623f47e7f59c9664b5f5065dcf94d84f0d2e50bcf00f2fc476e1c57f5ca2d57f625b8601d1a94a2000f846948c4d90829ce8f72d0163c1d5cf348a862d5506309485c42a7b34309bee2ed6a235f86d16f059deec5894cc2cedc53f0fa6d376336efb67e43d167169f3b78601d1a94a2000f8469435e7a025f4da968de7e4d7e4004197917f4070f194b1182abaeeb3b4d8eba7e6a4162eac7ace23d57394c4fd0d870da52e73de2dd8ded19fe3d26f43a1138601d1a94a2000f84694d6caa02bbebaebb5d7e581e4b66559e635f805ff94c07335cf083c1c46a487f0325769d88e163b653694efaff03b42e41f953a925fc43720e45fb61a19938601d1a94a2000" + init_validator_set_bytes = "f905ec80f905e8f846942a7cdd959bfe8d9487b2a43b33565295a698f7e294b6a7edd747c0554875d3fc531d19ba1497992c5e941ff80f3f7f110ffd8920a3ac38fdef318fe94a3f86048c27395000f846946488aa4d1955ee33403f8ccb1d4de5fb97c7ade294220f003d8bdfaadf52aa1e55ae4cc485e6794875941a87e90e440a39c99aa9cb5cea0ad6a3f0b2407b86048c27395000f846949ef9f4360c606c7ab4db26b016007d3ad0ab86a0946103af86a874b705854033438383c82575f25bc29418e2db06cbff3e3c5f856410a1838649e760175786048c27395000f84694ee01c3b1283aa067c58eab4709f85e99d46de5fe94ee4b9bfb1871c64e2bcabb1dc382dc8b7c4218a29415904ab26ab0e99d70b51c220ccdcccabee6e29786048c27395000f84694685b1ded8013785d6623cc18d214320b6bb6475994a20ef4e5e4e7e36258dbf51f4d905114cb1b34bc9413e39085dc88704f4394d35209a02b1a9520320c86048c27395000f8469478f3adfc719c99674c072166708589033e2d9afe9448a30d5eaa7b64492a160f139e2da2800ec3834e94055838358c29edf4dcc1ba1985ad58aedbb6be2b86048c27395000f84694c2be4ec20253b8642161bc3f444f53679c1f3d479466f50c616d737e60d7ca6311ff0d9c434197898a94d1d678a2506eeaa365056fe565df8bc8659f28b086048c27395000f846942f7be8361c80a4c1e7e9aaf001d0877f1cfde218945f93992ac37f3e61db2ef8a587a436a161fd210b94ecbc4fb1a97861344dad0867ca3cba2b860411f086048c27395000f84694ce2fd7544e0b2cc94692d4a704debef7bcb613289444abc67b4b2fba283c582387f54c9cba7c34bafa948acc2ab395ded08bb75ce85bf0f95ad2abc51ad586048c27395000f84694b8f7166496996a7da21cf1f1b04d9b3e26a3d077946770572763289aac606e4f327c2f6cc1aa3b3e3b94882d745ed97d4422ca8da1c22ec49d880c4c097286048c27395000f846942d4c407bbe49438ed859fe965b140dcf1aab71a9943ad0939e120f33518fbba04631afe7a3ed6327b194b2bbb170ca4e499a2b0f3cc85ebfa6e8c4dfcbea86048c27395000f846946bbad7cf34b5fa511d8e963dbba288b1960e75d694853b0f6c324d1f4e76c8266942337ac1b0af1a229442498946a51ca5924552ead6fc2af08b94fcba648601d1a94a2000f846944430b3230294d12c6ab2aac5c2cd68e80b16b581947b107f4976a252a6939b771202c28e64e03f52d694795811a7f214084116949fc4f53cedbf189eeab28601d1a94a2000f84694ea0a6e3c511bbd10f4519ece37dc24887e11b55d946811ca77acfb221a49393c193f3a22db829fcc8e9464feb7c04830dd9ace164fc5c52b3f5a29e5018a8601d1a94a2000f846947ae2f5b9e386cd1b50a4550696d957cb4900f03a94e83bcc5077e6b873995c24bac871b5ad856047e19464e48d4057a90b233e026c1041e6012ada897fe88601d1a94a2000f8469482012708dafc9e1b880fd083b32182b869be8e09948e5adc73a2d233a1b496ed3115464dd6c7b887509428b383d324bc9a37f4e276190796ba5a8947f5ed8601d1a94a2000f8469422b81f8e175ffde54d797fe11eb03f9e3bf75f1d94a1c3ef7ca38d8ba80cce3bfc53ebd2903ed21658942767f7447f7b9b70313d4147b795414aecea54718601d1a94a2000f8469468bf0b8b6fb4e317a0f9d6f03eaf8ce6675bc60d94675cfe570b7902623f47e7f59c9664b5f5065dcf94d84f0d2e50bcf00f2fc476e1c57f5ca2d57f625b8601d1a94a2000f846948c4d90829ce8f72d0163c1d5cf348a862d5506309485c42a7b34309bee2ed6a235f86d16f059deec5894cc2cedc53f0fa6d376336efb67e43d167169f3b78601d1a94a2000f8469435e7a025f4da968de7e4d7e4004197917f4070f194b1182abaeeb3b4d8eba7e6a4162eac7ace23d57394c4fd0d870da52e73de2dd8ded19fe3d26f43a1138601d1a94a2000f84694d6caa02bbebaebb5d7e581e4b66559e635f805ff94c07335cf083c1c46a487f0325769d88e163b653694efaff03b42e41f953a925fc43720e45fb61a19938601d1a94a2000" whitelist_1 = "0xb005741528b86F5952469d80A8614591E3c5B632" whitelist_2 = "0x446AA6E0DC65690403dF3F127750da1322941F3e" source_chain_id = "Binance-Chain-Tigris" @@ -337,7 +337,7 @@ def mainnet(): generate_slash_indicator(misdemeanor_threshold, felony_threshold, init_felony_slash_scope) generate_relayer_hub(whitelist_1, whitelist_2) generate_tendermint_light_client(init_consensus_bytes) - generate_validator_set(init_validatorset_bytes, init_burn_ratio, epoch) + generate_validator_set(init_validator_set_bytes, init_burn_ratio, epoch) generate_token_recover_portal(source_chain_id, token_recover_portal_protector) generate_stake_hub( breathe_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, max_elected_validators, @@ -364,7 +364,7 @@ def testnet(): # testnet init data init_consensus_bytes = "42696e616e63652d436861696e2d47616e67657300000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000aea1ac326886b992a991d21a6eb155f41b77867cbf659e78f31d89d8205122a84d1be64f0e9a466c2e66a53433928192783e29f8fa21beb2133499b5ef770f60000000e8d4a5100099308aa365c40554bc89982af505d85da95251445d5dd4a9bb37dd2584fd92d3000000e8d4a5100001776920ff0b0f38d78cf95c033c21adf7045785114e392a7544179652e0a612000000e8d4a51000" init_burn_ratio = "1000" - init_validatorset_bytes = "f901a880f901a4f844941284214b9b9c85549ab3d2b972df0deef66ac2c9946ddf42a51534fc98d0c0a3b42c963cace8441ddf946ddf42a51534fc98d0c0a3b42c963cace8441ddf8410000000f84494a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0948081ef03f1d9e0bb4a5bf38f16285c879299f07f948081ef03f1d9e0bb4a5bf38f16285c879299f07f8410000000f8449435552c16704d214347f29fa77f77da6d75d7c75294dc4973e838e3949c77aced16ac2315dc2d7ab11194dc4973e838e3949c77aced16ac2315dc2d7ab1118410000000f84494980a75ecd1309ea12fa2ed87a8744fbfc9b863d594cc6ac05c95a99c1f7b5f88de0e3486c82293b27094cc6ac05c95a99c1f7b5f88de0e3486c82293b2708410000000f84494f474cf03cceff28abc65c9cbae594f725c80e12d94e61a183325a18a173319dd8e19c8d069459e217594e61a183325a18a173319dd8e19c8d069459e21758410000000f84494b71b214cb885500844365e95cd9942c7276e7fd894d22ca3ba2141d23adab65ce4940eb7665ea2b6a794d22ca3ba2141d23adab65ce4940eb7665ea2b6a78410000000" + init_validator_set_bytes = "f901a880f901a4f844941284214b9b9c85549ab3d2b972df0deef66ac2c9946ddf42a51534fc98d0c0a3b42c963cace8441ddf946ddf42a51534fc98d0c0a3b42c963cace8441ddf8410000000f84494a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0948081ef03f1d9e0bb4a5bf38f16285c879299f07f948081ef03f1d9e0bb4a5bf38f16285c879299f07f8410000000f8449435552c16704d214347f29fa77f77da6d75d7c75294dc4973e838e3949c77aced16ac2315dc2d7ab11194dc4973e838e3949c77aced16ac2315dc2d7ab1118410000000f84494980a75ecd1309ea12fa2ed87a8744fbfc9b863d594cc6ac05c95a99c1f7b5f88de0e3486c82293b27094cc6ac05c95a99c1f7b5f88de0e3486c82293b2708410000000f84494f474cf03cceff28abc65c9cbae594f725c80e12d94e61a183325a18a173319dd8e19c8d069459e217594e61a183325a18a173319dd8e19c8d069459e21758410000000f84494b71b214cb885500844365e95cd9942c7276e7fd894d22ca3ba2141d23adab65ce4940eb7665ea2b6a794d22ca3ba2141d23adab65ce4940eb7665ea2b6a78410000000" whitelist_1 = "0x9fB29AAc15b9A4B7F17c3385939b007540f4d791" whitelist_2 = "0x37B8516a0F88E65D677229b402ec6C1e0E333004" source_chain_id = "Binance-Chain-Ganges" @@ -401,7 +401,7 @@ def testnet(): generate_slash_indicator(misdemeanor_threshold, felony_threshold, init_felony_slash_scope) generate_relayer_hub(whitelist_1, whitelist_2) generate_tendermint_light_client(init_consensus_bytes) - generate_validator_set(init_validatorset_bytes, init_burn_ratio, epoch) + generate_validator_set(init_validator_set_bytes, init_burn_ratio, epoch) generate_token_recover_portal(source_chain_id, token_recover_portal_protector) generate_stake_hub( breathe_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, max_elected_validators, @@ -478,7 +478,7 @@ def dev( check=True, cwd=work_dir ) - init_validatorset_bytes = result.stdout.strip()[2:] + init_validator_set_bytes = result.stdout.strip()[2:] except subprocess.CalledProcessError as e: raise Exception(f"Error getting init_validatorset_bytes: {e}") @@ -489,7 +489,7 @@ def dev( generate_slash_indicator(misdemeanor_threshold, felony_threshold, init_felony_slash_scope) generate_relayer_hub(whitelist_1, whitelist_2) generate_tendermint_light_client(init_consensus_bytes) - generate_validator_set(init_validatorset_bytes, init_burn_ratio, epoch) + generate_validator_set(init_validator_set_bytes, init_burn_ratio, epoch) generate_token_recover_portal(source_chain_id, token_recover_portal_protector) generate_stake_hub( breathe_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, max_elected_validators, @@ -512,12 +512,15 @@ def recover(): for file in os.listdir(contracts_dir): if file.endswith(".bak"): c_file = file[:-4] + ".sol" - os.replace(os.path.join(contracts_dir, file), os.path.join(contracts_dir, c_file)) + shutil.copyfile(os.path.join(contracts_dir, file), os.path.join(contracts_dir, c_file)) + os.remove(os.path.join(contracts_dir, file)) + contracts_dir = os.path.join(contracts_dir, "BC_fusion") for file in os.listdir(contracts_dir): if file.endswith(".bak"): c_file = file[:-4] + ".sol" - os.replace(os.path.join(contracts_dir, file), os.path.join(contracts_dir, c_file)) + shutil.copyfile(os.path.join(contracts_dir, file), os.path.join(contracts_dir, c_file)) + os.remove(os.path.join(contracts_dir, file)) print("Recover from the backup successfully") diff --git a/test/Governor.t.sol b/test/Governor.t.sol index 3c091b88..bb33e9b3 100644 --- a/test/Governor.t.sol +++ b/test/Governor.t.sol @@ -32,7 +32,7 @@ contract GovernorTest is Deployer { function testDelegateVote() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); // success case @@ -58,7 +58,7 @@ contract GovernorTest is Deployer { function testProposeErrorCase() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); assert(!governor.proposeStarted()); vm.deal(delegator, 20_000_000 ether); @@ -97,23 +97,23 @@ contract GovernorTest is Deployer { assertEq(governor.proposeStarted(), false, "propose should not start"); // mainnet totalSupply is already enough - // string memory description = "test"; - // // govBNB totalSupply not enough - // vm.expectRevert(); - // uint256 proposalId = governor.propose(targets, values, calldatas, description); - // assertEq(governor.proposeStarted(), false, "propose should not start"); + // // govBNB totalSupply not enough + // string memory description = "test"; + // vm.expectRevert(); + // uint256 proposalId = governor.propose(targets, values, calldatas, description); + // assertEq(governor.proposeStarted(), false, "propose should not start"); // - // bnbAmount = 1 ether; - // stakeHub.delegate{ value: bnbAmount }(validator, false); - // proposalId = governor.propose(targets, values, calldatas, description); - // assertEq(governor.proposeStarted(), true, "propose should start"); + // bnbAmount = 1 ether; + // stakeHub.delegate{ value: bnbAmount }(validator, false); + // proposalId = governor.propose(targets, values, calldatas, description); + // assertEq(governor.proposeStarted(), true, "propose should start"); // - // bnbAmount = 10000000 ether - 2000 ether; - // govBNBBalance = govToken.balanceOf(delegator); - // console.log("govBNBBalance", govBNBBalance); - // assertEq(govBNBBalance, bnbAmount); - // assertEq(govToken.getVotes(delegator), govBNBBalance); - // console.log("voting power before undelegate", govToken.getVotes(delegator)); + // bnbAmount = 10000000 ether - 2000 ether; + // govBNBBalance = govToken.balanceOf(delegator); + // console.log("govBNBBalance", govBNBBalance); + // assertEq(govBNBBalance, bnbAmount); + // assertEq(govToken.getVotes(delegator), govBNBBalance); + // console.log("voting power before undelegate", govToken.getVotes(delegator)); // voting power changed after undelegating staking share bnbAmount = 1 ether; @@ -124,7 +124,7 @@ contract GovernorTest is Deployer { function testProposalNotApproved() public { address delegator = _getNextUserAddress(); - (address validator,) = _createValidator(2000 ether); + (address validator,,,) = _createValidator(2000 ether); vm.startPrank(delegator); assert(!governor.proposeStarted()); vm.deal(delegator, 20_000_000 ether); @@ -182,7 +182,7 @@ contract GovernorTest is Deployer { function testProposalQuorumNotReached() public { address delegator = _getNextUserAddress(); - (address validator,) = _createValidator(2000 ether); + (address validator,,,) = _createValidator(2000 ether); vm.startPrank(delegator); assert(!governor.proposeStarted()); vm.deal(delegator, 20_000_000 ether); @@ -248,7 +248,7 @@ contract GovernorTest is Deployer { function testProposeQuorumReached() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); assert(!governor.proposeStarted()); @@ -325,7 +325,7 @@ contract GovernorTest is Deployer { function testPropose() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); assert(!governor.proposeStarted()); @@ -402,7 +402,7 @@ contract GovernorTest is Deployer { function testUndelegate() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); uint256 bnbAmount = 100 ether; @@ -435,7 +435,7 @@ contract GovernorTest is Deployer { function testUndelegateAll() public { uint256 selfDelegation = 2000 ether; uint256 toLock = stakeHub.LOCK_AMOUNT(); - (address validator, address credit) = _createValidator(selfDelegation); + (address validator,, address credit,) = _createValidator(selfDelegation); uint256 _totalShares = IStakeCredit(credit).totalSupply(); assertEq(_totalShares, selfDelegation + toLock, "wrong total shares"); uint256 _totalPooledBNB = IStakeCredit(credit).totalPooledBNB(); @@ -459,51 +459,4 @@ contract GovernorTest is Deployer { vm.stopPrank(); } - - function _createValidator(uint256 delegation) internal returns (address operatorAddress, address credit) { - operatorAddress = _getNextUserAddress(); - StakeHub.Commission memory commission = StakeHub.Commission({ rate: 10, maxRate: 100, maxChangeRate: 5 }); - StakeHub.Description memory description = StakeHub.Description({ - moniker: string.concat("T", vm.toString(uint24(uint160(operatorAddress)))), - identity: vm.toString(operatorAddress), - website: vm.toString(operatorAddress), - details: vm.toString(operatorAddress) - }); - bytes memory blsPubKey = bytes.concat( - hex"00000000000000000000000000000000000000000000000000000000", abi.encodePacked(operatorAddress) - ); - bytes memory blsProof = new bytes(96); - address consensusAddress = address(uint160(uint256(keccak256(blsPubKey)))); - - uint256 toLock = stakeHub.LOCK_AMOUNT(); - vm.prank(operatorAddress); - stakeHub.createValidator{ value: delegation + toLock }( - consensusAddress, blsPubKey, blsProof, commission, description - ); - - credit = stakeHub.getValidatorCreditContract(operatorAddress); - } - - function _encodeValidatorSetUpdatePack( - address[] memory valSet, - uint64[] memory votingPowers, - bytes[] memory voteAddrs - ) internal pure returns (bytes memory) { - bytes[] memory elements = new bytes[](2); - elements[0] = uint8(0).encodeUint(); - - bytes[] memory vals = new bytes[](valSet.length); - for (uint256 i; i < valSet.length; ++i) { - bytes[] memory tmp = new bytes[](5); - tmp[0] = valSet[i].encodeAddress(); - tmp[1] = valSet[i].encodeAddress(); - tmp[2] = valSet[i].encodeAddress(); - tmp[3] = votingPowers[i].encodeUint(); - tmp[4] = voteAddrs[i].encodeBytes(); - vals[i] = tmp.encodeList(); - } - - elements[1] = vals.encodeList(); - return elements.encodeList(); - } } diff --git a/test/SlashIndicator.t.sol b/test/SlashIndicator.t.sol index 041ada13..5d056061 100644 --- a/test/SlashIndicator.t.sol +++ b/test/SlashIndicator.t.sol @@ -4,11 +4,11 @@ import "./utils/Deployer.sol"; contract SlashIndicatorTest is Deployer { event validatorSlashed(address indexed validator0); - event maliciousVoteSlashed(bytes32 indexed voteAddrSlice); + event ValidatorSlashed(address indexed operatorAddress, uint256 jailUntil, uint256 slashAmount, uint8 slashType); uint256 public burnRatio; uint256 public burnRatioScale; - uint256 public systemRewardRatio; + uint256 public systemRewardBaseRatio; uint256 public systemRewardRatioScale; address public coinbase; @@ -20,8 +20,8 @@ contract SlashIndicatorTest is Deployer { bscValidatorSet.isSystemRewardIncluded() ? bscValidatorSet.burnRatio() : bscValidatorSet.INIT_BURN_RATIO(); burnRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); - systemRewardRatio = bscValidatorSet.isSystemRewardIncluded() - ? bscValidatorSet.systemRewardRatio() + systemRewardBaseRatio = bscValidatorSet.isSystemRewardIncluded() + ? bscValidatorSet.systemRewardBaseRatio() : bscValidatorSet.INIT_SYSTEM_REWARD_RATIO(); systemRewardRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); @@ -34,6 +34,16 @@ contract SlashIndicatorTest is Deployer { // set gas price to zero to send system slash tx vm.txGasPrice(0); + vm.mockCall(address(0x66), "", hex"01"); + + // close staking channel + // remove this after final sunset hard fork + if (crossChain.registeredContractChannelMap(VALIDATOR_CONTRACT_ADDR, STAKING_CHANNELID)) { + bytes memory key = "enableOrDisableChannel"; + bytes memory valueBytes = bytes(hex"0800"); + _updateParamByGovHub(key, valueBytes, address(crossChain)); + assertTrue(!crossChain.registeredContractChannelMap(VALIDATOR_CONTRACT_ADDR, STAKING_CHANNELID)); + } } function testGov() public { @@ -58,7 +68,6 @@ contract SlashIndicatorTest is Deployer { slashIndicator.slash(validator0); vm.startPrank(coinbase); - vm.txGasPrice(0); (, uint256 origin) = slashIndicator.getSlashIndicator(validator0); for (uint256 i = 1; i < 10; ++i) { vm.expectEmit(true, false, false, true, address(slashIndicator)); @@ -181,192 +190,180 @@ contract SlashIndicatorTest is Deployer { assertEq(bscValidatorSet.numOfMaintaining(), numOfMaintainingBefore); } - // todo: fix this after bc-fusion second sunset - // function testMisdemeanor() public { - // address[] memory vals = new address[](21); - // for (uint256 i; i < vals.length; ++i) { - // vals[i] = _getNextUserAddress(); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, vals)); - // - // vm.startPrank(coinbase); - // uint256 _deposit = 1 ether; - // uint256 _incoming = _calcIncoming(_deposit); - // bscValidatorSet.deposit{ value: _deposit }(vals[0]); - // assertEq(_incoming, bscValidatorSet.getIncoming(vals[0])); - // - // for (uint256 i; i < 50; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[0]); - // } - // (, uint256 count) = slashIndicator.getSlashIndicator(vals[0]); - // assertEq(50, count); - // assertEq(0, bscValidatorSet.getIncoming(vals[0])); - // - // // enter maintenance, cannot be slashed - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[0]); - // (, count) = slashIndicator.getSlashIndicator(vals[0]); - // assertEq(50, count); - // vm.stopPrank(); - // - // address[] memory newVals = new address[](3); - // for (uint256 i; i < newVals.length; ++i) { - // newVals[i] = vals[i]; - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newVals)); - // - // vm.startPrank(coinbase); - // bscValidatorSet.deposit{ value: 2 ether }(newVals[0]); - // assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[0])); - // - // for (uint256 i; i < 37; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(newVals[0]); - // } - // (, count) = slashIndicator.getSlashIndicator(newVals[0]); - // assertEq(50, count); - // assertEq(0, bscValidatorSet.getIncoming(newVals[0])); - // assertEq(_incoming, bscValidatorSet.getIncoming(newVals[1])); - // assertEq(_incoming, bscValidatorSet.getIncoming(newVals[2])); - // - // bscValidatorSet.deposit{ value: _deposit }(newVals[1]); - // assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[1])); - // for (uint256 i; i < 50; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(newVals[1]); - // } - // assertEq(_incoming, bscValidatorSet.getIncoming(newVals[0])); - // assertEq(0, bscValidatorSet.getIncoming(newVals[1])); - // assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[2])); - // - // assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[2])); - // for (uint256 i; i < 50; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(newVals[2]); - // } - // assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[0])); - // assertEq(_incoming, bscValidatorSet.getIncoming(newVals[1])); - // assertEq(0, bscValidatorSet.getIncoming(newVals[2])); - // vm.stopPrank(); - // } - // - // function testFelony() public { - // address[] memory vals = new address[](3); - // for (uint256 i; i < vals.length; ++i) { - // vals[i] = _getNextUserAddress(); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, vals)); - // - // vm.startPrank(coinbase); - // uint256 _deposit = 1 ether; - // uint256 _incoming = _calcIncoming(_deposit); - // bscValidatorSet.deposit{ value: _deposit }(vals[0]); - // assertEq(_incoming, bscValidatorSet.getIncoming(vals[0])); - // - // for (uint256 i; i < 50; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[0]); - // } - // (, uint256 count) = slashIndicator.getSlashIndicator(vals[0]); - // assertEq(50, count); - // assertEq(0, bscValidatorSet.getIncoming(vals[0])); - // vm.stopPrank(); - // - // vm.prank(vals[0]); - // bscValidatorSet.exitMaintenance(); - // - // vm.startPrank(coinbase); - // bscValidatorSet.deposit{ value: _deposit }(vals[0]); - // for (uint256 i; i < 100; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[0]); - // } - // (, count) = slashIndicator.getSlashIndicator(vals[0]); - // assertEq(0, count); - // assertEq(0, bscValidatorSet.getIncoming(vals[0])); - // assertEq(_incoming, bscValidatorSet.getIncoming(vals[1])); - // assertEq(_incoming, bscValidatorSet.getIncoming(vals[2])); - // - // vals = bscValidatorSet.getValidators(); - // assertEq(2, vals.length); - // vm.stopPrank(); - // } - // - // function testClean() public { - // // case 1: all clean. - // address[] memory vals = new address[](20); - // for (uint256 i; i < vals.length; ++i) { - // vals[i] = _getNextUserAddress(); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, vals)); - // - // vm.startPrank(coinbase); - // for (uint256 i; i < vals.length; ++i) { - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[i]); - // } - // vm.stopPrank(); - // - // // do clean - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, vals)); - // - // uint256 count; - // for (uint256 i; i < vals.length; ++i) { - // (, count) = slashIndicator.getSlashIndicator(vals[i]); - // assertEq(0, count); - // } - // - // // case 2: all stay. - // uint256 slashCount = 1 + slashIndicator.felonyThreshold() / slashIndicator.DECREASE_RATE(); - // vm.startPrank(coinbase); - // for (uint256 i; i < vals.length; ++i) { - // for (uint256 j; j < slashCount; ++j) { - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[i]); - // } - // } - // vm.stopPrank(); - // - // // do clean - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, vals)); - // - // for (uint256 i; i < vals.length; ++i) { - // (, count) = slashIndicator.getSlashIndicator(vals[i]); - // assertEq(1, count); - // } - // - // // case 3: partial stay. - // vm.startPrank(coinbase); - // for (uint256 i; i < 10; ++i) { - // for (uint256 j; j < slashCount; ++j) { - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[2 * i]); - // } - // vm.roll(block.number + 1); - // slashIndicator.slash(vals[2 * i + 1]); - // } - // vm.stopPrank(); - // - // // do clean - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, vals)); - // - // for (uint256 i; i < 10; ++i) { - // (, count) = slashIndicator.getSlashIndicator(vals[i]); - // if (i % 2 == 0) { - // assertEq(2, count); - // } else { - // assertEq(0, count); - // } - // } - // } + function testMisdemeanor() public { + (, address[] memory consensusAddrs, uint64[] memory votingPowers, bytes[] memory voteAddrs) = + _batchCreateValidators(21); + + vm.startPrank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + uint256 _deposit = 1 ether; + uint256 _incoming = _calcIncoming(_deposit); + bscValidatorSet.deposit{ value: _deposit }(consensusAddrs[0]); + assertEq(_incoming, bscValidatorSet.getIncoming(consensusAddrs[0])); + + for (uint256 i; i < 50; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[0]); + } + (, uint256 count) = slashIndicator.getSlashIndicator(consensusAddrs[0]); + assertEq(50, count); + assertEq(0, bscValidatorSet.getIncoming(consensusAddrs[0])); + + // enter maintenance, cannot be slashed + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[0]); + (, count) = slashIndicator.getSlashIndicator(consensusAddrs[0]); + assertEq(50, count); + + address[] memory newVals = new address[](3); + uint64[] memory newVotingPowers = new uint64[](3); + bytes[] memory newVoteAddrs = new bytes[](3); + for (uint256 i; i < 3; ++i) { + newVals[i] = consensusAddrs[i]; + newVotingPowers[i] = votingPowers[i]; + newVoteAddrs[i] = voteAddrs[i]; + } + bscValidatorSet.updateValidatorSetV2(newVals, newVotingPowers, newVoteAddrs); + + bscValidatorSet.deposit{ value: 2 ether }(newVals[0]); + assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[0])); + + for (uint256 i; i < 37; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(newVals[0]); + } + (, count) = slashIndicator.getSlashIndicator(newVals[0]); + assertEq(50, count); + assertEq(0, bscValidatorSet.getIncoming(newVals[0])); + assertEq(_incoming, bscValidatorSet.getIncoming(newVals[1])); + assertEq(_incoming, bscValidatorSet.getIncoming(newVals[2])); + + bscValidatorSet.deposit{ value: _deposit }(newVals[1]); + assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[1])); + for (uint256 i; i < 50; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(newVals[1]); + } + assertEq(_incoming, bscValidatorSet.getIncoming(newVals[0])); + assertEq(0, bscValidatorSet.getIncoming(newVals[1])); + assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[2])); + + assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[2])); + for (uint256 i; i < 50; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(newVals[2]); + } + assertEq(_incoming * 2, bscValidatorSet.getIncoming(newVals[0])); + assertEq(_incoming, bscValidatorSet.getIncoming(newVals[1])); + assertEq(0, bscValidatorSet.getIncoming(newVals[2])); + vm.stopPrank(); + } + + function testFelony() public { + (, address[] memory consensusAddrs, uint64[] memory votingPowers, bytes[] memory voteAddrs) = + _batchCreateValidators(3); + + vm.startPrank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + uint256 _deposit = 1 ether; + uint256 _incoming = _calcIncoming(_deposit); + bscValidatorSet.deposit{ value: _deposit }(consensusAddrs[0]); + assertEq(_incoming, bscValidatorSet.getIncoming(consensusAddrs[0])); + + for (uint256 i; i < 50; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[0]); + } + (, uint256 count) = slashIndicator.getSlashIndicator(consensusAddrs[0]); + assertEq(50, count); + assertEq(0, bscValidatorSet.getIncoming(consensusAddrs[0])); + vm.stopPrank(); + + vm.prank(consensusAddrs[0]); + bscValidatorSet.exitMaintenance(); + + vm.startPrank(coinbase); + bscValidatorSet.deposit{ value: _deposit }(consensusAddrs[0]); + for (uint256 i; i < 100; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[0]); + } + (, count) = slashIndicator.getSlashIndicator(consensusAddrs[0]); + assertEq(0, count); + assertEq(0, bscValidatorSet.getIncoming(consensusAddrs[0])); + assertEq(_incoming, bscValidatorSet.getIncoming(consensusAddrs[1])); + assertEq(_incoming, bscValidatorSet.getIncoming(consensusAddrs[2])); + + address[] memory vals = bscValidatorSet.getValidators(); + assertEq(2, vals.length); + vm.stopPrank(); + } + + function testClean() public { + (, address[] memory consensusAddrs, uint64[] memory votingPowers, bytes[] memory voteAddrs) = + _batchCreateValidators(20); + + // case 1: all clean. + vm.startPrank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + for (uint256 i; i < consensusAddrs.length; ++i) { + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[i]); + } + + // do clean + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + uint256 count; + for (uint256 i; i < consensusAddrs.length; ++i) { + (, count) = slashIndicator.getSlashIndicator(consensusAddrs[i]); + assertEq(0, count); + } + + // case 2: all stay. + uint256 slashCount = 1 + slashIndicator.felonyThreshold() / slashIndicator.DECREASE_RATE(); + for (uint256 i; i < consensusAddrs.length; ++i) { + for (uint256 j; j < slashCount; ++j) { + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[i]); + } + } + + // do clean + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + for (uint256 i; i < consensusAddrs.length; ++i) { + (, count) = slashIndicator.getSlashIndicator(consensusAddrs[i]); + assertEq(1, count); + } + + // case 3: partial stay. + for (uint256 i; i < 10; ++i) { + for (uint256 j; j < slashCount; ++j) { + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[2 * i]); + } + vm.roll(block.number + 1); + slashIndicator.slash(consensusAddrs[2 * i + 1]); + } + + // do clean + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + for (uint256 i; i < 10; ++i) { + (, count) = slashIndicator.getSlashIndicator(consensusAddrs[i]); + if (i % 2 == 0) { + assertEq(2, count); + } else { + assertEq(0, count); + } + } + + vm.stopPrank(); + } function testDoubleSignSlash() public { // mock data @@ -388,59 +385,62 @@ contract SlashIndicatorTest is Deployer { slashIndicator.submitDoubleSignEvidence(headerA, headerB); } - // function testMaliciousVoteSlash() public { - // if (!slashIndicator.enableMaliciousVoteSlash()) { - // bytes memory key = "enableMaliciousVoteSlash"; - // bytes memory value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000001"); - // _updateParamByGovHub(key, value, address(slashIndicator)); - // } - // - // address[] memory vals = new address[](20); - // bytes[] memory voteAddrs = new bytes[](20); - // for (uint256 i; i < vals.length; ++i) { - // vals[i] = _getNextUserAddress(); - // voteAddrs[i] = - // bytes.concat(hex"00000000000000000000000000000000000000000000000000000000", abi.encodePacked(vals[i])); // 28 + 20 - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeNewValidatorSetUpdatePack(0x00, vals, voteAddrs)); - // - // // case1: valid finality evidence: same target block - // uint256 srcNumA = block.number - 20; - // uint256 tarNumA = block.number - 10; - // uint256 srcNumB = block.number - 15; - // uint256 tarNumB = tarNumA; - // SlashIndicator.VoteData memory voteA; - // voteA.srcNum = srcNumA; - // voteA.srcHash = blockhash(srcNumA); - // voteA.tarNum = tarNumA; - // voteA.tarHash = blockhash(tarNumA); - // voteA.sig = - // hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"; - // - // SlashIndicator.VoteData memory voteB; - // voteB.srcNum = srcNumB; - // voteB.srcHash = blockhash(srcNumB); - // voteB.tarNum = tarNumB; - // voteB.tarHash = blockhash(tarNumB); - // voteB.sig = - // hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; - // - // SlashIndicator.FinalityEvidence memory evidence; - // evidence.voteA = voteA; - // evidence.voteB = voteB; - // evidence.voteAddr = voteAddrs[0]; - // - // vm.mockCall(address(0x66), "", hex"01"); - // bytes32 voteAddrSlice; // empty. don't check this - // vm.expectEmit(false, false, false, false, address(slashIndicator)); - // emit maliciousVoteSlashed(voteAddrSlice); - // vm.prank(relayer); - // slashIndicator.submitFinalityViolationEvidence(evidence); - // } + function testMaliciousVoteSlash() public { + if (!slashIndicator.enableMaliciousVoteSlash()) { + bytes memory key = "enableMaliciousVoteSlash"; + bytes memory value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000001"); + _updateParamByGovHub(key, value, address(slashIndicator)); + } + + ( + address[] memory operatorAddrs, + address[] memory consensusAddrs, + uint64[] memory votingPowers, + bytes[] memory voteAddrs + ) = _batchCreateValidators(20); + vm.prank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + // case1: valid finality evidence: same target block + uint256 srcNumA = block.number - 20; + uint256 tarNumA = block.number - 10; + uint256 srcNumB = block.number - 15; + uint256 tarNumB = tarNumA; + SlashIndicator.VoteData memory voteA; + voteA.srcNum = srcNumA; + voteA.srcHash = blockhash(srcNumA); + voteA.tarNum = tarNumA; + voteA.tarHash = blockhash(tarNumA); + voteA.sig = + hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"; + + SlashIndicator.VoteData memory voteB; + voteB.srcNum = srcNumB; + voteB.srcHash = blockhash(srcNumB); + voteB.tarNum = tarNumB; + voteB.tarHash = blockhash(tarNumB); + voteB.sig = + hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; + + SlashIndicator.FinalityEvidence memory evidence; + evidence.voteA = voteA; + evidence.voteB = voteB; + evidence.voteAddr = voteAddrs[0]; + + vm.expectEmit(true, false, false, false, address(stakeHub)); + emit ValidatorSlashed(operatorAddrs[0], 0, 0, 2); // only check operator address + vm.prank(relayer); + slashIndicator.submitFinalityViolationEvidence(evidence); + } function _calcIncoming(uint256 value) internal view returns (uint256 incoming) { - uint256 toSystemReward = (value * systemRewardRatio) / systemRewardRatioScale; + uint256 turnLength = bscValidatorSet.getTurnLength(); + uint256 systemRewardAntiMEVRatio = bscValidatorSet.systemRewardAntiMEVRatio(); + uint256 systemRewardRatio = systemRewardBaseRatio; + if (turnLength > 1 && systemRewardAntiMEVRatio > 0) { + systemRewardRatio += systemRewardAntiMEVRatio * (block.number % turnLength) / (turnLength - 1); + } + uint256 toSystemReward = (value * systemRewardBaseRatio) / systemRewardRatioScale; uint256 toBurn = (value * burnRatio) / burnRatioScale; incoming = value - toSystemReward - toBurn; } diff --git a/test/StakeHub.t.sol b/test/StakeHub.t.sol index 6e5d0413..92d42b43 100644 --- a/test/StakeHub.t.sol +++ b/test/StakeHub.t.sol @@ -27,6 +27,7 @@ contract StakeHubTest is Deployer { event MigrateFailed( address indexed operatorAddress, address indexed delegator, uint256 bnbAmount, StakeMigrationRespCode respCode ); + event AgentChanged(address indexed operatorAddress, address indexed oldAgent, address indexed newAgent); enum StakeMigrationRespCode { MIGRATE_SUCCESS, @@ -43,7 +44,7 @@ contract StakeHubTest is Deployer { function testCreateValidator() public { // create validator success - (address validator,) = _createValidator(2000 ether); + (address validator,,,) = _createValidator(2000 ether); address consensusAddress = stakeHub.getValidatorConsensusAddress(validator); bytes memory voteAddress = stakeHub.getValidatorVoteAddress(validator); @@ -87,7 +88,7 @@ contract StakeHubTest is Deployer { function testEditValidator() public { // create validator - (address validator,) = _createValidator(2000 ether); + (address validator,,,) = _createValidator(2000 ether); vm.startPrank(validator); // edit failed because of `UpdateTooFrequently` @@ -143,7 +144,7 @@ contract StakeHubTest is Deployer { function testDelegate() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); // failed with too small delegation amount @@ -163,7 +164,7 @@ contract StakeHubTest is Deployer { function testUndelegate() public { address delegator = _getNextUserAddress(); - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); vm.startPrank(delegator); uint256 bnbAmount = 100 ether; @@ -196,7 +197,7 @@ contract StakeHubTest is Deployer { function testUndelegateAll() public { uint256 selfDelegation = 2000 ether; uint256 toLock = stakeHub.LOCK_AMOUNT(); - (address validator, address credit) = _createValidator(selfDelegation); + (address validator,, address credit,) = _createValidator(selfDelegation); uint256 _totalShares = IStakeCredit(credit).totalSupply(); assertEq(_totalShares, selfDelegation + toLock, "wrong total shares"); uint256 _totalPooledBNB = IStakeCredit(credit).totalPooledBNB(); @@ -223,8 +224,8 @@ contract StakeHubTest is Deployer { function testRedelegate() public { address delegator = _getNextUserAddress(); - (address validator1, address credit1) = _createValidator(2000 ether); - (address validator2, address credit2) = _createValidator(2000 ether); + (address validator1,, address credit1,) = _createValidator(2000 ether); + (address validator2,, address credit2,) = _createValidator(2000 ether); vm.startPrank(delegator); uint256 bnbAmount = 100 ether; @@ -266,7 +267,7 @@ contract StakeHubTest is Deployer { assertTrue(!success); // send to credit contract directly - (, address credit) = _createValidator(2000 ether); + (,, address credit,) = _createValidator(2000 ether); (success,) = credit.call{ value: 1 ether }(""); assertTrue(!success); (success,) = credit.call{ value: 1 ether }(hex"12"); @@ -282,7 +283,7 @@ contract StakeHubTest is Deployer { function testDistributeReward() public { address delegator = _getNextUserAddress(); uint256 selfDelegation = 2000 ether; - (address validator, address credit) = _createValidator(selfDelegation); + (address validator,, address credit,) = _createValidator(selfDelegation); // 1. delegate 100 BNB and get 100 * 1e18 shares uint256 delegation = 100 ether; @@ -336,7 +337,7 @@ contract StakeHubTest is Deployer { // totalPooledBNB: 2200 ether uint256 selfDelegation = 2000 ether; uint256 reward = 100 ether; - (address validator, address credit) = _createValidator(selfDelegation); + (address validator,, address credit,) = _createValidator(selfDelegation); _createValidator(selfDelegation); // create 2 validator to avoid empty jail address delegator = _getNextUserAddress(); @@ -390,7 +391,7 @@ contract StakeHubTest is Deployer { // totalPooledBNB: 2200 ether uint256 selfDelegation = 2000 ether; uint256 reward = 100 ether; - (address validator, address credit) = _createValidator(selfDelegation); + (address validator,, address credit,) = _createValidator(selfDelegation); address delegator = _getNextUserAddress(); vm.prank(delegator); @@ -422,7 +423,7 @@ contract StakeHubTest is Deployer { // totalPooledBNB: 2200 ether uint256 selfDelegation = 2000 ether; uint256 reward = 100 ether; - (address validator, address credit) = _createValidator(selfDelegation); + (address validator,, address credit,) = _createValidator(selfDelegation); address delegator = _getNextUserAddress(); vm.prank(delegator); @@ -437,11 +438,10 @@ contract StakeHubTest is Deployer { uint256 preDelegatorBnbAmount = IStakeCredit(credit).getPooledBNBByShares(IStakeCredit(credit).balanceOf(delegator)); - vm.startPrank(SLASH_CONTRACT_ADDR); - // malicious vote slash type: 2 vm.expectEmit(true, false, false, false, address(stakeHub)); // as slash amount may vary by 1, we don't check the event data emit ValidatorSlashed(validator, 0, 0, 2); + vm.prank(SLASH_CONTRACT_ADDR); stakeHub.maliciousVoteSlash(voteAddr); // check delegator's share @@ -469,7 +469,7 @@ contract StakeHubTest is Deployer { bytes memory voteAddress; for (uint256 i; i < length; ++i) { votingPower = (2000 + uint64(i) * 2 + 1) * 1e8; - (operatorAddress,) = _createValidator(uint256(votingPower) * 1e10); + (operatorAddress,,,) = _createValidator(uint256(votingPower) * 1e10); consensusAddress = stakeHub.getValidatorConsensusAddress(operatorAddress); voteAddress = stakeHub.getValidatorVoteAddress(operatorAddress); newConsensusAddrs[length - i - 1] = consensusAddress; @@ -519,7 +519,7 @@ contract StakeHubTest is Deployer { bytes memory voteAddress; for (uint256 i; i < length; ++i) { votingPower = (2000 + uint64(i) * 2 + 1) * 1e8; - (operatorAddress,) = _createValidator(uint256(votingPower) * 1e10); + (operatorAddress,,,) = _createValidator(uint256(votingPower) * 1e10); consensusAddress = stakeHub.getValidatorConsensusAddress(operatorAddress); voteAddress = stakeHub.getValidatorVoteAddress(operatorAddress); newConsensusAddrs[length - i - 1] = consensusAddress; @@ -534,7 +534,7 @@ contract StakeHubTest is Deployer { function testHandleMigrationSynPackage() public { address delegator = _getNextUserAddress(); uint256 delegation = 1 ether; - (address validator, address credit) = _createValidator(2000 ether); + (address validator,, address credit,) = _createValidator(2000 ether); // failed for validator not existed bytes[] memory elements = new bytes[](4); @@ -705,30 +705,6 @@ contract StakeHubTest is Deployer { emit log_named_bytes("vote address bytes", vBz); } - function _createValidator(uint256 delegation) internal returns (address operatorAddress, address credit) { - operatorAddress = _getNextUserAddress(); - StakeHub.Commission memory commission = StakeHub.Commission({ rate: 10, maxRate: 100, maxChangeRate: 5 }); - StakeHub.Description memory description = StakeHub.Description({ - moniker: string.concat("T", vm.toString(uint24(uint160(operatorAddress)))), - identity: vm.toString(operatorAddress), - website: vm.toString(operatorAddress), - details: vm.toString(operatorAddress) - }); - bytes memory blsPubKey = bytes.concat( - hex"00000000000000000000000000000000000000000000000000000000", abi.encodePacked(operatorAddress) - ); - bytes memory blsProof = new bytes(96); - address consensusAddress = address(uint160(uint256(keccak256(blsPubKey)))); - - uint256 toLock = stakeHub.LOCK_AMOUNT(); - vm.prank(operatorAddress); - stakeHub.createValidator{ value: delegation + toLock }( - consensusAddress, blsPubKey, blsProof, commission, description - ); - - credit = stakeHub.getValidatorCreditContract(operatorAddress); - } - function _encodeValidatorSetUpdatePack( address[] memory valSet, uint64[] memory votingPowers, @@ -751,4 +727,73 @@ contract StakeHubTest is Deployer { elements[1] = vals.encodeList(); return elements.encodeList(); } + + function testAgent() external { + // create validator + (address validator,,,) = _createValidator(2000 ether); + vm.startPrank(validator); + + // edit failed because of `UpdateTooFrequently` + vm.expectRevert(StakeHub.UpdateTooFrequently.selector); + stakeHub.editConsensusAddress(address(1)); + + // update agent + address newAgent = validator; + vm.expectRevert(StakeHub.InvalidAgent.selector); + stakeHub.updateAgent(newAgent); + + newAgent = address(0x1234); + vm.expectEmit(true, true, false, true, address(stakeHub)); + emit AgentChanged(validator, address(0), newAgent); + stakeHub.updateAgent(newAgent); + + vm.stopPrank(); + + vm.startPrank(newAgent); + // edit consensus address + vm.warp(block.timestamp + 1 days); + address newConsensusAddress = address(0x1234); + vm.expectEmit(true, true, false, true, address(stakeHub)); + emit ConsensusAddressEdited(validator, newConsensusAddress); + stakeHub.editConsensusAddress(newConsensusAddress); + address realConsensusAddr = stakeHub.getValidatorConsensusAddress(validator); + assertEq(realConsensusAddr, newConsensusAddress); + + // edit commission rate + vm.warp(block.timestamp + 1 days); + vm.expectRevert(StakeHub.InvalidCommission.selector); + stakeHub.editCommissionRate(110); + vm.expectRevert(StakeHub.InvalidCommission.selector); + stakeHub.editCommissionRate(16); + vm.expectEmit(true, false, false, true, address(stakeHub)); + emit CommissionRateEdited(validator, 15); + stakeHub.editCommissionRate(15); + StakeHub.Commission memory realComm = stakeHub.getValidatorCommission(validator); + assertEq(realComm.rate, 15); + + // edit description + vm.warp(block.timestamp + 1 days); + StakeHub.Description memory description = stakeHub.getValidatorDescription(validator); + description.moniker = "Test"; + description.website = "Test"; + vm.expectEmit(true, false, false, true, address(stakeHub)); + emit DescriptionEdited(validator); + stakeHub.editDescription(description); + StakeHub.Description memory realDesc = stakeHub.getValidatorDescription(validator); + assertNotEq(realDesc.moniker, "Test"); // edit moniker will be ignored + assertEq(realDesc.website, "Test"); + + // edit vote address + vm.warp(block.timestamp + 1 days); + bytes memory newVoteAddress = + hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234"; + bytes memory blsProof = new bytes(96); + vm.expectEmit(true, false, false, true, address(stakeHub)); + emit VoteAddressEdited(validator, newVoteAddress); + stakeHub.editVoteAddress(newVoteAddress, blsProof); + bytes memory realVoteAddr = stakeHub.getValidatorVoteAddress(validator); + assertEq(realVoteAddr, newVoteAddress); + + vm.stopPrank(); + } } diff --git a/test/ValidatorSet.t.sol b/test/ValidatorSet.t.sol index 8e093e0a..c6d3c3c9 100644 --- a/test/ValidatorSet.t.sol +++ b/test/ValidatorSet.t.sol @@ -6,11 +6,8 @@ contract ValidatorSetTest is Deployer { using RLPEncode for *; event validatorSetUpdated(); - event batchTransfer(uint256 amount); - event batchTransferFailed(uint256 indexed amount, string reason); event systemTransfer(uint256 amount); - event directTransfer(address payable indexed validator, uint256 amount); - event directTransferFail(address payable indexed validator, uint256 amount); + event RewardDistributed(address indexed operatorAddress, uint256 reward); event deprecatedDeposit(address indexed validator, uint256 amount); event validatorDeposit(address indexed validator, uint256 amount); event failReasonWithStr(string message); @@ -23,7 +20,7 @@ contract ValidatorSetTest is Deployer { uint256 public burnRatioScale; uint256 public maxNumOfWorkingCandidates; uint256 public numOfCabinets; - uint256 public systemRewardRatio; + uint256 public systemRewardBaseRatio; uint256 public systemRewardRatioScale; address public coinbase; @@ -42,8 +39,8 @@ contract ValidatorSetTest is Deployer { burnRatio = bscValidatorSet.isSystemRewardIncluded() ? bscValidatorSet.burnRatio() : bscValidatorSet.INIT_BURN_RATIO(); burnRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); - systemRewardRatio = bscValidatorSet.isSystemRewardIncluded() - ? bscValidatorSet.systemRewardRatio() + systemRewardBaseRatio = bscValidatorSet.isSystemRewardIncluded() + ? bscValidatorSet.systemRewardBaseRatio() : bscValidatorSet.INIT_SYSTEM_REWARD_RATIO(); systemRewardRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); totalInComing = bscValidatorSet.totalInComing(); @@ -58,6 +55,16 @@ contract ValidatorSetTest is Deployer { // set gas price to zero to send system slash tx vm.txGasPrice(0); + vm.mockCall(address(0x66), "", hex"01"); + + // close staking channel + // remove this after final sunset hard fork + if (crossChain.registeredContractChannelMap(VALIDATOR_CONTRACT_ADDR, STAKING_CHANNELID)) { + key = "enableOrDisableChannel"; + valueBytes = bytes(hex"0800"); + _updateParamByGovHub(key, valueBytes, address(crossChain)); + assertTrue(!crossChain.registeredContractChannelMap(VALIDATOR_CONTRACT_ADDR, STAKING_CHANNELID)); + } } function testDeposit(uint256 amount) public { @@ -71,17 +78,35 @@ contract ValidatorSetTest is Deployer { vm.expectRevert("deposit value is zero"); bscValidatorSet.deposit(validator0); - uint256 realAmount = _calcIncoming(amount); + uint256 realAmount0 = _calcIncoming(amount); vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - emit validatorDeposit(validator0, realAmount); + emit validatorDeposit(validator0, realAmount0); + bscValidatorSet.deposit{ value: amount }(validator0); + + vm.stopPrank(); + assertEq(bscValidatorSet.getTurnLength(), 1); + bytes memory key = "turnLength"; + bytes memory value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000005"); // 5 + _updateParamByGovHub(key, value, address(bscValidatorSet)); + assertEq(bscValidatorSet.getTurnLength(), 5); + + key = "systemRewardAntiMEVRatio"; + value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000200"); // 512 + _updateParamByGovHub(key, value, address(bscValidatorSet)); + assertEq(bscValidatorSet.systemRewardAntiMEVRatio(), 512); + vm.startPrank(coinbase); + + uint256 realAmount1 = _calcIncoming(amount); + vm.expectEmit(true, false, false, true, address(bscValidatorSet)); + emit validatorDeposit(validator0, realAmount1); bscValidatorSet.deposit{ value: amount }(validator0); address newAccount = _getNextUserAddress(); vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - emit deprecatedDeposit(newAccount, realAmount); + emit deprecatedDeposit(newAccount, realAmount1); bscValidatorSet.deposit{ value: amount }(newAccount); - assertEq(bscValidatorSet.totalInComing(), totalInComing + realAmount); + assertEq(bscValidatorSet.totalInComing(), totalInComing + realAmount0 + realAmount1); vm.stopPrank(); } @@ -102,454 +127,121 @@ contract ValidatorSetTest is Deployer { _updateParamByGovHub(key, value, address(bscValidatorSet)); assertEq(bscValidatorSet.maxNumOfCandidates(), 5); assertEq(bscValidatorSet.maxNumOfWorkingCandidates(), 5); + + key = "systemRewardBaseRatio"; + value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000400"); // 1024 + _updateParamByGovHub(key, value, address(bscValidatorSet)); + assertEq(bscValidatorSet.systemRewardBaseRatio(), 1024); + } + + function testValidateSetChange() public { + for (uint256 i; i < 5; ++i) { + (, address[] memory consensusAddrs, uint64[] memory votingPowers, bytes[] memory voteAddrs) = + _batchCreateValidators(5); + vm.prank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + address[] memory valSet = bscValidatorSet.getValidators(); + for (uint256 j; j < 5; ++j) { + assertEq(valSet[j], consensusAddrs[j], "consensus address not equal"); + assertTrue(bscValidatorSet.isCurrentValidator(consensusAddrs[j]), "the address should be a validator"); + } + } + } + + function testGetMiningValidatorsWith41Vals() public { + (, address[] memory consensusAddrs, uint64[] memory votingPowers, bytes[] memory voteAddrs) = + _batchCreateValidators(41); + vm.prank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + address[] memory vals = bscValidatorSet.getValidators(); + (address[] memory miningVals,) = bscValidatorSet.getMiningValidators(); + + uint256 count; + uint256 _numOfCabinets; + uint256 _maxNumOfWorkingCandidates = maxNumOfWorkingCandidates; + if (numOfCabinets == 0) { + _numOfCabinets = bscValidatorSet.INIT_NUM_OF_CABINETS(); + } else { + _numOfCabinets = numOfCabinets; + } + if ((vals.length - _numOfCabinets) < _maxNumOfWorkingCandidates) { + _maxNumOfWorkingCandidates = vals.length - _numOfCabinets; + } + + for (uint256 i; i < _numOfCabinets; ++i) { + cabinets[vals[i]] = true; + } + for (uint256 i; i < _numOfCabinets; ++i) { + if (!cabinets[miningVals[i]]) { + ++count; + } + } + assertGe(_maxNumOfWorkingCandidates, count); + assertGe(count, 0); } - // todo: fix this after bc-fusion second sunset - // function testGetMiningValidatorsWith41Vals() public { - // address[] memory newValidators = new address[](41); - // for (uint256 i; i < 41; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidators)); - // - // address[] memory vals = bscValidatorSet.getValidators(); - // (address[] memory miningVals,) = bscValidatorSet.getMiningValidators(); - // - // uint256 count; - // uint256 _numOfCabinets; - // uint256 _maxNumOfWorkingCandidates = maxNumOfWorkingCandidates; - // if (numOfCabinets == 0) { - // _numOfCabinets = bscValidatorSet.INIT_NUM_OF_CABINETS(); - // } else { - // _numOfCabinets = numOfCabinets; - // } - // if ((vals.length - _numOfCabinets) < _maxNumOfWorkingCandidates) { - // _maxNumOfWorkingCandidates = vals.length - _numOfCabinets; - // } - // - // for (uint256 i; i < _numOfCabinets; ++i) { - // cabinets[vals[i]] = true; - // } - // for (uint256 i; i < _numOfCabinets; ++i) { - // if (!cabinets[miningVals[i]]) { - // ++count; - // } - // } - // assertGe(_maxNumOfWorkingCandidates, count); - // assertGe(count, 0); - // } - // - // function testDistributeAlgorithm() public { - // address[] memory newValidator = new address[](1); - // newValidator[0] = _getNextUserAddress(); - // - // // To reset the incoming - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidator)); - // - // address val = newValidator[0]; - // address tmp = _getNextUserAddress(); - // vm.deal(address(bscValidatorSet), 0); - // - // vm.startPrank(coinbase); - // for (uint256 i; i < 5; ++i) { - // bscValidatorSet.deposit{ value: 1 ether }(val); - // bscValidatorSet.deposit{ value: 1 ether }(tmp); - // bscValidatorSet.deposit{ value: 0.1 ether }(val); - // bscValidatorSet.deposit{ value: 0.1 ether }(tmp); - // } - // vm.stopPrank(); - // - // uint256 expectedBalance = _calcIncoming(11 ether); - // uint256 expectedIncoming = _calcIncoming(5.5 ether); - // uint256 balance = address(bscValidatorSet).balance; - // uint256 incoming = bscValidatorSet.totalInComing(); - // assertEq(balance, expectedBalance); - // assertEq(incoming, expectedIncoming); - // - // newValidator[0] = _getNextUserAddress(); - // - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit batchTransfer(expectedIncoming); - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit systemTransfer(expectedBalance - expectedIncoming); - // vm.expectEmit(false, false, false, false, address(bscValidatorSet)); - // emit validatorSetUpdated(); - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidator)); - // } - // - // function testMassiveDistribute() public { - // address[] memory newValidators = new address[](41); - // for (uint256 i; i < 41; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // - // // To reset the incoming - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidators)); - // - // vm.startPrank(coinbase); - // for (uint256 i; i < 41; ++i) { - // bscValidatorSet.deposit{ value: 1 ether }(newValidators[i]); - // } - // vm.stopPrank(); - // - // for (uint256 i; i < 41; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidators)); - // } - // - // function testUpdateValidatorExceedCap() public { - // uint256 cap = bscValidatorSet.MAX_NUM_OF_VALIDATORS(); - // address[] memory newValidators = new address[](cap + 1); - // for (uint256 i; i < cap + 1; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // - // // To reset the incoming - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit failReasonWithStr("the number of validators exceed the limit"); - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidators)); - // } - // - // function testComplicateDistribute1() public { - // address[] memory newValidators = new address[](5); - // address deprecated = _getNextUserAddress(); - // uint256 balance = deprecated.balance; - // for (uint256 i; i < 5; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // bytes memory pack = _encodeOldValidatorSetUpdatePack(0x00, newValidators); - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, pack); - // - // vm.startPrank(coinbase); - // bscValidatorSet.deposit{ value: 1e16 }(newValidators[0]); - // bscValidatorSet.deposit{ value: 1e16 }(newValidators[1]); - // bscValidatorSet.deposit{ value: 1e17 }(newValidators[2]); // middle case - // bscValidatorSet.deposit{ value: 1e18 }(newValidators[3]); - // bscValidatorSet.deposit{ value: 1e18 }(newValidators[4]); - // bscValidatorSet.deposit{ value: 1e18 }(deprecated); // deprecated case - // bscValidatorSet.deposit{ value: 1e5 }(newValidators[4]); // dust case - // vm.stopPrank(); - // - // uint256 directTransferAmount = _calcIncoming(1e16); - // uint256 crossTransferAmount = _calcIncoming(1e18); - // uint256 middleCase = _calcIncoming(1e17); - // uint256 batchTransferAmount = 2 * crossTransferAmount; - // if (middleCase >= 1e17) { - // batchTransferAmount += middleCase; - // } - // uint256 systemTransferAmount = crossTransferAmount + (_calcIncoming(1e5)); - // - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit batchTransfer(batchTransferAmount); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[0]), directTransferAmount); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[1]), directTransferAmount); - // if (middleCase < 1e17) { - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[2]), middleCase); - // } - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit systemTransfer(systemTransferAmount); - // vm.expectEmit(false, false, false, false, address(bscValidatorSet)); - // emit validatorSetUpdated(); - // - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, pack); - // - // assertEq(newValidators[0].balance, balance + directTransferAmount); - // assertEq(newValidators[1].balance, balance + directTransferAmount); - // if (middleCase < 1e17) { - // assertEq(newValidators[2].balance, balance + middleCase); - // } else { - // assertEq(newValidators[2].balance, balance); - // } - // assertEq(newValidators[3].balance, balance); - // assertEq(newValidators[4].balance, balance); - // assertEq(deprecated.balance, balance); - // } - // - // function testCannotUpdateValidatorSet() public { - // address[][] memory newValSet = new address[][](4); - // newValSet[0] = new address[](3); - // newValSet[0][0] = _getNextUserAddress(); - // newValSet[0][1] = newValSet[0][0]; - // newValSet[0][2] = _getNextUserAddress(); - // newValSet[1] = new address[](3); - // newValSet[1][0] = _getNextUserAddress(); - // newValSet[1][1] = _getNextUserAddress(); - // newValSet[1][2] = newValSet[1][1]; - // newValSet[2] = new address[](4); - // newValSet[2][0] = _getNextUserAddress(); - // newValSet[2][1] = _getNextUserAddress(); - // newValSet[2][2] = _getNextUserAddress(); - // newValSet[2][3] = _getNextUserAddress(); - // - // vm.startPrank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValSet[2])); - // for (uint256 i; i < 2; ++i) { - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit failReasonWithStr("duplicate consensus address of validatorSet"); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValSet[i])); - // } - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValSet[3])); - // vm.stopPrank(); - // - // uint64 _height = crossChain.channelSyncedHeaderMap(STAKING_CHANNELID); - // uint64 _sequence = crossChain.channelReceiveSequenceMap(STAKING_CHANNELID); - // vm.expectRevert(bytes("the msg sender is not a relayer")); - // crossChain.handlePackage(bytes("1"), bytes("2"), _height, _sequence, STAKING_CHANNELID); - // - // vm.expectRevert(bytes("light client not sync the block yet")); - // vm.startPrank(address(relayer)); - // crossChain.handlePackage(bytes("1"), bytes("2"), type(uint64).max, _sequence, STAKING_CHANNELID); - // - // vm.expectEmit(true, false, false, true, address(crossChain)); - // emit unsupportedPackage(_sequence, STAKING_CHANNELID, bytes("1")); - // vm.mockCall(address(0x65), "", hex"0000000000000000000000000000000000000000000000000000000000000001"); - // crossChain.handlePackage(bytes("1"), bytes("2"), _height, _sequence, STAKING_CHANNELID); - // - // vm.stopPrank(); - // } - // - // // one validator's fee addr is a contract - // function testComplicateDistribute2() public { - // address[] memory newValidators = new address[](5); - // address deprecated = _getNextUserAddress(); - // uint256 balance = deprecated.balance; - // newValidators[0] = address(slashIndicator); // set fee addr to a contract - // vm.deal(newValidators[0], 0); - // for (uint256 i = 1; i < 5; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // bytes memory pack = _encodeOldValidatorSetUpdatePack(0x00, newValidators); - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, pack); - // - // vm.startPrank(coinbase); - // bscValidatorSet.deposit{ value: 1e16 }(newValidators[0]); // fee addr is a contract - // bscValidatorSet.deposit{ value: 1e16 }(newValidators[1]); - // bscValidatorSet.deposit{ value: 1e17 }(newValidators[2]); // middle case - // bscValidatorSet.deposit{ value: 1e18 }(newValidators[3]); - // bscValidatorSet.deposit{ value: 1e18 }(newValidators[4]); - // bscValidatorSet.deposit{ value: 1e18 }(deprecated); // deprecated case - // bscValidatorSet.deposit{ value: 1e5 }(newValidators[4]); // dust case - // vm.stopPrank(); - // - // uint256 directTransferAmount = _calcIncoming(1e16); - // uint256 crossTransferAmount = _calcIncoming(1e18); - // uint256 middleCase = _calcIncoming(1e17); - // uint256 batchTransferAmount = 2 * crossTransferAmount; - // if (middleCase >= 1e17) { - // batchTransferAmount += middleCase; - // } - // uint256 systemTransferAmount = directTransferAmount + crossTransferAmount + (_calcIncoming(1e5)); - // - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit batchTransfer(batchTransferAmount); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransferFail(payable(newValidators[0]), directTransferAmount); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[1]), directTransferAmount); - // if (middleCase < 1e17) { - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[2]), middleCase); - // } - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit systemTransfer(systemTransferAmount); - // vm.expectEmit(false, false, false, false, address(bscValidatorSet)); - // emit validatorSetUpdated(); - // - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, pack); - // - // assertEq(newValidators[0].balance, 0); - // assertEq(newValidators[1].balance, balance + directTransferAmount); - // if (middleCase < 1e17) { - // assertEq(newValidators[2].balance, balance + middleCase); - // } else { - // assertEq(newValidators[2].balance, balance); - // } - // assertEq(newValidators[3].balance, balance); - // assertEq(newValidators[4].balance, balance); - // assertEq(deprecated.balance, balance); - // } - // - // // cross chain transfer failed - // function testComplicateDistribute3() public { - // address[] memory newValidators = new address[](5); - // address deprecated = _getNextUserAddress(); - // uint256 balance = deprecated.balance; - // newValidators[0] = address(slashIndicator); // set fee addr to a contract - // vm.deal(newValidators[0], 0); - // for (uint256 i = 1; i < 5; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // // set mock tokenHub - // address mockTokenHub = deployCode("MockTokenHub.sol"); - // vm.etch(address(tokenHub), mockTokenHub.code); - // (bool success,) = address(tokenHub).call(abi.encodeWithSignature("setPanicBatchTransferOut(bool)", true)); - // require(success); - // - // bytes memory pack = _encodeOldValidatorSetUpdatePack(0x00, newValidators); - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, pack); - // - // vm.startPrank(coinbase); - // bscValidatorSet.deposit{ value: 1e16 }(newValidators[0]); // fee addr is a contract - // bscValidatorSet.deposit{ value: 1e16 }(newValidators[1]); - // bscValidatorSet.deposit{ value: 1e17 }(newValidators[2]); // middle case - // bscValidatorSet.deposit{ value: 1e18 }(newValidators[3]); - // bscValidatorSet.deposit{ value: 1e18 }(newValidators[4]); - // bscValidatorSet.deposit{ value: 1e18 }(deprecated); // deprecated case - // bscValidatorSet.deposit{ value: 1e5 }(newValidators[4]); // dust case - // vm.stopPrank(); - // - // uint256 directTransferAmount = _calcIncoming(1e16); - // uint256 crossTransferAmount = _calcIncoming(1e18); - // uint256 middleCase = _calcIncoming(1e17); - // uint256 batchTransferAmount = 2 * crossTransferAmount; - // if (middleCase >= 1e17) { - // batchTransferAmount += middleCase; - // } - // uint256 systemTransferAmount = directTransferAmount + crossTransferAmount; - // - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit batchTransferFailed(batchTransferAmount, "panic in batchTransferOut"); - // vm.expectEmit(true, false, false, false, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[3]), crossTransferAmount); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[4]), crossTransferAmount + _calcIncoming(1e5)); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransferFail(payable(newValidators[0]), directTransferAmount); - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[1]), directTransferAmount); - // if (middleCase < 1e17) { - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit directTransfer(payable(newValidators[2]), middleCase); - // } - // vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - // emit systemTransfer(systemTransferAmount); - // vm.expectEmit(false, false, false, false, address(bscValidatorSet)); - // emit validatorSetUpdated(); - // - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, pack); - // - // assertEq(newValidators[0].balance, 0); - // assertEq(newValidators[1].balance, balance + directTransferAmount); - // assertEq(newValidators[2].balance, balance + middleCase); - // assertEq(newValidators[3].balance, balance + crossTransferAmount); - // assertEq(newValidators[4].balance, balance + crossTransferAmount + _calcIncoming(1e5)); - // assertEq(deprecated.balance, balance); - // } - - // function testValidateSetChange() public { - // address[][] memory newValSet = new address[][](5); - // for (uint256 i; i < 5; ++i) { - // address[] memory valSet = new address[](5 + i); - // for (uint256 j; j < 5 + i; ++j) { - // valSet[j] = _getNextUserAddress(); - // } - // newValSet[i] = valSet; - // } - // - // vm.startPrank(address(crossChain)); - // for (uint256 k; k < 5; ++k) { - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValSet[k])); - // address[] memory valSet = bscValidatorSet.getValidators(); - // for (uint256 l; l < 5 + k; ++l) { - // assertEq(valSet[l], newValSet[k][l], "consensusAddr not equal"); - // assertTrue(bscValidatorSet.isCurrentValidator(newValSet[k][l]), "the address should be a validator"); - // } - // } - // vm.stopPrank(); - // } - - // function testJail() public { - // address[] memory newValidators = new address[](3); - // for (uint256 i; i < 3; ++i) { - // newValidators[i] = _getNextUserAddress(); - // } - // - // vm.startPrank(address(crossChain)); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x00, newValidators)); - // - // address[] memory remainVals = bscValidatorSet.getValidators(); - // assertEq(remainVals.length, 3); - // for (uint256 i; i < 3; ++i) { - // assertEq(remainVals[i], newValidators[i]); - // } - // - // vm.expectEmit(false, false, false, true, address(bscValidatorSet)); - // emit failReasonWithStr("length of jail validators must be one"); - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x01, newValidators)); - // - // address[] memory jailVal = new address[](1); - // jailVal[0] = newValidators[0]; - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x01, jailVal)); - // - // remainVals = bscValidatorSet.getValidators(); - // assertEq(remainVals.length, 2); - // for (uint256 i; i < 2; ++i) { - // assertEq(remainVals[i], newValidators[i + 1]); - // } - // - // jailVal[0] = newValidators[1]; - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x01, jailVal)); - // remainVals = bscValidatorSet.getValidators(); - // assertEq(remainVals.length, 1); - // assertEq(remainVals[0], newValidators[2]); - // - // jailVal[0] = newValidators[2]; - // bscValidatorSet.handleSynPackage(STAKING_CHANNELID, _encodeOldValidatorSetUpdatePack(0x01, jailVal)); - // remainVals = bscValidatorSet.getValidators(); - // assertEq(remainVals.length, 1); - // assertEq(remainVals[0], newValidators[2]); - // vm.stopPrank(); - // } - - // function testDecodeNewCrossChainPack() public { - // uint256 maxElectedValidators = stakeHub.maxElectedValidators(); - // - // address[] memory newValidators = new address[](maxElectedValidators); - // bytes[] memory newVoteAddrs = new bytes[](maxElectedValidators); - // for (uint256 i; i < newValidators.length; ++i) { - // newValidators[i] = _getNextUserAddress(); - // newVoteAddrs[i] = abi.encodePacked(newValidators[i]); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage( - // STAKING_CHANNELID, _encodeNewValidatorSetUpdatePack(0x00, newValidators, newVoteAddrs) - // ); - // - // (address[] memory vals, bytes[] memory voteAddrs) = bscValidatorSet.getLivingValidators(); - // for (uint256 i; i < maxElectedValidators; ++i) { - // assertEq(voteAddrs[i], abi.encodePacked(vals[i])); - // } - // - // // edit vote addr for existed validator - // for (uint256 i; i < maxElectedValidators; ++i) { - // newVoteAddrs[i] = abi.encodePacked(newValidators[i], "0x1234567890"); - // } - // vm.prank(address(crossChain)); - // bscValidatorSet.handleSynPackage( - // STAKING_CHANNELID, _encodeNewValidatorSetUpdatePack(0x00, newValidators, newVoteAddrs) - // ); - // - // (vals, voteAddrs) = bscValidatorSet.getLivingValidators(); - // for (uint256 i; i < maxElectedValidators; ++i) { - // assertEq(voteAddrs[i], abi.encodePacked(newValidators[i], "0x1234567890")); - // } - // } + function testDistributeAlgorithm() public { + ( + address[] memory operatorAddrs, + address[] memory consensusAddrs, + uint64[] memory votingPowers, + bytes[] memory voteAddrs + ) = _batchCreateValidators(1); + + vm.startPrank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + address val = consensusAddrs[0]; + address deprecated = _getNextUserAddress(); + vm.deal(address(bscValidatorSet), 0); + + for (uint256 i; i < 5; ++i) { + bscValidatorSet.deposit{ value: 1 ether }(val); + bscValidatorSet.deposit{ value: 1 ether }(deprecated); + bscValidatorSet.deposit{ value: 0.1 ether }(val); + bscValidatorSet.deposit{ value: 0.1 ether }(deprecated); + } + + uint256 expectedBalance = _calcIncoming(11 ether); + uint256 expectedIncoming = _calcIncoming(5.5 ether); + uint256 balance = address(bscValidatorSet).balance; + uint256 incoming = bscValidatorSet.totalInComing(); + assertEq(balance, expectedBalance); + assertEq(incoming, expectedIncoming); + + vm.expectEmit(true, false, false, true, address(stakeHub)); + emit RewardDistributed(operatorAddrs[0], expectedIncoming); + vm.expectEmit(false, false, false, true, address(bscValidatorSet)); + emit systemTransfer(expectedBalance - expectedIncoming); + vm.expectEmit(false, false, false, false, address(bscValidatorSet)); + emit validatorSetUpdated(); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + vm.stopPrank(); + } + + function testMassiveDistribute() public { + ( + address[] memory operatorAddrs, + address[] memory consensusAddrs, + uint64[] memory votingPowers, + bytes[] memory voteAddrs + ) = _batchCreateValidators(41); + + vm.startPrank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + + for (uint256 i; i < 41; ++i) { + bscValidatorSet.deposit{ value: 1 ether }(consensusAddrs[i]); + } + vm.stopPrank(); + + (operatorAddrs, consensusAddrs, votingPowers, voteAddrs) = _batchCreateValidators(41); + vm.prank(coinbase); + bscValidatorSet.updateValidatorSetV2(consensusAddrs, votingPowers, voteAddrs); + } function testDistributeFinalityReward() public { address[] memory addrs = new address[](20); @@ -615,6 +307,12 @@ contract ValidatorSetTest is Deployer { } function _calcIncoming(uint256 value) internal view returns (uint256 incoming) { + uint256 turnLength = bscValidatorSet.getTurnLength(); + uint256 systemRewardAntiMEVRatio = bscValidatorSet.systemRewardAntiMEVRatio(); + uint256 systemRewardRatio = systemRewardBaseRatio; + if (turnLength > 1 && systemRewardAntiMEVRatio > 0) { + systemRewardRatio += systemRewardAntiMEVRatio * (block.number % turnLength) / (turnLength - 1); + } uint256 toSystemReward = (value * systemRewardRatio) / systemRewardRatioScale; uint256 toBurn = (value * burnRatio) / burnRatioScale; incoming = value - toSystemReward - toBurn; diff --git a/test/utils/Deployer.sol b/test/utils/Deployer.sol index 57c8e674..abfbc756 100644 --- a/test/utils/Deployer.sol +++ b/test/utils/Deployer.sol @@ -80,8 +80,9 @@ contract Deployer is Test { event paramChange(string key, bytes value); constructor() { - // create fork of mainnet - vm.createSelectFork("bsc"); + // please use the following command to run the test on mainnet fork instead: forge test --rpc-url ${fork_url} + // vm.createSelectFork("bsc"); + assertEq(block.chainid, 56); // setup system contracts bscValidatorSet = BSCValidatorSet(VALIDATOR_CONTRACT_ADDR); @@ -160,7 +161,6 @@ contract Deployer is Test { } function _getNextUserAddress() internal returns (address payable) { - //bytes32 to address conversion address payable user = payable(address(uint160(uint256(nextUser)))); nextUser = keccak256(abi.encodePacked(nextUser)); vm.deal(user, 10_000 ether); @@ -177,4 +177,61 @@ contract Deployer is Test { govHub.handleSynPackage(GOV_CHANNELID, elements.encodeList()); vm.stopPrank(); } + + function _createValidator(uint256 delegation) + internal + returns (address operatorAddress, address consensusAddress, address credit, bytes memory voteAddress) + { + uint256 toLock = stakeHub.LOCK_AMOUNT(); + + operatorAddress = _getNextUserAddress(); + StakeHub.Commission memory commission = StakeHub.Commission({ rate: 10, maxRate: 100, maxChangeRate: 5 }); + StakeHub.Description memory description = StakeHub.Description({ + moniker: string.concat("T", vm.toString(uint24(uint160(operatorAddress)))), + identity: vm.toString(operatorAddress), + website: vm.toString(operatorAddress), + details: vm.toString(operatorAddress) + }); + voteAddress = bytes.concat( + hex"00000000000000000000000000000000000000000000000000000000", abi.encodePacked(operatorAddress) + ); + bytes memory blsProof = new bytes(96); + consensusAddress = address(uint160(uint256(keccak256(voteAddress)))); + + vm.prank(operatorAddress); + stakeHub.createValidator{ value: delegation + toLock }( + consensusAddress, voteAddress, blsProof, commission, description + ); + + credit = stakeHub.getValidatorCreditContract(operatorAddress); + } + + function _batchCreateValidators(uint256 number) + internal + returns ( + address[] memory operatorAddrs, + address[] memory consensusAddrs, + uint64[] memory votingPowers, + bytes[] memory voteAddrs + ) + { + operatorAddrs = new address[](number); + consensusAddrs = new address[](number); + votingPowers = new uint64[](number); + voteAddrs = new bytes[](number); + + address operatorAddress; + address consensusAddress; + uint64 votingPower; + bytes memory voteAddress; + for (uint256 i; i < number; ++i) { + votingPower = 2000 * 1e8; + (operatorAddress, consensusAddress,, voteAddress) = _createValidator(uint256(votingPower) * 1e10); + + operatorAddrs[i] = operatorAddress; + consensusAddrs[i] = consensusAddress; + votingPowers[i] = votingPower; + voteAddrs[i] = voteAddress; + } + } } diff --git a/test/utils/RLPDecode.sol b/test/utils/RLPDecode.sol index 7021d6cb..092a4a92 100644 --- a/test/utils/RLPDecode.sol +++ b/test/utils/RLPDecode.sol @@ -140,7 +140,7 @@ library RLPDecode { assembly { result := mload(memPtr) - // shfit to the correct location if neccesary + // shift to the correct location if necessary if lt(len, 32) { result := div(result, exp(256, sub(32, len))) } } diff --git a/test/utils/interface/IBSCValidatorSet.sol b/test/utils/interface/IBSCValidatorSet.sol index 960be078..219f404a 100644 --- a/test/utils/interface/IBSCValidatorSet.sol +++ b/test/utils/interface/IBSCValidatorSet.sol @@ -75,6 +75,7 @@ interface BSCValidatorSet { function VALIDATORS_UPDATE_MESSAGE_TYPE() external view returns (uint8); function VALIDATOR_CONTRACT_ADDR() external view returns (address); function alreadyInit() external view returns (bool); + function systemRewardAntiMEVRatio() external view returns (uint256); function bscChainID() external view returns (uint16); function burnRatio() external view returns (uint256); function burnRatioInitialized() external view returns (bool); @@ -102,12 +103,14 @@ interface BSCValidatorSet { function getIncoming(address validator) external view returns (uint256); function getLivingValidators() external view returns (address[] memory, bytes[] memory); function getMiningValidators() external view returns (address[] memory, bytes[] memory); + function getTurnLength() external view returns (uint256); function getValidators() external view returns (address[] memory); function getWorkingValidatorCount() external view returns (uint256 workingValidatorCount); function handleAckPackage(uint8 channelId, bytes memory msgBytes) external; function handleFailAckPackage(uint8 channelId, bytes memory msgBytes) external; function handleSynPackage(uint8, bytes memory msgBytes) external returns (bytes memory responsePayload); function init() external; + function systemRewardBaseRatio() external view returns (uint256); function isCurrentValidator(address validator) external view returns (bool); function isMonitoredForMaliciousVote(bytes memory voteAddr) external view returns (bool); function isSystemRewardIncluded() external view returns (bool); @@ -124,8 +127,8 @@ interface BSCValidatorSet { function previousHeight() external view returns (uint256); function previousVoteAddrFullSet(uint256) external view returns (bytes memory); function removeTmpMigratedValidator(address validator) external; - function systemRewardRatio() external view returns (uint256); function totalInComing() external view returns (uint256); + function turnLength() external view returns (uint256); function updateParam(string memory key, bytes memory value) external; function updateValidatorSetV2( address[] memory _consensusAddrs, diff --git a/test/utils/interface/IStakeHub.sol b/test/utils/interface/IStakeHub.sol index 45968a8b..28c829be 100644 --- a/test/utils/interface/IStakeHub.sol +++ b/test/utils/interface/IStakeHub.sol @@ -51,6 +51,8 @@ interface StakeHub { error ValidatorNotJailed(); error VoteAddressExpired(); error ZeroShares(); + error InvalidAgent(); + error InvalidValidator(); event BlackListed(address indexed target); event Claimed(address indexed operatorAddress, address indexed delegator, uint256 bnbAmount); @@ -94,6 +96,7 @@ interface StakeHub { ); event ValidatorUnjailed(address indexed operatorAddress); event VoteAddressEdited(address indexed operatorAddress, bytes newVoteAddress); + event AgentChanged(address indexed operatorAddress, address indexed oldAgent, address indexed newAgent); receive() external payable; @@ -179,4 +182,7 @@ interface StakeHub { function updateParam(string memory key, bytes memory value) external; function voteExpiration(bytes memory) external view returns (uint256); function voteToOperator(bytes memory) external view returns (address); + + function agentToOperator(address) external view returns (address); + function updateAgent(address newAgent) external; }