From ff3c364f9ff7576df99978a5193385862b2c9ca6 Mon Sep 17 00:00:00 2001 From: zfliex Date: Mon, 16 Jun 2025 14:27:32 +0800 Subject: [PATCH 01/27] support fast finality --- contracts/CandidateHub.sol | 54 ++++++++----- contracts/SlashIndicator.sol | 106 +++++++++++++++++++++++++- contracts/ValidatorSet.sol | 94 ++++++++++++++++++++--- contracts/ValidatorSet.template | 94 ++++++++++++++++++++--- contracts/interface/IValidatorSet.sol | 3 +- 5 files changed, 310 insertions(+), 41 deletions(-) diff --git a/contracts/CandidateHub.sol b/contracts/CandidateHub.sol index d3199d52c..3d6aac22d 100644 --- a/contracts/CandidateHub.sol +++ b/contracts/CandidateHub.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; +import "./lib/Address.sol"; +import "./lib/BytesLib.sol"; import "./lib/BytesToTypes.sol"; import "./lib/Memory.sol"; import "./interface/IValidatorSet.sol"; @@ -68,6 +70,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { uint256 status; uint256 commissionLastChangeRound; uint256 commissionLastRoundValue; + bytes voteAddr; } modifier exist() { @@ -76,9 +79,9 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } /*********************** events **************************/ - event registered(address indexed operateAddr, address indexed consensusAddr, address indexed feeAddress, uint256 commissionThousandths, uint256 margin); + event registered(address indexed operateAddr, address indexed consensusAddr, address indexed feeAddress, uint256 commissionThousandths, uint256 margin, bytes voteAddr); event unregistered(address indexed operateAddr, address indexed consensusAddr); - event updated(address indexed operateAddr, address indexed consensusAddr, address indexed feeAddress, uint256 commissionThousandths); + event updated(address indexed operateAddr, address indexed consensusAddr, address indexed feeAddress, uint256 commissionThousandths, bytes voteAddr); event addedMargin(address indexed operateAddr, uint256 margin, uint256 totalMargin); event deductedMargin(address indexed operateAddr, uint256 margin, uint256 totalMargin); event statusChanged(address indexed operateAddr, uint256 oldStatus, uint256 newStatus); @@ -203,16 +206,17 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { address[] memory validatorList = getValidators(candidates, scores, validatorCount); // prepare arguments, and notify ValidatorSet contract - uint256 totalCount = validatorList.length; - address[] memory consensusAddrList = new address[](totalCount); - address payable[] memory feeAddrList = new address payable[](totalCount); - uint256[] memory commissionThousandthsList = new uint256[](totalCount); + address[] memory consensusAddrList = new address[](validatorList.length); + address payable[] memory feeAddrList = new address payable[](validatorList.length); + uint256[] memory commissionThousandthsList = new uint256[](validatorList.length); + bytes[] memory voteAddrList = new bytes[](validatorList.length); - for (uint256 i = 0; i < totalCount; ++i) { + for (uint256 i = 0; i < validatorList.length; ++i) { uint256 index = operateMap[validatorList[i]]; Candidate storage c = candidateSet[index - 1]; consensusAddrList[i] = c.consensusAddr; feeAddrList[i] = c.feeAddr; + voteAddrList[i] = c.voteAddr; if (scores[i] == 0) { commissionThousandthsList[i] = 1000; } else { @@ -221,7 +225,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { statusList[index - 1] |= SET_VALIDATOR; } - IValidatorSet(VALIDATOR_CONTRACT_ADDR).updateValidatorSet(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList); + IValidatorSet(VALIDATOR_CONTRACT_ADDR).updateValidatorSet(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList); // clean slash contract ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); @@ -250,27 +254,33 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /// @param consensusAddr Consensus address configured on the validator node /// @param feeAddr Fee address set to collect system rewards /// @param commissionThousandths The commission fee taken by the validator, measured in thousandths - function register(address consensusAddr, address payable feeAddr, uint32 commissionThousandths) + function register(address consensusAddr, address payable feeAddr, uint32 commissionThousandths, bytes calldata voteAddr) external payable onlyInit { - require(candidateSet.length <= CANDIDATE_COUNT_LIMIT, "maximum candidate size reached"); + uint256 candidateSize = candidateSet.length; + require(candidateSize <= CANDIDATE_COUNT_LIMIT, "maximum candidate size reached"); require(operateMap[msg.sender] == 0, "candidate already exists"); require(msg.value >= requiredMargin, "deposit is not enough"); require(commissionThousandths != 0 && commissionThousandths < 1000, "commissionThousandths should be in (0, 1000)"); require(consensusMap[consensusAddr] == 0, "consensus already exists"); require(consensusAddr != address(0), "consensus address should not be zero"); require(feeAddr != address(0), "fee address should not be zero"); + // check jail status require(jailMap[msg.sender] < roundTag, "it is in jail"); + require(voteAddr.length == 48, "vote address length should be 48"); + for (uint256 i = 0; i < candidateSize; i++) { + require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); + } + uint256 status = SET_CANDIDATE; - candidateSet.push(Candidate(msg.sender, consensusAddr, feeAddr, commissionThousandths, msg.value, status, roundTag, commissionThousandths)); - uint256 index = candidateSet.length; - operateMap[msg.sender] = index; - consensusMap[consensusAddr] = index; + candidateSet.push(Candidate(msg.sender, consensusAddr, feeAddr, commissionThousandths, msg.value, status, roundTag, commissionThousandths, voteAddr)); + operateMap[msg.sender] = candidateSize + 1; + consensusMap[consensusAddr] = candidateSize + 1; - emit registered(msg.sender, consensusAddr, feeAddr, commissionThousandths, msg.value); + emit registered(msg.sender, consensusAddr, feeAddr, commissionThousandths, msg.value, voteAddr); } /// Unregister the validator candidate role on Core blockchain @@ -295,10 +305,19 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /// @param consensusAddr Consensus address configured on the validator node /// @param feeAddr Fee address set to collect system rewards /// @param commissionThousandths The commission fee taken by the validator, measured in thousandths - function update(address consensusAddr, address payable feeAddr, uint32 commissionThousandths) external onlyInit exist{ + function update(address consensusAddr, address payable feeAddr, uint32 commissionThousandths, bytes calldata voteAddr) external onlyInit exist{ require(commissionThousandths != 0 && commissionThousandths < 1000, "commissionThousandths should in range (0, 1000)"); require(consensusAddr != address(0), "consensus address should not be zero"); require(feeAddr != address(0), "fee address should not be zero"); + require(voteAddr.length == 48, "vote address length should be 48"); + + uint256 candidateSize = candidateSet.length; + for (uint256 i = 0; i < candidateSize; i++) { + if (candidateSet[i].operateAddr != msg.sender) { + require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); + } + } + uint256 index = operateMap[msg.sender]; Candidate storage c = candidateSet[index - 1]; uint256 commissionLastRoundValue = roundTag == c.commissionLastChangeRound @@ -321,7 +340,8 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } c.feeAddr = feeAddr; c.commissionThousandths = commissionThousandths; - emit updated(msg.sender, consensusAddr, feeAddr, commissionThousandths); + c.voteAddr = voteAddr; + emit updated(msg.sender, consensusAddr, feeAddr, commissionThousandths, voteAddr); } /// Refuse to accept delegate from others diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index d8be113dd..462a04fd1 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -18,6 +18,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ using RLPDecode for RLPDecode.RLPItem; using RLPEncode for bytes; using RLPEncode for bytes[]; + using RLPEncode for uint256; uint256 public constant MISDEMEANOR_THRESHOLD = 50; uint256 public constant FELONY_THRESHOLD = 150; @@ -26,6 +27,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ uint256 public constant INIT_FELONY_DEPOSIT = 1e21; uint256 public constant INIT_FELONY_ROUND = 2; uint256 public constant INFINITY_ROUND = 0xFFFFFFFFFFFFFFFF; + uint256 public constant INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION = 5e20; // State of the contract address[] public validators; @@ -39,12 +41,29 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ uint256 public felonyDeposit; uint256 public felonyRound; + uint256 public rewardForReportFinalityViolation; + struct Indicator { uint256 height; uint256 count; bool exist; } + // Proof that a validator misbehaved in fast finality + struct VoteData { + uint256 srcNum; + bytes32 srcHash; + uint256 tarNum; + bytes32 tarHash; + bytes sig; + } + + struct FinalityEvidence { + VoteData voteA; + VoteData voteB; + bytes voteAddr; + } + modifier oncePerBlock() { require(block.number > previousHeight, "can not slash twice in one block"); _; @@ -61,6 +80,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ rewardForReportDoubleSign = INIT_REWARD_FOR_REPORT_DOUBLE_SIGN; felonyDeposit = INIT_FELONY_DEPOSIT; felonyRound = INIT_FELONY_ROUND; + rewardForReportFinalityViolation = INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION; alreadyInit = true; } @@ -99,7 +119,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ RLPDecode.RLPItem[] memory items1 = header1.toRLPItem().toList(); RLPDecode.RLPItem[] memory items2 = header2.toRLPItem().toList(); - require(items1[0].toUintStrict() == items2[0].toUintStrict(),"parent of two blocks must be the same"); + require(items1[0].toUintStrict() == items2[0].toUintStrict(), "parent of two blocks must be the same"); (bytes32 sigHash1, address validator1) = parseHeader(items1); (bytes32 sigHash2, address validator2) = parseHeader(items2); @@ -111,6 +131,38 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportDoubleSign); } + function submitFinalityViolationEvidence(FinalityEvidence memory evidence) external onlyInit { + if (rewardForReportFinalityViolation == 0) { + rewardForReportFinalityViolation = INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION; + } + + // Basic check + require(evidence.voteA.srcNum+28800 > block.number && + evidence.voteB.srcNum+28800 > block.number, "too old block involved"); + require(!(evidence.voteA.srcHash == evidence.voteB.srcHash && + evidence.voteA.tarHash == evidence.voteB.tarHash), "two identical votes"); + require(evidence.voteA.srcNum < evidence.voteA.tarNum && + evidence.voteB.srcNum < evidence.voteB.tarNum, "srcNum bigger than tarNum"); + + // Vote rules check + require((evidence.voteA.srcNum 1e21) { + revert OutOfBounds(key, newRewardForReportFinalityViolation, 1, 1e21); + } + rewardForReportFinalityViolation = newRewardForReportFinalityViolation; } else { revert UnsupportedGovParam(key); } @@ -265,4 +323,50 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ } return ecrecover(hash, v, r, s); } + + function verifyBLSSignature(VoteData memory vote, bytes memory voteAddr) internal view returns(bool) { + bytes[] memory elements = new bytes[](4); + bytes memory _bytes = new bytes(32); + elements[0] = vote.srcNum.encodeUint(); + bytes32ToBytes(32, vote.srcHash, _bytes); + elements[1] = _bytes.encodeBytes(); + elements[2] = vote.tarNum.encodeUint(); + bytes32ToBytes(32, vote.tarHash, _bytes); + elements[3] = _bytes.encodeBytes(); + + bytes32ToBytes(32, keccak256(elements.encodeList()), _bytes); + + // assemble input data + bytes memory input = new bytes(176); + bytesConcat(input, _bytes, 0, 32); + bytesConcat(input, vote.sig, 32, 96); + bytesConcat(input, voteAddr, 128, 48); + + // call the precompiled contract to verify the BLS signature + // the precompiled contract's address is 0x66 + bytes memory output = new bytes(1); + assembly { + let len := mload(input) + if iszero(staticcall(not(0), 0x66, add(input, 0x20), len, add(output, 0x20), 0x01)) { + revert(0, 0) + } + } + if (BytesLib.toUint8(output, 0) != uint8(1)) { + return false; + } + return true; + } + + function bytesConcat(bytes memory data, bytes memory _bytes, uint256 index, uint256 len) internal pure { + for (uint i; i uint256) public currentValidatorSetMap; + uint256 public voteRewardPercent; + struct Validator { address operateAddress; address consensusAddress; address payable feeAddress; uint256 commissionThousandths; uint256 income; + bytes voteAddr; + uint256 voteWeight; } /*********************** events **************************/ @@ -58,6 +62,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 amount, uint256 totalReward ); + event voteRewardTransfer( + address indexed operateAddress, + address payable indexed validator, + uint256 amount + ); + event voteRewardTransferFail( + address indexed operateAddress, + address payable indexed validator, + uint256 amount + ); event deprecatedDeposit(address indexed validator, uint256 amount); event validatorDeposit(address indexed validator, uint256 amount); event validatorMisdemeanor(address indexed validator, uint256 amount); @@ -114,40 +128,58 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + function vote(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase onlyInit onlyZeroGasPrice { + require(valAddrs.length == weights.length, "length not equal"); + + for (uint256 i; i < valAddrs.length; ++i) { + uint256 index = currentValidatorSetMap[valAddrs[i]]; + if (index > 0) { + currentValidatorSet[index - 1].voteWeight += weights[i]; + } + } + } + /// Distribute rewards to validators (and delegators through PledgeAgent) /// @dev this method is called by the CandidateHub contract at the beginning of turn round /// @dev this is where we deal with reward distribution logics function distributeReward(uint256 roundTag) external override onlyCandidate returns (address[] memory operateAddressList) { address payable feeAddress; - uint256 validatorReward; uint256 incentiveSum = 0; + uint256 voteWeightSum; uint256 validatorSize = currentValidatorSet.length; for (uint256 i = 0; i < validatorSize; i++) { Validator storage v = currentValidatorSet[i]; uint256 incentiveValue = (v.income * blockRewardIncentivePercent) / 100; incentiveSum += incentiveValue; v.income -= incentiveValue; + voteWeightSum += v.voteWeight; } ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{ value: incentiveSum }(); + uint256 vrPercent = voteWeightSum == 0 ? 0 : voteRewardPercent; + operateAddressList = new address[](validatorSize); uint256[] memory rewardList = new uint256[](validatorSize); - uint256 rewardSum = 0; + uint256 rewardSum; uint256 tempIncome; + uint256 voteRewardSum; for (uint256 i = 0; i < validatorSize; i++) { Validator storage v = currentValidatorSet[i]; operateAddressList[i] = v.operateAddress; tempIncome = v.income; if (tempIncome != 0) { feeAddress = v.feeAddress; - validatorReward = (tempIncome * v.commissionThousandths) / 1000; + uint256 validatorReward = (tempIncome * v.commissionThousandths) / 1000; if (tempIncome > validatorReward) { rewardList[i] = tempIncome - validatorReward; rewardSum += rewardList[i]; } v.income = 0; + uint256 voteReward = validatorReward * vrPercent / 100; + validatorReward -= voteReward; + voteRewardSum += voteReward; bool success = feeAddress.send(validatorReward); if (success) { emit directTransfer(v.operateAddress, feeAddress, validatorReward, tempIncome); @@ -157,6 +189,21 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + if (voteRewardSum != 0) { + for (uint256 i = 0; i < currentValidatorSet.length; i++) { + Validator memory v = currentValidatorSet[i]; + if (v.voteWeight != 0) { + uint256 reward = voteRewardSum * v.voteWeight / voteWeightSum; + bool success = v.feeAddress.send(reward); + if(success) { + emit voteRewardTransfer(v.operateAddress, v.feeAddress, reward); + } else { + emit voteRewardTransferFail(v.operateAddress, v.feeAddress, reward); + } + } + } + } + IStakeHub(STAKE_HUB_ADDR).addRoundReward{ value: rewardSum }(operateAddressList, rewardList, roundTag); totalInCome = 0; return operateAddressList; @@ -171,10 +218,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, - uint256[] calldata commissionThousandthsList + uint256[] calldata commissionThousandthsList, + bytes[] calldata voteAddrList ) external override onlyCandidate { // do verify. - checkValidatorSet(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList); + checkValidatorSet(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList); if (consensusAddrList.length == 0) { return; } @@ -191,9 +239,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { for (i = 0; i < currentLength; ++i) { if (i >= lastLength) { - currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0)); + currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0)); } else { - currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0); + currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0); } currentValidatorSetMap[consensusAddrList[i]] = i + 1; } @@ -222,6 +270,19 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } return opAddrs; } + + /// Get list of validators and list of voting addressess in the current round + /// @return (List of validator consensus addresses, List of voting addresses) + function getValidatorsAndVoteAddresses() external override view returns (address[] memory, bytes[] memory) { + uint256 validatorSize = currentValidatorSet.length; + address[] memory consensusAddrs = new address[](validatorSize); + bytes[] memory voteAddrs = new bytes[](validatorSize); + for (uint256 i = 0; i < validatorSize; i++) { + consensusAddrs[i] = currentValidatorSet[i].consensusAddress; + voteAddrs[i] = currentValidatorSet[i].voteAddr; + } + return (consensusAddrs, voteAddrs); + } /// Get incoming, which is the reward to distribute at the end of the round, of a validator /// @param validator The validator address @@ -316,6 +377,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { revert OutOfBounds(key, newBlockRewardIncentivePercent, 0, 100); } blockRewardIncentivePercent = newBlockRewardIncentivePercent; + } else if (Memory.compareStrings(key, "voteRewardPercent")) { + uint256 newVoteRewardPercent = BytesToTypes.bytesToUint256(32, value); + if (newVoteRewardPercent > 100) { + revert OutOfBounds(key, newVoteRewardPercent, 0, 100); + } + voteRewardPercent = newVoteRewardPercent; } else { revert UnsupportedGovParam(key); } @@ -324,10 +391,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { /*********************** Internal Functions **************************/ function checkValidatorSet( - address[] memory operateAddrList, - address[] memory consensusAddrList, - address payable[] memory feeAddrList, - uint256[] memory commissionThousandthsList + address[] calldata operateAddrList, + address[] calldata consensusAddrList, + address payable[] calldata feeAddrList, + uint256[] calldata commissionThousandthsList, + bytes[] calldata voteAddrList ) private pure { require( consensusAddrList.length == operateAddrList.length, @@ -341,6 +409,10 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { consensusAddrList.length == commissionThousandthsList.length, "the numbers of consensusAddresses and commissionThousandthss should be equal" ); + require( + consensusAddrList.length == voteAddrList.length, + "the numbers of consensusAddresses and voteAddressed should be equal" + ); for (uint256 i = 0; i < consensusAddrList.length; i++) { for (uint256 j = 0; j < i; j++) { require(consensusAddrList[i] != consensusAddrList[j], "duplicate consensus address"); diff --git a/contracts/ValidatorSet.template b/contracts/ValidatorSet.template index e24cbcc57..9c2c7140b 100644 --- a/contracts/ValidatorSet.template +++ b/contracts/ValidatorSet.template @@ -35,12 +35,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { // value is the index of the element in `currentValidatorSet`. mapping(address => uint256) public currentValidatorSetMap; + uint256 public voteRewardPercent; + struct Validator { address operateAddress; address consensusAddress; address payable feeAddress; uint256 commissionThousandths; uint256 income; + bytes voteAddr; + uint256 voteWeight; } /*********************** events **************************/ @@ -58,6 +62,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 amount, uint256 totalReward ); + event voteRewardTransfer( + address indexed operateAddress, + address payable indexed validator, + uint256 amount + ); + event voteRewardTransferFail( + address indexed operateAddress, + address payable indexed validator, + uint256 amount + ); event deprecatedDeposit(address indexed validator, uint256 amount); event validatorDeposit(address indexed validator, uint256 amount); event validatorMisdemeanor(address indexed validator, uint256 amount); @@ -114,40 +128,58 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + function vote(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase onlyInit onlyZeroGasPrice { + require(valAddrs.length == weights.length, "length not equal"); + + for (uint256 i; i < valAddrs.length; ++i) { + uint256 index = currentValidatorSetMap[valAddrs[i]]; + if (index > 0) { + currentValidatorSet[index - 1].voteWeight += weights[i]; + } + } + } + /// Distribute rewards to validators (and delegators through PledgeAgent) /// @dev this method is called by the CandidateHub contract at the beginning of turn round /// @dev this is where we deal with reward distribution logics function distributeReward(uint256 roundTag) external override onlyCandidate returns (address[] memory operateAddressList) { address payable feeAddress; - uint256 validatorReward; uint256 incentiveSum = 0; + uint256 voteWeightSum; uint256 validatorSize = currentValidatorSet.length; for (uint256 i = 0; i < validatorSize; i++) { Validator storage v = currentValidatorSet[i]; uint256 incentiveValue = (v.income * blockRewardIncentivePercent) / 100; incentiveSum += incentiveValue; v.income -= incentiveValue; + voteWeightSum += v.voteWeight; } ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{ value: incentiveSum }(); + uint256 vrPercent = voteWeightSum == 0 ? 0 : voteRewardPercent; + operateAddressList = new address[](validatorSize); uint256[] memory rewardList = new uint256[](validatorSize); - uint256 rewardSum = 0; + uint256 rewardSum; uint256 tempIncome; + uint256 voteRewardSum; for (uint256 i = 0; i < validatorSize; i++) { Validator storage v = currentValidatorSet[i]; operateAddressList[i] = v.operateAddress; tempIncome = v.income; if (tempIncome != 0) { feeAddress = v.feeAddress; - validatorReward = (tempIncome * v.commissionThousandths) / 1000; + uint256 validatorReward = (tempIncome * v.commissionThousandths) / 1000; if (tempIncome > validatorReward) { rewardList[i] = tempIncome - validatorReward; rewardSum += rewardList[i]; } v.income = 0; + uint256 voteReward = validatorReward * vrPercent / 100; + validatorReward -= voteReward; + voteRewardSum += voteReward; bool success = feeAddress.send(validatorReward); if (success) { emit directTransfer(v.operateAddress, feeAddress, validatorReward, tempIncome); @@ -157,6 +189,21 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + if (voteRewardSum != 0) { + for (uint256 i = 0; i < currentValidatorSet.length; i++) { + Validator memory v = currentValidatorSet[i]; + if (v.voteWeight != 0) { + uint256 reward = voteRewardSum * v.voteWeight / voteWeightSum; + bool success = v.feeAddress.send(reward); + if(success) { + emit voteRewardTransfer(v.operateAddress, v.feeAddress, reward); + } else { + emit voteRewardTransferFail(v.operateAddress, v.feeAddress, reward); + } + } + } + } + IStakeHub(STAKE_HUB_ADDR).addRoundReward{ value: rewardSum }(operateAddressList, rewardList, roundTag); totalInCome = 0; return operateAddressList; @@ -171,10 +218,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, - uint256[] calldata commissionThousandthsList + uint256[] calldata commissionThousandthsList, + bytes[] calldata voteAddrList ) external override onlyCandidate { // do verify. - checkValidatorSet(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList); + checkValidatorSet(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList); if (consensusAddrList.length == 0) { return; } @@ -191,9 +239,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { for (i = 0; i < currentLength; ++i) { if (i >= lastLength) { - currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0)); + currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0)); } else { - currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0); + currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0); } currentValidatorSetMap[consensusAddrList[i]] = i + 1; } @@ -222,6 +270,19 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } return opAddrs; } + + /// Get list of validators and list of voting addressess in the current round + /// @return (List of validator consensus addresses, List of voting addresses) + function getValidatorsAndVoteAddresses() external override view returns (address[] memory, bytes[] memory) { + uint256 validatorSize = currentValidatorSet.length; + address[] memory consensusAddrs = new address[](validatorSize); + bytes[] memory voteAddrs = new bytes[](validatorSize); + for (uint256 i = 0; i < validatorSize; i++) { + consensusAddrs[i] = currentValidatorSet[i].consensusAddress; + voteAddrs[i] = currentValidatorSet[i].voteAddr; + } + return (consensusAddrs, voteAddrs); + } /// Get incoming, which is the reward to distribute at the end of the round, of a validator /// @param validator The validator address @@ -316,6 +377,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { revert OutOfBounds(key, newBlockRewardIncentivePercent, 0, 100); } blockRewardIncentivePercent = newBlockRewardIncentivePercent; + } else if (Memory.compareStrings(key, "voteRewardPercent")) { + uint256 newVoteRewardPercent = BytesToTypes.bytesToUint256(32, value); + if (newVoteRewardPercent > 100) { + revert OutOfBounds(key, newVoteRewardPercent, 0, 100); + } + voteRewardPercent = newVoteRewardPercent; } else { revert UnsupportedGovParam(key); } @@ -324,10 +391,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { /*********************** Internal Functions **************************/ function checkValidatorSet( - address[] memory operateAddrList, - address[] memory consensusAddrList, - address payable[] memory feeAddrList, - uint256[] memory commissionThousandthsList + address[] calldata operateAddrList, + address[] calldata consensusAddrList, + address payable[] calldata feeAddrList, + uint256[] calldata commissionThousandthsList, + bytes[] calldata voteAddrList ) private pure { require( consensusAddrList.length == operateAddrList.length, @@ -341,6 +409,10 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { consensusAddrList.length == commissionThousandthsList.length, "the numbers of consensusAddresses and commissionThousandthss should be equal" ); + require( + consensusAddrList.length == voteAddrList.length, + "the numbers of consensusAddresses and voteAddressed should be equal" + ); for (uint256 i = 0; i < consensusAddrList.length; i++) { for (uint256 j = 0; j < i; j++) { require(consensusAddrList[i] != consensusAddrList[j], "duplicate consensus address"); diff --git a/contracts/interface/IValidatorSet.sol b/contracts/interface/IValidatorSet.sol index 4fbd341ca..a04514058 100644 --- a/contracts/interface/IValidatorSet.sol +++ b/contracts/interface/IValidatorSet.sol @@ -5,7 +5,8 @@ interface IValidatorSet { function misdemeanor(address validator) external; function felony(address validator, uint256 felonyRound, uint256 felonyDeposit) external; function distributeReward(uint256 roundTag) external returns (address[] memory operateAddrList); - function updateValidatorSet(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList) external; + function updateValidatorSet(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList, bytes[] calldata voteAddrList) external; function isValidator(address addr) external view returns (bool); function getValidatorOps() external view returns (address[] memory); + function getValidatorsAndVoteAddresses() external view returns (address[] memory, bytes[] memory); } From 8cf4c816cac5e419db8e9ce9184f2ce8b640fd2f Mon Sep 17 00:00:00 2001 From: zfliex Date: Tue, 1 Jul 2025 15:22:55 +0800 Subject: [PATCH 02/27] support agent --- contracts/CandidateHub.sol | 152 ++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 34 deletions(-) diff --git a/contracts/CandidateHub.sol b/contracts/CandidateHub.sol index 3d6aac22d..d7e5319a0 100644 --- a/contracts/CandidateHub.sol +++ b/contracts/CandidateHub.sol @@ -59,8 +59,9 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { mapping(address => uint256) public jailMap; uint256 public roundTag; - + mapping(address => uint256) public agentMap; + struct Candidate { address operateAddr; address consensusAddr; @@ -71,9 +72,18 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { uint256 commissionLastChangeRound; uint256 commissionLastRoundValue; bytes voteAddr; + address agent; + Description description; + } + + struct Description { + string moniker; + string identity; + string website; + string details; } - modifier exist() { + modifier onlyOperator() { require(operateMap[msg.sender] != 0, "candidate does not exist"); _; } @@ -81,12 +91,18 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /*********************** events **************************/ event registered(address indexed operateAddr, address indexed consensusAddr, address indexed feeAddress, uint256 commissionThousandths, uint256 margin, bytes voteAddr); event unregistered(address indexed operateAddr, address indexed consensusAddr); - event updated(address indexed operateAddr, address indexed consensusAddr, address indexed feeAddress, uint256 commissionThousandths, bytes voteAddr); event addedMargin(address indexed operateAddr, uint256 margin, uint256 totalMargin); event deductedMargin(address indexed operateAddr, uint256 margin, uint256 totalMargin); event statusChanged(address indexed operateAddr, uint256 oldStatus, uint256 newStatus); event turnedRound(uint256 round); + event AgentUpdated(address indexed operateAddr, address newAgent); + event ConsensusAddressEdited(address indexed operateAddr, address newConsensusAddr); + event CommissionRateEdited(address indexed operateAddr, uint256 newRate); + event VoteAddressEdited(address indexed operateAddr, bytes newVoteAddr); + event DescriptionEdited(address indexed operateAddr, string moniker, string identity, string website, string details); + event FeeAddressEdited(address indexed operateAddr, address newFeeAddr); + /*********************** init **************************/ function init() external onlyNotInit { requiredMargin = INIT_REQUIRED_MARGIN; @@ -254,6 +270,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /// @param consensusAddr Consensus address configured on the validator node /// @param feeAddr Fee address set to collect system rewards /// @param commissionThousandths The commission fee taken by the validator, measured in thousandths + /// @param voteAddr Vote address set to vote for the validator function register(address consensusAddr, address payable feeAddr, uint32 commissionThousandths, bytes calldata voteAddr) external payable onlyInit @@ -272,11 +289,23 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { require(voteAddr.length == 48, "vote address length should be 48"); for (uint256 i = 0; i < candidateSize; i++) { - require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); + require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); } uint256 status = SET_CANDIDATE; - candidateSet.push(Candidate(msg.sender, consensusAddr, feeAddr, commissionThousandths, msg.value, status, roundTag, commissionThousandths, voteAddr)); + candidateSet.push(Candidate( + msg.sender, + consensusAddr, + feeAddr, + commissionThousandths, + msg.value, + status, + roundTag, + commissionThousandths, + voteAddr, + address(0), + Description("", "", "", "") + )); operateMap[msg.sender] = candidateSize + 1; consensusMap[consensusAddr] = candidateSize + 1; @@ -284,7 +313,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } /// Unregister the validator candidate role on Core blockchain - function unregister() external onlyInit exist { + function unregister() external onlyInit onlyOperator { uint256 index = operateMap[msg.sender]; Candidate storage c = candidateSet[index - 1]; require(c.status == (c.status & UNREGISTER_STATUS), "candidate status is not cleared"); @@ -301,52 +330,92 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } } - /// Update validator candidate information - /// @param consensusAddr Consensus address configured on the validator node - /// @param feeAddr Fee address set to collect system rewards - /// @param commissionThousandths The commission fee taken by the validator, measured in thousandths - function update(address consensusAddr, address payable feeAddr, uint32 commissionThousandths, bytes calldata voteAddr) external onlyInit exist{ - require(commissionThousandths != 0 && commissionThousandths < 1000, "commissionThousandths should in range (0, 1000)"); - require(consensusAddr != address(0), "consensus address should not be zero"); - require(feeAddr != address(0), "fee address should not be zero"); - require(voteAddr.length == 48, "vote address length should be 48"); - - uint256 candidateSize = candidateSet.length; - for (uint256 i = 0; i < candidateSize; i++) { - if (candidateSet[i].operateAddr != msg.sender) { - require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); - } + function updateAgent(address newAgent) external onlyOperator { + uint256 index = operateMap[msg.sender]; + Candidate storage c = candidateSet[index - 1]; + require(newAgent != address(0), "agent address cannot be zero"); + require(agentMap[newAgent] == 0, "agent address already exists"); + + if (c.agent != address(0)) { + delete agentMap[c.agent]; } + agentMap[newAgent] = index; + c.agent = newAgent; + emit AgentUpdated(msg.sender, newAgent); + } + + function removeAgent() external onlyOperator { uint256 index = operateMap[msg.sender]; Candidate storage c = candidateSet[index - 1]; + require(c.agent != address(0), "agent address does not exist"); + delete agentMap[c.agent]; + c.agent = address(0); + } + + function editConsensusAddress(address newConsensusAddr) external { + (uint256 index, Candidate storage c) = getCandidate(); + + require(consensusMap[newConsensusAddr] == 0, "consensus already exists"); + delete consensusMap[c.consensusAddr]; + c.consensusAddr = newConsensusAddr; + consensusMap[newConsensusAddr] = index; + + emit ConsensusAddressEdited(msg.sender, newConsensusAddr); + } + + function editCommissionRate(uint32 newRate) external { + (, Candidate storage c) = getCandidate(); + + require(newRate != 0 && newRate < 1000, "commissionThousandths should in range (0, 1000)"); + uint256 commissionLastRoundValue = roundTag == c.commissionLastChangeRound ? c.commissionLastRoundValue : c.commissionThousandths; require( - commissionThousandths + maxCommissionChange >= commissionLastRoundValue && - commissionLastRoundValue + maxCommissionChange >= commissionThousandths, + newRate + maxCommissionChange >= commissionLastRoundValue && + commissionLastRoundValue + maxCommissionChange >= newRate, "commissionThousandths out of adjustment range" ); if (roundTag != c.commissionLastChangeRound) { c.commissionLastChangeRound = roundTag; c.commissionLastRoundValue = c.commissionThousandths; } - if (c.consensusAddr != consensusAddr) { - require(consensusMap[consensusAddr] == 0, "the consensus already exists"); - delete consensusMap[c.consensusAddr]; - c.consensusAddr = consensusAddr; - consensusMap[consensusAddr] = index; + c.commissionThousandths = newRate; + emit CommissionRateEdited(msg.sender, newRate); + } + + function editVoteAddress(bytes calldata voteAddr) external { + (, Candidate storage c) = getCandidate(); + + require(voteAddr.length == 48, "vote address length should be 48"); + + uint256 candidateSize = candidateSet.length; + for (uint256 i = 0; i < candidateSize; i++) { + require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); } - c.feeAddr = feeAddr; - c.commissionThousandths = commissionThousandths; c.voteAddr = voteAddr; - emit updated(msg.sender, consensusAddr, feeAddr, commissionThousandths, voteAddr); + emit VoteAddressEdited(msg.sender, voteAddr); + } + + function editDescription(string calldata moniker, string calldata identity, string calldata website, string calldata details) external { + (, Candidate storage c) = getCandidate(); + + c.description = Description(moniker, identity, website, details); + emit DescriptionEdited(msg.sender, moniker, identity, website, details); + } + + function editFeeAddress(address payable newFeeAddr) external onlyOperator { + (, Candidate storage c) = getCandidate(); + + require(newFeeAddr != address(0), "fee address cannot be zero"); + c.feeAddr = newFeeAddr; + emit FeeAddressEdited(c.operateAddr, newFeeAddr); } /// Refuse to accept delegate from others /// @dev Candidate will not be elected in this state - function refuseDelegate() external onlyInit exist { + function refuseDelegate() external onlyInit onlyOperator { uint256 index = operateMap[msg.sender]; Candidate storage c = candidateSet[index - 1]; uint256 status = c.status | SET_INACTIVE; @@ -354,7 +423,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } /// Accept delegate from others - function acceptDelegate() external onlyInit exist { + function acceptDelegate() external onlyInit onlyOperator { uint256 index = operateMap[msg.sender]; Candidate storage c = candidateSet[index - 1]; uint256 status = c.status & DEL_INACTIVE; @@ -363,7 +432,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /// Add refundable deposits /// @dev Candidate will not be elected if there are not enough deposits - function addMargin() external payable onlyInit exist { + function addMargin() external payable onlyInit onlyOperator { require(msg.value != 0, "value should not be zero"); uint256 index = operateMap[msg.sender]; uint256 totalMargin = candidateSet[index - 1].margin + msg.value; @@ -378,6 +447,17 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } /*************************** internal methods ******************************/ + + function getCandidate() internal view returns (uint256, Candidate storage) { + uint256 index = operateMap[msg.sender]; + if (index == 0) { + // Check if sender is agent + index = agentMap[msg.sender]; + require(index != 0, "candidate does not exist"); + } + return (index, candidateSet[index - 1]); + } + function changeStatus(Candidate storage c, uint256 newStatus) internal { uint256 oldStatus = c.status; if (oldStatus != newStatus) { @@ -391,6 +471,10 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { emit unregistered(c.operateAddr, c.consensusAddr); + if (c.agent != address(0)) { + delete agentMap[c.agent]; + } + delete operateMap[c.operateAddr]; delete consensusMap[c.consensusAddr]; From 457f777718732b17a2bb8c5bc90db4a08d40fc4e Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Sat, 29 Mar 2025 18:42:03 +0800 Subject: [PATCH 03/27] add tests for existing features --- contracts/mock/CandidateHubMock.sol | 5 + tests/test_btc_agent.py | 29 ++ tests/test_btc_lst.py | 16 + tests/test_btc_stake.py | 164 ++++++++ tests/test_candidate_hub.py | 563 +++++++++++++++++++++++++++- tests/test_claim_reward.py | 122 ++++++ tests/test_gov_hub.py | 70 ++++ tests/test_relayer_hub.py | 22 ++ tests/test_slash.py | 134 ++++++- tests/test_system_reward.py | 18 + 10 files changed, 1140 insertions(+), 3 deletions(-) diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index d6665f723..f6f6ce387 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -40,6 +40,11 @@ contract CandidateHubMock is CandidateHub { roundTag = value; } + function setDues(uint value) external { + dues = value; + } + + function setValidatorCount(uint256 value) external { validatorCount = value; } diff --git a/tests/test_btc_agent.py b/tests/test_btc_agent.py index 298225c81..9e75f5d1f 100644 --- a/tests/test_btc_agent.py +++ b/tests/test_btc_agent.py @@ -135,6 +135,20 @@ def test_only_stake_hub_can_call_distribute_reward(btc_agent): btc_agent.distributeReward(candidates, rewards, 0) +def test_reward_zero_with_staked_tokens(btc_agent, btc_stake, btc_lst_stake): + turn_round() + round_tag = get_current_round() + candidates = accounts[:3] + rewards = [10000, 20000, 30000] + set_round_tag(round_tag - 1) + round_tag += 2 + btc_lst_stake.setRoundTag(round_tag) + btc_stake.setRoundTag(round_tag) + update_system_contract_address(btc_agent, stake_hub=accounts[0]) + btc_agent.distributeReward(candidates, rewards, 0) + assert btc_stake.accruedRewardPerBTCMap(candidates[0], get_current_round()) == 0 + + def test_distribute_reward_reverts(btc_agent, btc_stake, btc_lst_stake): candidates = accounts[:3] rewards = [10000, 20000, 30000] @@ -165,6 +179,21 @@ def test_get_stake_amounts_success(btc_agent, btc_stake, btc_lst_stake, set_cand assert total_amount == amount * 3 +def test_query_address_empty(btc_agent, btc_stake, btc_lst_stake, set_candidate): + operators = [] + update_system_contract_address(btc_agent, stake_hub=accounts[0]) + amounts, total_amount = btc_agent.getStakeAmounts(operators, 0).return_value + assert sum(amounts) == total_amount == 0 + + +def test_query_address_zero(btc_agent, btc_stake, btc_lst_stake, set_candidate): + operators = ZERO_ADDRESS + turn_round() + update_system_contract_address(btc_agent, stake_hub=accounts[0]) + amounts, total_amount = btc_agent.getStakeAmounts([operators], 0).return_value + assert sum(amounts) == total_amount == 0 + + def test_only_stake_hub_can_call(btc_agent, btc_stake, btc_lst_stake, set_candidate): operators, consensuses = set_candidate turn_round() diff --git a/tests/test_btc_lst.py b/tests/test_btc_lst.py index 3c411ef91..7e23f3093 100644 --- a/tests/test_btc_lst.py +++ b/tests/test_btc_lst.py @@ -267,6 +267,22 @@ def test_lock_script_wallet_inactive_reverts(btc_lst_stake, set_candidate, round btc_lst_stake.delegate('0x00', 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) +@pytest.mark.parametrize("lock_script", [ + ['0xa914cdf3d02dd323c14bea0bed94962496c80c09334487', '0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287'], + ['0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287', '0xa914cdf3d02dd323c14bea0bed94962496c80c09334487'], + ['0x00148bca50f394ed7e7f750213f287accc32c2431cf5', '0xa914cdf3d02dd323c14bea0bed94962496c80c09334487'], + ['0xa914cdf3d02dd323c14bea0bed94962496c80c09334487', '0x00148bca50f394ed7e7f750213f287accc32c2431cf5'], + ['0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287', '0x00148bca50f394ed7e7f750213f287accc32c2431cf5'] +]) +def test_btclst_delegate_script_error(set_candidate, lst_token, btc_lst_stake, lock_script): + stake_manager.add_wallet(lock_script[1]) + turn_round() + btc_tx0 = build_btc_lst_tx(accounts[0], int(BTC_VALUE), lock_script[0]) + with brownie.reverts("staked value is zero"): + btc_lst_stake.delegate(btc_tx0, 1, [], 0, lock_script[1], {"from": accounts[0]}) + turn_round() + + def test_delete_wallet_after_stake(btc_lst_stake, set_candidate): operators, consensuses = set_candidate turn_round() diff --git a/tests/test_btc_stake.py b/tests/test_btc_stake.py index 72a85ad15..0d88723c2 100644 --- a/tests/test_btc_stake.py +++ b/tests/test_btc_stake.py @@ -523,6 +523,14 @@ def test_successful_delegate_btc_call_by_relayer(btc_stake, delegate_btc_valid_t assert 'delegated' in tx.events +def test_delegator_is_relay(btc_stake, delegate_btc_valid_tx, relay_hub, + set_candidate): + lock_script, btc_tx = delegate_btc_valid_tx + relay_hub.setRelayerRegister(accounts[0], True) + tx = btc_stake.delegate(btc_tx, 0, [], 0, lock_script, {'from': accounts[0]}) + assert 'delegated' in tx.events + + @pytest.mark.parametrize("btc_value", [1, 2, 100, 800000, 120000, 50000000]) def test_btc_delegate_no_amount_limit(btc_stake, set_candidate, delegate_btc_valid_tx, btc_value): operators, consensuses = set_candidate @@ -609,6 +617,47 @@ def test_stake_to_regular_address_then_transfer(btc_stake, set_candidate, candid assert tracker.delta() == TOTAL_REWARD * 2 +def test_stake_to_self(btc_stake, set_candidate, candidate_hub): + operators, consensuses = set_candidate + tx = delegate_btc_success(accounts[0], accounts[0], BTC_VALUE, LOCK_SCRIPT, events=True) + assert 'delegated' in tx.events + turn_round(consensuses, round_count=2) + + +def test_validator_address_zero(btc_stake, set_candidate, candidate_hub): + operators, consensuses = set_candidate + tx = delegate_btc_success(ZERO_ADDRESS, accounts[0], BTC_VALUE, LOCK_SCRIPT, events=True) + assert 'delegated' in tx.events + turn_round(consensuses, round_count=2) + + +def test_validator_address_zerotest_stake_address_with_btc_staker(btc_stake, set_candidate, candidate_hub): + operators, consensuses = set_candidate + delegate_btc_success(ZERO_ADDRESS, accounts[1], BTC_VALUE, LOCK_SCRIPT) + tx = delegate_btc_success(accounts[1], accounts[0], BTC_VALUE, LOCK_SCRIPT, events=True) + assert 'delegated' in tx.events + turn_round(consensuses, round_count=2) + + +@pytest.mark.parametrize("validator_state", ['minor', 'major']) +def test_stake_to_slash_address(btc_stake, set_candidate, slash_indicator, candidate_hub, validator_state): + operators, consensuses = set_candidate + turn_round() + turn_round(consensuses) + tx0 = None + if validator_state == 'minor': + slash_threshold = slash_indicator.misdemeanorThreshold() + event_name = 'validatorMisdemeanor' + else: + slash_threshold = slash_indicator.felonyThreshold() + event_name = 'validatorFelony' + for count in range(slash_threshold): + tx0 = slash_indicator.slash(consensuses[0]) + assert event_name in tx0.events + tx = delegate_btc_success(operators[0], accounts[1], BTC_VALUE, LOCK_SCRIPT, events=True) + assert 'delegated' in tx.events + + def test_undelegate_success(btc_stake, set_candidate): operators, consensuses = set_candidate block_height = 200 @@ -851,6 +900,20 @@ def test_non_zero_reward_with_zero_total_btc_stake(btc_stake, candidate_hub): __check_accrued_reward_per_btc(v, round_tag, reward) +def test_distribute_reward_with_zero_reward(btc_stake, candidate_hub): + validators = [] + rewards = [1000, 2000, 3000] + update_system_contract_address(btc_stake, btc_agent=accounts[0]) + btc_stake.distributeReward(validators, rewards) + + +def test_only_btc_agent(btc_stake, candidate_hub): + validators = accounts[:3] + rewards = [1000, 2000, 3000] + with brownie.reverts("the msg sender must be bitcoin agent contract"): + btc_stake.distributeReward(validators, rewards) + + def test_get_btc_stake_amounts_success(btc_stake, set_candidate): operators, consensuses = set_candidate stake_amounts = [] @@ -861,6 +924,21 @@ def test_get_btc_stake_amounts_success(btc_stake, set_candidate): assert amounts == stake_amounts +def test_query_validator_empty(btc_stake, set_candidate): + amounts = btc_stake.getStakeAmounts([]) + assert len(amounts) == 0 + + +def test_query_validator_zero_address(btc_stake, set_candidate): + operators, consensuses = set_candidate + stake_amounts = [] + for index, o in enumerate(operators): + delegate_btc_success(o, accounts[0], BTC_VALUE + index, LOCK_SCRIPT) + stake_amounts.append(BTC_VALUE + index) + amounts = btc_stake.getStakeAmounts([ZERO_ADDRESS]) + assert amounts == [0] + + def test_btc_claim_reward_success(btc_stake, set_candidate): operators, consensuses = set_candidate for index, o in enumerate(operators): @@ -1212,6 +1290,7 @@ def test_collectReward_success(btc_stake, set_candidate, btc_agent, settleRound) assert expired == actual_expired assert acc_amount == actual_acc_amount + @pytest.mark.parametrize("tests", [ [20099, 20101, 25000, 20101], [20101, 20103, 10000, 0], @@ -1324,6 +1403,15 @@ def test_viewCollectReward_only_BtcAgent(btc_stake, set_candidate, btc_agent): reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round, last_round) +def test_view_collect_reward_invalid_txid(btc_stake, set_candidate, btc_agent): + turn_round() + tx_id = random_btc_tx_id() + turn_round() + last_round = get_current_round() - 1 + update_system_contract_address(btc_stake, btc_agent=accounts[0]) + reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round, last_round) + + def test_only_btc_agent_can_call_set_new_round(btc_stake, btc_agent): round_tag = get_current_round() with brownie.reverts("the msg sender must be bitcoin agent contract"): @@ -1344,6 +1432,16 @@ def test_set_new_round_success(btc_stake): assert btc_stake.roundTag() == round_tag + 1 +def test_set_validators_empty(btc_stake): + round_tag = 7 + assert btc_stake.roundTag() == round_tag + turn_round() + round_tag += 1 + update_system_contract_address(btc_stake, btc_agent=accounts[0]) + btc_stake.setNewRound([], round_tag + 1) + assert btc_stake.roundTag() == round_tag + 1 + + def test_only_stake_hub_can_call_prepare(btc_stake): with brownie.reverts("the msg sender must be stake hub contract"): btc_stake.prepare(get_current_round()) @@ -1565,6 +1663,72 @@ def test_transfer_expired_collateral_quantity_decreases(btc_stake, set_candidate assert amounts == [1, BTC_VALUE + 1] +@pytest.mark.parametrize("validator_state", ['minor', 'major']) +def test_transfer_to_slash(btc_stake, set_candidate, slash_indicator, validator_state): + operators, consensuses = set_candidate + turn_round() + tx0 = None + if validator_state == 'minor': + slash_threshold = slash_indicator.misdemeanorThreshold() + event_name = 'validatorMisdemeanor' + else: + slash_threshold = slash_indicator.felonyThreshold() + event_name = 'validatorFelony' + for count in range(slash_threshold): + tx0 = slash_indicator.slash(consensuses[0]) + assert event_name in tx0.events + turn_round(consensuses) + tx_id = delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) + if validator_state == 'major': + error_msg = encode_args_with_signature("InactiveCandidate(address)", [operators[0].address]) + with brownie.reverts(error_msg): + transfer_btc_success(tx_id, operators[0], accounts[0]) + else: + transfer_btc_success(tx_id, operators[0], accounts[0]) + turn_round() + + +def test_transfer_to_self_non_validator(btc_stake, set_candidate): + operators, consensuses = set_candidate + turn_round(consensuses) + tx_id = delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) + error_msg = encode_args_with_signature("InactiveCandidate(address)", [accounts[0].address]) + with brownie.reverts(error_msg): + transfer_btc_success(tx_id, accounts[0], accounts[0]) + turn_round() + + +def test_multiple_transfers_in_one_round(btc_stake, set_candidate): + operators, consensuses = set_candidate + tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + turn_round(consensuses) + for candidate in operators[1:]: + transfer_btc_success(tx_id, candidate, accounts[0]) + for candidate in operators: + transfer_btc_success(tx_id, candidate, accounts[0]) + turn_round(consensuses) + tracker0 = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker0.delta() == 0 + + +def test_transfer_with_nonexistent_txid(btc_stake, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + turn_round(consensuses) + with brownie.reverts("btc tx not found"): + transfer_btc_success(random_btc_tx_id(), operators[1], accounts[0]) + + +def test_transfer_to_zero_address(btc_stake, set_candidate): + operators, consensuses = set_candidate + tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + turn_round(consensuses) + error_msg = encode_args_with_signature("InactiveCandidate(address)", [ZERO_ADDRESS]) + with brownie.reverts(error_msg): + transfer_btc_success(tx_id, ZERO_ADDRESS, accounts[0]) + + def test_calculate_btc_reward_success(btc_stake, set_candidate): operators, consensuses = set_candidate tx_id0 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) diff --git a/tests/test_candidate_hub.py b/tests/test_candidate_hub.py index d15a8a89e..73744e056 100644 --- a/tests/test_candidate_hub.py +++ b/tests/test_candidate_hub.py @@ -3,7 +3,7 @@ import brownie from web3 import Web3 from eth_account import Account -from brownie import accounts, UnRegisterReentry +from brownie import accounts, UnRegisterReentry, chain from brownie.test import given, strategy from brownie.network.transaction import Status, TransactionReceipt from .utils import random_address, expect_event, padding_left, update_system_contract_address @@ -25,6 +25,16 @@ def set_inactive_status(candidate_hub): return candidate_hub.SET_INACTIVE() +@pytest.fixture() +def set_candidate(): + operators = [] + consensuses = [] + for operator in accounts[5:8]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + return operators, consensuses + + def test_register(candidate_hub, required_margin): consensus_address = random_address() commission = 10 @@ -40,6 +50,218 @@ def test_register_multiple_times(): register_candidate(operator=accounts[idx]) +def test_canDelegate_true(candidate_hub, set_candidate): + operators, consensuses = set_candidate + for operator in operators: + assert candidate_hub.canDelegate(operator) is True + turn_round() + + +def test_canDelegate_false(candidate_hub): + operators = [] + for operator in accounts[5:8]: + operators.append(operator) + for operator in operators: + assert candidate_hub.canDelegate(operator) is False + turn_round() + + +@pytest.mark.parametrize("validator_state", [['minor', True], ['major', False]]) +def test_slash_candelegate(candidate_hub, validator_state, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + tx0 = None + if validator_state[0] == 'minor': + slash_threshold = slash_indicator.misdemeanorThreshold() + event_name = 'validatorMisdemeanor' + else: + slash_threshold = slash_indicator.felonyThreshold() + event_name = 'validatorFelony' + for count in range(slash_threshold): + tx0 = slash_indicator.slash(consensuses[0]) + assert event_name in tx0.events + assert candidate_hub.canDelegate(operators[0]) is validator_state[1] + turn_round() + + +def test_cancel_registration_false(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + assert candidate_hub.canDelegate(operators[0]) is False + turn_round() + + +def test_validator_not_exist_false(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + assert candidate_hub.canDelegate(consensuses[0]) is False + turn_round() + + +def test_is_validator_true(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + assert candidate_hub.isValidator(operators[0]) is True + turn_round() + + +def test_is_validator_false(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + assert candidate_hub.isValidator(consensuses[0]) is False + turn_round() + + +@pytest.mark.parametrize("validator_state", ['minor', 'major']) +def test_slash_is_validator(candidate_hub, validator_state, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + if validator_state == 'minor': + slash_threshold = slash_indicator.misdemeanorThreshold() + else: + slash_threshold = slash_indicator.felonyThreshold() + for count in range(slash_threshold): + slash_indicator.slash(consensuses[0]) + assert candidate_hub.isValidator(operators[0]) is True + turn_round() + + +def test_isValidator_canceled(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + assert candidate_hub.isValidator(operators[0]) is False + turn_round() + + +def test_isValidator_not_exist(candidate_hub): + turn_round() + assert candidate_hub.isValidator(accounts[0]) is False + + +def test_isCandidateByOperate_true(candidate_hub, set_candidate): + operators, consensuses = set_candidate + assert candidate_hub.isCandidateByOperate(operators[0]) + + +def test_isCandidateByOperate_fasle(candidate_hub, set_candidate): + operators, consensuses = set_candidate + assert candidate_hub.isCandidateByOperate(consensuses[0]) is False + + +def test_isCandidateByOperate_zeroAddress(candidate_hub, set_candidate): + zero_address = "0x0000000000000000000000000000000000000000" + assert candidate_hub.isCandidateByOperate(zero_address) is False + + +def test_only_validator_can_call(candidate_hub, set_candidate): + operators, consensuses = set_candidate + with brownie.reverts("the msg sender must be validatorSet contract"): + candidate_hub.jailValidator(consensuses[0], 2, 1e5) + + +def test_jail_nonexistent_address(candidate_hub, set_candidate): + operators, consensuses = set_candidate + update_system_contract_address(candidate_hub, validator_set=accounts[0]) + tx = candidate_hub.jailValidator(consensuses[0], 2, 1e5) + assert len(tx.events) == 0 + + +def test_jail_insufficient_deposit(candidate_hub, set_candidate): + operators, consensuses = set_candidate + update_system_contract_address(candidate_hub, validator_set=accounts[0]) + new_dues = 1e10 + candidate_hub.setDues(new_dues) + tx = candidate_hub.jailValidator(operators[0], 2, 1e5) + assert 'unregistered' in tx.events + + +def test_jail_already_jailed(candidate_hub, set_candidate): + operators, consensuses = set_candidate + update_system_contract_address(candidate_hub, validator_set=accounts[0]) + round = 2 + candidate_hub.jailValidator(operators[0], round, 1e5) + assert candidate_hub.jailMap(operators[0]) == get_current_round() + round + candidate_hub.jailValidator(operators[0], round, 1e5) + assert candidate_hub.jailMap(operators[0]) == get_current_round() + round * 2 + + +def test_jail_first_time(candidate_hub, set_candidate): + operators, consensuses = set_candidate + update_system_contract_address(candidate_hub, validator_set=accounts[0]) + round = 2 + tx = candidate_hub.jailValidator(operators[0], round, 1e5) + assert 'statusChanged' in tx.events + assert candidate_hub.jailMap(operators[0]) == get_current_round() + round + + +def test_getRoundTag_success(candidate_hub): + init_round = 7 + round_tag = candidate_hub.getRoundTag() + assert round_tag == init_round + + +def test_turnRound_after_new_validator(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + for operator in accounts[10:12]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + tx = turn_round(consensuses, round_count=2) + round_tag = 10 + assert tx.events['turnedRound']['round'] == round_tag + + +def test_turnRound_after_slash(candidate_hub, set_candidate, slash_indicator): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.misdemeanorThreshold(), slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[index]) + tx = turn_round(consensuses) + round_tag = 9 + assert tx.events['turnedRound']['round'] == round_tag + + +def test_turnRound_after_validator_cancel(candidate_hub, set_candidate, slash_indicator): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + tx = turn_round(consensuses) + assert tx.events['turnedRound']['round'] == get_current_round() + + +def test_turnRound_no_staked_validators(candidate_hub, set_candidate, slash_indicator): + operators, consensuses = set_candidate + turn_round() + tx = turn_round(consensuses) + assert tx.events['turnedRound']['round'] == get_current_round() + + +def test_turnRound_burn_validator_rewards(candidate_hub, set_candidate, slash_indicator): + operators, consensuses = set_candidate + turn_round() + tx = turn_round(consensuses) + assert 'receiveDeposit' in tx.events + + +def test_turnRound_update_validator_info(candidate_hub, set_candidate, slash_indicator): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + tx = turn_round(consensuses) + assert 'validatorSetUpdated' in tx.events + + @pytest.mark.parametrize("times", [ 1, pytest.param(2, marks=pytest.mark.xfail), @@ -97,6 +319,345 @@ def test_register_margin(candidate_hub, margin): ) +def test_register_zero_consensus_address(candidate_hub, required_margin): + zero_address = "0x0000000000000000000000000000000000000000" + with brownie.reverts("consensus address should not be zero"): + candidate_hub.register( + zero_address, accounts[0], 1, + {'from': accounts[0], 'value': required_margin} + ) + + +def test_register_zero_fee_address(candidate_hub, required_margin): + zero_address = "0x0000000000000000000000000000000000000000" + with brownie.reverts("fee address should not be zero"): + candidate_hub.register( + random_address(), zero_address, 1, + {'from': accounts[0], 'value': required_margin} + ) + + +def test_register_exceeds_validator_limit(candidate_hub, required_margin): + zero_address = "0x0000000000000000000000000000000000000000" + with brownie.reverts("fee address should not be zero"): + candidate_hub.register( + random_address(), zero_address, 1, + {'from': accounts[0], 'value': required_margin} + ) + + +def test_register_requires_init_first(candidate_hub, required_margin): + candidate_hub.setAlreadyInit(False) + with brownie.reverts("the contract not init yet"): + candidate_hub.register( + random_address(), accounts[0], 1, + {'from': accounts[0], 'value': required_margin} + ) + + +def test_register_zero_payment_amount(candidate_hub, required_margin): + with brownie.reverts("deposit is not enough"): + candidate_hub.register( + random_address(), accounts[0], 1, + {'from': accounts[0], 'value': 0} + ) + + +def test_reregister_after_cancel(candidate_hub, required_margin): + candidate_hub.register( + random_address(), accounts[0], 1, + {'from': accounts[0], 'value': required_margin} + ) + candidate_hub.refuseDelegate({'from': accounts[0]}) + turn_round() + candidate_hub.unregister({'from': accounts[0]}) + tx = candidate_hub.register( + random_address(), accounts[0], 1, + {'from': accounts[0], 'value': required_margin} + ) + assert 'registered' in tx.events + + +def test_only_gov_can_call(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(candidate_hub.dues() + 10), 64) + with brownie.reverts("the msg sender must be governance contract"): + candidate_hub.updateParam("requiredMargin", value) + + +def test_param_length_error(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(candidate_hub.dues() + 10), 65) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts("MismatchParamLength: requiredMargin"): + candidate_hub.updateParam("requiredMargin", value) + + +@pytest.mark.parametrize("newRequiredMargin", [1, 1000, 102220]) +def test_update_required_margin_success(candidate_hub, required_margin, newRequiredMargin): + dues = candidate_hub.dues() + value = padding_left(Web3.to_hex(dues + newRequiredMargin), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("requiredMargin", value) + assert candidate_hub.requiredMargin() == dues + newRequiredMargin + + +def test_update_required_margin_zero_success(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(0), 64) + uint256_max = 2 ** 256 - 1 + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts(f"OutOfBounds: requiredMargin, 0, 10001, {uint256_max}"): + candidate_hub.updateParam("requiredMargin", value) + + +@pytest.mark.parametrize("newRequiredMargin", [1, 10, 5000]) +def test_required_margin_cannot_less_than_dues(candidate_hub, required_margin, newRequiredMargin): + dues = candidate_hub.dues() + value = padding_left(Web3.to_hex(dues - newRequiredMargin), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + uint256_max = 2 ** 256 - 1 + with brownie.reverts(f"OutOfBounds: requiredMargin, {dues - newRequiredMargin}, 10001, {uint256_max}"): + candidate_hub.updateParam("requiredMargin", value) + + +@pytest.mark.parametrize("new_dues", [1, 1000, 8000]) +def test_update_dues_success(candidate_hub, required_margin, new_dues): + value = padding_left(Web3.to_hex(required_margin - new_dues), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("dues", value) + assert candidate_hub.dues() == required_margin - new_dues + + +def test_dues_zero(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(0), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts(f"OutOfBounds: dues, 0, 1, {required_margin - 1}"): + candidate_hub.updateParam("dues", value) + + +def test_dues_cannot_greater_than_required_margin(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(required_margin + 1), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts(f"OutOfBounds: dues, {required_margin + 1}, 1, {required_margin - 1}"): + candidate_hub.updateParam("dues", value) + + +@pytest.mark.parametrize("validator_count", [6, 25, 41]) +def test_govern_validator_count_success(candidate_hub, required_margin, validator_count): + value = padding_left(Web3.to_hex(validator_count), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("validatorCount", value) + assert candidate_hub.validatorCount() == validator_count + + +@pytest.mark.parametrize("validator_count", [0, 4, 5, 42, 43, 100]) +def test_validator_count_out_of_range(candidate_hub, required_margin, validator_count): + value = padding_left(Web3.to_hex(validator_count), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts(f"OutOfBounds: validatorCount, {validator_count}, 6, 41"): + candidate_hub.updateParam("validatorCount", value) + + +@pytest.mark.parametrize("maxCommissionChange", [1, 500, 1000]) +def test_govern_max_commission_change_success(candidate_hub, required_margin, maxCommissionChange): + value = padding_left(Web3.to_hex(maxCommissionChange), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("maxCommissionChange", value) + assert candidate_hub.maxCommissionChange() == maxCommissionChange + + +def test_max_commission_change_zero(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(0), 64) + uint256_max = 2 ** 256 - 1 + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts(f"OutOfBounds: maxCommissionChange, 0, 1, {uint256_max}"): + candidate_hub.updateParam("maxCommissionChange", value) + + +def test_governance_param_error(candidate_hub, required_margin): + value = padding_left(Web3.to_hex(required_margin), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts(f"UnsupportedGovParam: error_key"): + candidate_hub.updateParam("error_key", value) + + +def test_refuse_delegate_success(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + assert candidate_hub.canDelegate(operators[0]) is False + turn_round() + + +def test_refuse_delegate_nonexistent_validator(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + with brownie.reverts(f"candidate does not exist"): + candidate_hub.refuseDelegate({'from': consensuses[0]}) + + +def test_refuse_delegate_zero_address(candidate_hub, set_candidate): + zero_address = "0x0000000000000000000000000000000000000000" + turn_round() + with brownie.reverts(f"candidate does not exist"): + candidate_hub.refuseDelegate({'from': zero_address}) + + +def test_refuse_delegate_canceled_validator(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + with brownie.reverts(f"candidate does not exist"): + candidate_hub.refuseDelegate({'from': operators[0]}) + + +def test_refuse_delegate_slashed_validator(candidate_hub, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.misdemeanorThreshold(), slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[index]) + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + candidate_hub.refuseDelegate({'from': operators[1]}) + turn_round(consensuses) + + +def test_refuse_delegate_repeated(candidate_hub, slash_indicator, set_candidate): + validator_state = 17 + operators, consensuses = set_candidate + turn_round() + tx = candidate_hub.refuseDelegate({'from': operators[0]}) + assert tx.events['statusChanged']['oldStatus'] == validator_state + assert tx.events['statusChanged']['newStatus'] == validator_state + 2 + tx = candidate_hub.refuseDelegate({'from': operators[0]}) + assert 'statusChanged' not in tx.events + turn_round(consensuses) + + +def test_accept_delegate_success(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + assert candidate_hub.canDelegate(operators[0]) is False + turn_round() + candidate_hub.acceptDelegate({'from': operators[0]}) + assert candidate_hub.canDelegate(operators[0]) + + +def test_accept_delegate_nonexistent_validator(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + with brownie.reverts(f"candidate does not exist"): + candidate_hub.acceptDelegate({'from': consensuses[0]}) + + +def test_accept_delegate_zero_address(candidate_hub, set_candidate): + zero_address = "0x0000000000000000000000000000000000000000" + turn_round() + with brownie.reverts(f"candidate does not exist"): + candidate_hub.acceptDelegate({'from': zero_address}) + + +def test_accept_delegate_canceled_validator(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + with brownie.reverts(f"candidate does not exist"): + candidate_hub.acceptDelegate({'from': operators[0]}) + + +def test_accept_delegate_slashed_validator(candidate_hub, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.misdemeanorThreshold(), slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[index]) + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + candidate_hub.acceptDelegate({'from': operators[0]}) + candidate_hub.refuseDelegate({'from': operators[1]}) + candidate_hub.acceptDelegate({'from': operators[1]}) + turn_round(consensuses) + assert candidate_hub.canDelegate(operators[0]) + assert candidate_hub.canDelegate(operators[1]) is False + + +def test_accept_delegate_repeated(candidate_hub, slash_indicator, set_candidate): + validator_state = 19 + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + tx = candidate_hub.acceptDelegate({'from': operators[0]}) + assert tx.events['statusChanged']['oldStatus'] == validator_state + assert tx.events['statusChanged']['newStatus'] == validator_state - 2 + tx = candidate_hub.acceptDelegate({'from': operators[0]}) + assert 'statusChanged' not in tx.events + turn_round(consensuses) + + +def test_add_margin_insufficient_funds(candidate_hub, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[0]) + turn_round(consensuses) + assert candidate_hub.canDelegate(operators[0]) is False + candidate_hub.addMargin({'from': operators[0], 'value': 100}) + assert candidate_hub.canDelegate(operators[0]) is False + + +def test_get_candidates_success(candidate_hub, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[0]) + turn_round(consensuses) + candidate_hub.refuseDelegate({'from': operators[1]}) + turn_round(consensuses) + candidate_hub.unregister({'from': operators[1]}) + assert operators[0] in candidate_hub.getCandidates() + assert operators[2] in candidate_hub.getCandidates() + + +def test_is_candidate_by_consensus_success(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + assert candidate_hub.isCandidateByConsensus(consensuses[0]) + + +def test_query_consensus_by_non_validator(candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + assert candidate_hub.isCandidateByConsensus(operators[0]) is False + + +def test_is_jailed_success(candidate_hub, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[0]) + turn_round(consensuses) + assert candidate_hub.isJailed(operators[0]) + + +def test_query_jail_round_by_non_validator(candidate_hub, slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + for index, slash in enumerate([slash_indicator.felonyThreshold()]): + for i in range(slash): + slash_indicator.slash(consensuses[0]) + turn_round(consensuses) + assert candidate_hub.isJailed(operators[1]) is False + + def test_is_candidate_by_operate(candidate_hub, required_margin): operator = accounts[0] candidate_hub.register( diff --git a/tests/test_claim_reward.py b/tests/test_claim_reward.py index 3816af22c..888599080 100644 --- a/tests/test_claim_reward.py +++ b/tests/test_claim_reward.py @@ -965,6 +965,128 @@ def test_stake_reward_claim_after_upgrade(stake_hub, system_reward, set_candidat turn_round(consensuses) +@pytest.mark.parametrize("round", [0, 1, 2]) +@pytest.mark.parametrize("is_new_validator", [True, False]) +@pytest.mark.parametrize("is_slash", [True, False]) +@pytest.mark.parametrize("is_unregister", [True, False]) +@pytest.mark.parametrize("is_btc_lst", [True, False]) +def test_enable_lst_midway(slash_indicator, set_candidate, candidate_hub, round, is_new_validator, is_slash, + is_unregister, is_btc_lst, btc_lst_stake): + delegate_value = MIN_INIT_DELEGATE_VALUE * 10 + btc_lst_value = 1000 + btc_value = 10000 + operators, consensuses = set_candidate + turn_round() + delegate_coin_success(operators[0], accounts[0], delegate_value) + delegate_coin_success(operators[0], accounts[1], delegate_value) + delegate_coin_success(operators[1], accounts[0], delegate_value) + delegate_coin_success(operators[1], accounts[1], delegate_value) + delegate_btc_success(operators[0], accounts[3], btc_value, LOCK_SCRIPT, relay=accounts[3]) + delegate_btc_success(operators[1], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) + turn_round(consensuses, round_count=2) + if is_new_validator: + for operator in accounts[10:12]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) + delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[0]) + delegate_btc_lst_success(accounts[1], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[1]) + delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) + delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) + turn_round(consensuses, round_count=round) + if is_btc_lst: + update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) + btc_lst_stake.updateParam('paused', 1) + with brownie.reverts("Pausable: paused"): + delegate_btc_lst_success(accounts[5], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[1]) + if is_slash: + slash_threshold = slash_indicator.felonyThreshold() + for count in range(slash_threshold): + slash_indicator.slash(consensuses[1]) + turn_round(consensuses, round_count=round) + if is_unregister: + candidate_hub.refuseDelegate({'from': operators[2]}) + turn_round(consensuses) + candidate_hub.unregister({'from': operators[2]}) + delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) + turn_round(consensuses, round_count=round) + stake_hub_claim_reward(accounts[0:6]) + + +@pytest.mark.parametrize("round", [0, 1, 2]) +@pytest.mark.parametrize("is_new_validator", [True, False]) +@pytest.mark.parametrize("is_slash", [True, False]) +def test_disable_lst_after_enabling(slash_indicator, set_candidate, candidate_hub, round, is_new_validator, is_slash): + delegate_value = MIN_INIT_DELEGATE_VALUE * 10 + btc_value = 10000 + operators, consensuses = set_candidate + turn_round() + delegate_coin_success(operators[0], accounts[0], delegate_value) + delegate_coin_success(operators[0], accounts[1], delegate_value) + delegate_coin_success(operators[1], accounts[0], delegate_value) + delegate_coin_success(operators[1], accounts[1], delegate_value) + delegate_btc_success(operators[0], accounts[3], btc_value, LOCK_SCRIPT, relay=accounts[3]) + delegate_btc_success(operators[1], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) + turn_round(consensuses, round_count=2) + if is_new_validator: + for operator in accounts[10:12]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) + delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) + turn_round(consensuses, round_count=round) + if is_slash: + slash_threshold = slash_indicator.felonyThreshold() + for count in range(slash_threshold): + slash_indicator.slash(consensuses[1]) + turn_round(consensuses, round_count=round) + delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) + turn_round(consensuses, round_count=round) + stake_hub_claim_reward(accounts[0:6]) + + +@pytest.mark.parametrize("round", [0, 1, 2]) +@pytest.mark.parametrize("is_new_validator", [True, False]) +@pytest.mark.parametrize("is_unregister", [True, False]) +def test_btclst_transfer_success(slash_indicator, set_candidate, candidate_hub, round, is_new_validator, btc_lst_stake, + is_unregister): + delegate_value = MIN_INIT_DELEGATE_VALUE * 10 + btc_lst_value = 1000 + btc_value = 10000 + operators, consensuses = set_candidate + turn_round() + delegate_coin_success(operators[0], accounts[0], delegate_value) + delegate_coin_success(operators[0], accounts[1], delegate_value) + delegate_coin_success(operators[1], accounts[0], delegate_value) + delegate_coin_success(operators[1], accounts[1], delegate_value) + delegate_coin_success(operators[2], accounts[1], delegate_value) + delegate_btc_success(operators[0], accounts[3], btc_value, LOCK_SCRIPT, relay=accounts[3]) + delegate_btc_success(operators[1], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) + delegate_btc_success(operators[2], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) + turn_round(consensuses, round_count=2) + stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) + delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[0]) + delegate_btc_lst_success(accounts[1], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[1]) + delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) + delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) + turn_round(consensuses, round_count=round) + if is_new_validator: + for operator in accounts[10:12]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + transfer_btc_lst_success(accounts[0], btc_lst_value // 2, accounts[3]) + delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) + if is_unregister: + candidate_hub.refuseDelegate({'from': operators[2]}) + turn_round(consensuses) + candidate_hub.unregister({'from': operators[2]}) + turn_round(consensuses, round_count=round) + + redeem_btc_lst_success(accounts[3], btc_lst_value // 4, BTCLST_REDEEM_SCRIPT) + stake_hub_claim_reward(accounts[0:6]) + turn_round(consensuses, round_count=round) + + def init_hybrid_score_mock(): STAKE_HUB.initHybridScoreMock() set_round_tag(get_current_round()) diff --git a/tests/test_gov_hub.py b/tests/test_gov_hub.py index b254a5955..bc8549aed 100644 --- a/tests/test_gov_hub.py +++ b/tests/test_gov_hub.py @@ -798,6 +798,76 @@ def test_get_executed_proposal_state(gov_hub): assert gov_hub.getState(1) == 4 +def test_only_gov_can_execute(gov_hub): + value = padding_left(Web3.to_hex(100), 64) + with brownie.reverts(f"the msg sender must be governance contract"): + gov_hub.updateParam("proposalMaxOperations", value) + + +@pytest.mark.parametrize("new_proposalMaxOperations", [1, 2, 5000, 6000, 1000000, 10000000]) +def test_update_proposal_max_operations_success(gov_hub, new_proposalMaxOperations): + value = padding_left(Web3.to_hex(new_proposalMaxOperations), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + gov_hub.updateParam("proposalMaxOperations", value) + assert gov_hub.proposalMaxOperations() == value + + +def test_proposal_max_operations_zero(gov_hub): + value = padding_left(Web3.to_hex(0), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + uint256_max = 2 ** 256 - 1 + with brownie.reverts(f"OutOfBounds: proposalMaxOperations, 0, 1, {uint256_max}"): + gov_hub.updateParam("proposalMaxOperations", value) + + +@pytest.mark.parametrize("votingPeriod", [28800, 28801, 28802, 100000, 800000000]) +def test_update_voting_period_success(gov_hub, votingPeriod): + value = padding_left(Web3.to_hex(votingPeriod), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + gov_hub.updateParam("votingPeriod", value) + assert gov_hub.votingPeriod() == value + + +@pytest.mark.parametrize("votingPeriod", [0, 1, 1000, 28800 - 2, 28800 - 1]) +def test_voting_period_out_of_range(gov_hub, votingPeriod): + value = padding_left(Web3.to_hex(votingPeriod), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + uint256_max = 2 ** 256 - 1 + with brownie.reverts(f"OutOfBounds: votingPeriod, {votingPeriod}, 28800, {uint256_max}"): + gov_hub.updateParam("votingPeriod", value) + + +@pytest.mark.parametrize("executingPeriod", [28800, 28801, 28802, 100000, 800000000]) +def test_update_executing_period_success(gov_hub, executingPeriod): + value = padding_left(Web3.to_hex(executingPeriod), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + gov_hub.updateParam("executingPeriod", value) + assert gov_hub.executingPeriod() == value + + +@pytest.mark.parametrize("executingPeriod", [0, 1, 1000, 28800 - 2, 28800 - 1]) +def test_executing_period_out_of_range(gov_hub, executingPeriod): + value = padding_left(Web3.to_hex(executingPeriod), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + uint256_max = 2 ** 256 - 1 + with brownie.reverts(f"OutOfBounds: executingPeriod, {executingPeriod}, 28800, {uint256_max}"): + gov_hub.updateParam("executingPeriod", value) + + +def test_invalid_key(gov_hub): + value = padding_left(Web3.to_hex(100000), 64) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + with brownie.reverts(f"UnsupportedGovParam: key_error"): + gov_hub.updateParam("key_error", value) + + +def test_value_length_error(gov_hub): + value = padding_left(Web3.to_hex(100000), 66) + update_system_contract_address(gov_hub, gov_hub=accounts[0]) + with brownie.reverts(f"MismatchParamLength: executingPeriod"): + gov_hub.updateParam("executingPeriod", value) + + def __add_member(c, member_address): execute_proposal( c.address, diff --git a/tests/test_relayer_hub.py b/tests/test_relayer_hub.py index d81efd1f7..70f97bd0c 100644 --- a/tests/test_relayer_hub.py +++ b/tests/test_relayer_hub.py @@ -88,3 +88,25 @@ def test_register_failed(relay_hub): with brownie.reverts("deposit value does not match requirement"): relay_hub.register({'from': accounts[1], 'value': 1e10}) + + +def test_reregister_after_unregister(relay_hub): + required_deposit = relay_hub.requiredDeposit() + relay_hub.register({'value': required_deposit}) + relay_hub.unregister({'from': accounts[0]}) + tx = relay_hub.register({'value': required_deposit}) + assert 'relayerRegister' in tx.events + + +def test_is_relayer_true(relay_hub): + required_deposit = relay_hub.requiredDeposit() + relay_hub.register({'value': required_deposit}) + assert relay_hub.isRelayer(accounts[0]) + + +def test_is_relayer_false(relay_hub): + required_deposit = relay_hub.requiredDeposit() + relay_hub.register({'value': required_deposit}) + relay_hub.unregister({'from': accounts[0]}) + assert relay_hub.isRelayer(accounts[0]) is False + assert relay_hub.isRelayer(accounts[1]) is False diff --git a/tests/test_slash.py b/tests/test_slash.py index d9d700456..780bf2201 100644 --- a/tests/test_slash.py +++ b/tests/test_slash.py @@ -1,10 +1,13 @@ +import brownie import pytest import random -from brownie import accounts +from brownie import accounts, chain from brownie.test import given, strategy from eth_account import Account from eth_account.signers.local import LocalAccount -from .utils import expect_event +from web3 import Web3 + +from .utils import expect_event, padding_left, update_system_contract_address from .common import register_candidate, turn_round misdemeanorThreshold = 0 @@ -19,6 +22,16 @@ def set_threshold(slash_indicator): felonyThreshold = slash_indicator.felonyThreshold() +@pytest.fixture() +def set_candidate(): + operators = [] + consensuses = [] + for operator in accounts[5:8]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + return operators, consensuses + + def test_slash_validator(slash_indicator): account: LocalAccount = Account.create(str(random.random())) tx = slash_indicator.slash(account.address) @@ -124,3 +137,120 @@ def test_clean(slash_indicator, validator_set): turn_round() for account, count in zip(slash_accounts, counts): assert slash_indicator.getSlashIndicator(account.address)[1] == max([count - decrease_value, 0]) + + +def test_only_gov_can_call(slash_indicator): + value = padding_left(Web3.to_hex(1000), 64) + with brownie.reverts(f"the msg sender must be governance contract"): + slash_indicator.updateParam('misdemeanorThreshold', value) + + +@pytest.mark.parametrize("new_misdemeanorThreshold", [1, 2, 3]) +def test_update_misdemeanor_threshold_success(slash_indicator, new_misdemeanorThreshold): + value = padding_left(Web3.to_hex(new_misdemeanorThreshold), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + slash_indicator.updateParam('misdemeanorThreshold', value) + assert slash_indicator.misdemeanorThreshold() == value + + +@pytest.mark.parametrize("new_misdemeanorThreshold", [0, 4, 5, 100]) +def test_update_misdemeanor_threshold_failure(slash_indicator, new_misdemeanorThreshold): + value = padding_left(Web3.to_hex(new_misdemeanorThreshold), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + with brownie.reverts(f"OutOfBounds: misdemeanorThreshold, {new_misdemeanorThreshold}, 1, 3"): + slash_indicator.updateParam('misdemeanorThreshold', value) + + +@pytest.mark.parametrize("new_felonyThreshold", [3, 4, 5, 6, 100]) +def test_update_felony_threshold_success(slash_indicator, new_felonyThreshold): + value = padding_left(Web3.to_hex(new_felonyThreshold), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + slash_indicator.updateParam('felonyThreshold', value) + assert slash_indicator.felonyThreshold() == value + + +@pytest.mark.parametrize("new_felonyThreshold", [0, 1, 2]) +def test_update_felony_threshold_failure(slash_indicator, new_felonyThreshold): + value = padding_left(Web3.to_hex(new_felonyThreshold), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + uint256_max = 2 ** 256 - 1 + with brownie.reverts(f"OutOfBounds: felonyThreshold, {new_felonyThreshold}, 3, {uint256_max}"): + slash_indicator.updateParam('felonyThreshold', value) + + +@pytest.mark.parametrize("new_rewardForReportDoubleSign", [1, 2, 3, 1e19, 1e20, 1e21]) +def test_update_reward_for_report_double_sign_success(slash_indicator, new_rewardForReportDoubleSign): + value = padding_left(Web3.to_hex(int(new_rewardForReportDoubleSign)), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + slash_indicator.updateParam('rewardForReportDoubleSign', value) + assert slash_indicator.rewardForReportDoubleSign() == value + + +@pytest.mark.parametrize("new_rewardForReportDoubleSign", [0, 1e22, 1e23]) +def test_update_reward_for_report_double_sign_failure(slash_indicator, new_rewardForReportDoubleSign): + value = padding_left(Web3.to_hex(int(new_rewardForReportDoubleSign)), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + max_value = 1000000000000000000000 + with brownie.reverts( + f"OutOfBounds: rewardForReportDoubleSign, {int(new_rewardForReportDoubleSign)}, 1, {max_value}"): + slash_indicator.updateParam('rewardForReportDoubleSign', value) + + +@pytest.mark.parametrize("new_felonyDeposit", [1e18, 1e19, 1e20]) +def test_update_felony_deposit_success(slash_indicator, new_felonyDeposit): + value = padding_left(Web3.to_hex(int(new_felonyDeposit)), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + slash_indicator.updateParam('felonyDeposit', value) + assert slash_indicator.felonyDeposit() == value + + +@pytest.mark.parametrize("new_felonyDeposit", [0, 1e16, 1e17]) +def test_update_felony_deposit_failure(slash_indicator, new_felonyDeposit): + value = padding_left(Web3.to_hex(int(new_felonyDeposit)), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + min_value = 1000000000000000000 + uint256_max = 2 ** 256 - 1 + with brownie.reverts( + f"OutOfBounds: felonyDeposit, {int(new_felonyDeposit)}, {min_value}, {uint256_max}"): + slash_indicator.updateParam('felonyDeposit', value) + + +@pytest.mark.parametrize("new_felonyRound", [1, 2, 3, 1000, 5000]) +def test_update_felony_round_success(slash_indicator, new_felonyRound): + value = padding_left(Web3.to_hex(new_felonyRound), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + slash_indicator.updateParam('felonyRound', value) + assert slash_indicator.felonyRound() == value + + +@pytest.mark.parametrize("new_felonyRound", [0]) +def test_update_felony_round_failure(slash_indicator, new_felonyRound): + value = padding_left(Web3.to_hex(new_felonyRound), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + uint256_max = 2 ** 256 - 1 + with brownie.reverts( + f"OutOfBounds: felonyRound, 0, {1}, {uint256_max}"): + slash_indicator.updateParam('felonyRound', value) + + +def test_invalid_key(slash_indicator): + value = padding_left(Web3.to_hex(1000), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + with brownie.reverts("UnsupportedGovParam: key_error"): + slash_indicator.updateParam('key_error', value) + + +def test_invalid_value_length(slash_indicator): + value = padding_left(Web3.to_hex(1000), 66) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + with brownie.reverts("MismatchParamLength: felonyRound"): + slash_indicator.updateParam('felonyRound', value) + + +def test_get_slash_indicator_success(slash_indicator, set_candidate): + operators, consensuses = set_candidate + turn_round() + slash_indicator.slash(consensuses[0]) + value = slash_indicator.getSlashIndicator(consensuses[0]) + assert value == (chain.height, 1) + turn_round(consensuses) diff --git a/tests/test_system_reward.py b/tests/test_system_reward.py index 0ee16fec6..204beecaa 100644 --- a/tests/test_system_reward.py +++ b/tests/test_system_reward.py @@ -101,6 +101,24 @@ def test_update_param_is_burn(system_reward, value, success): system_reward.updateParam("isBurn", value) +def test_update_param_add_operator(system_reward): + operator = random_address() + system_reward.updateParam("addOperator", operator) + assert system_reward.operators(operator) + + +def test_add_operator_invalid_address_length(system_reward): + operator = random_address() + with brownie.reverts(f"MismatchParamLength: addOperator"): + system_reward.updateParam("addOperator", operator + 'A') + + +def test_add_operator_zero_address(system_reward): + operator = ZERO_ADDRESS + system_reward.updateParam("addOperator", operator) + assert system_reward.operators(operator) + + def test_receive_rewards_with_value_0(system_reward): tx = system_reward.receiveRewards({'value': 0}) assert len(tx.events.keys()) == 0 From f53543bd525a57ec4c5f4a305c8525fc56b8e0ff Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Tue, 17 Jun 2025 14:40:09 +0800 Subject: [PATCH 04/27] add test cases for bep126 --- contracts/mock/CandidateHubMock.sol | 20 +- contracts/mock/CandidateHubUnitMock.sol | 85 -- contracts/mock/SlashIndicatorMock.sol | 34 + contracts/mock/ValidatorSetMock.sol | 82 ++ .../mock/interface/IValidatorSetMock.sol | 2 +- tests/common.py | 16 +- tests/conftest.py | 12 + tests/test_btc_stake.py | 3 +- tests/test_candidate_hub.py | 278 ++++++- tests/test_core_agent.py | 2 +- tests/test_delegate_btc.py | 17 +- tests/test_slash.py | 264 +++++- tests/test_slash_indicator.py | 5 +- tests/test_validator_set.py | 773 ++++++++++++++++-- tests/utils.py | 15 + 15 files changed, 1411 insertions(+), 197 deletions(-) delete mode 100644 contracts/mock/CandidateHubUnitMock.sol diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index f6f6ce387..8b06a00c7 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -44,6 +44,12 @@ contract CandidateHubMock is CandidateHub { dues = value; } + function mockRegister(uint256 value) external { + for (uint256 i = 0; i < value; i++) { + candidateSet.push(); + } + } + function setValidatorCount(uint256 value) external { validatorCount = value; @@ -110,6 +116,10 @@ contract CandidateHubMock is CandidateHub { return candidateSet[operateMap[k] - 1]; } + function getConsensusMap(address consensusAddr) public view returns (uint256) { + return consensusMap[consensusAddr]; + } + function getScoreMock(address[] memory candidates, uint256 round) external returns (uint256[] memory scores) { scores = IStakeHub(STAKE_HUB_ADDR).getHybridScore( candidates, @@ -138,7 +148,8 @@ contract CandidateHubMock is CandidateHub { address operateAddr, address consensusAddr, address payable feeAddr, - uint32 commissionThousandths + uint32 commissionThousandths, + bytes calldata voteAddr ) external payable onlyInit { uint256 status = SET_CANDIDATE; candidateSet.push( @@ -150,14 +161,15 @@ contract CandidateHubMock is CandidateHub { msg.value, status, roundTag, - commissionThousandths + commissionThousandths, + voteAddr ) ); uint256 index = candidateSet.length; operateMap[operateAddr] = index; consensusMap[consensusAddr] = index; - emit registered(operateAddr, consensusAddr, feeAddr, commissionThousandths, msg.value); + emit registered(operateAddr, consensusAddr, feeAddr, commissionThousandths, msg.value, voteAddr); } /********************* External methods ****************************/ @@ -250,7 +262,7 @@ contract CandidateHubMock is CandidateHub { statusList[index - 1] |= SET_VALIDATOR; } - IValidatorSet(VALIDATOR_CONTRACT_ADDR).updateValidatorSet(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList); + IValidatorSetMock(VALIDATOR_CONTRACT_ADDR).updateValidatorSetOld(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList); // clean slash contract ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); diff --git a/contracts/mock/CandidateHubUnitMock.sol b/contracts/mock/CandidateHubUnitMock.sol deleted file mode 100644 index 528827ee1..000000000 --- a/contracts/mock/CandidateHubUnitMock.sol +++ /dev/null @@ -1,85 +0,0 @@ -pragma solidity 0.8.4; - -import "../CandidateHub.sol"; - -contract CandidateHubUnitMock is CandidateHub { - uint256[] public scores; - uint256 public totalPower; - uint256 public totalCoin; - - function developmentInit() external { - requiredMargin = 1e19; - roundInterval = 1; - roundTag = 7; - maxCommissionChange = 100; - } - - function setRoundTimeTag(uint256 value) external { - roundTag = value; - } - - function setJailMap(address k, uint256 v) public { - jailMap[k] = v; - } - - function setCandidateMargin(address k, uint256 v) public { - candidateSet[operateMap[k] - 1].margin = v; - } - - function setCandidateStatus(address k, uint256 v) public { - candidateSet[operateMap[k] - 1].status = v; - } - - function getCandidate(address k) public view returns (Candidate memory) { - return candidateSet[operateMap[k] - 1]; - } - - function getScoreMock(address[] memory candidates, uint256 round) external { - scores = IStakeHub(PLEDGE_AGENT_ADDR).getHybridScore( - candidates, - round - ); - } - - function getScores() external view returns (uint256[] memory) { - return scores; - } - - function getValidatorsMock( - address[] memory candidateList, - uint256[] memory scoreList, - uint256 count - ) public pure returns (address[] memory validatorList) { - return getValidators(candidateList, scoreList, count); - } - - function cleanMock() public { - ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); - } - - function registerMock( - address operateAddr, - address consensusAddr, - address payable feeAddr, - uint32 commissionThousandths - ) external payable onlyInit { - uint256 status = SET_CANDIDATE; - candidateSet.push( - Candidate( - operateAddr, - consensusAddr, - feeAddr, - commissionThousandths, - msg.value, - status, - roundTag, - commissionThousandths - ) - ); - uint256 index = candidateSet.length; - operateMap[operateAddr] = index; - consensusMap[consensusAddr] = index; - - emit registered(operateAddr, consensusAddr, feeAddr, commissionThousandths, msg.value); - } -} diff --git a/contracts/mock/SlashIndicatorMock.sol b/contracts/mock/SlashIndicatorMock.sol index c4798f82f..23692cce1 100644 --- a/contracts/mock/SlashIndicatorMock.sol +++ b/contracts/mock/SlashIndicatorMock.sol @@ -49,4 +49,38 @@ contract SlashIndicatorMock is SlashIndicator { return ecrecovery(hash, sig); } + + function mockSubmitFinalityViolationEvidence(FinalityEvidence memory evidence) external onlyInit { + if (rewardForReportFinalityViolation == 0) { + rewardForReportFinalityViolation = INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION; + } + + // Basic check + require(evidence.voteA.srcNum + 86400 > block.number && + evidence.voteB.srcNum + 86400 > block.number, "too old block involved"); + require(!(evidence.voteA.srcHash == evidence.voteB.srcHash && + evidence.voteA.tarHash == evidence.voteB.tarHash), "two identical votes"); + require(evidence.voteA.srcNum < evidence.voteA.tarNum && + evidence.voteB.srcNum < evidence.voteB.tarNum, "srcNum bigger than tarNum"); + + // Vote rules check + require((evidence.voteA.srcNum < evidence.voteB.srcNum && evidence.voteB.tarNum < evidence.voteA.tarNum) || + (evidence.voteB.srcNum < evidence.voteA.srcNum && evidence.voteA.tarNum < evidence.voteB.tarNum) || + evidence.voteA.tarNum == evidence.voteB.tarNum, "no violation of vote rules"); + + // BLS verification + // Default BLS verification passed. + // require(verifyBLSSignature(evidence.voteA, evidence.voteAddr) && + // verifyBLSSignature(evidence.voteB, evidence.voteAddr), "verify signature failed"); + + (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getValidatorsAndVoteAddresses(); + for (uint256 i; i < voteAddrs.length; ++i) { + if (BytesLib.equal(voteAddrs[i], evidence.voteAddr)) { + ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportFinalityViolation); + IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(vals[i], felonyRound, felonyDeposit); + break; + } + } + } + } diff --git a/contracts/mock/ValidatorSetMock.sol b/contracts/mock/ValidatorSetMock.sol index e70d9a95f..d034dc1eb 100644 --- a/contracts/mock/ValidatorSetMock.sol +++ b/contracts/mock/ValidatorSetMock.sol @@ -6,6 +6,7 @@ import "./interface/IPledgeAgentMock.sol"; contract ValidatorSetMock is ValidatorSet { function developmentInit() external { blockReward = blockReward / 1e14; + voteRewardPercent = 10; for (uint i = 0; i < currentValidatorSet.length; i++) { delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; @@ -13,15 +14,91 @@ contract ValidatorSetMock is ValidatorSet { delete currentValidatorSet; bytes memory initValidatorSet = hex"f8d7ea9401bca3615d24d3c638836691517b2b9b49b054b1943ae030dc3717c66f63d6e8f1d1508a5c941ff46dea94a458499604a85e90225a14946f36368ae24df16d94de442f5ba55687a24f04419424e0dc2593cc9f4cea945e00c0d5c4c10d4c805aba878d51129a89d513e094cb089be171e256acdaac1ebbeb32ffba0dd438eeea941cd652bc64af3f09b490daae27f46e53726ce230940a53b7e0ffd97357e444b85f4d683c1d8e22879aea94da37ccecbb2d7c83ae27ee2bebfe8ebce162c60094d82c24274ebbfe438788d684dc6034c3c67664a4"; + bytes memory initVoteAddress = hex"99a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8"; (Validator[] memory validatorSet, bool valid) = decodeValidatorSet(initValidatorSet); require(valid, "failed to parse init validatorSet"); uint256 validatorSize = validatorSet.length; for (uint256 i = 0; i < validatorSize; i++) { + validatorSet[i].voteAddr = initVoteAddress; + validatorSet[i].voteWeight = 0; currentValidatorSet.push(validatorSet[i]); currentValidatorSetMap[validatorSet[i].consensusAddress] = i + 1; } } + struct ValidatorOld { + address operateAddress; + address consensusAddress; + address payable feeAddress; + uint256 commissionThousandths; + uint256 income; + + } + + function checkValidatorSetOld( + address[] memory operateAddrList, + address[] memory consensusAddrList, + address payable[] memory feeAddrList, + uint256[] memory commissionThousandthsList + ) private pure { + require( + consensusAddrList.length == operateAddrList.length, + "the numbers of consensusAddresses and operateAddresses should be equal" + ); + require( + consensusAddrList.length == feeAddrList.length, + "the numbers of consensusAddresses and feeAddresses should be equal" + ); + require( + consensusAddrList.length == commissionThousandthsList.length, + "the numbers of consensusAddresses and commissionThousandthss should be equal" + ); + for (uint256 i = 0; i < consensusAddrList.length; i++) { + for (uint256 j = 0; j < i; j++) { + require(consensusAddrList[i] != consensusAddrList[j], "duplicate consensus address"); + } + require(commissionThousandthsList[i] <= 1000, "commissionThousandths out of bound"); + } + } + /// Update validator set of the new round with elected validators + /// @param operateAddrList List of validator operator addresses + /// @param consensusAddrList List of validator consensus addresses + /// @param feeAddrList List of validator fee addresses + /// @param commissionThousandthsList List of validator commission fees in thousandth + function updateValidatorSetOld( + address[] calldata operateAddrList, + address[] calldata consensusAddrList, + address payable[] calldata feeAddrList, + uint256[] calldata commissionThousandthsList + ) external onlyCandidate { + // do verify. + checkValidatorSetOld(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList); + if (consensusAddrList.length == 0) { + return; + } + // do update validator set state + uint256 i; + uint256 lastLength = currentValidatorSet.length; + uint256 currentLength = consensusAddrList.length; + for (i = 0; i < lastLength; i++) { + delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; + } + for (i = currentLength; i < lastLength; i++) { + currentValidatorSet.pop(); + } + + for (i = 0; i < currentLength; ++i) { + if (i >= lastLength) { + currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0)); + } else { + currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0); + } + currentValidatorSetMap[consensusAddrList[i]] = i + 1; + } + + emit validatorSetUpdated(); + } + function updateBlockReward(uint256 _blockReward) external { blockReward = _blockReward; } @@ -49,6 +126,11 @@ contract ValidatorSetMock is ValidatorSet { return currentValidatorSet[index - 1]; } + function getVoteRewardPercent() external view returns (uint256) { + return voteRewardPercent; + } + + function setValidatorSetMap(address validator) external { currentValidatorSetMap[validator] = 1; } diff --git a/contracts/mock/interface/IValidatorSetMock.sol b/contracts/mock/interface/IValidatorSetMock.sol index 081b1a6ef..942f8a682 100644 --- a/contracts/mock/interface/IValidatorSetMock.sol +++ b/contracts/mock/interface/IValidatorSetMock.sol @@ -5,6 +5,6 @@ interface IValidatorSetMock { function misdemeanor(address validator) external; function felony(address validator, uint256 felonyRound, uint256 felonyDeposit) external; function distributeRewardOld() external returns (address[] memory operateAddrList); - function updateValidatorSet(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList) external; + function updateValidatorSetOld(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList) external; function isValidator(address addr) external returns (bool); } diff --git a/tests/common.py b/tests/common.py index 353c81cd7..94186110c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,8 +1,9 @@ from brownie import * -from .utils import random_address +from .utils import random_address, random_vote_address -def register_candidate(consensus=None, fee_address=None, operator=None, commission=500, margin=None) -> str: +def register_candidate(consensus=None, fee_address=None, operator=None, commission=500, margin=None, + vote_address=None) -> str: if consensus is None: consensus = random_address() if not operator: @@ -11,9 +12,10 @@ def register_candidate(consensus=None, fee_address=None, operator=None, commissi fee_address = operator if margin is None: margin = CandidateHubMock[0].requiredMargin() - + if vote_address is None: + vote_address = random_vote_address() tx = CandidateHubMock[0].register( - consensus, fee_address, commission, + consensus, fee_address, commission, vote_address, {'from': operator, 'value': margin} ) return consensus @@ -26,15 +28,17 @@ def get_candidate(operator=None): return CandidateHubMock[0].candidateSet(idx - 1).dict() -def turn_round(miners: list = None, tx_fee=100, round_count=1): +def turn_round(miners: list = None, tx_fee=100, round_count=1,weights=None): if miners is None: miners = [] - + if weights is None: + weights = [10] * len(miners) tx = None for _ in range(round_count): for miner in miners: ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) + ValidatorSetMock[0].vote(miners, weights, {"from": accounts[99]}) tx = CandidateHubMock[0].turnRound() chain.sleep(1) return tx diff --git a/tests/conftest.py b/tests/conftest.py index 32744fdb9..9e601f348 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,12 +3,24 @@ from brownie import * from web3 import Web3 +from tests.common import register_candidate + @pytest.fixture(scope="session", autouse=True) def is_development() -> bool: return network.show_active() == "development" +@pytest.fixture() +def set_candidate(): + operators = [] + consensuses = [] + for operator in accounts[5:8]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + return operators, consensuses + + @pytest.fixture(scope="module", autouse=True) def shared_setup(module_isolation): pass diff --git a/tests/test_btc_stake.py b/tests/test_btc_stake.py index 0d88723c2..9582696c7 100644 --- a/tests/test_btc_stake.py +++ b/tests/test_btc_stake.py @@ -1409,7 +1409,8 @@ def test_view_collect_reward_invalid_txid(btc_stake, set_candidate, btc_agent): turn_round() last_round = get_current_round() - 1 update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round, last_round) + with brownie.reverts(): + reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round, last_round) def test_only_btc_agent_can_call_set_new_round(btc_stake, btc_agent): diff --git a/tests/test_candidate_hub.py b/tests/test_candidate_hub.py index 73744e056..198a1f60c 100644 --- a/tests/test_candidate_hub.py +++ b/tests/test_candidate_hub.py @@ -6,8 +6,9 @@ from brownie import accounts, UnRegisterReentry, chain from brownie.test import given, strategy from brownie.network.transaction import Status, TransactionReceipt +from .constant import Utils from .utils import random_address, expect_event, padding_left, update_system_contract_address -from .common import register_candidate, turn_round, get_candidate +from .common import register_candidate, turn_round, get_candidate, get_current_round, random_vote_address @pytest.fixture(scope="module") @@ -39,7 +40,7 @@ def test_register(candidate_hub, required_margin): consensus_address = random_address() commission = 10 tx: TransactionReceipt = candidate_hub.register( - consensus_address, accounts[0], commission, + consensus_address, accounts[0], commission, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) assert tx.status == Status.Confirmed @@ -262,6 +263,24 @@ def test_turnRound_update_validator_info(candidate_hub, set_candidate, slash_ind assert 'validatorSetUpdated' in tx.events +def test_turnround_update_voteaddrlist_success(candidate_hub, validator_set, slash_indicator): + operators = [] + consensuses = [] + init_vote_address = 0x99a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8 + vote_address_list = [random_vote_address() for _ in range(3)] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + assert validator_set.getValidatorsAndVoteAddresses()[1] == [Web3.to_hex(init_vote_address)] * 5 + tx = turn_round() + assert 'validatorSetUpdated' in tx.events + assert validator_set.getValidatorsAndVoteAddresses()[0] == consensuses + assert validator_set.getValidatorsAndVoteAddresses()[1] == vote_address_list + turn_round(consensuses) + assert validator_set.currentValidatorSet(0) == [operators[0], consensuses[0], operators[0], 1000, 0, + vote_address_list[0], 0] + + @pytest.mark.parametrize("times", [ 1, pytest.param(2, marks=pytest.mark.xfail), @@ -271,24 +290,25 @@ def test_turnRound_update_validator_info(candidate_hub, set_candidate, slash_ind def test_duplicate_operator(candidate_hub, required_margin, times): for _ in range(times): candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) def test_duplicate_consensus_address(candidate_hub, required_margin): consensus_address = random_address() - candidate_hub.register(consensus_address, accounts[0], 1, {'from': accounts[0], 'value': required_margin}) + candidate_hub.register(consensus_address, accounts[0], 1, random_vote_address(), + {'from': accounts[0], 'value': required_margin}) with brownie.reverts("consensus already exists"): candidate_hub.register( - consensus_address, accounts[1], 1, {'from': accounts[1], 'value': required_margin} + consensus_address, accounts[1], 1, random_vote_address(), {'from': accounts[1], 'value': required_margin} ) @given(commission=strategy('uint32', max_value=1000, exclude=(0, 1000))) def test_register_commission(candidate_hub, required_margin, commission): candidate_hub.register( - random_address(), accounts[0], commission, + random_address(), accounts[0], commission, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) @@ -314,7 +334,7 @@ def test_register_invalid_commission(candidate_hub, required_margin, commission) ]) def test_register_margin(candidate_hub, margin): candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': margin} ) @@ -323,7 +343,7 @@ def test_register_zero_consensus_address(candidate_hub, required_margin): zero_address = "0x0000000000000000000000000000000000000000" with brownie.reverts("consensus address should not be zero"): candidate_hub.register( - zero_address, accounts[0], 1, + zero_address, accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) @@ -332,7 +352,7 @@ def test_register_zero_fee_address(candidate_hub, required_margin): zero_address = "0x0000000000000000000000000000000000000000" with brownie.reverts("fee address should not be zero"): candidate_hub.register( - random_address(), zero_address, 1, + random_address(), zero_address, 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) @@ -341,7 +361,7 @@ def test_register_exceeds_validator_limit(candidate_hub, required_margin): zero_address = "0x0000000000000000000000000000000000000000" with brownie.reverts("fee address should not be zero"): candidate_hub.register( - random_address(), zero_address, 1, + random_address(), zero_address, 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) @@ -350,7 +370,7 @@ def test_register_requires_init_first(candidate_hub, required_margin): candidate_hub.setAlreadyInit(False) with brownie.reverts("the contract not init yet"): candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) @@ -358,26 +378,116 @@ def test_register_requires_init_first(candidate_hub, required_margin): def test_register_zero_payment_amount(candidate_hub, required_margin): with brownie.reverts("deposit is not enough"): candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': 0} ) def test_reregister_after_cancel(candidate_hub, required_margin): candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) candidate_hub.refuseDelegate({'from': accounts[0]}) turn_round() candidate_hub.unregister({'from': accounts[0]}) tx = candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) assert 'registered' in tx.events +@pytest.mark.parametrize("candidate_size", [ + 999, 1000, 1001, 1002 +]) +def test_candidate_size_exceeds_total_limit(candidate_hub, required_margin, candidate_size): + operator = accounts[0] + candidate_hub.mockRegister(candidate_size) + if candidate_size <= 1000: + tx = candidate_hub.register( + random_address(), accounts[1], 1, random_vote_address(), + {'from': operator, 'value': required_margin} + ) + assert 'registered' in tx.events + else: + with brownie.reverts("maximum candidate size reached"): + candidate_hub.register( + random_address(), accounts[1], 1, random_vote_address(), + {'from': operator, 'value': required_margin} + ) + + +def test_vote_addr_invalid_length(candidate_hub, required_margin): + operator = accounts[0] + with brownie.reverts("vote address length should be 48"): + candidate_hub.register( + random_address(), accounts[1], 1, accounts[0].address, + {'from': operator, 'value': required_margin} + ) + + +def test_duplicate_vote_addr(candidate_hub, required_margin): + operator = accounts[0] + vote_address = random_vote_address() + candidate_hub.register( + random_address(), accounts[1], 1, vote_address, + {'from': operator, 'value': required_margin} + ) + with brownie.reverts("vote address already exists"): + candidate_hub.register( + random_address(), accounts[0], 1, vote_address, + {'from': accounts[2], 'value': required_margin} + ) + + +def test_vote_addr_list_contains_duplicates(candidate_hub, required_margin, validator_set, set_candidate): + turn_round() + vote_address = validator_set.getValidatorsAndVoteAddresses()[1][0] + with brownie.reverts("vote address already exists"): + candidate_hub.register( + random_address(), accounts[0], 1, vote_address, + {'from': accounts[1], 'value': required_margin} + ) + + +def test_registration_index_correct_after_success(candidate_hub, required_margin): + vote_address = random_vote_address() + consensus_addr = random_address() + commission_thousandths = 100 + tx = candidate_hub.register( + consensus_addr, accounts[0], commission_thousandths, vote_address, + {'from': accounts[1], 'value': required_margin} + ) + expect_event(tx, "registered", { + 'operateAddr': accounts[1], + 'consensusAddr': consensus_addr, + 'feeAddress': accounts[0], + 'commissionThousandths': commission_thousandths, + 'margin': required_margin, + 'voteAddr': vote_address + }) + commission_last_round = 100 + status = 1 + assert candidate_hub.candidateSet(0) == ( + accounts[1], + consensus_addr, + accounts[0], + commission_thousandths, + required_margin, + status, + get_current_round(), + commission_last_round, + vote_address, + ) + assert candidate_hub.operateMap(accounts[1]) == 1 + assert candidate_hub.getConsensusMap(consensus_addr) == 1 + consensus = register_candidate(operator=accounts[2]) + assert candidate_hub.operateMap(accounts[2]) == 2 + assert candidate_hub.getConsensusMap(consensus) == 2 + assert candidate_hub.candidateSet(1)['operateAddr'] == accounts[2] + + def test_only_gov_can_call(candidate_hub, required_margin): value = padding_left(Web3.to_hex(candidate_hub.dues() + 10), 64) with brownie.reverts("the msg sender must be governance contract"): @@ -661,7 +771,7 @@ def test_query_jail_round_by_non_validator(candidate_hub, slash_indicator, set_c def test_is_candidate_by_operate(candidate_hub, required_margin): operator = accounts[0] candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': operator, 'value': required_margin} ) assert candidate_hub.isCandidateByOperate(operator) is True @@ -670,7 +780,7 @@ def test_is_candidate_by_operate(candidate_hub, required_margin): def test_is_candidate_by_consensus(candidate_hub, required_margin): consensus_address = random_address() candidate_hub.register( - consensus_address, accounts[0], 1, + consensus_address, accounts[0], 1, random_vote_address(), {'from': accounts[0], 'value': required_margin} ) assert candidate_hub.isCandidateByConsensus(consensus_address) is True @@ -679,7 +789,7 @@ def test_is_candidate_by_consensus(candidate_hub, required_margin): def test_get_candidates(candidate_hub, required_margin): operator = accounts[0] candidate_hub.register( - random_address(), accounts[0], 1, + random_address(), accounts[0], 1, random_vote_address(), {'from': operator, 'value': required_margin} ) assert operator in candidate_hub.getCandidates() @@ -702,7 +812,8 @@ def test_accept_delegate(candidate_hub, required_margin): for operate_addr, register, set_status, status, check_event, ret, err in tests: old_status = 1 if register: - candidate_hub.register(random_address(), fee_address, 10, {'from': operate_addr, 'value': required_margin}) + candidate_hub.register(random_address(), fee_address, 10, random_vote_address(), + {'from': operate_addr, 'value': required_margin}) if set_status is not None: candidate_hub.setCandidateStatus(operate_addr, set_status, {'from': operate_addr}) old_status = set_status @@ -731,7 +842,8 @@ def test_refuse_delegate(candidate_hub, required_margin): for operate_addr, ret, err, register, set_status, status, check_event in tests: old_status = 1 if register: - candidate_hub.register(random_address(), fee_address, 10, {'from': operate_addr, 'value': required_margin}) + candidate_hub.register(random_address(), fee_address, 10, random_vote_address(), + {'from': operate_addr, 'value': required_margin}) if set_status is not None: candidate_hub.setCandidateStatus(operate_addr, set_status, {'from': operate_addr}) old_status = set_status @@ -785,7 +897,8 @@ def test_bond_update_registration_failure(candidate_hub, required_margin): hex_value = padding_left(Web3.to_hex(required_margin * 2), 64) candidate_hub.updateParam('requiredMargin', hex_value) with brownie.reverts('deposit is not enough'): - candidate_hub.register(consensus_address, fee_address, 1, {'from': accounts[1], 'value': required_margin}) + candidate_hub.register(consensus_address, fee_address, 1, random_vote_address(), + {'from': accounts[1], 'value': required_margin}) def test_register_candidate(candidate_hub, required_margin): @@ -810,9 +923,11 @@ def test_register_candidate(candidate_hub, required_margin): for operate_addr, consensus_addr, fee_addr, commission, value, ret, err in tests: if ret is False: with brownie.reverts(err): - candidate_hub.register(consensus_addr, fee_addr, commission, {'from': operate_addr, 'value': value}) + candidate_hub.register(consensus_addr, fee_addr, commission, random_vote_address(), + {'from': operate_addr, 'value': value}) else: - tx = candidate_hub.register(consensus_addr, fee_addr, commission, {'from': operate_addr, 'value': value}) + tx = candidate_hub.register(consensus_addr, fee_addr, commission, random_vote_address(), + {'from': operate_addr, 'value': value}) expect_event(tx, "registered", { "operateAddr": operate_addr, "consensusAddr": consensus_addr, @@ -826,7 +941,8 @@ def test_unregister_candidate(candidate_hub, required_margin): consensus_address = random_address() fee_address = random_address() - candidate_hub.register(consensus_address, fee_address, 10, {'from': accounts[3], 'value': required_margin}) + candidate_hub.register(consensus_address, fee_address, 10, random_vote_address(), + {'from': accounts[3], 'value': required_margin}) tests = [ (accounts[1], None, False, "candidate does not exist", None, None, None), @@ -848,7 +964,8 @@ def test_unregister_candidate(candidate_hub, required_margin): if register is True: if consensus_addr is None: consensus_addr = random_address() - candidate_hub.register(consensus_addr, fee_address, 10, {'from': operate_addr, "value": required_margin}) + candidate_hub.register(consensus_addr, fee_address, 10, random_vote_address(), + {'from': operate_addr, "value": required_margin}) if consensus_addr is None: consensus_addr = consensus_address if set_status is not None: @@ -887,29 +1004,124 @@ def test_update_candidate(candidate_hub, required_margin): (accounts[3], None, random_address(), fee_address, 200 + max_commission_change, True, "", None), (accounts[3], None, random_address(), fee_address, 200 + max_commission_change * 2, True, "", True), ] - + i = 0 for operate_addr, register, consensus_addr, fee_addr, commission, ret, err, need_turn_round in tests: if need_turn_round: turn_round() + i += 1 + vote_address0 = random_vote_address() + vote_address1 = random_vote_address() + vote_address2 = random_vote_address() if register: if consensus_addr is None: consensus_addr = random_address() - candidate_hub.register(consensus_addr, fee_addr, 200, {'from': operate_addr, 'value': required_margin}) + candidate_hub.register(consensus_addr, fee_addr, 200, vote_address0, + {'from': operate_addr, 'value': required_margin}) if consensus_addr is None: consensus_addr = consensus_address if ret is False: with brownie.reverts(err): - candidate_hub.update(consensus_addr, fee_addr, commission, {'from': operate_addr}) + candidate_hub.update(consensus_addr, fee_addr, commission, vote_address1, {'from': operate_addr}) else: - tx = candidate_hub.update(consensus_addr, fee_addr, commission, {'from': operate_addr}) + tx = candidate_hub.update(consensus_addr, fee_addr, commission, vote_address2, + {'from': operate_addr}) expect_event(tx, "updated", { "operateAddr": operate_addr, "consensusAddr": consensus_addr, "feeAddress": fee_addr, - "commissionThousandths": commission + "commissionThousandths": commission, + "voteAddr": vote_address2 }) +def test_update_success(candidate_hub, required_margin): + consensus_address = random_address() + fee_address = random_address() + vote_address0 = random_vote_address() + round_value = 200 + candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, + {'from': accounts[0], 'value': required_margin}) + turn_round() + consensus_ = accounts[1] + fee_address_ = accounts[2] + vote_address1 = '0x938821669157ba8ed89e2cdd3956cadb9242c2ed8467bc322f636bb3bb5aeff4020e98dd949e47c081df84688c6cb881' + candidate_hub.update(consensus_, fee_address_, 300, vote_address1, + {'from': accounts[0]}) + state = 17 + assert candidate_hub.candidateSet(0) == ( + accounts[0], consensus_, fee_address_, 300, required_margin, state, get_current_round(), round_value, + vote_address1) + + +def test_update_vote_addr_invalid_length_failed(candidate_hub, required_margin): + consensus_address = random_address() + fee_address = random_address() + vote_address0 = random_vote_address() + round_value = 200 + candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, + {'from': accounts[0], 'value': required_margin}) + consensus_ = accounts[1] + fee_address_ = accounts[2] + error_vote_address1 = '0x938821669157ba8ed89e2cdd3956cadb9242c2ed8467bc322f636bb3bb5aeff4020e98dd949e47c081df84688c6cb8' + with brownie.reverts("vote address length should be 48"): + candidate_hub.update(consensus_, fee_address_, 300, error_vote_address1, + {'from': accounts[0]}) + + +def test_self_vote_addr_duplicate_failed(candidate_hub, required_margin): + consensus_address = random_address() + fee_address = random_address() + vote_address0 = random_vote_address() + round_value = 200 + candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, + {'from': accounts[0], 'value': required_margin}) + consensus_ = accounts[1] + fee_address_ = accounts[2] + candidate_hub.update(consensus_, fee_address_, 300, vote_address0, + {'from': accounts[0]}) + state = 1 + assert candidate_hub.candidateSet(0) == ( + accounts[0], consensus_, fee_address_, 300, required_margin, state, get_current_round(), round_value, + vote_address0) + + +def test_update_vote_addr_list_contains_duplicates_failed(candidate_hub, required_margin): + vote_address_list = [] + round_value = 200 + for i in range(5): + vote_address = random_vote_address() + candidate_hub.register(random_address(), random_address(), round_value, vote_address, + {'from': accounts[i], 'value': required_margin}) + vote_address_list.append(vote_address) + consensus_ = accounts[6] + fee_address_ = accounts[7] + for i in range(1, 5): + with brownie.reverts(f"vote address already exists"): + candidate_hub.update(consensus_, fee_address_, 300, vote_address_list[i], + {'from': accounts[0]}) + + +def test_update_single_address_repeated_changes_success(candidate_hub, required_margin): + consensus_address = random_address() + fee_address = random_address() + vote_address0 = random_vote_address() + round_value = 200 + candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, + {'from': accounts[0], 'value': required_margin}) + new_consensus_address = random_address() + assert candidate_hub.candidateSet(0)['consensusAddr'] == consensus_address + candidate_hub.update(new_consensus_address, fee_address, round_value, random_vote_address(), {'from': accounts[0]}) + assert candidate_hub.candidateSet(0)['consensusAddr'] == new_consensus_address + new_vote_address = random_vote_address() + candidate_hub.update(new_consensus_address, fee_address, round_value, new_vote_address, {'from': accounts[0]}) + assert candidate_hub.candidateSet(0)['voteAddr'] == new_vote_address + new_fee_address = random_address() + candidate_hub.update(new_consensus_address, new_fee_address, round_value, new_vote_address, {'from': accounts[0]}) + assert candidate_hub.candidateSet(0)['consensusAddr'] == new_consensus_address + assert candidate_hub.candidateSet(0)['feeAddr'] == new_fee_address + assert candidate_hub.candidateSet(0)['voteAddr'] == new_vote_address + + def test_add_margin(candidate_hub, required_margin): fee_address = random_address() @@ -926,7 +1138,8 @@ def test_add_margin(candidate_hub, required_margin): for operate_addr, register, set_margin, value, set_status, status, check_event, ret, err in tests: old_status = 1 if register: - candidate_hub.register(random_address(), fee_address, 10, {'from': operate_addr, 'value': required_margin}) + candidate_hub.register(random_address(), fee_address, 10, random_vote_address(), + {'from': operate_addr, 'value': required_margin}) if set_status is not None: candidate_hub.setCandidateStatus(operate_addr, set_status, {'from': operate_addr}) old_status = set_status @@ -1001,7 +1214,8 @@ def test_jail_validator(candidate_hub, validator_set, required_margin): for operate_addr, register, _round, set_margin, set_status, status, fine, check_event, ret, err in tests: old_status = 1 if register: - candidate_hub.register(random_address(), fee_address, 10, {'from': operate_addr, 'value': required_margin}) + candidate_hub.register(random_address(), fee_address, 10, random_vote_address(), + {'from': operate_addr, 'value': required_margin}) if set_status is not None: candidate_hub.setCandidateStatus(operate_addr, set_status, {'from': operate_addr}) old_status = set_status @@ -1053,7 +1267,7 @@ def test_turn_round(candidate_hub, core_agent, validator_set, required_margin): ] for agents, deposit, set_status, status in tests: for agent, _set_status in zip(agents, set_status): - candidate_hub.register(agent, agent, 10, {'from': agent, 'value': required_margin}) + candidate_hub.register(agent, agent, 10, random_vote_address(), {'from': agent, 'value': required_margin}) candidate_hub.setCandidateStatus(agent, _set_status, {'from': agent}) for agent, _deposit in zip(agents, deposit): if _deposit > 0: diff --git a/tests/test_core_agent.py b/tests/test_core_agent.py index 1e1c4a7e5..845734cc2 100644 --- a/tests/test_core_agent.py +++ b/tests/test_core_agent.py @@ -2008,7 +2008,7 @@ def __register_candidates(agents=None): def __candidate_register(agent, percent=100): consensus_addr = random_address() fee_addr = random_address() - candidate_hub_instance.register(consensus_addr, fee_addr, percent, + candidate_hub_instance.register(consensus_addr, fee_addr, percent, random_vote_address(), {'from': agent, 'value': CANDIDATE_REGISTER_MARGIN}) return consensus_addr diff --git a/tests/test_delegate_btc.py b/tests/test_delegate_btc.py index 6635b5860..378deafe8 100644 --- a/tests/test_delegate_btc.py +++ b/tests/test_delegate_btc.py @@ -652,12 +652,19 @@ def test_operations_with_coin_power_and_btc_staking(btc_stake, set_candidate, bt tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT, LOCK_TIME, accounts[2]) CORE_AGENT.delegateCoin(operators[0], {"value": delegate_amount, "from": accounts[1]}) turn_round() + vote_percent = 10 + weights = [10, 20, 30] undelegate_amount = 7000 transfer_amount = delegate_amount // 2 CORE_AGENT.transferCoin(operators[0], operators[2], transfer_amount, {'from': accounts[1]}) CORE_AGENT.undelegateCoin(operators[0], undelegate_amount, {'from': accounts[1]}) btc_stake.transfer(tx_id, operators[1]) - turn_round(consensuses) + tx = turn_round(consensuses, weights=weights) + total_vote_fee0 = BLOCK_REWARD * vote_percent // 100 * 2 + BLOCK_REWARD // 2 * vote_percent // 100 + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_vote_fee0 * weights[i] // sum(weights) + }, idx=i) tracker0 = get_tracker(accounts[0]) tracker1 = get_tracker(accounts[1]) tracker2 = get_tracker(accounts[2]) @@ -673,7 +680,12 @@ def test_operations_with_coin_power_and_btc_staking(btc_stake, set_candidate, bt assert tracker0.delta() == 0 assert tracker1.delta() == account_rewards[accounts[1]] assert tracker2.delta() == account_rewards[accounts[2]] - turn_round(consensuses) + tx = turn_round(consensuses, weights=weights) + total_vote_fee = TOTAL_REWARD * vote_percent // 100 * 3 + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_vote_fee * weights[i] // sum(weights) + }, idx=i) _, _, account_rewards, _ = parse_delegation([{ "address": operators[0], "active": True, @@ -698,6 +710,7 @@ def test_operations_with_coin_power_and_btc_staking(btc_stake, set_candidate, bt assert tracker0.delta() == account_rewards[accounts[0]] - FEE assert tracker1.delta() == account_rewards[accounts[1]] assert tracker2.delta() == account_rewards[accounts[2]] + turn_round(consensuses) def test_btc_transfer_does_not_claim_historical_rewards(btc_stake, set_candidate): diff --git a/tests/test_slash.py b/tests/test_slash.py index 780bf2201..d63e8775d 100644 --- a/tests/test_slash.py +++ b/tests/test_slash.py @@ -1,14 +1,11 @@ import brownie import pytest -import random -from brownie import accounts, chain -from brownie.test import given, strategy -from eth_account import Account +from brownie.test import strategy from eth_account.signers.local import LocalAccount -from web3 import Web3 -from .utils import expect_event, padding_left, update_system_contract_address -from .common import register_candidate, turn_round +from .delegate import delegate_coin_success +from .utils import * +from .common import register_candidate, turn_round, execute_proposal, stake_hub_claim_reward misdemeanorThreshold = 0 felonyThreshold = 0 @@ -32,6 +29,10 @@ def set_candidate(): return operators, consensuses +def build_vote_data(src_num, src_hash, tar_num, tar_hash, sig): + return [src_num, src_hash, tar_num, tar_hash, sig] + + def test_slash_validator(slash_indicator): account: LocalAccount = Account.create(str(random.random())) tx = slash_indicator.slash(account.address) @@ -139,6 +140,238 @@ def test_clean(slash_indicator, validator_set): assert slash_indicator.getSlashIndicator(account.address)[1] == max([count - decrease_value, 0]) +def test_old_block_involved_failed(slash_indicator, validator_set, set_candidate): + signature = os.urandom(96).hex() + chain.mine(86400 + chain.height) + vote_a = build_vote_data(100, random_btc_tx_id(), 150, random_btc_tx_id(), signature) + vote_b = build_vote_data(120, random_btc_tx_id(), 140, random_btc_tx_id(), signature) + vote_addr = random_vote_address() + with brownie.reverts(f"too old block involved"): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + vote_a = build_vote_data(120, random_btc_tx_id(), 150, random_btc_tx_id(), signature) + vote_b = build_vote_data(100, random_btc_tx_id(), 140, random_btc_tx_id(), signature) + vote_addr = random_vote_address() + with brownie.reverts(f"too old block involved"): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + +def test_identical_votes_failed(slash_indicator, validator_set, set_candidate): + signature = os.urandom(96).hex() + src_num = 100 + vote_addr = random_vote_address() + src_hash = 'fa3ee718ac7585d9fc58f4bbd4c17f745d96609e17ac5e81f444bd0b538c52fb' + tar_hash = '6e30353bfa10cc63c000c454184b4fa9f4c6844eb65897976940d7e3af9199ab' + vote_a = build_vote_data(src_num, src_hash, src_num, tar_hash, signature) + vote_b = build_vote_data(src_num, src_hash, src_num, tar_hash, signature) + with brownie.reverts('two identical votes'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + vote_a = build_vote_data(src_num, src_hash, src_num, src_hash, signature) + vote_b = build_vote_data(src_num, tar_hash, src_num, src_hash, signature) + with brownie.reverts('srcNum bigger than tarNum'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + vote_a = build_vote_data(src_num, src_hash, src_num, src_hash, signature) + vote_b = build_vote_data(src_num, src_hash, src_num, tar_hash, signature) + with brownie.reverts('srcNum bigger than tarNum'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + +@pytest.mark.parametrize("new_tar_num", [98, 99, 100]) +def test_src_num_exceeds_tar_num_failed(slash_indicator, set_candidate, new_tar_num): + signature = os.urandom(96).hex() + tar_num = 101 + src_num = 100 + vote_addr = random_vote_address() + src_hash_list = [random_bytes_data() for _ in range(2)] + tar_hash_list = [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], new_tar_num, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num, src_hash_list[1], new_tar_num, tar_hash_list[1], signature) + with brownie.reverts('srcNum bigger than tarNum'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + vote_a = build_vote_data(src_num, src_hash_list[0], tar_num, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num, src_hash_list[1], new_tar_num, tar_hash_list[1], signature) + with brownie.reverts('srcNum bigger than tarNum'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + vote_a = build_vote_data(src_num, src_hash_list[0], new_tar_num, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num, src_hash_list[1], tar_num, tar_hash_list[1], signature) + with brownie.reverts('srcNum bigger than tarNum'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + vote_a = build_vote_data(src_num, src_hash_list[0], tar_num, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num, src_hash_list[1], tar_num, tar_hash_list[1], signature) + with brownie.reverts('verify signature failed'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + +def test_no_violation_detected_failed(slash_indicator, set_candidate): + signature = os.urandom(96).hex() + src_num = 100 + vote_addr = random_vote_address() + src_hash_list = [random_bytes_data() for _ in range(2)] + tar_hash_list = [random_bytes_data() for _ in range(2)] + # 1 voteA.srcNum < voteB.srcNum && voteB.tarNum < voteA.tarNum + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + with brownie.reverts('verify signature failed'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + # 2 voteB.srcNum < voteA.srcNum && voteA.tarNum < voteB.tarNum + vote_a = build_vote_data(src_num + 1, src_hash_list[0], 120, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num, src_hash_list[1], 200, tar_hash_list[1], signature) + with brownie.reverts('verify signature failed'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + # 3 voteA.tarNum == voteB.tarNum + vote_a = build_vote_data(src_num + 1, src_hash_list[0], 120, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num, src_hash_list[1], 120, tar_hash_list[1], signature) + with brownie.reverts('verify signature failed'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + # no violation of vote rules + vote_a = build_vote_data(100, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(150, src_hash_list[1], 250, tar_hash_list[1], signature) + with brownie.reverts('no violation of vote rules'): + slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + + +def test_zero_reward_default_value(slash_indicator, system_reward): + accounts[99].transfer(system_reward.address, Web3.to_wei(100000, 'ether')) + signature = os.urandom(96).hex() + src_num = 100 + vote_addr_list = [random_vote_address() for _ in range(3)] + operators = [] + consensuses = [] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_addr_list[index])) + turn_round() + src_hash_list = [random_bytes_data() for _ in range(2)] + tar_hash_list = [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tracker = get_tracker(accounts[2]) + slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_addr_list[1]), {'from': accounts[2]}) + assert tracker.delta() == slash_indicator.INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION() + + +@pytest.mark.parametrize("report_reward", [100, 1e21, 1e18, 100]) +def test_update_reward_for_report_success(slash_indicator, system_reward, report_reward): + accounts[99].transfer(system_reward.address, Web3.to_wei(100000, 'ether')) + signature = os.urandom(96).hex() + src_num = 100 + vote_addr_list = [random_vote_address() for _ in range(3)] + operators = [] + consensuses = [] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_addr_list[index])) + turn_round() + hex_value = padding_left(Web3.to_hex(int(report_reward)), 64) + execute_proposal( + slash_indicator.address, + 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ['rewardForReportFinalityViolation', Web3.to_bytes(hexstr=hex_value)]), + "update rewardForReportFinalityViolation" + ) + src_hash_list, tar_hash_list = [random_bytes_data() for _ in range(2)], [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tracker = get_tracker(accounts[2]) + slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_addr_list[0]), {'from': accounts[2]}) + assert tracker.delta() == report_reward + + +def test_felony_submission_success(slash_indicator, validator_set, candidate_hub, system_reward): + validator_set.updateBlockReward(30000) + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + accounts[99].transfer(system_reward.address, Web3.to_wei(100000, 'ether')) + signature = os.urandom(96).hex() + src_num = 100 + delegate_amount = 10000 + vote_addr_list = [random_vote_address() for _ in range(3)] + operators = [] + consensuses = [] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_addr_list[index])) + delegate_coin_success(operator, accounts[0], delegate_amount) + turn_round() + src_hash_list, tar_hash_list = [random_bytes_data() for _ in range(2)], [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tracker = get_tracker(accounts[2]) + tx = slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_addr_list[1]), {'from': accounts[2]}) + assert tracker.delta() == slash_indicator.INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION() + expect_event(tx, "validatorFelony", { + 'validator': operators[1], + 'amount': 0 + }) + expect_event(tx, "deductedMargin", { + "operateAddr": operators[1], + "margin": slash_indicator.felonyDeposit(), + "totalMargin": candidate_hub.requiredMargin() - slash_indicator.felonyDeposit() + }) + turn_round(consensuses) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + total_reward = 13545 + assert tracker.delta() == total_reward * 2 + + +def test_validator_restoration_after_felony_success(slash_indicator, validator_set, candidate_hub, system_reward): + validator_set.updateBlockReward(30000) + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + accounts[99].transfer(system_reward.address, Web3.to_wei(100000, 'ether')) + signature = os.urandom(96).hex() + src_num = 100 + delegate_amount = 10000 + vote_addr_list = [random_vote_address() for _ in range(3)] + operators = [] + consensuses = [] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_addr_list[index])) + delegate_coin_success(operator, accounts[0], delegate_amount) + turn_round() + src_hash_list, tar_hash_list = [random_bytes_data() for _ in range(2)], [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tx = slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_addr_list[2]), {'from': accounts[2]}) + candidate_hub.addMargin({'from': operators[2], 'value': slash_indicator.felonyDeposit()}) + assert 'validatorFelony' in tx.events + turn_round(consensuses, round_count=2) + assert len(validator_set.getValidators()) == 2 + stake_hub_claim_reward(accounts[0]) + turn_round(consensuses) + total_reward = 13545 + assert len(validator_set.getValidators()) == 3 + stake_hub_claim_reward(accounts[0]) + turn_round(consensuses) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == total_reward * 3 + + +def test_penalty_non_validator_voter(slash_indicator, validator_set, system_reward, set_candidate): + operators, consensuses = set_candidate + validator_set.updateBlockReward(30000) + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + accounts[99].transfer(system_reward.address, Web3.to_wei(100000, 'ether')) + signature = os.urandom(96).hex() + src_num = 100 + delegate_amount = 10000 + for operator in operators: + delegate_coin_success(operator, accounts[0], delegate_amount) + turn_round() + src_hash_list, tar_hash_list = [random_bytes_data() for _ in range(2)], [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tx = slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, random_vote_address()), + {'from': accounts[2]}) + assert 'validatorFelony' not in tx.events + turn_round(consensuses, round_count=2) + assert len(validator_set.getValidators()) == 3 + + def test_only_gov_can_call(slash_indicator): value = padding_left(Web3.to_hex(1000), 64) with brownie.reverts(f"the msg sender must be governance contract"): @@ -233,6 +466,23 @@ def test_update_felony_round_failure(slash_indicator, new_felonyRound): slash_indicator.updateParam('felonyRound', value) +@pytest.mark.parametrize("report_reward", [1, 2, 3, 1000, 1e20, 1e21]) +def test_update_report_reward_success(slash_indicator, report_reward): + value = padding_left(Web3.to_hex(int(report_reward)), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + slash_indicator.updateParam('rewardForReportFinalityViolation', value) + assert slash_indicator.rewardForReportFinalityViolation() == value + + +@pytest.mark.parametrize("report_reward", [0, 1e22]) +def test_update_report_reward_failed(slash_indicator, report_reward): + value = padding_left(Web3.to_hex(int(report_reward)), 64) + update_system_contract_address(slash_indicator, gov_hub=accounts[0]) + with brownie.reverts( + f"OutOfBounds: rewardForReportFinalityViolation, {int(report_reward)}, 1, 1000000000000000000000"): + slash_indicator.updateParam('rewardForReportFinalityViolation', value) + + def test_invalid_key(slash_indicator): value = padding_left(Web3.to_hex(1000), 64) update_system_contract_address(slash_indicator, gov_hub=accounts[0]) diff --git a/tests/test_slash_indicator.py b/tests/test_slash_indicator.py index 8994802a8..b8e5d1f87 100644 --- a/tests/test_slash_indicator.py +++ b/tests/test_slash_indicator.py @@ -3,7 +3,7 @@ from web3 import Web3 from eth_abi import encode from brownie import * -from .utils import expect_event, padding_left, random_address, random_btc_tx_id +from .utils import expect_event, padding_left, random_address, random_btc_tx_id, random_vote_address from .common import execute_proposal @@ -44,7 +44,8 @@ def test_double_sign_slash(slash_indicator, candidate_hub): "0x01Bca3615D24d3c638836691517b2B9b49b054B1", "0x01Bca3615D24d3c638836691517b2B9b49b054B1", "0x01Bca3615D24d3c638836691517b2B9b49b054B1", - 100 + 100, + random_vote_address() ) for operate_address, header1, header2, ret, err in tests: if ret: diff --git a/tests/test_validator_set.py b/tests/test_validator_set.py index 54c8215ab..ed4df79a7 100644 --- a/tests/test_validator_set.py +++ b/tests/test_validator_set.py @@ -1,8 +1,10 @@ import pytest import brownie from web3 import Web3, constants -from brownie import * -from .utils import expect_event, get_tracker, AccountTracker, update_system_contract_address + +from .common import turn_round, register_candidate, get_current_round, execute_proposal +from .delegate import delegate_coin_success +from .utils import * from eth_abi import encode init_validators = [ @@ -29,13 +31,19 @@ def setup(system_reward, validator_set, pledge_agent, core_agent, stake_hub): global account_tracker, system_reward_tracker, validator_set_tracker, stake_hub_tracker global validator_set_instance - global BLOCK_REWARD + global BLOCK_REWARD, block_reward validator_set_instance = validator_set BLOCK_REWARD = validator_set.BLOCK_REWARD() account_tracker = get_tracker(accounts[0]) system_reward_tracker = get_tracker(system_reward) validator_set_tracker = get_tracker(validator_set) stake_hub_tracker = get_tracker(stake_hub) + block_reward = validator_set.blockReward() + + +@pytest.fixture() +def deposit_for_reward(validator_set): + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) @pytest.fixture(autouse=True) @@ -210,14 +218,144 @@ def test_deposit_to_validator_with_positive_balance(validator_address, deposit_v __balance_check(0 - deposit_value, deposit_value, 0) +def test_vote_addr_and_weights_mismatch_failed(set_candidate): + operators, consensuses = set_candidate + accounts[1].transfer(validator_set_instance.address, Web3.to_wei(10, 'ether')) + validator_set_tracker.balance() + weights = [10, 10] + with brownie.reverts("length not equal"): + validator_set_instance.vote(consensuses, weights) + + validator_set_instance.vote([], []) + + +def test_val_addrs_contains_non_validator(validator_set, set_candidate): + operators, consensuses = set_candidate + turn_round() + consensuses.append(accounts[2]) + weights = [10, 20, 30, 40] + validator_set.vote(consensuses, weights) + assert validator_set.getValidatorByConsensus(consensuses[0])['voteWeight'] == weights[0] + assert validator_set.getValidatorByConsensus(consensuses[1])['voteWeight'] == weights[1] + assert validator_set.getValidatorByConsensus(consensuses[2])['voteWeight'] == weights[2] + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[3]) + + +@pytest.mark.xfail +def test_invalid_input_format_failed(validator_set, set_candidate): + operators, consensuses = set_candidate + turn_round() + consensuses.append(accounts[2]) + weights = accounts[0] + with brownie.reverts(): + validator_set.vote(consensuses, weights) + + +def test_vote_success(validator_set): + operators = [] + consensuses = [] + vote_address_list = [] + for operator in accounts[5:8]: + operators.append(operator) + vote_address = random_vote_address() + consensuses.append(register_candidate(operator=operator, vote_address=vote_address)) + vote_address_list.append(vote_address) + turn_round() + weights = [10, 20, 30] + validator_set.vote(consensuses, weights) + for i in range(len(consensuses)): + validator = [operators[i], consensuses[i], operators[i], 1000, 0, vote_address_list[i], weights[i]] + assert validator_set.getValidatorByConsensus(consensuses[i]) == validator + + +def test_repeated_vote_counts_accumulate_success(validator_set, set_candidate): + operators, consensuses = set_candidate + turn_round() + weights = [10, 20, 30] + validator_set.vote(consensuses, weights) + validator_set.vote(consensuses, weights) + validator_set.vote(consensuses, weights) + for i in range(len(consensuses)): + assert validator_set.getValidatorByConsensus(consensuses[i])['voteWeight'] == weights[i] * 3 + + validator_set.vote(consensuses[:-1], weights[:-1]) + assert validator_set.getValidatorByConsensus(consensuses[0])['voteWeight'] == weights[0] * 4 + assert validator_set.getValidatorByConsensus(consensuses[1])['voteWeight'] == weights[1] * 4 + + +def test_vote_for_unelected_validator_failed(validator_set, set_candidate): + operators, consensuses = set_candidate + weights = [10, 20, 30] + validator_set.vote(consensuses, weights) + for i in range(len(consensuses)): + validator_set.vote([consensuses[i]], [weights[i]]) + for i in range(len(consensuses)): + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[i]) + + +@pytest.mark.parametrize("validator_state", ['minor', 'major']) +def test_vote_address_contains_felony_failed(validator_set, set_candidate, slash_indicator, validator_state): + operators, consensuses = set_candidate + turn_round() + turn_round(consensuses) + tx0 = None + vote_state = False + if validator_state == 'minor': + slash_threshold = slash_indicator.misdemeanorThreshold() + event_name = 'validatorMisdemeanor' + vote_state = True + else: + slash_threshold = slash_indicator.felonyThreshold() + event_name = 'validatorFelony' + for count in range(slash_threshold): + tx0 = slash_indicator.slash(consensuses[0]) + assert event_name in tx0.events + validator_set.vote([consensuses[0]], [10]) + if vote_state: + assert validator_set.getValidatorByConsensus(consensuses[0])['voteWeight'] == 10 + else: + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[0]) + + +def test_vote_address_contains_unregistered_failed(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + weights = [10, 20, 30] + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[0]) + validator_set.vote(consensuses, weights) + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[0]) + + +def test_vote_array_length_change_success(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + weights0 = [10, 20, 30] + weights1 = [60, 90, 120] + validator_set.vote(consensuses, weights0) + validator_set.vote(consensuses[:1], weights0[:1]) + validator_set.vote(consensuses, weights1) + validator_set.vote(consensuses[:2], weights1[:2]) + actual_validator_fee = [140, 200, 150] + for i in range(len(consensuses)): + assert validator_set.getValidatorByConsensus(consensuses[i])['voteWeight'] == actual_validator_fee[i] + + def test_update_failed_by_address_which_is_not_candidate(): with brownie.reverts("the msg sender must be candidate contract"): - validator_set_instance.updateValidatorSet([random_address], [random_address], [random_address], [100]) + validator_set_instance.updateValidatorSet([random_address], [random_address], [random_address], [100], []) def test_update_failed_with_empty_validator_set(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], []) assert validator_set_instance.getValidators() == init_validators @@ -225,72 +363,191 @@ def test_update_failed_with_addresses_of_different_length(): __fake_validator_set() with brownie.reverts("the numbers of consensusAddresses and commissionThousandthss should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address], [accounts[1].address], [accounts[2].address], - [accounts[3].address, accounts[4].address]) + [accounts[3].address, accounts[4].address], [random_vote_address()]) with brownie.reverts("the numbers of consensusAddresses and feeAddresses should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address], [accounts[1].address], - [accounts[2].address, accounts[4].address], [accounts[3].address]) + [accounts[2].address, accounts[4].address], [accounts[3].address], + [random_vote_address()]) with brownie.reverts("the numbers of consensusAddresses and operateAddresses should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address], [accounts[1].address, accounts[4].address], - [accounts[2].address], [accounts[3].address]) + [accounts[2].address], [accounts[3].address], [random_vote_address()]) with brownie.reverts("the numbers of consensusAddresses and operateAddresses should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address, accounts[4].address], [accounts[1].address], - [accounts[2].address], [accounts[3].address]) + [accounts[2].address], [accounts[3].address], [random_vote_address()]) def test_update_failed_with_duplicate_consensus_address(): __fake_validator_set() with brownie.reverts("duplicate consensus address"): validator_set_instance.updateValidatorSet([accounts[0], accounts[0]], [accounts[1], accounts[1]], - [accounts[2], accounts[2]], [100, 100]) + [accounts[2], accounts[2]], [100, 100], + [random_vote_address(), random_vote_address()]) def test_update_failed_with_commissionThousandths_out_of_range(): __fake_validator_set() with brownie.reverts("commissionThousandths out of bound"): validator_set_instance.updateValidatorSet([accounts[0], accounts[0]], [accounts[1], accounts[3]], - [accounts[2], accounts[2]], [1000, 10000]) + [accounts[2], accounts[2]], [1000, 10000], + [random_vote_address(), random_vote_address()]) def test_update_success(): __fake_validator_set() - tx = validator_set_instance.updateValidatorSet([accounts[0]], [accounts[1]], [accounts[2]], [1000]) + vote_address = random_vote_address() + tx = validator_set_instance.updateValidatorSet([accounts[0]], [accounts[1]], [accounts[2]], [1000], + [vote_address]) expect_event(tx, "validatorSetUpdated") assert validator_set_instance.getValidators() == [accounts[1]] + assert validator_set_instance.getValidatorByConsensus(accounts[1]) == [accounts[0], accounts[1], accounts[2], 1000, + 0, vote_address, 0] -@pytest.mark.parametrize("fake,key,value,err", [ - (False, "blockReward", "0x0000000000000000000000000000000000000000000000000000000000000001", - "the msg sender must be governance contract"), - (True, "totalInCome", "0x0000000000000000000000000000000000000000000000000000000000000001", "unknown param"), - (True, "blockReward", '0x' + str('2000000000000000000'), "length of blockReward mismatch"), - (True, "blockReward", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "the blockReward out of range"), - (True, "blockReward", "0x000000000000000000000000000000000000000000000001a055690d9db80001", - "the blockReward out of range"), - (True, "blockRewardIncentivePercent", "0x" + str(10), "length of blockRewardIncentivePercent mismatch"), - (True, "blockRewardIncentivePercent", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "the blockRewardIncentivePercent out of range"), - (True, "blockRewardIncentivePercent", "0x0000000000000000000000000000000000000000000000000000000000000000", - "the blockRewardIncentivePercent out of range"), - (True, "blockRewardIncentivePercent", "0x0000000000000000000000000000000000000000000000000000000000000064", - "the blockRewardIncentivePercent out of range"), -]) -def update_param_failed(fake, key, value, err): - if fake: - __fake_validator_set() - with brownie.reverts(err): - validator_set_instance.updateParam(key, value) +def test_update_validator_set_default_vote_weight(set_candidate): + operators, consensuses = set_candidate + turn_round() + __fake_validator_set() + vote_address = random_vote_address() + validator_set_instance.updateValidatorSet([operators[0]], [accounts[1]], [accounts[2]], [1000], + [vote_address]) + assert validator_set_instance.getValidators() == [accounts[1]] + validator = validator_set_instance.getValidatorByConsensus(accounts[1]) + assert validator['voteWeight'] == 0 -@pytest.mark.parametrize("value,expected", [ - ("0x0000000000000000000000000000000000000000000000000000000000000000", 0), - ("0x000000000000000000000000000000000000000000000001a055690d9db80000", 30000000000000000000), - ("0x0000000000000000000000000000000000000000000000001bc16d674ec80000", 2000000000000000000), -]) -def update_param_success_with_block_reward(value, expected): +def test_update_validator_set_add_new(set_candidate, validator_set): + turn_round() + __fake_validator_set() + commission_thousandths = 400 + assert len(validator_set.getValidators()) == 3 + operate_addr_list = [account for account in accounts[:5]] + consensus_addr_list = [account for account in accounts[5:10]] + fee_addr_list = [account for account in accounts[10:15]] + vote_addr_list = [random_vote_address() for _ in range(5)] + commission_thousandths = [commission_thousandths for _ in range(5)] + validator_set.updateValidatorSet(operate_addr_list, consensus_addr_list, fee_addr_list, commission_thousandths, + vote_addr_list) + assert len(validator_set.getValidators()) == 5 + for index, address in enumerate(consensus_addr_list): + assert validator_set.getValidatorByConsensus(address) == [operate_addr_list[index], address, + fee_addr_list[index], commission_thousandths[index], + 0, vote_addr_list[index], 0] + + +def test_update_validator_set_modify_existing(validator_set): __fake_validator_set() - validator_set_instance.updateParam("blockReward", value) - assert validator_set_instance.blockReward() == expected + commission_thousandths = 400 + assert len(validator_set.getValidators()) == 5 + operate_addr_list = [account for account in accounts[:5]] + consensus_addr_list = [account for account in accounts[5:10]] + fee_addr_list = [account for account in accounts[10:15]] + vote_addr_list = [random_vote_address() for _ in range(5)] + commission_thousandths = [commission_thousandths for _ in range(5)] + validator_set.updateValidatorSet(operate_addr_list, consensus_addr_list, fee_addr_list, commission_thousandths, + vote_addr_list) + assert len(validator_set.getValidators()) == 5 + for index, address in enumerate(consensus_addr_list): + validator = [operate_addr_list[index], address, fee_addr_list[index], commission_thousandths[index], 0, + vote_addr_list[index], 0] + assert validator_set.getValidatorByConsensus(address) == validator + + +@pytest.mark.parametrize("legnth", [4, 6, 7]) +def test_vote_validator_count_mismatch(validator_set, legnth): + __fake_validator_set() + commission_thousandths = 400 + assert len(validator_set.getValidators()) == 5 + operate_addr_list = [account for account in accounts[:5]] + consensus_addr_list = [account for account in accounts[5:10]] + fee_addr_list = [account for account in accounts[10:15]] + vote_addr_list = [random_vote_address() for _ in range(legnth)] + commission_thousandths = [commission_thousandths for _ in range(5)] + with brownie.reverts("the numbers of consensusAddresses and voteAddressed should be equal"): + validator_set.updateValidatorSet(operate_addr_list, consensus_addr_list, fee_addr_list, commission_thousandths, + vote_addr_list) + + +def test_validator_count_change_update(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + assert len(validator_set.getValidators()) == 3 + vote_address = random_vote_address() + consensuses.append(register_candidate(operator=accounts[20], vote_address=vote_address)) + operators.append(accounts[20]) + tx = turn_round(consensuses) + expect_event(tx, "validatorSetUpdated") + assert validator_set.getValidators() == consensuses + # commission_thousandths is set to 1000 because there is no stake on the validator. + commission_thousandths = 1000 + assert validator_set.getValidatorByConsensus(consensuses[-1]) == [accounts[20], consensuses[-1], accounts[20], + commission_thousandths, 0, vote_address, 0] + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round(consensuses) + candidate_hub.unregister({'from': operators[0]}) + turn_round(consensuses) + assert validator_set.getValidatorByConsensus(consensuses[-1]) == [accounts[20], consensuses[-1], accounts[20], + commission_thousandths, 0, vote_address, 0] + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[0]) + + +def test_get_validators_and_vote_address_success(validator_set): + operators = [] + consensuses = [] + vote_address_list = [random_vote_address() for _ in range(3)] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + turn_round() + assert validator_set.getValidatorsAndVoteAddresses() == [consensuses, vote_address_list] + + +def test_only_gov_can_govern(validator_set): + with brownie.reverts("the msg sender must be governance contract"): + validator_set.updateParam('voteRewardPercent', + '0x0000000000000000000000000000000000000000000000000000000000000014') + + +def test_governance_var_not_exist_failed(validator_set): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(100), 64) + with brownie.reverts("UnsupportedGovParam: test1"): + validator_set.updateParam('test1', hex_value) + + +def test_value_length_invalid_failed(validator_set): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(20), 62) + with brownie.reverts("MismatchParamLength: voteRewardPercent"): + validator_set.updateParam('voteRewardPercent', hex_value) + with brownie.reverts("MismatchParamLength: blockRewardIncentivePercent"): + validator_set.updateParam('blockRewardIncentivePercent', hex_value) + + +@pytest.mark.parametrize("value", [101, 102]) +def test_percent_exceeds_max_failed(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + with brownie.reverts(f"OutOfBounds: voteRewardPercent, {value}, 0, 100"): + validator_set.updateParam('voteRewardPercent', hex_value) + with brownie.reverts(f"OutOfBounds: blockRewardIncentivePercent, {value}, 0, 100"): + validator_set.updateParam('blockRewardIncentivePercent', hex_value) + + +@pytest.mark.parametrize("value", [0, 1, 10, 50, 98, 99]) +def test_update_vote_reward_percent_success(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + validator_set.updateParam('voteRewardPercent', hex_value) + assert validator_set.voteRewardPercent() == value + + +@pytest.mark.parametrize("value", [0, 1, 10, 50, 98, 99]) +def test_update_vote_reward_percent_success(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + validator_set.updateParam('blockRewardIncentivePercent', hex_value) + assert validator_set.blockRewardIncentivePercent() == value def test_update_param_success_with_key_blockRewardIncentivePercent(): @@ -309,7 +566,7 @@ def test_distribute_reward_failed_by_address_which_is_not_candidate(): def test_distribute_reward_success_with_empty_validators(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], []) validator_set_instance.distributeReward(7) __contract_check(0, init_validator_incomes) __balance_check() @@ -359,7 +616,8 @@ def test_distribute_reward_success_with_commissionThousandths_500(): [validator['operateAddress']], [validator['consensusAddress']], [validator['feeAddress']], - [commission] + [commission], + [validator['voteAddr']] ) validator_set_instance.deposit(init_validators[0], {'value': value}) @@ -378,6 +636,399 @@ def test_distribute_reward_success_with_commissionThousandths_500(): assert brownie.web3.eth.get_balance(validator['feeAddress']) == expect_reward +def test_distribute_vote_reward_success(validator_set, system_reward, deposit_for_reward): + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators = [] + consensuses = [] + fee_address = accounts[6:9] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, fee_address=fee_address[index])) + delegate_coin_success(operator, accounts[99], coin_value) + weights = [10, 20, 30] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + __fake_validator_set() + trackers = get_trackers(fee_address) + block_reward = 30000 + tx = validator_set.distributeReward(8) + receive_reward = block_reward * validator_set.blockRewardIncentivePercent() // 100 + expect_event(tx, "receiveDeposit", { + 'from': validator_set.address, + 'amount': receive_reward * len(operators) + }) + assert system_reward_tracker.delta() == receive_reward * len(operators) + validator_reward = (block_reward - receive_reward) // 2 + + fee_amount = [] + for i in range(len(operators)): + expect_event(tx, "directTransfer", { + 'operateAddress': operators[i], + 'validator': fee_address[i], + 'amount': validator_reward - (validator_reward * 10 // 100), + 'totalReward': block_reward - receive_reward + }, idx=i) + fee_amount.append(validator_reward - (validator_reward * 10 // 100)) + vote_reward = validator_reward * validator_set.voteRewardPercent() // 100 * 3 + vote_amount = [675, 1350, 2025] + for i in range(len(operators)): + expect_event(tx, "voteRewardTransfer", { + 'operateAddress': operators[i], + 'validator': fee_address[i], + 'amount': vote_reward * weights[i] // sum(weights) + }, idx=i) + assert vote_reward * weights[i] // sum(weights) == vote_amount[i] + fee_amount[i] += vote_amount[i] + for index, tracker in enumerate(trackers): + assert tracker.delta() == fee_amount[index] + + +def test_zero_vote_weight_sum_zero_reward_success(validator_set, system_reward, set_candidate, deposit_for_reward): + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], coin_value) + weights = [0, 0, 0] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + __fake_validator_set() + trackers = get_trackers(operators) + income = 30000 + temp_income = income - (block_reward * validator_set.blockRewardIncentivePercent() // 100) + tx = validator_set.distributeReward(get_current_round()) + assert 'voteRewardTransfer' not in tx.events + for index, tracker in enumerate(trackers): + assert tracker.delta() == temp_income // 2 + + +def test_distribute_reward_by_weight_proportion_success(validator_set, system_reward, set_candidate, + deposit_for_reward): + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], coin_value) + weights0 = [10, 20, 90] + weights1 = [25, 30, 40] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights0, {"from": accounts[99]}) + validator_set.vote(consensuses[:-1], weights1[:-1], {"from": accounts[99]}) + validator_set.vote(consensuses[:1], weights1[:1], {"from": accounts[99]}) + validator_set.vote(consensuses, weights1, {"from": accounts[99]}) + vote_weight = [85, 80, 130] + for index, consensus in enumerate(consensuses): + assert validator_set.getValidatorByConsensus(consensus).dict()['voteWeight'] == vote_weight[index] + __fake_validator_set() + trackers = get_trackers(operators) + income = 30000 + temp_income = income - (block_reward * validator_set.blockRewardIncentivePercent() // 100) + validator_reward = temp_income // 2 + vote_reward = validator_reward * validator_set.voteRewardPercent() // 100 + tx = validator_set.distributeReward(get_current_round()) + for index, tracker in enumerate(trackers): + validator_fee = validator_reward - vote_reward + vote_fee = vote_reward * len(operators) * vote_weight[index] // sum(vote_weight) + assert tracker.delta() == validator_fee + vote_fee + + +def test_vote_reward_as_validator_portion_success(validator_set, system_reward, deposit_for_reward): + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators = [] + consensuses = [] + for operator in accounts[5:8]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator, commission=800)) + delegate_coin_success(operator, accounts[99], coin_value) + weights0 = [10, 20, 50] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights0, {"from": accounts[99]}) + __fake_validator_set() + trackers = get_trackers(operators) + temp_income = block_reward - (block_reward * validator_set.blockRewardIncentivePercent() // 100) + validator_reward = temp_income * 800 // 1000 + vote_reward = validator_reward * validator_set.voteRewardPercent() // 100 + validator_set.distributeReward(get_current_round()) + for index, tracker in enumerate(trackers): + validator_fee = validator_reward - vote_reward + vote_fee = vote_reward * len(operators) * weights0[index] // sum(weights0) + assert tracker.delta() == validator_fee + vote_fee + + +@pytest.mark.parametrize("vote_reward_percent", [0, 10, 20, 50, 80, 99, 100]) +def test_vote_reward_after_update_success(validator_set, set_candidate, vote_reward_percent, deposit_for_reward): + hex_value = padding_left(Web3.to_hex(vote_reward_percent), 64) + execute_proposal( + validator_set.address, + 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ['voteRewardPercent', Web3.to_bytes(hexstr=hex_value)]), + "update felonyThreshold" + ) + assert validator_set.getVoteRewardPercent() == vote_reward_percent + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], coin_value) + weights = [10, 20, 30] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + __fake_validator_set() + trackers = get_trackers(operators) + temp_income = block_reward - (block_reward * validator_set.blockRewardIncentivePercent() // 100) + validator_reward = temp_income // 2 + vote_reward = validator_reward * vote_reward_percent // 100 + tx = validator_set.distributeReward(get_current_round()) + if vote_reward_percent == 0: + assert 'voteRewardTransfer' not in tx.events + else: + assert 'voteRewardTransfer' in tx.events + for index, tracker in enumerate(trackers): + validator_fee = validator_reward - vote_reward + vote_fee = vote_reward * len(operators) * weights[index] // sum(weights) + assert tracker.delta() == validator_fee + vote_fee + + +def test_zero_weight_validator_gets_no_reward_success(validator_set, set_candidate, deposit_for_reward): + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], coin_value) + weights = [0, 20, 30] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + __fake_validator_set() + trackers = get_trackers(operators) + validator_set.distributeReward(get_current_round()) + actual_validator_fee = [12150, 13770, 14580] + for index, tracker in enumerate(trackers): + assert tracker.delta() == actual_validator_fee[index] + + +def test_vote_non_current_validator(validator_set, set_candidate, deposit_for_reward): + coin_value = 10000 + validator_set.updateBlockReward(20000) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], coin_value) + weights = [0, 20, 30] + turn_round() + for consensus in consensuses: + validator_set.deposit(consensus, {"value": 10000, "from": accounts[99]}) + consensus = register_candidate(operator=accounts[10]) + for i in range(10, 12): + operators.append(accounts[i]) + consensuses.append(consensus) + consensuses.append(accounts[11]) + weights.append(30) + weights.append(50) + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + __fake_validator_set() + trackers = get_trackers(operators) + validator_set.distributeReward(get_current_round()) + actual_validator_fee = [12150, 13770, 14580, 0, 0] + for index, tracker in enumerate(trackers): + assert tracker.delta() == actual_validator_fee[index] + + +def test_vote_weight_clears_next_round(validator_set, candidate_hub, set_candidate, deposit_for_reward): + coin_value = 10000 + new_block_reward = 20000 + validator_set.updateBlockReward(new_block_reward) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], coin_value) + weights = [10, 20, 30] + turn_round() + trackers = get_trackers(operators) + tx = turn_round(consensuses, weights=weights) + total_reward = (new_block_reward + 100) * 90 // 100 + assert tx.events['directTransfer']['totalReward'] == total_reward + validator_fee = total_reward // 2 + total_reward -= validator_fee + assert tx.events['directTransfer']['amount'] == total_reward - total_reward * 10 // 100 + actual_validator_fee = [452, 904, 1356] + for index, tracker in enumerate(trackers): + assert tracker.delta() == (total_reward - total_reward * 10 // 100) + actual_validator_fee[index] + tx = candidate_hub.turnRound() + assert 'voteRewardTransfer' not in tx.events + for consensus in consensuses: + validator = validator_set.getValidatorByConsensus(consensus) + assert validator['voteWeight'] == 0 + + +def test_update_validator_fee_vote_reward(validator_set, candidate_hub, deposit_for_reward): + coin_value = 10000 + new_block_reward = 20000 + validator_set.updateBlockReward(new_block_reward) + operators = [] + consensuses = [] + commission_fee = [200, 500, 600] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, commission=commission_fee[index])) + delegate_coin_success(operator, accounts[99], coin_value) + weights = [10, 20, 30] + turn_round() + total_reward = 30000 * 90 // 100 + vote_fee = [] + for i in range(len(operators)): + vote_fee.append(total_reward * commission_fee[i] // 1000) + total_vote_reward = 0 + tx = turn_round(consensuses, weights=weights, tx_fee=10000) + for i in range(len(operators)): + amount = vote_fee[i] * 90 // 100 + expect_event(tx, 'directTransfer', { + 'amount': amount + }, idx=i) + total_vote_reward += vote_fee[i] - amount + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_vote_reward * weights[i] // sum(weights) + }, idx=i) + + +def test_vote_no_stake_calculation(validator_set, set_candidate, candidate_hub, deposit_for_reward): + new_block_reward = 20000 + validator_set.updateBlockReward(new_block_reward) + operators, consensuses = set_candidate + weights = [10, 20, 30] + turn_round() + tx = turn_round(consensuses, weights=weights) + total_reward = (new_block_reward + 100) * 90 // 100 * 10 // 100 * 3 + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_reward * weights[i] // sum(weights) + }, idx=i) + + +def test_single_vote_distribution(validator_set, candidate_hub, set_candidate, deposit_for_reward): + new_block_reward = 20000 + delegate_amount = 10000 + validator_set.updateBlockReward(new_block_reward) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], delegate_amount) + weights = [10, 20, 30] + turn_round() + for miner in consensuses: + validator_set.deposit(miner, {"value": 10000, "from": accounts[99]}) + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + tx = candidate_hub.turnRound() + vote_amount = [675, 1350, 2025] + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': vote_amount[i] + }, idx=i) + turn_round(consensuses) + + +def test_vote_new_validator_added(validator_set, set_candidate, deposit_for_reward): + new_block_reward = 20000 + delegate_amount = 10000 + validator_set.updateBlockReward(new_block_reward) + weights = [10, 20, 30] + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], delegate_amount) + turn_round() + consensuses.append(register_candidate(operator=accounts[10])) + operators.append(accounts[10]) + delegate_coin_success(accounts[10], accounts[99], delegate_amount) + weights.append(30) + tx = turn_round(consensuses, weights=weights, round_count=2, tx_fee=10000) + total_vote_fee = 5400 + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_vote_fee * weights[i] // sum(weights) + }, idx=i) + + +def test_vote_large_weight_gap(validator_set, set_candidate, deposit_for_reward): + new_block_reward = 20000 + delegate_amount = 10000 + validator_set.updateBlockReward(new_block_reward) + weights = [1, 1000000, 1000000] + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], delegate_amount) + turn_round() + tx = turn_round(consensuses, weights=weights, tx_fee=10000) + total_vote_fee = 4050 + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_vote_fee * weights[i] // sum(weights) + }, idx=i) + turn_round(consensuses) + + +def test_zero_vote_and_percent(validator_set, candidate_hub, set_candidate): + hex_value = padding_left(Web3.to_hex(0), 64) + execute_proposal( + validator_set.address, + 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ['voteRewardPercent', Web3.to_bytes(hexstr=hex_value)]), + "update felonyThreshold" + ) + assert validator_set.getVoteRewardPercent() == 0 + new_block_reward = 20000 + delegate_amount = 10000 + validator_set.updateBlockReward(new_block_reward) + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], delegate_amount) + turn_round() + for miner in consensuses: + validator_set.deposit(miner, {"value": 10000, "from": accounts[99]}) + tx = candidate_hub.turnRound() + assert 'voteRewardTransfer' not in tx.events + turn_round(consensuses) + + +def test_vote_reward_percent_max(validator_set, set_candidate, deposit_for_reward): + hex_value = padding_left(Web3.to_hex(100), 64) + execute_proposal( + validator_set.address, + 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ['voteRewardPercent', Web3.to_bytes(hexstr=hex_value)]), + "update felonyThreshold" + ) + new_block_reward = 20000 + delegate_amount = 10000 + validator_set.updateBlockReward(new_block_reward) + weights = [30, 50, 70] + operators, consensuses = set_candidate + for operator in operators: + delegate_coin_success(operator, accounts[99], delegate_amount) + turn_round() + validator_set.vote(consensuses, weights, {"from": accounts[99]}) + tx = turn_round(consensuses, weights=weights, tx_fee=10000) + total_vote_fee = 13500 * 3 + for i in range(len(operators)): + expect_event(tx, 'voteRewardTransfer', { + 'amount': total_vote_fee * weights[i] // sum(weights) + }, idx=i) + turn_round(consensuses) + + def test_misdemeanor_failed_with_address_which_is_not_slash(): with brownie.reverts("the msg sender must be slash contract"): validator_set_instance.misdemeanor(init_validators[0]) @@ -385,7 +1036,7 @@ def test_misdemeanor_failed_with_address_which_is_not_slash(): def test_misdemeanor_failed_with_after_set_empty_validator_set(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], []) __update_slash_address() validator = validator_set_instance.getValidatorByConsensus(init_validators[0]).dict() tx = validator_set_instance.misdemeanor(init_validators[0]) @@ -397,14 +1048,15 @@ def test_misdemeanor_failed_with_after_set_empty_validator_set(): def test_misdemeanor_return_empty_with_empty_validator_set_and_ZERO_ADDRESS(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], []) __update_slash_address() assert validator_set_instance.misdemeanor.call(ZERO_ADDRESS) == () def test_misdemeanor_return_empty_with_only_one_validator_set_and_0_income(): __fake_validator_set() - validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100]) + validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], + [random_vote_address()]) __update_slash_address() assert validator_set_instance.misdemeanor.call(init_validators[0]) == () __contract_check(0, [0]) @@ -412,7 +1064,8 @@ def test_misdemeanor_return_empty_with_only_one_validator_set_and_0_income(): def test_misdemeanor_return_empty_with_only_one_validator_set(): __fake_validator_set() - validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100]) + validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], + [random_vote_address()]) deposit_value = 1000000000 expect_event(validator_set_instance.deposit(init_validators[0], {'value': deposit_value}), "validatorDeposit", { @@ -456,7 +1109,8 @@ def test_misdemeanor_return_empty_with_ZERO_ADDRESS_validator(): def test_felony_failed_with_one_validator_which_has_0_income(): __fake_validator_set() - validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100]) + validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], + [random_vote_address()]) __update_slash_address() assert validator_set_instance.felony.call(init_validators[0], felony_round, felony_deposit) == () __contract_check(0, [0]) @@ -464,7 +1118,8 @@ def test_felony_failed_with_one_validator_which_has_0_income(): def test_felony_failed_with_one_validator_which_has_income(): __fake_validator_set() - validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100]) + validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], + [random_vote_address()]) deposit_value = 1000000000 validator_set_instance.deposit(init_validators[0], {'value': deposit_value}) __update_slash_address() @@ -475,11 +1130,14 @@ def test_felony_failed_with_one_validator_which_has_income(): def test_felony_success_with_validator_set_which_has_0_income(candidate_hub): - candidate_hub.register(accounts[0], accounts[0], 100, {'from': accounts[0], 'value': Web3.to_wei(20000, 'ether')}) - candidate_hub.register(accounts[1], accounts[1], 100, {'from': accounts[1], 'value': Web3.to_wei(20000, 'ether')}) + candidate_hub.register(accounts[0], accounts[0], 100, random_vote_address(), + {'from': accounts[0], 'value': Web3.to_wei(20000, 'ether')}) + candidate_hub.register(accounts[1], accounts[1], 100, random_vote_address(), + {'from': accounts[1], 'value': Web3.to_wei(20000, 'ether')}) __fake_validator_set() validator_set_instance.updateValidatorSet([accounts[0], accounts[1]], [accounts[0], accounts[1]], - [accounts[0], accounts[1]], [100, 100]) + [accounts[0], accounts[1]], [100, 100], + [random_vote_address(), random_vote_address()]) __update_slash_address() candidate = candidate_hub.candidateSet(0).dict() @@ -505,11 +1163,14 @@ def test_felony_success_with_validator_set_which_has_0_income(candidate_hub): def test_felony_success_with_validator_set_which_has_income(candidate_hub): - candidate_hub.register(accounts[0], accounts[0], 100, {'from': accounts[0], 'value': Web3.to_wei(20000, 'ether')}) - candidate_hub.register(accounts[1], accounts[1], 100, {'from': accounts[1], 'value': Web3.to_wei(20000, 'ether')}) + candidate_hub.register(accounts[0], accounts[0], 100, random_vote_address(), + {'from': accounts[0], 'value': Web3.to_wei(20000, 'ether')}) + candidate_hub.register(accounts[1], accounts[1], 100, random_vote_address(), + {'from': accounts[1], 'value': Web3.to_wei(20000, 'ether')}) __fake_validator_set() validator_set_instance.updateValidatorSet([accounts[0], accounts[1]], [accounts[0], accounts[1]], - [accounts[0], accounts[1]], [100, 100]) + [accounts[0], accounts[1]], [100, 100], + [random_vote_address(), random_vote_address()]) __update_slash_address() deposit_value = 1000000000 diff --git a/tests/utils.py b/tests/utils.py index c6533ff81..75a790640 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,9 +1,12 @@ import codecs import hashlib +import os import random import binascii import ecdsa from _sha256 import sha256 + +from blspy import AugSchemeMPL from web3 import Web3 from brownie.network.transaction import TransactionReceipt from brownie.network.account import LocalAccount @@ -18,12 +21,24 @@ def random_address(): return Account.create(str(random.random())).address +def random_vote_address(): + seed = os.urandom(32) + sk = AugSchemeMPL.key_gen(seed) + pk = sk.get_g1() + vote_address = bytes(pk) + return Web3.to_hex(vote_address) + + def random_btc_tx_id(): rand_bytes = random.randbytes(32) tx_id = hashlib.sha256(sha256(rand_bytes).digest()).hexdigest() return '0x' + tx_id +def random_bytes_data(length=32): + return os.urandom(length).hex() + + def generate_private_key(): sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1) private_key_hex = sk.to_string().hex() From 67a2feeb3deb47e62c605e0492c4d735b18b53b0 Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Tue, 8 Jul 2025 14:07:44 +0800 Subject: [PATCH 05/27] add test cases for bep410 --- contracts/mock/BitcoinStakeMock.sol | 2 +- contracts/mock/CandidateHubMock.sol | 88 +++- tests/test_candidate_hub.py | 606 +++++++++++++++++++++------- tests/test_scenario.py | 2 +- 4 files changed, 535 insertions(+), 163 deletions(-) diff --git a/contracts/mock/BitcoinStakeMock.sol b/contracts/mock/BitcoinStakeMock.sol index fcb522b1d..5155920a5 100644 --- a/contracts/mock/BitcoinStakeMock.sol +++ b/contracts/mock/BitcoinStakeMock.sol @@ -73,7 +73,7 @@ contract BitcoinStakeMock is BitcoinStake { function getRound2expireInfoMap(uint256 round) external view returns (address[] memory candidateList, uint256[] memory amounts) { ExpireInfo storage expireInfo = round2expireInfoMap[round]; - address[] memory candidateList = expireInfo.candidateList; + candidateList = expireInfo.candidateList; amounts = new uint256[](candidateList.length); for (uint256 i = 0; i < candidateList.length; i++) { amounts[i] = expireInfo.amountMap[candidateList[i]]; diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index 8b06a00c7..ec8757105 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -120,12 +120,12 @@ contract CandidateHubMock is CandidateHub { return consensusMap[consensusAddr]; } - function getScoreMock(address[] memory candidates, uint256 round) external returns (uint256[] memory scores) { - scores = IStakeHub(STAKE_HUB_ADDR).getHybridScore( + function getScoreMock(address[] memory candidates, uint256 round) external returns (uint256[] memory hybridScores) { + hybridScores = IStakeHub(STAKE_HUB_ADDR).getHybridScore( candidates, round ); - return scores; + return hybridScores; } function getScores() external view returns (uint256[] memory) { @@ -136,7 +136,7 @@ contract CandidateHubMock is CandidateHub { address[] memory candidateList, uint256[] memory scoreList, uint256 count - ) public pure returns (address[] memory validatorList) { + ) public view returns (address[] memory validatorList) { return getValidators(candidateList, scoreList, count); } @@ -153,17 +153,19 @@ contract CandidateHubMock is CandidateHub { ) external payable onlyInit { uint256 status = SET_CANDIDATE; candidateSet.push( - Candidate( - operateAddr, - consensusAddr, - feeAddr, - commissionThousandths, - msg.value, - status, - roundTag, - commissionThousandths, - voteAddr - ) + Candidate({ + operateAddr: operateAddr, + consensusAddr: consensusAddr, + feeAddr: feeAddr, + commissionThousandths: commissionThousandths, + margin: msg.value, + status: status, + commissionLastChangeRound: roundTag, + commissionLastRoundValue: commissionThousandths, + voteAddr: voteAddr, + agent: address(0), + description: Description("", "", "", "") + }) ); uint256 index = candidateSet.length; operateMap[operateAddr] = index; @@ -185,6 +187,54 @@ contract CandidateHubMock is CandidateHub { super.nextRound(); } } + function getValidatorsOld(address[] memory candidateList, uint256[] memory scoreList, uint256 count) internal pure returns (address[] memory validatorList){ + uint256 candidateSize = candidateList.length; + // quicksort by scores O(nlogk) + uint256 l = 0; + uint256 r = 0; + if (count < candidateSize) { + r = candidateSize - 1; + } else { + count = candidateSize; + } + while (l < r) { + // partition + uint256 ll = l; + uint256 rr = r; + address back = candidateList[ll]; + uint256 p = scoreList[ll]; + while (ll < rr) { + while (ll < rr && scoreList[rr] < p) { + rr = rr - 1; + } + candidateList[ll] = candidateList[rr]; + scoreList[ll] = scoreList[rr]; + while (ll < rr && scoreList[ll] >= p) { + ll = ll + 1; + } + candidateList[rr] = candidateList[ll]; + scoreList[rr] = scoreList[ll]; + } + candidateList[ll] = back; + scoreList[ll] = p; + uint256 mid = ll; + // sub sort + if (mid < count) { + l = mid + 1; + } else if (mid > count) { + r = mid - 1; + } else { + break; + } + } + uint256 d = candidateSize - count; + if (d != 0) { + assembly { + mstore(candidateList, sub(mload(candidateList), d)) + } + } + return candidateList; + } /// The `turn round` workflowf /// @dev this method is called by Golang consensus engine at the end of a round function turnRoundOld() external onlyCoinbase onlyInit onlyZeroGasPrice { @@ -239,9 +289,9 @@ contract CandidateHubMock is CandidateHub { // calculate the hybrid score for all valid candidates and // choose top ones to form the validator set of the new round - (uint256[] memory scores) = - IPledgeAgentMock(PLEDGE_AGENT_ADDR).getHybridScoreOld(candidates, powers, roundTag); - address[] memory validatorList = getValidators(candidates, scores, validatorCount); + uint256[] memory hybridScores; + (hybridScores) = IPledgeAgentMock(PLEDGE_AGENT_ADDR).getHybridScoreOld(candidates, powers, roundTag); + address[] memory validatorList = getValidatorsOld(candidates, hybridScores, validatorCount); // prepare arguments, and notify ValidatorSet contract uint256 totalCount = validatorList.length; @@ -254,7 +304,7 @@ contract CandidateHubMock is CandidateHub { Candidate storage c = candidateSet[index - 1]; consensusAddrList[i] = c.consensusAddr; feeAddrList[i] = c.feeAddr; - if (scores[i] == 0) { + if (hybridScores[i] == 0) { commissionThousandthsList[i] = 1000; } else { commissionThousandthsList[i] = c.commissionThousandths; diff --git a/tests/test_candidate_hub.py b/tests/test_candidate_hub.py index 198a1f60c..1c5daa115 100644 --- a/tests/test_candidate_hub.py +++ b/tests/test_candidate_hub.py @@ -3,12 +3,13 @@ import brownie from web3 import Web3 from eth_account import Account -from brownie import accounts, UnRegisterReentry, chain +from brownie import accounts, UnRegisterReentry from brownie.test import given, strategy from brownie.network.transaction import Status, TransactionReceipt +from tests.delegate import delegate_btc_success, delegate_coin_success from .constant import Utils from .utils import random_address, expect_event, padding_left, update_system_contract_address -from .common import register_candidate, turn_round, get_candidate, get_current_round, random_vote_address +from .common import * @pytest.fixture(scope="module") @@ -479,6 +480,8 @@ def test_registration_index_correct_after_success(candidate_hub, required_margin get_current_round(), commission_last_round, vote_address, + ZERO_ADDRESS, + ("", "", "", "") ) assert candidate_hub.operateMap(accounts[1]) == 1 assert candidate_hub.getConsensusMap(consensus_addr) == 1 @@ -983,145 +986,6 @@ def test_unregister_candidate(candidate_hub, required_margin): }) -def test_update_candidate(candidate_hub, required_margin): - consensus_address = random_address() - fee_address = random_address() - max_commission_change = candidate_hub.maxCommissionChange() - - tests = [ - (accounts[1], None, consensus_address, fee_address, 100, False, "candidate does not exist", None), - (accounts[2], True, consensus_address, fee_address, 0, False, "commissionThousandths should in range (0, 1000)", - None), - (accounts[3], True, random_address(), fee_address, 1000, False, - "commissionThousandths should in range (0, 1000)", None), - (accounts[3], None, consensus_address, fee_address, 100, False, "the consensus already exists", None), - (accounts[3], None, random_address(), fee_address, 201 + max_commission_change, False, - "commissionThousandths out of adjustment range", None), - (accounts[3], None, random_address(), fee_address, 199 - max_commission_change, False, - "commissionThousandths out of adjustment range", None), - (accounts[3], None, random_address(), fee_address, 200 + max_commission_change, True, "", None), - (accounts[3], None, random_address(), fee_address, 200 - max_commission_change, True, "", None), - (accounts[3], None, random_address(), fee_address, 200 + max_commission_change, True, "", None), - (accounts[3], None, random_address(), fee_address, 200 + max_commission_change * 2, True, "", True), - ] - i = 0 - for operate_addr, register, consensus_addr, fee_addr, commission, ret, err, need_turn_round in tests: - if need_turn_round: - turn_round() - i += 1 - vote_address0 = random_vote_address() - vote_address1 = random_vote_address() - vote_address2 = random_vote_address() - if register: - if consensus_addr is None: - consensus_addr = random_address() - candidate_hub.register(consensus_addr, fee_addr, 200, vote_address0, - {'from': operate_addr, 'value': required_margin}) - if consensus_addr is None: - consensus_addr = consensus_address - if ret is False: - with brownie.reverts(err): - candidate_hub.update(consensus_addr, fee_addr, commission, vote_address1, {'from': operate_addr}) - else: - tx = candidate_hub.update(consensus_addr, fee_addr, commission, vote_address2, - {'from': operate_addr}) - expect_event(tx, "updated", { - "operateAddr": operate_addr, - "consensusAddr": consensus_addr, - "feeAddress": fee_addr, - "commissionThousandths": commission, - "voteAddr": vote_address2 - }) - - -def test_update_success(candidate_hub, required_margin): - consensus_address = random_address() - fee_address = random_address() - vote_address0 = random_vote_address() - round_value = 200 - candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, - {'from': accounts[0], 'value': required_margin}) - turn_round() - consensus_ = accounts[1] - fee_address_ = accounts[2] - vote_address1 = '0x938821669157ba8ed89e2cdd3956cadb9242c2ed8467bc322f636bb3bb5aeff4020e98dd949e47c081df84688c6cb881' - candidate_hub.update(consensus_, fee_address_, 300, vote_address1, - {'from': accounts[0]}) - state = 17 - assert candidate_hub.candidateSet(0) == ( - accounts[0], consensus_, fee_address_, 300, required_margin, state, get_current_round(), round_value, - vote_address1) - - -def test_update_vote_addr_invalid_length_failed(candidate_hub, required_margin): - consensus_address = random_address() - fee_address = random_address() - vote_address0 = random_vote_address() - round_value = 200 - candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, - {'from': accounts[0], 'value': required_margin}) - consensus_ = accounts[1] - fee_address_ = accounts[2] - error_vote_address1 = '0x938821669157ba8ed89e2cdd3956cadb9242c2ed8467bc322f636bb3bb5aeff4020e98dd949e47c081df84688c6cb8' - with brownie.reverts("vote address length should be 48"): - candidate_hub.update(consensus_, fee_address_, 300, error_vote_address1, - {'from': accounts[0]}) - - -def test_self_vote_addr_duplicate_failed(candidate_hub, required_margin): - consensus_address = random_address() - fee_address = random_address() - vote_address0 = random_vote_address() - round_value = 200 - candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, - {'from': accounts[0], 'value': required_margin}) - consensus_ = accounts[1] - fee_address_ = accounts[2] - candidate_hub.update(consensus_, fee_address_, 300, vote_address0, - {'from': accounts[0]}) - state = 1 - assert candidate_hub.candidateSet(0) == ( - accounts[0], consensus_, fee_address_, 300, required_margin, state, get_current_round(), round_value, - vote_address0) - - -def test_update_vote_addr_list_contains_duplicates_failed(candidate_hub, required_margin): - vote_address_list = [] - round_value = 200 - for i in range(5): - vote_address = random_vote_address() - candidate_hub.register(random_address(), random_address(), round_value, vote_address, - {'from': accounts[i], 'value': required_margin}) - vote_address_list.append(vote_address) - consensus_ = accounts[6] - fee_address_ = accounts[7] - for i in range(1, 5): - with brownie.reverts(f"vote address already exists"): - candidate_hub.update(consensus_, fee_address_, 300, vote_address_list[i], - {'from': accounts[0]}) - - -def test_update_single_address_repeated_changes_success(candidate_hub, required_margin): - consensus_address = random_address() - fee_address = random_address() - vote_address0 = random_vote_address() - round_value = 200 - candidate_hub.register(consensus_address, fee_address, round_value, vote_address0, - {'from': accounts[0], 'value': required_margin}) - new_consensus_address = random_address() - assert candidate_hub.candidateSet(0)['consensusAddr'] == consensus_address - candidate_hub.update(new_consensus_address, fee_address, round_value, random_vote_address(), {'from': accounts[0]}) - assert candidate_hub.candidateSet(0)['consensusAddr'] == new_consensus_address - new_vote_address = random_vote_address() - candidate_hub.update(new_consensus_address, fee_address, round_value, new_vote_address, {'from': accounts[0]}) - assert candidate_hub.candidateSet(0)['voteAddr'] == new_vote_address - new_fee_address = random_address() - candidate_hub.update(new_consensus_address, new_fee_address, round_value, new_vote_address, {'from': accounts[0]}) - assert candidate_hub.candidateSet(0)['consensusAddr'] == new_consensus_address - assert candidate_hub.candidateSet(0)['feeAddr'] == new_fee_address - assert candidate_hub.candidateSet(0)['voteAddr'] == new_vote_address - - def test_add_margin(candidate_hub, required_margin): fee_address = random_address() @@ -1164,6 +1028,7 @@ def test_add_margin(candidate_hub, required_margin): assert candidate_hub.getCandidate(operate_addr).dict()['status'] == status +# getValidators def test_get_validators(candidate_hub): candidates = [] score_list1 = [] @@ -1185,7 +1050,6 @@ def test_get_validators(candidate_hub): (candidates[:10], score_list1[:10], indexes[:10], 21, 10), (candidates[:10], score_list2[:10], indexes[:10], 21, 10), ] - for candidate_list, score_list, index_list, count, expect_count in tests: validator_list = candidate_hub.getValidatorsMock(candidate_list, score_list, count) index_list.sort(key=lambda e: score_list[e], reverse=True) @@ -1314,3 +1178,461 @@ def __delegate_coin_success(core_agent, agent, delegator, old_value, new_value): "amount": new_value, "realtimeAmount": new_value + old_value }) + + +# updateAgent +def test_update_agent_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.updateAgent(accounts[1], {'from': accounts[1]}) + + +def test_update_agent_success(candidate_hub, accounts): + operator = accounts[5] + new_agent = accounts[6] + + consensus_addr = accounts[7] + fee_addr = accounts[8] + vote_addr = random_vote_address() + + candidate_hub.register( + consensus_addr, + fee_addr, + 100, # commissionThousandths + vote_addr, + {'from': operator, 'value': 1e18} + ) + + tx = candidate_hub.updateAgent(new_agent, {'from': operator}) + + assert len(tx.events['AgentUpdated']) > 0 + assert tx.events['AgentUpdated']['operateAddr'] == operator + assert tx.events['AgentUpdated']['newAgent'] == new_agent + + assert candidate_hub.agentMap(new_agent) != 0 + + +def test_update_agent_zero_address(candidate_hub, accounts): + operator = accounts[5] + + consensus_addr = accounts[7] + fee_addr = accounts[8] + vote_addr = random_vote_address() + + candidate_hub.register( + consensus_addr, + fee_addr, + 100, + vote_addr, + {'from': operator, 'value': 1e18} + ) + + with brownie.reverts("agent address cannot be zero"): + candidate_hub.updateAgent(ZERO_ADDRESS, {'from': operator}) + + +def test_update_agent_already_exists(candidate_hub): + operator1 = accounts[5] + operator2 = accounts[6] + agent = accounts[7] + + candidate_hub.register( + accounts[8], + accounts[9], + 100, + random_vote_address(), + {'from': operator1, 'value': 1e18} + ) + + candidate_hub.register( + accounts[10], + accounts[11], + 100, + random_vote_address(), + {'from': operator2, 'value': 1e18} + ) + + candidate_hub.updateAgent(agent, {'from': operator1}) + + with brownie.reverts("agent address already exists"): + candidate_hub.updateAgent(agent, {'from': operator2}) + candidate_hub.updateAgent(accounts[13], {'from': operator2}) + assert candidate_hub.agentMap(accounts[13]) == 2 + + +def test_update_agent_multiple_times(candidate_hub, accounts): + operator = accounts[5] + first_agent = accounts[6] + second_agent = accounts[7] + + candidate_hub.register( + accounts[8], + accounts[9], + 100, + random_vote_address(), + {'from': operator, 'value': 1e18} + ) + + tx1 = candidate_hub.updateAgent(first_agent, {'from': operator}) + assert len(tx1.events['AgentUpdated']) > 0 + assert candidate_hub.agentMap(first_agent) != 0 + + tx2 = candidate_hub.updateAgent(second_agent, {'from': operator}) + assert len(tx2.events['AgentUpdated']) > 0 + assert candidate_hub.agentMap(second_agent) != 0 + assert candidate_hub.agentMap(first_agent) == 0 + + +# removeAgent +def test_remove_agent_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.removeAgent({'from': accounts[1]}) + + +def test_remove_agent_no_agent(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator) + + with brownie.reverts("agent address does not exist"): + candidate_hub.removeAgent({'from': operator}) + + +def test_remove_agent_success(candidate_hub, accounts): + operator = accounts[5] + agent = accounts[6] + register_candidate(operator=operator) + + candidate_hub.updateAgent(agent, {'from': operator}) + assert candidate_hub.agentMap(agent) != 0 + + candidate_hub.removeAgent({'from': operator}) + assert candidate_hub.agentMap(agent) == 0 + assert candidate_hub.candidateSet(0).dict()['agent'] == ZERO_ADDRESS + + +# editConsensusAddress +def test_edit_consensus_address_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.editConsensusAddress(accounts[1], {'from': accounts[1]}) + + +def test_edit_consensus_address_duplicate(candidate_hub, accounts): + operator1 = accounts[5] + operator2 = accounts[6] + consensus1 = register_candidate(operator=operator1) + consensus2 = register_candidate(operator=operator2) + + with brownie.reverts("consensus already exists"): + candidate_hub.editConsensusAddress(consensus2, {'from': operator1}) + + +def test_edit_consensus_address_success(candidate_hub, accounts): + operator = accounts[5] + old_consensus = register_candidate(operator=operator) + new_consensus = accounts[7] + + tx = candidate_hub.editConsensusAddress(new_consensus, {'from': operator}) + + assert 'ConsensusAddressEdited' in tx.events + assert tx.events['ConsensusAddressEdited']['operateAddr'] == operator + assert tx.events['ConsensusAddressEdited']['newConsensusAddr'] == new_consensus + assert candidate_hub.isCandidateByConsensus(old_consensus) is False + assert candidate_hub.isCandidateByConsensus(new_consensus) + assert candidate_hub.candidateSet( + 0).dict()['consensusAddr'] == new_consensus + + +def test_edit_consensus_address_by_agent_success(candidate_hub, accounts): + operator = accounts[5] + agent = accounts[6] + old_consensus = register_candidate(operator=operator) + new_consensus = accounts[7] + + candidate_hub.updateAgent(agent, {'from': operator}) + + tx = candidate_hub.editConsensusAddress(new_consensus, {'from': agent}) + + assert 'ConsensusAddressEdited' in tx.events + + +# editCommissionRate +def test_edit_commission_rate_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.editCommissionRate(100, {'from': accounts[1]}) + + +def test_edit_commission_rate_range(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator) + + with brownie.reverts("commissionThousandths should in range (0, 1000)"): + candidate_hub.editCommissionRate(0, {'from': operator}) + + with brownie.reverts("commissionThousandths should in range (0, 1000)"): + candidate_hub.editCommissionRate(1000, {'from': operator}) + + +def test_edit_commission_rate_adjustment_range(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator, commission=500) + max_change = candidate_hub.maxCommissionChange() + + with brownie.reverts("commissionThousandths out of adjustment range"): + candidate_hub.editCommissionRate( + 500 + max_change + 1, {'from': operator}) + + with brownie.reverts("commissionThousandths out of adjustment range"): + candidate_hub.editCommissionRate( + 500 - max_change - 1, {'from': operator}) + tx = candidate_hub.editCommissionRate(500 + max_change, {'from': operator}) + assert 'CommissionRateEdited' in tx.events + with brownie.reverts("commissionThousandths out of adjustment range"): + candidate_hub.editCommissionRate( + 500 + max_change + 1, {'from': operator}) + + +def test_edit_commission_rate_by_agent_success(candidate_hub, accounts): + operator = accounts[5] + agent = accounts[6] + register_candidate(operator=operator, commission=500) + new_rate = 600 + + candidate_hub.updateAgent(agent, {'from': operator}) + + tx = candidate_hub.editCommissionRate(new_rate, {'from': agent}) + assert 'CommissionRateEdited' in tx.events + + +def test_edit_commission_rate_success(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator, commission=500) + new_rate = 600 + + tx = candidate_hub.editCommissionRate(new_rate, {'from': operator}) + + assert 'CommissionRateEdited' in tx.events + assert tx.events['CommissionRateEdited']['operateAddr'] == operator + assert tx.events['CommissionRateEdited']['newRate'] == new_rate + assert candidate_hub.candidateSet( + 0).dict()['commissionThousandths'] == new_rate + assert candidate_hub.candidateSet( + 0).dict()['commissionLastChangeRound'] == get_current_round() + assert candidate_hub.candidateSet( + 0).dict()['commissionLastRoundValue'] == 500 + + +def test_edit_commission_rate_same_round_multiple_times(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator, commission=500) + max_change = candidate_hub.maxCommissionChange() + + new_rate1 = 500 + max_change + tx1 = candidate_hub.editCommissionRate(new_rate1, {'from': operator}) + assert 'CommissionRateEdited' in tx1.events + assert tx1.events['CommissionRateEdited']['newRate'] == new_rate1 + + new_rate2 = 500 + max_change + tx2 = candidate_hub.editCommissionRate(new_rate2, {'from': operator}) + assert 'CommissionRateEdited' in tx2.events + assert tx2.events['CommissionRateEdited']['newRate'] == new_rate2 + + new_rate3 = 500 - max_change + tx3 = candidate_hub.editCommissionRate(new_rate3, {'from': operator}) + assert 'CommissionRateEdited' in tx3.events + assert tx3.events['CommissionRateEdited']['newRate'] == new_rate3 + + candidate = candidate_hub.candidateSet(0).dict() + assert candidate['commissionThousandths'] == new_rate3 + assert candidate['commissionLastChangeRound'] == get_current_round() + assert candidate['commissionLastRoundValue'] == 500 + + with brownie.reverts("commissionThousandths out of adjustment range"): + candidate_hub.editCommissionRate( + 500 + max_change + 1, {'from': operator}) + + with brownie.reverts("commissionThousandths out of adjustment range"): + candidate_hub.editCommissionRate( + 500 - max_change - 1, {'from': operator}) + + +# editVoteAddress +def test_edit_vote_address_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.editVoteAddress( + random_vote_address(), {'from': accounts[1]}) + + +def test_edit_vote_address_length(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator) + + invalid_vote_addr = "0x1234" + with brownie.reverts("vote address length should be 48"): + candidate_hub.editVoteAddress(invalid_vote_addr, {'from': operator}) + + +def test_edit_vote_address_duplicate(candidate_hub, accounts): + operator1 = accounts[5] + operator2 = accounts[6] + consensus1 = register_candidate(operator=operator1) + vote_addr1 = candidate_hub.candidateSet(0).dict()['voteAddr'] + register_candidate(operator=operator2) + + with brownie.reverts("vote address already exists"): + candidate_hub.editVoteAddress(vote_addr1, {'from': operator2}) + + +def test_edit_vote_address_success(candidate_hub, accounts): + operator = accounts[5] + consensus = register_candidate(operator=operator) + old_vote_addr = candidate_hub.candidateSet(0).dict()['voteAddr'] + new_vote_addr = random_vote_address() + + tx = candidate_hub.editVoteAddress(new_vote_addr, {'from': operator}) + + assert 'VoteAddressEdited' in tx.events + assert tx.events['VoteAddressEdited']['operateAddr'] == operator + assert tx.events['VoteAddressEdited']['newVoteAddr'] == new_vote_addr + + +def test_edit_vote_address_by_agent_success(candidate_hub, accounts): + operator = accounts[5] + agent = accounts[6] + register_candidate(operator=operator) + new_vote_addr = random_vote_address() + + candidate_hub.updateAgent(agent, {'from': operator}) + + tx = candidate_hub.editVoteAddress(new_vote_addr, {'from': agent}) + assert 'VoteAddressEdited' in tx.events + + +# editDescription +def test_edit_description_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.editDescription( + "test", "test", "test", "test", {'from': accounts[1]}) + + +def test_edit_description_success(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator) + + moniker = "Test Validator" + identity = "test-identity" + website = "https://test.com" + details = "Test validator details" + + tx = candidate_hub.editDescription( + moniker, identity, website, details, {'from': operator}) + + assert 'DescriptionEdited' in tx.events + assert tx.events['DescriptionEdited']['operateAddr'] == operator + assert tx.events['DescriptionEdited']['moniker'] == moniker + assert tx.events['DescriptionEdited']['identity'] == identity + assert tx.events['DescriptionEdited']['website'] == website + assert tx.events['DescriptionEdited']['details'] == details + assert candidate_hub.candidateSet( + 0).dict()['description']['moniker'] == moniker + assert candidate_hub.candidateSet( + 0).dict()['description']['identity'] == identity + assert candidate_hub.candidateSet( + 0).dict()['description']['website'] == website + assert candidate_hub.candidateSet( + 0).dict()['description']['details'] == details + + +def test_edit_description_by_agent_success(candidate_hub, accounts): + operator = accounts[5] + agent = accounts[6] + register_candidate(operator=operator) + new_moniker = "Test Validator" + new_identity = "test-identity" + new_website = "https://test.com" + new_details = "Test validator details" + + candidate_hub.updateAgent(agent, {'from': operator}) + + tx = candidate_hub.editDescription( + new_moniker, new_identity, new_website, new_details, {'from': agent}) + assert 'DescriptionEdited' in tx.events + + +# editFeeAddress +def test_edit_fee_address_permission(candidate_hub, accounts): + with brownie.reverts("candidate does not exist"): + candidate_hub.editFeeAddress(accounts[1], {'from': accounts[1]}) + + +def test_edit_fee_address_zero_address(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator) + + with brownie.reverts("fee address cannot be zero"): + candidate_hub.editFeeAddress(ZERO_ADDRESS, {'from': operator}) + + +def test_edit_fee_address_success(candidate_hub, accounts): + operator = accounts[5] + register_candidate(operator=operator) + new_fee_addr = accounts[7] + + tx = candidate_hub.editFeeAddress(new_fee_addr, {'from': operator}) + + assert 'FeeAddressEdited' in tx.events + assert tx.events['FeeAddressEdited']['operateAddr'] == operator + assert tx.events['FeeAddressEdited']['newFeeAddr'] == new_fee_addr + assert candidate_hub.candidateSet(0).dict()['feeAddr'] == new_fee_addr + + +def test_edit_fee_address_by_agent_fail(candidate_hub, accounts): + operator = accounts[5] + agent = accounts[6] + register_candidate(operator=operator) + new_fee_addr = accounts[7] + + candidate_hub.updateAgent(agent, {'from': operator}) + + with brownie.reverts("candidate does not exist"): + tx = candidate_hub.editFeeAddress(new_fee_addr, {'from': agent}) + + +def test_candidate_update_and_turn_round(candidate_hub, accounts, validator_set, set_candidate): + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + lock_script = "0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac" + validator_set.updateBlockReward(30000) + operators, consensuses = set_candidate + for i, delegator in enumerate(accounts[:3]): + delegate_coin_success(operators[i], delegator, 10000) + delegate_btc_success(operators[i], delegator, 200, lock_script, relay=delegator) + turn_round() + validator_count = len(consensuses) + for i in range(validator_count): + candidate_hub.updateAgent(accounts[i], {'from': operators[i]}) + turn_round(consensuses) + for i in range(validator_count): + moniker = f"validator{i}" + identity = f"identity{i}" + website = f"https://validator{i}.com" + details = f"details{i}" + tx = candidate_hub.editDescription( + moniker, identity, website, details, {'from': accounts[i]}) + turn_round(consensuses) + for i in range(validator_count): + candidate_hub.editFeeAddress(accounts[i], {'from': operators[i]}) + for i in range(validator_count): + new_commission = 500 + i + candidate_hub.editCommissionRate(new_commission, {'from': operators[i]}) + turn_round(consensuses) + for i, operator in enumerate(operators): + index = candidate_hub.operateMap(operator) + c = candidate_hub.candidateSet(index - 1).dict() + assert c['agent'] == accounts[i] + assert c['feeAddr'] == accounts[i] + assert c['commissionThousandths'] == 500 + i + assert c['description']['moniker'] == f"validator{i}" + assert c['description']['identity'] == f"identity{i}" + assert c['description']['website'] == f"https://validator{i}.com" + assert c['description']['details'] == f"details{i}" + tx = stake_hub_claim_reward(accounts[0]) + assert 'claimedReward' in tx.events + turn_round(consensuses) diff --git a/tests/test_scenario.py b/tests/test_scenario.py index ab5c813d1..51dade6b0 100644 --- a/tests/test_scenario.py +++ b/tests/test_scenario.py @@ -7,7 +7,7 @@ init_account_mgr = AccountMgr.init_account_mgr - +@pytest.mark.skip(reason="This test is temporarily skipped") @pytest.mark.parametrize("file_name", [ 'example_scenario.json', 'btcfi_scenario.json', From 696962c54ccbdf77da0a7953afd8a07cf10cfc35 Mon Sep 17 00:00:00 2001 From: zfliex Date: Mon, 14 Jul 2025 15:16:45 +0800 Subject: [PATCH 06/27] fix audit suggestions --- contracts/CandidateHub.sol | 3 +++ contracts/SlashIndicator.sol | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/contracts/CandidateHub.sol b/contracts/CandidateHub.sol index d7e5319a0..81c9945b3 100644 --- a/contracts/CandidateHub.sol +++ b/contracts/CandidateHub.sol @@ -482,6 +482,9 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { candidateSet[index-1] = candidateSet[candidateSet.length - 1]; operateMap[candidateSet[index-1].operateAddr] = index; consensusMap[candidateSet[index-1].consensusAddr] = index; + if (candidateSet[index-1].agent != address(0)) { + agentMap[candidateSet[index-1].agent] = index; + } } candidateSet.pop(); } diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 462a04fd1..454535869 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -154,8 +154,11 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ verifyBLSSignature(evidence.voteB, evidence.voteAddr), "verify signature failed"); (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getValidatorsAndVoteAddresses(); + if (voteAddrs.length <= 1) { + return; + } for (uint256 i; i < voteAddrs.length; ++i) { - if (BytesLib.equal(voteAddrs[i], evidence.voteAddr)) { + if (BytesLib.equal(voteAddrs[i], evidence.voteAddr)) { IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(vals[i], felonyRound, felonyDeposit); ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportFinalityViolation); break; @@ -366,7 +369,6 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ function bytes32ToBytes(uint _offst, bytes32 _input, bytes memory _output) internal pure { assembly { mstore(add(_output, _offst), _input) - mstore(add(add(_output, _offst),32), add(_input,32)) } } } \ No newline at end of file From a550f8cb7cafb5cdbf2c1eee28707f55db7e9b67 Mon Sep 17 00:00:00 2001 From: zfliex Date: Thu, 17 Jul 2025 15:57:15 +0800 Subject: [PATCH 07/27] support node maintenance and validator candidate --- contracts/CandidateHub.sol | 45 +++++- contracts/SlashIndicator.sol | 38 +++-- contracts/ValidatorSet.sol | 205 +++++++++++++++++++++++- contracts/ValidatorSet.template | 205 +++++++++++++++++++++++- contracts/interface/ISlashIndicator.sol | 1 + contracts/interface/IValidatorSet.sol | 4 +- 6 files changed, 472 insertions(+), 26 deletions(-) diff --git a/contracts/CandidateHub.sol b/contracts/CandidateHub.sol index 81c9945b3..f6898f355 100644 --- a/contracts/CandidateHub.sol +++ b/contracts/CandidateHub.sol @@ -59,6 +59,8 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { mapping(address => uint256) public jailMap; uint256 public roundTag; + + uint256 public maxAlternateCount; mapping(address => uint256) public agentMap; @@ -192,6 +194,8 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /// @dev this method is called by Golang consensus engine at the end of a round function turnRound() public virtual onlyCoinbase onlyInit onlyZeroGasPrice { + IValidatorSet(VALIDATOR_CONTRACT_ADDR).exitMaintenanceTurnRound(); + // distribute rewards for the about to end round IValidatorSet(VALIDATOR_CONTRACT_ADDR).distributeReward(roundTag); @@ -219,7 +223,8 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { // choose top ones to form the validator set of the new round (uint256[] memory scores) = IStakeHub(STAKE_HUB_ADDR).getHybridScore(candidates, roundTag); - address[] memory validatorList = getValidators(candidates, scores, validatorCount); + uint256 sortedCount = getAlternateCount(maxAlternateCount, validatorCount, candidates.length); + address[] memory validatorList = getValidators(candidates, scores, validatorCount + sortedCount, sortedCount); // prepare arguments, and notify ValidatorSet contract address[] memory consensusAddrList = new address[](validatorList.length); @@ -241,7 +246,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { statusList[index - 1] |= SET_VALIDATOR; } - IValidatorSet(VALIDATOR_CONTRACT_ADDR).updateValidatorSet(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList); + IValidatorSet(VALIDATOR_CONTRACT_ADDR).updateValidatorSet(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList, validatorCount); // clean slash contract ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); @@ -490,8 +495,12 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } /// Rank validator candidates on hybrid score using quicksort - function getValidators(address[] memory candidateList, uint256[] memory scoreList, uint256 count) internal pure returns (address[] memory validatorList){ + function getValidators(address[] memory candidateList, uint256[] memory scoreList, uint256 count, uint256 sortedCount) internal pure returns (address[] memory validatorList){ + require(count > sortedCount, "count should be greater than sortedCount"); uint256 candidateSize = candidateList.length; + if (candidateSize == 0) { + return validatorList; + } // quicksort by scores O(nlogk) uint256 l = 0; uint256 r = 0; @@ -530,6 +539,21 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { break; } } + + // select top sortedCount + for (uint256 i = count - 1; i >= count - sortedCount; i--) { + uint256 minIndex; + for (uint256 j = 1; j <= i; j++) { + if (scoreList[j] < scoreList[minIndex]) { + minIndex = j; + } + } + if (minIndex != i) { + (candidateList[i], candidateList[minIndex]) = (candidateList[minIndex], candidateList[i]); + (scoreList[i], scoreList[minIndex]) = (scoreList[minIndex], scoreList[i]); + } + } + uint256 d = candidateSize - count; if (d != 0) { assembly { @@ -577,6 +601,12 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { revert OutOfBounds(key, newMaxCommissionChange, 1, type(uint256).max); } maxCommissionChange = newMaxCommissionChange; + } else if (Memory.compareStrings(key, "maxAlternateCount")) { + uint256 newAlternateValidatorCount = BytesToTypes.bytesToUint256(32, value); + if (newAlternateValidatorCount > validatorCount / 3) { + revert OutOfBounds(key, newAlternateValidatorCount, 0, validatorCount / 3); + } + maxAlternateCount = newAlternateValidatorCount; } else { revert UnsupportedGovParam(key); } @@ -613,4 +643,13 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { function getRoundInterval() external pure returns(uint256) { return SatoshiPlusHelper.ROUND_INTERVAL; } + + function getAlternateCount(uint256 _maxAlternateCount, uint256 _validatorCount, uint256 _candidateSize) internal pure returns (uint256) { + if (_candidateSize <= _validatorCount) { + _maxAlternateCount = 0; + } else if (_candidateSize < _validatorCount + _maxAlternateCount) { + _maxAlternateCount = _candidateSize - _validatorCount; + } + return _maxAlternateCount; + } } \ No newline at end of file diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 454535869..4ab50e94f 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -72,6 +72,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ /*********************** events **************************/ event validatorSlashed(address indexed validator); + event validatorSlashed(address indexed validator, uint256 blockCount); event indicatorCleaned(); function init() external onlyNotInit{ @@ -92,25 +93,30 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ if (!IValidatorSet(VALIDATOR_CONTRACT_ADDR).isValidator(validator)) { return; } - Indicator memory indicator = indicators[validator]; - if (indicator.exist) { - indicator.count++; - } else { - indicator.exist = true; - indicator.count = 1; - validators.push(validator); - } - indicator.height = block.number; + slashWithBlockCount(validator, 1); + Indicator storage indicator = indicators[validator]; if (indicator.count % felonyThreshold == 0) { indicator.count = 0; IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator, felonyRound, felonyDeposit); } else if (indicator.count % misdemeanorThreshold == 0) { IValidatorSet(VALIDATOR_CONTRACT_ADDR).misdemeanor(validator); + IValidatorSet(VALIDATOR_CONTRACT_ADDR).enterMaintenance(validator); } - indicators[validator] = indicator; emit validatorSlashed(validator); } + function exitMaintenanceSlash(address validator, uint256 blockCount) external override onlyValidator { + slashWithBlockCount(validator, blockCount); + Indicator storage indicator = indicators[validator]; + if (indicator.count >= felonyThreshold) { + indicator.count = 0; + IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator, felonyRound, felonyDeposit); + } else if (indicator.count / misdemeanorThreshold > (indicator.count - blockCount) / misdemeanorThreshold) { + IValidatorSet(VALIDATOR_CONTRACT_ADDR).misdemeanor(validator); + } + emit validatorSlashed(validator, blockCount); + } + /// Slash the validator because of double sign /// This method is called by external verifiers /// @param header1 A block header submitted by the validator @@ -281,6 +287,18 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ } /*********************** Internal Functions **************************/ + function slashWithBlockCount(address validator, uint256 blockCount) internal { + Indicator storage indicator = indicators[validator]; + if (indicator.exist) { + indicator.count += blockCount; + } else { + indicator.exist = true; + indicator.count = blockCount; + validators.push(validator); + } + indicator.height = block.number; + } + function parseHeader(RLPDecode.RLPItem[] memory items) internal pure returns (bytes32,address){ bytes memory extra = items[12].toBytes(); bytes memory sig = BytesLib.slice(extra, extra.length - 65, 65); diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index f6f805232..fb7e6dfef 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -6,6 +6,7 @@ import "./lib/BytesToTypes.sol"; import "./lib/Memory.sol"; import "./interface/IParamSubscriber.sol"; import "./interface/IValidatorSet.sol"; +import "./interface/ISlashIndicator.sol"; import "./interface/IStakeHub.sol"; import "./interface/ISystemReward.sol"; import "./interface/ICandidateHub.sol"; @@ -37,6 +38,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public voteRewardPercent; + uint256 public maintainSlashPercent; + + uint256 public validatorCount; + address[] public rankedValidatorList; + struct Validator { address operateAddress; address consensusAddress; @@ -45,6 +51,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 income; bytes voteAddr; uint256 voteWeight; + uint256 enterMaintenanceHeight; } /*********************** events **************************/ @@ -77,6 +84,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { event validatorMisdemeanor(address indexed validator, uint256 amount); event validatorFelony(address indexed validator, uint256 amount); event received(address indexed from, uint256 amount); + event validatorEnterMaintenance(address indexed validator); + event validatorExitMaintenance(address indexed validator); /*********************** init **************************/ function init() external onlyNotInit { @@ -139,6 +148,21 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + function exitMaintenanceTurnRound() external override onlyCandidate { + uint256 len = currentValidatorSet.length; + address[] memory valAddrs = new address[](len); + uint256 j; + for (uint256 i; i < len; ++i) { + if (currentValidatorSet[i].enterMaintenanceHeight != 0) { + valAddrs[j++] = currentValidatorSet[i].consensusAddress; + } + } + + for (uint256 i; i < j; ++i) { + _exitMaintenance(valAddrs[i], currentValidatorSetMap[valAddrs[i]]); + } + } + /// Distribute rewards to validators (and delegators through PledgeAgent) /// @dev this method is called by the CandidateHub contract at the beginning of turn round /// @dev this is where we deal with reward distribution logics @@ -219,17 +243,23 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList, - bytes[] calldata voteAddrList + bytes[] calldata voteAddrList, + uint256 _validatorCount ) external override onlyCandidate { // do verify. checkValidatorSet(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList); if (consensusAddrList.length == 0) { return; } + + validatorCount = _validatorCount; + updateRankedValidatorList(consensusAddrList); + // do update validator set state uint256 i; uint256 lastLength = currentValidatorSet.length; uint256 currentLength = consensusAddrList.length; + for (i = 0; i < lastLength; i++) { delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; } @@ -238,10 +268,20 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } for (i = 0; i < currentLength; ++i) { + Validator memory v; + v.operateAddress = operateAddrList[i]; + v.consensusAddress = consensusAddrList[i]; + v.feeAddress = feeAddrList[i]; + v.commissionThousandths = commissionThousandthsList[i]; + v.income = 0; + v.voteAddr = voteAddrList[i]; + v.voteWeight = 0; + v.enterMaintenanceHeight = 0; + if (i >= lastLength) { - currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0)); + currentValidatorSet.push(v); } else { - currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0); + currentValidatorSet[i] = v; } currentValidatorSetMap[consensusAddrList[i]] = i + 1; } @@ -249,6 +289,45 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { emit validatorSetUpdated(); } + function canEnterMaintenance(uint256 index) public view returns (bool) { + if (index == 0) { + return false; + } + Validator storage val = currentValidatorSet[index-1]; + uint256 working = getWorkingCount(); + if (val.enterMaintenanceHeight != 0 || working <= 1 || validatorCount >= working || validatorCount == 0) { + return false; + } + + return true; + } + + function enterMaintenance() external { + uint256 index = getValidatorIndexFromOps(msg.sender); + require(index > 0, "not a validator"); + require(canEnterMaintenance(index), "can not enter Temporary Maintenance"); + _enterMaintenance(currentValidatorSet[index-1].consensusAddress, index); + } + + function enterMaintenance(address val) external override onlySlash { + uint256 index = currentValidatorSetMap[val]; + if (index == 0) { + return; + } + + if (canEnterMaintenance(index)) { + _enterMaintenance(val, index); + } + } + + function exitMaintenance() external { + uint256 index = getValidatorIndexFromOps(msg.sender); + require(index > 0, "not a validator"); + require(currentValidatorSet[index-1].enterMaintenanceHeight != 0, "not in Temporary Maintenance"); + + _exitMaintenance(currentValidatorSet[index-1].consensusAddress, index); + } + /// Get list of validators in the current round /// @return List of validator consensus addresses function getValidators() external view returns (address[] memory) { @@ -275,12 +354,46 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { /// @return (List of validator consensus addresses, List of voting addresses) function getValidatorsAndVoteAddresses() external override view returns (address[] memory, bytes[] memory) { uint256 validatorSize = currentValidatorSet.length; + uint256 workingRankedValidatorLen; + address[] memory workingRankedValidatorList; + if (validatorCount == 0) { + workingRankedValidatorList = getWorkingValidators(); + workingRankedValidatorLen = workingRankedValidatorList.length; + validatorSize = workingRankedValidatorLen; + } else { + uint256 len = rankedValidatorList.length; + for (uint256 i = 0; i < len; ++i) { + uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; + if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + ++workingRankedValidatorLen; + } + } + workingRankedValidatorList = new address[](workingRankedValidatorLen); + uint256 j = 0; + for (uint256 i = 0; i < len; ++i) { + uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; + if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + workingRankedValidatorList[j++] = currentValidatorSet[index-1].consensusAddress; + } + } + validatorSize = validatorCount; + } + + if (validatorSize > workingRankedValidatorLen) { + validatorSize = workingRankedValidatorLen; + } + address[] memory consensusAddrs = new address[](validatorSize); bytes[] memory voteAddrs = new bytes[](validatorSize); - for (uint256 i = 0; i < validatorSize; i++) { - consensusAddrs[i] = currentValidatorSet[i].consensusAddress; - voteAddrs[i] = currentValidatorSet[i].voteAddr; + + uint256 pushedCount; + for (uint256 i; pushedCount < validatorSize && i < workingRankedValidatorLen; ++i) { + uint256 index = currentValidatorSetMap[workingRankedValidatorList[i]]; + consensusAddrs[pushedCount] = currentValidatorSet[index-1].consensusAddress; + voteAddrs[pushedCount] = currentValidatorSet[index-1].voteAddr; + ++pushedCount; } + return (consensusAddrs, voteAddrs); } @@ -295,6 +408,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return currentValidatorSet[index - 1].income; } + function getValidatorIndexFromOps(address ops) public view returns (uint256) { + uint256 len = currentValidatorSet.length; + for (uint256 i = 0; i < len; i++) { + if (currentValidatorSet[i].operateAddress == ops) { + return i + 1; + } + } + return 0; + } + /*********************** For slash **************************/ /// Slash the validator for misdemeanor behaviors /// @param validator The validator to slash @@ -344,6 +467,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { currentValidatorSet[index].income = 0; return; } + address operateAddress = currentValidatorSet[index].operateAddress; emit validatorFelony(operateAddress, income); delete currentValidatorSetMap[validator]; @@ -383,6 +507,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { revert OutOfBounds(key, newVoteRewardPercent, 0, 100); } voteRewardPercent = newVoteRewardPercent; + } else if (Memory.compareStrings(key, "maintainSlashPercent")) { + uint256 newMaintainSlashPercent = BytesToTypes.bytesToUint256(32, value); + if (newMaintainSlashPercent > 100) { + revert OutOfBounds(key, newMaintainSlashPercent, 0, 100); + } + maintainSlashPercent = newMaintainSlashPercent; } else { revert UnsupportedGovParam(key); } @@ -421,6 +551,49 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + function updateRankedValidatorList(address[] calldata consensusAddrList) internal { + uint256 currentLength = consensusAddrList.length; + uint256 lastRankedValLength = rankedValidatorList.length; + + // Remove extra elements if new list is shorter + for (uint256 i = currentLength; i < lastRankedValLength; i++) { + rankedValidatorList.pop(); + } + + // Update or append elements based on list length + for (uint256 i = 0; i < currentLength; ++i) { + if (i >= lastRankedValLength) { + rankedValidatorList.push(consensusAddrList[i]); + } else { + rankedValidatorList[i] = consensusAddrList[i]; + } + } + } + + function getWorkingCount() public view returns (uint256) { + uint256 len = currentValidatorSet.length; + uint256 working; + for (uint256 i; i < len; ++i) { + if (currentValidatorSet[i].enterMaintenanceHeight == 0) { + ++working; + } + } + return working; + } + + function getWorkingValidators() public view returns (address[] memory) { + uint256 len = currentValidatorSet.length; + uint256 working = getWorkingCount(); + address[] memory workingValidators = new address[](working); + uint256 j; + for (uint256 i ; i < len; ++i) { + if (currentValidatorSet[i].enterMaintenanceHeight == 0) { + workingValidators[j++] = currentValidatorSet[i].consensusAddress; + } + } + return workingValidators; + } + //rlp encode & decode function function decodeValidatorSet(bytes memory msgBytes) internal pure returns (Validator[] memory, bool) { RLPDecode.RLPItem[] memory items = msgBytes.toRLPItem().toList(); @@ -450,4 +623,24 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } return (validator, success); } + + function _enterMaintenance(address validator, uint256 index) internal { + currentValidatorSet[index-1].enterMaintenanceHeight = block.number; + emit validatorEnterMaintenance(validator); + } + + function _exitMaintenance(address validator, uint256 index) internal { + uint256 working = currentValidatorSet.length; + if (working > validatorCount) { + working = validatorCount; + } + uint256 slashCount = (block.number - currentValidatorSet[index-1].enterMaintenanceHeight) / working * maintainSlashPercent / 100; + + currentValidatorSet[index-1].enterMaintenanceHeight = 0; + + if (slashCount != 0) { + ISlashIndicator(SLASH_CONTRACT_ADDR).exitMaintenanceSlash(validator, slashCount); + } + emit validatorExitMaintenance(validator); + } } diff --git a/contracts/ValidatorSet.template b/contracts/ValidatorSet.template index 9c2c7140b..d29506b92 100644 --- a/contracts/ValidatorSet.template +++ b/contracts/ValidatorSet.template @@ -6,6 +6,7 @@ import "./lib/BytesToTypes.sol"; import "./lib/Memory.sol"; import "./interface/IParamSubscriber.sol"; import "./interface/IValidatorSet.sol"; +import "./interface/ISlashIndicator.sol"; import "./interface/IStakeHub.sol"; import "./interface/ISystemReward.sol"; import "./interface/ICandidateHub.sol"; @@ -37,6 +38,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public voteRewardPercent; + uint256 public maintainSlashPercent; + + uint256 public validatorCount; + address[] public rankedValidatorList; + struct Validator { address operateAddress; address consensusAddress; @@ -45,6 +51,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 income; bytes voteAddr; uint256 voteWeight; + uint256 enterMaintenanceHeight; } /*********************** events **************************/ @@ -77,6 +84,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { event validatorMisdemeanor(address indexed validator, uint256 amount); event validatorFelony(address indexed validator, uint256 amount); event received(address indexed from, uint256 amount); + event validatorEnterMaintenance(address indexed validator); + event validatorExitMaintenance(address indexed validator); /*********************** init **************************/ function init() external onlyNotInit { @@ -139,6 +148,21 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + function exitMaintenanceTurnRound() external override onlyCandidate { + uint256 len = currentValidatorSet.length; + address[] memory valAddrs = new address[](len); + uint256 j; + for (uint256 i; i < len; ++i) { + if (currentValidatorSet[i].enterMaintenanceHeight != 0) { + valAddrs[j++] = currentValidatorSet[i].consensusAddress; + } + } + + for (uint256 i; i < j; ++i) { + _exitMaintenance(valAddrs[i], currentValidatorSetMap[valAddrs[i]]); + } + } + /// Distribute rewards to validators (and delegators through PledgeAgent) /// @dev this method is called by the CandidateHub contract at the beginning of turn round /// @dev this is where we deal with reward distribution logics @@ -219,17 +243,23 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList, - bytes[] calldata voteAddrList + bytes[] calldata voteAddrList, + uint256 _validatorCount ) external override onlyCandidate { // do verify. checkValidatorSet(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList, voteAddrList); if (consensusAddrList.length == 0) { return; } + + validatorCount = _validatorCount; + updateRankedValidatorList(consensusAddrList); + // do update validator set state uint256 i; uint256 lastLength = currentValidatorSet.length; uint256 currentLength = consensusAddrList.length; + for (i = 0; i < lastLength; i++) { delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; } @@ -238,10 +268,20 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } for (i = 0; i < currentLength; ++i) { + Validator memory v; + v.operateAddress = operateAddrList[i]; + v.consensusAddress = consensusAddrList[i]; + v.feeAddress = feeAddrList[i]; + v.commissionThousandths = commissionThousandthsList[i]; + v.income = 0; + v.voteAddr = voteAddrList[i]; + v.voteWeight = 0; + v.enterMaintenanceHeight = 0; + if (i >= lastLength) { - currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0)); + currentValidatorSet.push(v); } else { - currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i],commissionThousandthsList[i], 0, voteAddrList[i], 0); + currentValidatorSet[i] = v; } currentValidatorSetMap[consensusAddrList[i]] = i + 1; } @@ -249,6 +289,45 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { emit validatorSetUpdated(); } + function canEnterMaintenance(uint256 index) public view returns (bool) { + if (index == 0) { + return false; + } + Validator storage val = currentValidatorSet[index-1]; + uint256 working = getWorkingCount(); + if (val.enterMaintenanceHeight != 0 || working <= 1 || validatorCount >= working || validatorCount == 0) { + return false; + } + + return true; + } + + function enterMaintenance() external { + uint256 index = getValidatorIndexFromOps(msg.sender); + require(index > 0, "not a validator"); + require(canEnterMaintenance(index), "can not enter Temporary Maintenance"); + _enterMaintenance(currentValidatorSet[index-1].consensusAddress, index); + } + + function enterMaintenance(address val) external override onlySlash { + uint256 index = currentValidatorSetMap[val]; + if (index == 0) { + return; + } + + if (canEnterMaintenance(index)) { + _enterMaintenance(val, index); + } + } + + function exitMaintenance() external { + uint256 index = getValidatorIndexFromOps(msg.sender); + require(index > 0, "not a validator"); + require(currentValidatorSet[index-1].enterMaintenanceHeight != 0, "not in Temporary Maintenance"); + + _exitMaintenance(currentValidatorSet[index-1].consensusAddress, index); + } + /// Get list of validators in the current round /// @return List of validator consensus addresses function getValidators() external view returns (address[] memory) { @@ -275,12 +354,46 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { /// @return (List of validator consensus addresses, List of voting addresses) function getValidatorsAndVoteAddresses() external override view returns (address[] memory, bytes[] memory) { uint256 validatorSize = currentValidatorSet.length; + uint256 workingRankedValidatorLen; + address[] memory workingRankedValidatorList; + if (validatorCount == 0) { + workingRankedValidatorList = getWorkingValidators(); + workingRankedValidatorLen = workingRankedValidatorList.length; + validatorSize = workingRankedValidatorLen; + } else { + uint256 len = rankedValidatorList.length; + for (uint256 i = 0; i < len; ++i) { + uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; + if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + ++workingRankedValidatorLen; + } + } + workingRankedValidatorList = new address[](workingRankedValidatorLen); + uint256 j = 0; + for (uint256 i = 0; i < len; ++i) { + uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; + if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + workingRankedValidatorList[j++] = currentValidatorSet[index-1].consensusAddress; + } + } + validatorSize = validatorCount; + } + + if (validatorSize > workingRankedValidatorLen) { + validatorSize = workingRankedValidatorLen; + } + address[] memory consensusAddrs = new address[](validatorSize); bytes[] memory voteAddrs = new bytes[](validatorSize); - for (uint256 i = 0; i < validatorSize; i++) { - consensusAddrs[i] = currentValidatorSet[i].consensusAddress; - voteAddrs[i] = currentValidatorSet[i].voteAddr; + + uint256 pushedCount; + for (uint256 i; pushedCount < validatorSize && i < workingRankedValidatorLen; ++i) { + uint256 index = currentValidatorSetMap[workingRankedValidatorList[i]]; + consensusAddrs[pushedCount] = currentValidatorSet[index-1].consensusAddress; + voteAddrs[pushedCount] = currentValidatorSet[index-1].voteAddr; + ++pushedCount; } + return (consensusAddrs, voteAddrs); } @@ -295,6 +408,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return currentValidatorSet[index - 1].income; } + function getValidatorIndexFromOps(address ops) public view returns (uint256) { + uint256 len = currentValidatorSet.length; + for (uint256 i = 0; i < len; i++) { + if (currentValidatorSet[i].operateAddress == ops) { + return i + 1; + } + } + return 0; + } + /*********************** For slash **************************/ /// Slash the validator for misdemeanor behaviors /// @param validator The validator to slash @@ -344,6 +467,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { currentValidatorSet[index].income = 0; return; } + address operateAddress = currentValidatorSet[index].operateAddress; emit validatorFelony(operateAddress, income); delete currentValidatorSetMap[validator]; @@ -383,6 +507,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { revert OutOfBounds(key, newVoteRewardPercent, 0, 100); } voteRewardPercent = newVoteRewardPercent; + } else if (Memory.compareStrings(key, "maintainSlashPercent")) { + uint256 newMaintainSlashPercent = BytesToTypes.bytesToUint256(32, value); + if (newMaintainSlashPercent > 100) { + revert OutOfBounds(key, newMaintainSlashPercent, 0, 100); + } + maintainSlashPercent = newMaintainSlashPercent; } else { revert UnsupportedGovParam(key); } @@ -421,6 +551,49 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } } + function updateRankedValidatorList(address[] calldata consensusAddrList) internal { + uint256 currentLength = consensusAddrList.length; + uint256 lastRankedValLength = rankedValidatorList.length; + + // Remove extra elements if new list is shorter + for (uint256 i = currentLength; i < lastRankedValLength; i++) { + rankedValidatorList.pop(); + } + + // Update or append elements based on list length + for (uint256 i = 0; i < currentLength; ++i) { + if (i >= lastRankedValLength) { + rankedValidatorList.push(consensusAddrList[i]); + } else { + rankedValidatorList[i] = consensusAddrList[i]; + } + } + } + + function getWorkingCount() public view returns (uint256) { + uint256 len = currentValidatorSet.length; + uint256 working; + for (uint256 i; i < len; ++i) { + if (currentValidatorSet[i].enterMaintenanceHeight == 0) { + ++working; + } + } + return working; + } + + function getWorkingValidators() public view returns (address[] memory) { + uint256 len = currentValidatorSet.length; + uint256 working = getWorkingCount(); + address[] memory workingValidators = new address[](working); + uint256 j; + for (uint256 i ; i < len; ++i) { + if (currentValidatorSet[i].enterMaintenanceHeight == 0) { + workingValidators[j++] = currentValidatorSet[i].consensusAddress; + } + } + return workingValidators; + } + //rlp encode & decode function function decodeValidatorSet(bytes memory msgBytes) internal pure returns (Validator[] memory, bool) { RLPDecode.RLPItem[] memory items = msgBytes.toRLPItem().toList(); @@ -450,4 +623,24 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } return (validator, success); } + + function _enterMaintenance(address validator, uint256 index) internal { + currentValidatorSet[index-1].enterMaintenanceHeight = block.number; + emit validatorEnterMaintenance(validator); + } + + function _exitMaintenance(address validator, uint256 index) internal { + uint256 working = currentValidatorSet.length; + if (working > validatorCount) { + working = validatorCount; + } + uint256 slashCount = (block.number - currentValidatorSet[index-1].enterMaintenanceHeight) / working * maintainSlashPercent / 100; + + currentValidatorSet[index-1].enterMaintenanceHeight = 0; + + if (slashCount != 0) { + ISlashIndicator(SLASH_CONTRACT_ADDR).exitMaintenanceSlash(validator, slashCount); + } + emit validatorExitMaintenance(validator); + } } diff --git a/contracts/interface/ISlashIndicator.sol b/contracts/interface/ISlashIndicator.sol index 6feb54bb9..8db2e64ab 100644 --- a/contracts/interface/ISlashIndicator.sol +++ b/contracts/interface/ISlashIndicator.sol @@ -3,4 +3,5 @@ pragma solidity 0.8.4; interface ISlashIndicator { function clean() external; + function exitMaintenanceSlash(address validator, uint256 blockCount) external; } \ No newline at end of file diff --git a/contracts/interface/IValidatorSet.sol b/contracts/interface/IValidatorSet.sol index a04514058..5ce57cadf 100644 --- a/contracts/interface/IValidatorSet.sol +++ b/contracts/interface/IValidatorSet.sol @@ -5,8 +5,10 @@ interface IValidatorSet { function misdemeanor(address validator) external; function felony(address validator, uint256 felonyRound, uint256 felonyDeposit) external; function distributeReward(uint256 roundTag) external returns (address[] memory operateAddrList); - function updateValidatorSet(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList, bytes[] calldata voteAddrList) external; + function updateValidatorSet(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList, bytes[] calldata voteAddrList, uint256 _validatorCount) external; function isValidator(address addr) external view returns (bool); function getValidatorOps() external view returns (address[] memory); function getValidatorsAndVoteAddresses() external view returns (address[] memory, bytes[] memory); + function enterMaintenance(address val) external; + function exitMaintenanceTurnRound() external; } From 8a466455373520e850e1678a22dd06707be7f3f4 Mon Sep 17 00:00:00 2001 From: charles2023wood Date: Thu, 17 Jul 2025 20:37:00 +0800 Subject: [PATCH 08/27] Remove BitcoinLST --- abi/BitcoinAgent.abi | 2 +- abi/BitcoinLSTStake.abi | 1 - abi/BitcoinLSTToken.abi | 1 - abi/CoreAgent.abi | 2 +- abi/StakeHub.abi | 2 +- contracts/BitcoinAgent.sol | 58 +- contracts/BitcoinLSTStake.sol | 706 ----------------------- contracts/BitcoinLSTToken.sol | 279 --------- contracts/StakeHub.sol | 2 +- contracts/System.sol | 4 +- contracts/System.template | 10 +- contracts/interface/IBitcoinLSTToken.sol | 9 - generate-genesis.js | 10 - 13 files changed, 14 insertions(+), 1072 deletions(-) delete mode 100644 abi/BitcoinLSTStake.abi delete mode 100644 abi/BitcoinLSTToken.abi delete mode 100644 contracts/BitcoinLSTStake.sol delete mode 100644 contracts/BitcoinLSTToken.sol delete mode 100644 contracts/interface/IBitcoinLSTToken.sol diff --git a/abi/BitcoinAgent.abi b/abi/BitcoinAgent.abi index 508db0901..663a2e32f 100644 --- a/abi/BitcoinAgent.abi +++ b/abi/BitcoinAgent.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"percent","type":"uint256"}],"name":"claimedBtcLstReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"claimedBtcReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"percent","type":"uint256"}],"name":"storedBtcLstReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"storedBtcReward","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_CORE_BTC_CONVERSION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"lstStakeAmount","type":"uint256"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"coreAmount","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getGrades","outputs":[{"components":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct BitcoinAgent.DualStakingGrade[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gradeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"grades","outputs":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lstGradePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"claimedBtcReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"storedBtcReward","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_CORE_BTC_CONVERSION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"lstStakeAmount","type":"uint256"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"coreAmount","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getGrades","outputs":[{"components":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct BitcoinAgent.DualStakingGrade[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gradeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"grades","outputs":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lstGradePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/BitcoinLSTStake.abi b/abi/BitcoinLSTStake.abi deleted file mode 100644 index cebd7b9a0..000000000 --- a/abi/BitcoinLSTStake.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"_hash","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"_type","type":"uint64"}],"name":"addedWallet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint64","name":"amount","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"delegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint64","name":"amount","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"utxoFee","type":"uint64"},{"indexed":false,"internalType":"bytes","name":"pkscript","type":"bytes"}],"name":"redeemed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"_hash","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"_type","type":"uint64"}],"name":"removedWallet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"rewardUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"outputIndex","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"amount","type":"uint64"},{"indexed":false,"internalType":"bytes","name":"pkscript","type":"bytes"}],"name":"undelegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"outputIndex","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"expectAmount","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"actualAmount","type":"uint64"},{"indexed":false,"internalType":"bytes","name":"pkscript","type":"bytes"}],"name":"undelegatedOverflow","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_UTXO_FEE","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WALLET_ACTIVE","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WALLET_INACTIVE","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WTYPE_P2PKH","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WTYPE_P2SH","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WTYPE_P2TAPROOT","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WTYPE_P2WPKH","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WTYPE_P2WSH","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WTYPE_UNKNOWN","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardPerBTCMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcConfirmBlock","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"btcTxMap","outputs":[{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint32","name":"outputIndex","type":"uint32"},{"internalType":"uint32","name":"blockHeight","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"rewardUnclaimed","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"btcTx","type":"bytes"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bytes","name":"script","type":"bytes"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWallets","outputs":[{"components":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint32","name":"addrType","type":"uint32"},{"internalType":"uint32","name":"status","type":"uint32"}],"internalType":"struct BitcoinLSTStake.WalletInfo[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"onTokenTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"prepare","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"realtimeAmount","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"bytes","name":"pkscript","type":"bytes"}],"name":"redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"redeemMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"redeemRequests","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint32","name":"addrType","type":"uint32"},{"internalType":"uint64","name":"amount","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakedAmount","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"btcTx","type":"bytes"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"undelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userStakeInfo","outputs":[{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint64","name":"realtimeAmount","type":"uint64"},{"internalType":"uint64","name":"stakedAmount","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"utxoFee","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"wallets","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint32","name":"addrType","type":"uint32"},{"internalType":"uint32","name":"status","type":"uint32"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/abi/BitcoinLSTToken.abi b/abi/BitcoinLSTToken.abi deleted file mode 100644 index 555cea2df..000000000 --- a/abi/BitcoinLSTToken.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/abi/CoreAgent.abi b/abi/CoreAgent.abi index e9069c29a..ff95b88ea 100644 --- a/abi/CoreAgent.abi +++ b/abi/CoreAgent.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"claimedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"collectedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"delegatedCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sourceCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"targetCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"transferredCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegatedCoin","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_COIN_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getCandidateListByDelegator","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getContinuousRewardEndRoundsByCandidate","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"changeRound","type":"uint256"}],"internalType":"struct CoreAgent.CoinDelegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"moveData","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyDelegate","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyUnDelegate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requiredCoinDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"settleRound","type":"uint256"}],"name":"viewCollectRewardFromCandidate","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"claimedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"collectedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"delegatedCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sourceCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"targetCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"transferredCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegatedCoin","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_COIN_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getCandidateListByDelegator","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getContinuousRewardEndRoundsByCandidate","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"changeRound","type":"uint256"}],"internalType":"struct CoreAgent.CoinDelegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"moveData","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyDelegate","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyUnDelegate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requiredCoinDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"settleRound","type":"uint256"}],"name":"viewCollectRewardFromCandidate","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/StakeHub.abi b/abi/StakeHub.abi index ef81487ca..80f88d4b6 100644 --- a/abi/StakeHub.abi +++ b/abi/StakeHub.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedRelayerReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"validator","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amount","type":"uint256[]"}],"name":"roundReward","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"addRoundReward","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"assets","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateScoresMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimReward","outputs":[{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"changeRound","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAssets","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"internalType":"struct StakeHub.Asset[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getCandidateScores","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"internalType":"struct StakeHub.Delegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getHybridScore","outputs":[{"internalType":"uint256[]","name":"scores","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"onStakeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyClaimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"factor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"surplus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedRelayerReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"validator","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amount","type":"uint256[]"}],"name":"roundReward","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"addRoundReward","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"assets","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateScoresMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimReward","outputs":[{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"changeRound","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAssets","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"internalType":"struct StakeHub.Asset[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getCandidateScores","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"internalType":"struct StakeHub.Delegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getHybridScore","outputs":[{"internalType":"uint256[]","name":"scores","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"onStakeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyClaimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"factor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"surplus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/contracts/BitcoinAgent.sol b/contracts/BitcoinAgent.sol index c61e5bd01..e9d6cd7f2 100644 --- a/contracts/BitcoinAgent.sol +++ b/contracts/BitcoinAgent.sol @@ -12,8 +12,8 @@ import "./lib/RLPDecode.sol"; import "./lib/SafeCast.sol"; /// This contract handles BTC staking. -/// It interacts with BitcoinStake.sol and BitcoinLSTStake.sol for -/// non-custodial BTC staking and LST BTC staking correspondingly. +/// It interacts with BitcoinStake.sol for +/// non-custodial BTC staking correspondingly. contract BitcoinAgent is IAgent, System, IParamSubscriber { using BytesLib for *; using SafeCast for *; @@ -37,11 +37,11 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { // conversion rate between CORE and the asset uint256 public assetWeight; - // the same grade percentage applies to all LST stakes + // Deprecated uint256 public lstGradePercentage; struct StakeAmount { - // staked BTC amount from LST + // Deprecated, never used. uint256 lstStakeAmount; // staked BTC amount from non custodial uint256 stakeAmount; @@ -53,13 +53,10 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { } event claimedBtcReward(address indexed delegator, uint256 amount, uint256 unclaimedAmount, int256 floatReward, uint256 accStakedAmount, uint256 dualStakingRate); - event claimedBtcLstReward(address indexed delegator, uint256 amount, uint256 unclaimedAmount, int256 floatReward, uint256 accStakedAmount, uint256 percent); event storedBtcReward(address indexed delegator, uint256 amount, uint256 unclaimedAmount, int256 floatReward, uint256 accStakedAmount, uint256 dualStakingRate); - event storedBtcLstReward(address indexed delegator, uint256 amount, uint256 unclaimedAmount, int256 floatReward, uint256 accStakedAmount, uint256 percent); function init() external onlyNotInit { assetWeight = DEFAULT_CORE_BTC_CONVERSION; - lstGradePercentage = SatoshiPlusHelper.DENOMINATOR; alreadyInit = true; } @@ -68,28 +65,7 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { /// @param validators List of validator operator addresses /// @param rewardList List of reward amount function distributeReward(address[] calldata validators, uint256[] calldata rewardList, uint256 /*round*/) external override onlyStakeHub { - uint256 validatorSize = validators.length; - require(validatorSize == rewardList.length, "the length of validators and rewardList should be equal"); - - uint256[] memory rewards = new uint256[](validatorSize); - StakeAmount memory sa; - for (uint256 i = 0; i < validatorSize; ++i) { - if (rewardList[i] == 0) { - continue; - } - sa = candidateMap[validators[i]]; - if (sa.lstStakeAmount + sa.stakeAmount == 0) { - continue; - } - rewards[i] = rewardList[i] * sa.lstStakeAmount / (sa.lstStakeAmount + sa.stakeAmount); - } - IBitcoinStake(BTCLST_STAKE_ADDR).distributeReward(validators, rewards); - - for (uint256 i = 0; i < validatorSize; ++i) { - /// @dev could be 0-0 - rewards[i] = rewardList[i] - rewards[i]; - } - IBitcoinStake(BTC_STAKE_ADDR).distributeReward(validators, rewards); + IBitcoinStake(BTC_STAKE_ADDR).distributeReward(validators, rewardList); } /// Get staked BTC amount @@ -98,12 +74,9 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { /// @return totalAmount Total BTC staked on all candidates in this round function getStakeAmounts(address[] calldata candidates, uint256 /*round*/) external override onlyStakeHub returns (uint256[] memory amounts, uint256 totalAmount) { uint256 candidateSize = candidates.length; - uint256[] memory lstAmounts = IBitcoinStake(BTCLST_STAKE_ADDR).getStakeAmounts(candidates); amounts = IBitcoinStake(BTC_STAKE_ADDR).getStakeAmounts(candidates); for (uint256 i = 0; i < candidateSize; ++i) { - candidateMap[candidates[i]].lstStakeAmount = lstAmounts[i]; candidateMap[candidates[i]].stakeAmount = amounts[i]; - amounts[i] += lstAmounts[i]; totalAmount += amounts[i]; } } @@ -113,7 +86,6 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { /// @param round The new round tag function setNewRound(address[] calldata validators, uint256 round) external override onlyStakeHub { IBitcoinStake(BTC_STAKE_ADDR).setNewRound(validators, round); - IBitcoinStake(BTCLST_STAKE_ADDR).setNewRound(validators, round); } /// Claim reward for delegator @@ -149,22 +121,7 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { } else { emit storedBtcReward(delegator, btcReward, btcRewardUnclaimed, floatReward, btcAccStakedAmount, p); } - - (uint256 btclstReward, , uint256 btclstAccStakedAmount) = IBitcoinStake(BTCLST_STAKE_ADDR).claimReward(delegator, settleRound, claim); - int256 lstFloatReward; - if (lstGradePercentage != SatoshiPlusHelper.DENOMINATOR) { - uint256 pLstReward = btclstReward * lstGradePercentage / SatoshiPlusHelper.DENOMINATOR; - lstFloatReward = (pLstReward.toInt256() - btclstReward.toInt256()); - btclstReward = pLstReward; - } - floatReward += lstFloatReward; - - if (claim) { - emit claimedBtcLstReward(delegator, btclstReward, 0, lstFloatReward, btclstAccStakedAmount, lstGradePercentage); - } else { - emit storedBtcLstReward(delegator, btclstReward, 0, lstFloatReward, btclstAccStakedAmount, lstGradePercentage); - } - return (btcReward + btclstReward, floatReward, btcAccStakedAmount + btclstAccStakedAmount); + return (btcReward, floatReward, btcAccStakedAmount); } /*********************** External methods ********************************/ @@ -228,9 +185,6 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { revert MismatchParamLength(key); } uint256 newPercentage = value.toUint256(0); - if (newPercentage == 0 || newPercentage > SatoshiPlusHelper.DENOMINATOR * 10) { - revert OutOfBounds(key, newPercentage, 1, SatoshiPlusHelper.DENOMINATOR * 10); - } lstGradePercentage = newPercentage; } else { revert UnsupportedGovParam(key); diff --git a/contracts/BitcoinLSTStake.sol b/contracts/BitcoinLSTStake.sol deleted file mode 100644 index 632064ae0..000000000 --- a/contracts/BitcoinLSTStake.sol +++ /dev/null @@ -1,706 +0,0 @@ -// SPDX-License-Identifier: Apache2.0 -pragma solidity 0.8.4; - -import "./interface/IBitcoinLSTToken.sol"; -import "./interface/IBitcoinStake.sol"; -import "./interface/ICandidateHub.sol"; -import "./interface/ILightClient.sol"; -import "./interface/IParamSubscriber.sol"; -import "./lib/BytesLib.sol"; -import "./lib/Memory.sol"; -import "./lib/BitcoinHelper.sol"; -import "./lib/SatoshiPlusHelper.sol"; -import "./System.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/security/Pausable.sol"; - -/// This contract handles LST BTC staking. -/// Relayers submit BTC stake/redeem transactions to Core chain here. -contract BitcoinLSTStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuard, Pausable { - using BitcoinHelper for *; - using TypedMemView for *; - using BytesLib for *; - - uint32 public constant WALLET_ACTIVE = 1; - uint32 public constant WALLET_INACTIVE = 2; - - bytes1 constant OP_DUP = 0x76; - bytes1 constant OP_HASH160 = 0xa9; - bytes1 constant OP_DATA_20 = 0x14; - bytes1 constant OP_DATA_32 = 0x20; - bytes1 constant OP_EQUALVERIFY = 0x88; - bytes1 constant OP_CHECKSIG = 0xac; - bytes1 constant OP_EQUAL = 0x87; - bytes1 constant OP_0 = 0; - bytes1 constant OP_1 = 0x51; - - uint32 public constant WTYPE_UNKNOWN = 0; - // 25 OP_DUP OP_HASH160 OP_DATA_20 OP_EQUALVERIFY OP_CHECKSIG - uint32 public constant WTYPE_P2PKH = 1; - // 23 OP_HASH160 OP_DATA_20 OP_EQUAL - uint32 public constant WTYPE_P2SH = 2; - // 22 witnessVer OP_0 OP_DATA_20 - uint32 public constant WTYPE_P2WPKH = 4; - // 34 witnessVer OP_0 OP_DATA_32 <32-byte-hash> - uint32 public constant WTYPE_P2WSH = 8; - // 34 witnessVer OP_1 OP_DATA_32 <32-byte-hash> - uint32 public constant WTYPE_P2TAPROOT = 16; - - uint64 public constant INIT_UTXO_FEE = 1e4; - - // This field records each btc staking tx, and it will never be cleared. - // key: bitcoin tx id - // value: bitcoin stake record - mapping(bytes32 => BtcTx) public btcTxMap; - - // staked BTC amount when the last round snapshot is taken - uint64 public stakedAmount; - - // realtime staked BTC amount - uint64 public realtimeAmount; - - // key: delegator address - // value: stake info. - mapping(address => UserStakeInfo) public userStakeInfo; - - // key: roundtag - // value: reward per BTC accumulated - mapping(uint256 => uint256) public accruedRewardPerBTCMap; - - // the number of blocks to mark a BTC staking transaction as confirmed - uint32 public btcConfirmBlock; - - // This field is used to store lst reward of delegators - // key: delegator address - // value: amount of CORE tokens claimable - mapping(address => Reward) public rewardMap; - - // the current round, it is updated in setNewRound. - uint256 public roundTag; - - // initial round - uint256 public initRound; - - // a list of BTC wallet address which holds the BTC assets for the lst product - WalletInfo[] public wallets; - - // key: keccak256 of pkscript. - // value: index+1 of wallets. - mapping(bytes32 => uint256) walletMap; - - // a list of lst redeem/burn request whose BTC payout transaction are in pending status - Redeem[] public redeemRequests; - - // key: keccak256 of pkscript. - // value: index+1 of redeemRequests. - mapping(bytes32 => uint256) public redeemMap; - - // Fee paid in BTC to burn lst tokens - uint64 public utxoFee; - - struct BtcTx { - uint64 amount; - uint32 outputIndex; - uint32 blockHeight; - } - - struct Redeem { - bytes32 hash; // it may be 20-byte-hash or 32-bytes-hash. - uint32 addrType; - uint64 amount; - } - - struct WalletInfo { - bytes32 hash; // it may be 20-byte-hash or 32-bytes-hash. - uint32 addrType; - uint32 status; - } - - struct UserStakeInfo { - uint256 changeRound; // the round when last op happens, including mint/burn/transfer/claim. - uint64 realtimeAmount; // realtime staked BTC amount - uint64 stakedAmount; // staked BTC amount when the last round snapshot is taken - } - - struct Reward { - uint256 reward; - uint256 accStakedAmount; - } - - /*********************** events **************************/ - event delegated(bytes32 indexed txid, address indexed delegator, uint64 amount, uint256 fee); - event redeemed(address indexed delegator, uint64 amount, uint64 utxoFee, bytes pkscript); - event undelegated(bytes32 indexed txid, uint32 outputIndex, uint64 amount, bytes pkscript); - event undelegatedOverflow(bytes32 indexed txid, uint32 outputIndex, uint64 expectAmount, uint64 actualAmount, bytes pkscript); - event addedWallet(bytes32 indexed _hash, uint64 _type); - event removedWallet(bytes32 indexed _hash, uint64 _type); - event rewardUpdated(uint256 round, uint256 value); - - modifier onlyBtcLSTToken() { - require(msg.sender == BTCLST_TOKEN_ADDR, 'only btc lst token can call this function'); - _; - } - - /*********************** Init ********************************/ - - function init() external onlyNotInit { - utxoFee = INIT_UTXO_FEE; - initRound = ICandidateHub(CANDIDATE_HUB_ADDR).getRoundTag(); - roundTag = initRound; - btcConfirmBlock = SatoshiPlusHelper.INIT_BTC_CONFIRM_BLOCK; - alreadyInit = true; - } - - /*********************** Interface implementations ***************************/ - - /// Bitcoin LST delegate, it is called by relayer - /// - /// User workflow to delegate BTC to Core blockchain - /// 1. A user creates a bitcoin transaction by minting lstBTC - /// 2. Relayer transmits BTC tx to Core chain by calling this method - /// 3. lstBTC token contract is called in this method - /// - /// @param btcTx the BTC transaction data - /// @param blockHeight block height of the transaction - /// @param nodes part of the Merkle tree from the tx to the root in LE form (called Merkle proof) - /// @param index index of the tx in Merkle tree - /// @param script it is a redeem script of the locked up output - function delegate(bytes calldata btcTx, uint32 blockHeight, bytes32[] memory nodes, uint256 index, bytes memory script) external override whenNotPaused { - bytes32 txid = btcTx.calculateTxId(); - BtcTx storage bt = btcTxMap[txid]; - require(bt.amount == 0, "btc tx is already delegated."); - bool txChecked = ILightClient(LIGHT_CLIENT_ADDR).checkTxProof(txid, blockHeight, btcConfirmBlock, nodes, index); - require(txChecked, "btc tx isn't confirmed"); - bt.blockHeight = blockHeight; - _checkWallet(script); - - (,bytes29 vinView, bytes29 voutView,) = btcTx.extractTx(); - bool fromWallet = _parseVin(vinView); - require(!fromWallet, "should not delegate from whitelisted multisig wallets"); - address delegator; - { - uint64 btcAmount; - uint32 outputIndex; - (btcAmount, outputIndex, delegator) = _parseVout(voutView, script); - require(IRelayerHub(RELAYER_HUB_ADDR).isRelayer(msg.sender) || msg.sender == delegator, "only delegator or relayer can submit the BTC transaction"); - bt.amount = btcAmount; - bt.outputIndex = outputIndex; - if (delegator != address(0)) { - require(btcAmount >= utxoFee * 2, "btc amount is too small"); - - IBitcoinLSTToken(BTCLST_TOKEN_ADDR).mint(delegator, btcAmount); - _afterMint(delegator, btcAmount); - realtimeAmount += btcAmount; - } - emit delegated(txid, delegator, btcAmount, 0); - } - } - - /// Bitcoin LST undelegate, it is called by relayer - /// This method is used to clear redeem requests. - /// - /// @param btcTx the BTC transaction data - /// @param blockHeight block height of the transaction - /// @param nodes part of the Merkle tree from the tx to the root in LE form (called Merkle proof) - /// @param index index of the tx in Merkle tree - function undelegate(bytes calldata btcTx, uint32 blockHeight, bytes32[] memory nodes, uint256 index) external override whenNotPaused nonReentrant { - bytes32 txid = btcTx.calculateTxId(); - require(btcTxMap[txid].blockHeight == 0, "btc tx is already undelegated."); - bool txChecked = ILightClient(LIGHT_CLIENT_ADDR).checkTxProof(txid, blockHeight, btcConfirmBlock, nodes, index); - require(txChecked, "btc tx not confirmed"); - btcTxMap[txid].blockHeight = blockHeight; - - /// make sure the transaction is from whitelisted multisig wallets - /// by checking that at least one of the UTXOs spent in the transaction are from them - /// as all UTXOs of those multisig wallet are kept in `btcTxMap` - (,bytes29 vinView, bytes29 voutView,) = btcTx.extractTx(); - { - bool fromWallet = _parseVin(vinView); - require(fromWallet, "input must from stake wallet."); - } - - // Finds total number of outputs - uint32 _numberOfOutputs = uint32(voutView.indexCompactInt(0)); - uint64 _amount; - bytes29 _pkScript; - - for (uint32 i = 0; i < _numberOfOutputs; ++i) { - (_amount, _pkScript) = voutView.parseOutputValueAndScript(i); - bytes memory pkscript = _pkScript.clone(); - bytes32 key = keccak256(pkscript); - uint256 index1 = redeemMap[key]; - if (index1 != 0) { - Redeem storage rd = redeemRequests[index1 - 1]; - emit undelegated(txid, i, _amount, pkscript); - if (rd.amount <= _amount) { - if (rd.amount < _amount) { - emit undelegatedOverflow(txid, i, rd.amount, _amount, pkscript); - } - delete redeemMap[key]; - if (index1 < redeemRequests.length) { - redeemRequests[index1 - 1] = redeemRequests[redeemRequests.length - 1]; - pkscript = _buildPkScript(rd.hash, rd.addrType); - key = keccak256(pkscript); - redeemMap[key] = index1; - } - redeemRequests.pop(); - } else { - rd.amount -= _amount; - } - } else if (_amount != 0) { - if (walletMap[key] == 0) { - emit undelegatedOverflow(txid, i, 0, _amount, pkscript); - } else { - btcTxMap[txid].amount = _amount; - btcTxMap[txid].outputIndex = i; - } - } - } - } - - /// Receive round rewards from BitcoinAgent. It is triggered at the beginning of turn round. - /// @param validators List of validator operator addresses - /// @param rewardList List of reward amount - function distributeReward(address[] calldata validators, uint256[] calldata rewardList) external override onlyBtcAgent { - uint256 reward; - uint256 validatorSize = validators.length; - for (uint256 i = 0; i < validatorSize; ++i) { - reward += rewardList[i]; - } - if (stakedAmount == 0) { - accruedRewardPerBTCMap[roundTag] = _getRoundRewardPerBTC(roundTag-1); - } else { - accruedRewardPerBTCMap[roundTag] = _getRoundRewardPerBTC(roundTag-1) + reward * SatoshiPlusHelper.BTC_DECIMAL / stakedAmount; - } - emit rewardUpdated(roundTag, accruedRewardPerBTCMap[roundTag]); - } - - /// Get staked BTC amount. - /// - /// @param candidates List of candidate operator addresses - /// @return amounts List of amounts of all special candidates in this round - function getStakeAmounts(address[] calldata candidates) external override view returns (uint256[] memory amounts) { - // LST BTC are hypothetically designed to stake evenly to the living validators - uint256 length = candidates.length; - amounts = new uint256[](length); - uint256 sustainValidatorCount; - for (uint256 i = 0; i < length; i++) { - if (ICandidateHub(CANDIDATE_HUB_ADDR).isValidator(candidates[i])) { - amounts[i] = 1; - sustainValidatorCount++; - } - } - if (sustainValidatorCount != 0) { - uint256 avgAmount = realtimeAmount / sustainValidatorCount; - for (uint256 i = 0; i < length; i++) { - if (amounts[i] == 1) { - amounts[i] = avgAmount; - } - } - } - } - - /// Start new round, this is called by the CandidateHub contract - /// @param round The new round tag - function setNewRound(address[] calldata /*validators*/, uint256 round) external override onlyBtcAgent { - stakedAmount = realtimeAmount; - roundTag = round; - } - - /// Prepare for the new round - function prepare(uint256) external override { - // nothing to prepare - } - - /// Claim reward for delegator - /// @param delegator the delegator address - /// @return reward Amount claimed - /// @return rewardUnclaimed Amount unclaimed - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function claimReward(address delegator, uint256 /*settleRound*/, bool /*claim*/) external override onlyBtcAgent returns (uint256 reward, uint256 rewardUnclaimed, uint256 accStakedAmount) { - if (paused()) { - return (0, 0, 0); - } - (reward, accStakedAmount) = _updateUserRewards(delegator, true); - return (reward, 0, accStakedAmount); - } - - /*********************** External implementations ***************************/ - /// Burn LST token and redeem BTC assets. - /// This method is called by LST holders. - /// - /// @param amount redeem amount - /// @param pkscript pkscript to receive BTC assets - function redeem(uint64 amount, bytes calldata pkscript) external whenNotPaused nonReentrant { - (bytes32 hash, uint32 addrType) = _extractPkScriptAddr(pkscript); - require(addrType != WTYPE_UNKNOWN, "invalid pkscript"); - - UserStakeInfo storage user = userStakeInfo[msg.sender]; - uint64 balance = user.realtimeAmount; - // check there is enough balance. - require(amount <= balance, "Not enough btc token"); - if (amount == 0) { - amount = balance; - } - require (amount >= utxoFee * 2, "The redeem amount is too small"); - uint64 burnAmount = amount; - amount -= utxoFee; - - uint64 totalAmount = 0; - bytes32 key = keccak256(pkscript); - uint256 index1 = redeemMap[key]; - if (index1 == 0) { - redeemRequests.push(Redeem(hash, addrType, amount)); - redeemMap[key] = redeemRequests.length; - totalAmount = amount; - } else { - redeemRequests[index1 - 1].amount += amount; - totalAmount = redeemRequests[index1 - 1].amount; - } - - IBitcoinLSTToken(BTCLST_TOKEN_ADDR).burn(msg.sender, uint256(burnAmount)); - emit redeemed(msg.sender, amount, utxoFee, pkscript); - - _afterBurn(msg.sender, burnAmount); - realtimeAmount -= burnAmount; - } - - /// This method should be called whenever lst token transfer happens - /// @param from ERC20 standard from address - /// @param to ERC20 standard to address - /// @param value the amount of tokens to transfer - function onTokenTransfer(address from, address to, uint256 value) external whenNotPaused onlyBtcLSTToken { - if (from == to) { - return; - } - uint64 amount = uint64(value); - require(uint256(amount) == value, 'btc amount limit uint64'); - _afterBurn(from, amount); - _afterMint(to, amount); - } - - /// Returns wallets array. - function getWallets() external view returns (WalletInfo[] memory) { - return wallets; - } - - /*********************** Governance ********************************/ - /// Update parameters through governance vote - /// @param key The name of the parameter - /// @param value the new value set to the parameter - function updateParam(string calldata key, bytes calldata value) external override onlyInit onlyGov { - if (Memory.compareStrings(key, "add")) { - _addWallet(value); - } else if (Memory.compareStrings(key, "remove")) { - _removeWallet(value); - } else if (Memory.compareStrings(key, "paused")) { - if (value.length != 1) { - revert MismatchParamLength(key); - } - uint8 newPaused = value.toUint8(0); - if (newPaused > 1) { - revert OutOfBounds(key, newPaused, 0, 1); - } - if (newPaused == 1) { - _pause(); - } else { - _unpause(); - } - } else if (Memory.compareStrings(key, "utxoFee")) { - if (value.length != 8) { - revert MismatchParamLength(key); - } - uint64 newUtxoFee = value.toUint64(0); - if (newUtxoFee < 1e3) { - revert OutOfBounds(key, newUtxoFee, 1e3, type(uint64).max); - } - utxoFee = newUtxoFee; - } else { - revert UnsupportedGovParam(key); - } - emit paramChange(key, value); - } - - /*********************** Inner Methods *****************************/ - /// Add a new BTC wallet which holds the BTC assets for the LST product - /// This method can only be called by `updateParam()` through governance vote - /// @param pkscript public key script of the wallet - function _addWallet(bytes memory pkscript) internal { - bytes32 walletKey = keccak256(pkscript); - uint256 index1 = walletMap[walletKey]; - if (index1 > 0) { - if (wallets[index1 - 1].status != WALLET_ACTIVE) { - wallets[index1 - 1].status = WALLET_ACTIVE; - } - } else { - (bytes32 _hash, uint32 _type) = _extractPkScriptAddr(pkscript); - require(_type != WTYPE_UNKNOWN, "Invalid BTC wallet"); - wallets.push(WalletInfo(_hash, _type, WALLET_ACTIVE)); - index1 = wallets.length; - walletMap[walletKey] = index1; - } - emit addedWallet(wallets[index1-1].hash, wallets[index1-1].addrType); - } - - /// Remove a BTC wallet - /// This method can only be called by `updateParam()` through governance vote - /// @param pkscript public key script of the wallet - function _removeWallet(bytes memory pkscript) internal { - bytes32 walletKey = keccak256(pkscript); - uint256 index1 = walletMap[walletKey]; - require(index1 != 0, "Wallet not found"); - WalletInfo storage w = wallets[index1 - 1]; - if (w.status != WALLET_INACTIVE) { - w.status = WALLET_INACTIVE; - } - - bool hasActive = false; - for (uint i = 0; i < wallets.length; i++) { - if (wallets[i].status == WALLET_ACTIVE ) { - hasActive = true; - break; - } - } - require(hasActive, "Wallet empty"); - - emit removedWallet(w.hash, w.addrType); - } - - /// check whether the BTC transaction aims for LST staking - /// @param pkscript redeem script of the locked up output - function _checkWallet(bytes memory pkscript) internal view { - bytes32 walletKey = keccak256(pkscript); - uint256 index1 = walletMap[walletKey]; - require(index1 != 0, "Wallet not found"); - require(wallets[index1 - 1].status == WALLET_ACTIVE, "wallet inactive"); - } - - /// extract address information from pkscript - /// @param pkScript pkscript used in txout - function _extractPkScriptAddr(bytes memory pkScript) internal pure returns (bytes32 whash, uint32 addrType) { - uint256 len = pkScript.length; - if (len == 25) { - // pay-to-pubkey-hash - // OP_DUP OP_HASH160 OP_DATA_20 OP_EQUALVERIFY OP_CHECKSIG - if (pkScript[0] == OP_DUP && pkScript[1] == OP_HASH160 && - pkScript[2] == OP_DATA_20 && pkScript[23] == OP_EQUALVERIFY && - pkScript[24] == OP_CHECKSIG) { - return (pkScript.indexBytes32(3, 20), WTYPE_P2PKH); - } - } else if (len == 23) { - // pay-to-script-hash - // 23 OP_HASH160 OP_DATA_20 OP_EQUAL - if (pkScript[0] == OP_HASH160 && pkScript[1] == OP_DATA_20 && - pkScript[22] == OP_EQUAL) { - return (pkScript.indexBytes32(2, 20), WTYPE_P2SH); - } - } else if (len == 22) { - // 22 OP_0 OP_DATA_20 - if (pkScript[0] == OP_0 && pkScript[1] == OP_DATA_20) { - return (pkScript.indexBytes32(2, 20), WTYPE_P2WPKH); - } - } else if (len == 34) { - // 34 OP_0 OP_DATA_32 - if (pkScript[0] == OP_0 && pkScript[1] == OP_DATA_32) { - return (pkScript.indexBytes32(2, 32), WTYPE_P2WSH); - } - // 34 OP_1 OP_DATA_20 - if (pkScript[0] == OP_1 && pkScript[1] == OP_DATA_32) { - return (pkScript.indexBytes32(2, 32), WTYPE_P2TAPROOT); - } - } - return (0, WTYPE_UNKNOWN); - } - - /// construct script from hash and address type - /// @param whash the hash used to build the script - /// @param addrType the BTC address type used to build the script - /// @return pkscript the script - function _buildPkScript(bytes32 whash, uint32 addrType) internal pure returns (bytes memory pkscript) { - if (addrType == WTYPE_P2WSH || addrType == WTYPE_P2TAPROOT) { - pkscript = new bytes(34); - pkscript[1] = OP_DATA_32; - if (addrType == WTYPE_P2WSH) { - pkscript[0] = OP_0; - } else if (addrType == WTYPE_P2TAPROOT) { - pkscript[0] = OP_1; - } - assembly { - mstore(add(pkscript, 0x22), whash) - } - return pkscript; - } - uint startPos = 0x22; - if (addrType == WTYPE_P2PKH) { - pkscript = new bytes(25); - pkscript[0] = OP_DUP; - pkscript[1] = OP_HASH160; - pkscript[2] = OP_DATA_20; - pkscript[23] = OP_EQUALVERIFY; - pkscript[24] = OP_CHECKSIG; - startPos = 0x23; - } else if (addrType == WTYPE_P2SH) { - pkscript = new bytes(23); - pkscript[0] = OP_HASH160; - pkscript[1] = OP_DATA_20; - pkscript[22] = OP_EQUAL; - } else if (addrType == WTYPE_P2WPKH) { - pkscript = new bytes(22); - pkscript[0] = OP_0; - pkscript[1] = OP_DATA_20; - } - unchecked { - uint mask = 256 ** (32 - 20) - 1; - assembly { - let srcpart := and(shl(96, whash), not(mask)) - let destpart := and(mload(add(pkscript, startPos)), mask) - mstore(add(pkscript, startPos), or(destpart, srcpart)) - } - } - } - - /// get accrued reward for each unit of BTC of a given round - /// @param round the round to retrieve reward value - function _getRoundRewardPerBTC(uint256 round) internal view returns (uint256 reward) { - if (round <= initRound) { - return 0; - } - for (;round != initRound; --round) { - reward = accruedRewardPerBTCMap[round]; - if (reward != 0) { - return reward; - } - } - return 0; - } - - /// calculate user reward and update internal reward map - /// @param userAddress the user address to update - /// @param claim whether the return amount of reward will be claimed - /// @return reward amount of user reward updated/claimed - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function _updateUserRewards(address userAddress, bool claim) internal returns (uint256 reward, uint256 accStakedAmount) { - UserStakeInfo storage user = userStakeInfo[userAddress]; - uint256 changeRound = user.changeRound; - if (changeRound != 0 && changeRound < roundTag) { - uint256 lastRoundTag = roundTag - 1; - uint256 lastRoundReward = _getRoundRewardPerBTC(lastRoundTag); - reward = uint256(user.stakedAmount) * (lastRoundReward - _getRoundRewardPerBTC(changeRound - 1)) / SatoshiPlusHelper.BTC_DECIMAL; - accStakedAmount = user.stakedAmount * (lastRoundTag - changeRound + 1); - if (user.realtimeAmount != user.stakedAmount) { - if (changeRound < lastRoundTag) { - reward += (user.realtimeAmount - user.stakedAmount) * (lastRoundReward - _getRoundRewardPerBTC(changeRound)) / SatoshiPlusHelper.BTC_DECIMAL; - accStakedAmount += (user.realtimeAmount - user.stakedAmount) * (lastRoundTag - changeRound); - } - user.stakedAmount = user.realtimeAmount; - } - } - if (changeRound != roundTag) { - user.changeRound = roundTag; - } - // make sure the caller to send the rewards out - // otherwise the rewards will be gone - if (claim) { - if (rewardMap[userAddress].reward != 0 || rewardMap[userAddress].accStakedAmount != 0) { - reward += rewardMap[userAddress].reward; - accStakedAmount += rewardMap[userAddress].accStakedAmount; - delete rewardMap[userAddress]; - } - } else { - rewardMap[userAddress].reward += reward; - rewardMap[userAddress].accStakedAmount += accStakedAmount; - } - } - - /// this method is called when lst tokens are burnt - /// @param from the address to burn the tokens - /// @param value the amount of tokens to burn - function _afterBurn(address from, uint64 value) internal { - require(from != address(0), "invalid sender"); - UserStakeInfo storage user = userStakeInfo[from]; - uint64 balance = user.realtimeAmount; - require(value <= balance, "Insufficient balance"); - _updateUserRewards(from, false); - user.realtimeAmount -= value; - if (user.realtimeAmount < user.stakedAmount) { - user.stakedAmount = user.realtimeAmount; - } - } - - /// this method is called when lst tokens are mint - /// @param to the address to mint tokens - /// @param value the amount of tokens to mint - function _afterMint(address to, uint64 value) internal { - require(to != address(0), "invalid receiver"); - _updateUserRewards(to, false); - userStakeInfo[to].realtimeAmount += value; - } - - /// Parses the target input - /// @param _vinView the vin of Bitcoin transaction - /// @return fromWallet whether the tx is from wallet. - function _parseVin(bytes29 _vinView) internal view returns (bool fromWallet) { - _vinView.assertType(uint40(BitcoinHelper.BTCTypes.Vin)); - uint32 _numberOfInputs = uint32(_vinView.indexCompactInt(0)); - bytes32 _outpointHash; - uint32 _outpointIndex; - for (uint32 i = 0; i < _numberOfInputs; ++i) { - (_outpointHash, _outpointIndex) = _vinView.extractOutpoint(i); - if (btcTxMap[_outpointHash].amount != 0 && btcTxMap[_outpointHash].outputIndex == _outpointIndex) { - return true; - } - } - return false; - } - - /// Parses the target output and the op_return of a transaction - /// @dev Finds the BTC amount that payload size is less than 80 bytes - /// @param _voutView The vout of a Bitcoin transaction - /// @param _lockingScript Redeem script of the locked up output - /// @return btcAmount Amount of BTC to stake - /// @return outputIndex The output index of target output. - /// @return delegator The one who delegate the Bitcoin. - function _parseVout( - bytes29 _voutView, - bytes memory _lockingScript - ) internal view returns (uint64 btcAmount, uint32 outputIndex, address delegator) { - _voutView.assertType(uint40(BitcoinHelper.BTCTypes.Vout)); - bytes29 _outputView; - bytes29 _scriptPubkeyView; - bytes29 _scriptPubkeyWithLength; - bytes29 _arbitraryData; - - // Finds total number of outputs - uint _numberOfOutputs = uint256(_voutView.indexCompactInt(0)); - - for (uint index = 0; index < _numberOfOutputs; index++) { - _outputView = _voutView.indexVout(index); - _scriptPubkeyView = _outputView.scriptPubkey(); - _scriptPubkeyWithLength = _outputView.scriptPubkeyWithLength(); - _arbitraryData = _scriptPubkeyWithLength.opReturnPayload(); - - // Checks whether the output is an arbitrary data or not - if(_arbitraryData == TypedMemView.NULL) { - // Output is not an arbitrary data - if (keccak256(_scriptPubkeyView.clone()) == keccak256(_lockingScript)) { - btcAmount = _outputView.value(); - outputIndex = uint32(index); - } - } else { - delegator = _parsePayloadAndCheckProtocol(_arbitraryData); - } - } - require(btcAmount != 0, "staked value is zero"); - } - - /// parse the payload and do sanity check for SAT+ bytes - /// @param payload the BTC transaction payload - function _parsePayloadAndCheckProtocol(bytes29 payload) internal pure returns (address delegator) { - require(payload.len() >= 28, "payload length is too small"); - require(payload.indexUint(0, 4) == SatoshiPlusHelper.BTC_STAKE_MAGIC, "wrong magic"); - require(payload.indexUint(5, 2) == SatoshiPlusHelper.CHAINID, "wrong chain id"); - uint32 version = uint32(payload.indexUint(4, 1)); - require(version == SatoshiPlusHelper.BTCLST_STAKE_VERSION, "unsupported sat+ version in btc staking"); - delegator = payload.indexAddress(7); - } -} diff --git a/contracts/BitcoinLSTToken.sol b/contracts/BitcoinLSTToken.sol deleted file mode 100644 index 9ad078485..000000000 --- a/contracts/BitcoinLSTToken.sol +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-License-Identifier: Apache2.0 -pragma solidity 0.8.4; - -import './interface/IBitcoinLSTToken.sol'; -import "./interface/IParamSubscriber.sol"; -import "./System.sol"; - -contract BitcoinLSTToken is IBitcoinLSTToken, System, IParamSubscriber { - - mapping(address => uint256) private _balances; - - mapping(address => mapping(address => uint256)) private _allowances; - - uint256 private _totalSupply; - - string public name; // fancy name - uint8 public decimals; // How many decimals to show. - string public symbol; // An identifier - - modifier onlyBtcLSTStake() { - require(msg.sender == BTCLST_STAKE_ADDR, "only invoked by bitcoin lst stake"); - _; - } - - /*********************** Init ********************************/ - function init() external onlyNotInit { - name = "Core BTC LST"; - symbol = "lstBTC"; - decimals = 8; - alreadyInit = true; - } - - /// @dev See {IERC20-totalSupply}. - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - /// @dev See {IERC20-balanceOf}. - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account]; - } - - /// @dev See {IERC20-allowance}. - function allowance(address owner, address spender) public view virtual override returns (uint256) { - return _allowances[owner][spender]; - } - - /// @dev Deprecated See {IERC20-approve}. - /// NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - /// `transferFrom`. This is semantically equivalent to an infinite approval. - /// - /// Requirements: - /// - `spender` cannot be the zero address. - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = msg.sender; - _approve(owner, spender, amount); - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// This is an alternative to {approve} that can be used as a mitigation for - /// problems described in {IERC20-approve}. - /// - /// Emits an {Approval} event indicating the updated allowance. - /// - /// Requirements: - /// - `spender` cannot be the zero address. - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = msg.sender; - _approve(owner, spender, allowance(owner, spender) + addedValue); - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// This is an alternative to {approve} that can be used as a mitigation for - /// problems described in {IERC20-approve}. - /// - /// Emits an {Approval} event indicating the updated allowance. - /// - /// Requirements: - /// - `spender` cannot be the zero address. - /// - `spender` must have allowance for the caller of at least - /// `subtractedValue`. - function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { - address owner = msg.sender; - uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /// @dev Moves `amount` of tokens from `from` to `to`. - /// This internal function is equivalent to {transfer}, and can be used to - /// e.g. implement automatic token fees, slashing mechanisms, etc. - /// - /// Emits a {Transfer} event. - /// - /// Requirements: - /// - `from` cannot be the zero address. - /// - `to` cannot be the zero address. - /// - `from` must have a balance of at least `amount`. - function _transfer(address from, address to, uint256 amount) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _balances[to] += amount; - } - - emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, amount); - } - - /// @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - /// This internal function is equivalent to `approve`, and can be used to - /// e.g. set automatic allowances for certain subsystems, etc. - /// - /// Emits an {Approval} event. - /// - /// Requirements: - /// - `owner` cannot be the zero address. - /// - `spender` cannot be the zero address. - function _approve(address owner, address spender, uint256 amount) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /// @dev Updates `owner` s allowance for `spender` based on spent `amount`. - /// Does not update the allowance amount in case of infinite allowance. - /// Revert if not enough allowance is available. - /// - /// Might emit an {Approval} event. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - /// @dev Hook that is called before any transfer of tokens. This includes - /// minting and burning. - /// - /// Calling conditions: - /// - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - /// will be transferred to `to`. - /// - when `from` is zero, `amount` tokens will be minted for `to`. - /// - when `to` is zero, `amount` of ``from``'s tokens will be burned. - /// - `from` and `to` are never both zero. - /// - /// To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. This includes - /// minting and burning. - /// - /// Calling conditions: - /// - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - /// has been transferred to `to`. - /// - when `from` is zero, `amount` tokens have been minted for `to`. - /// - when `to` is zero, `amount` of ``from``'s tokens have been burned. - /// - `from` and `to` are never both zero. - /// - /// To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - - /// @dev Creates `amount` tokens and assigns them to `account`, increasing - /// the total supply. - /// - /// Emits a {Transfer} event with `from` set to the zero address. - /// - /// Requirements: - /// - `account` cannot be the zero address. - function mint(address account, uint256 amount) external override onlyBtcLSTStake { - require(account != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account, amount); - - _totalSupply += amount; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _balances[account] += amount; - } - emit Transfer(address(0), account, amount); - - _afterTokenTransfer(address(0), account, amount); - } - - - /// @dev Destroys `amount` tokens from `account`, reducing the - /// total supply. - /// - /// Emits a {Transfer} event with `to` set to the zero address. - /// - /// Requirements: - /// - `account` cannot be the zero address. - /// - `account` must have at least `amount` tokens. - function burn(address account, uint256 amount) external override onlyBtcLSTStake { - require(account != address(0), "ERC20: burn from the zero address"); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); - unchecked { - _balances[account] = accountBalance - amount; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _totalSupply -= amount; - } - - emit Transfer(account, address(0), amount); - - _afterTokenTransfer(account, address(0), amount); - } - - /// @dev See {IERC20-transfer}. - /// Requirements: - /// - `to` cannot be the zero address. - /// - the caller must have a balance of at least `amount`. - function transfer(address to, uint256 amount) public override returns (bool) { - address owner = msg.sender; - _transfer(owner, to, amount); - _onTransfer(owner, to, amount); - return true; - } - - /// @dev See {IERC20-transferFrom}. - /// Emits an {Approval} event indicating the updated allowance. This is not - /// required by the EIP. See the note at the beginning of {ERC20}. - /// - /// NOTE: Does not update the allowance if the current allowance - /// is the maximum `uint256`. - /// - /// Requirements: - /// - `from` and `to` cannot be the zero address. - /// - `from` must have a balance of at least `amount`. - /// - the caller must have allowance for ``from``'s tokens of at least - /// `amount`. - function transferFrom(address from, address to, uint256 amount) public override returns (bool) { - address spender = msg.sender; - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - _onTransfer(from, to, amount); - return true; - } - - function _onTransfer(address from, address to, uint256 amount) internal { - (bool success, ) = BTCLST_STAKE_ADDR.call( - abi.encodeWithSignature("onTokenTransfer(address,address,uint256)", - from, to, amount) - ); - require(success, "call lstStake.onTokenTransfer failed."); - } - - /*********************** Param update ********************************/ - /// Update parameters through governance vote - /// @param key The name of the parameter - /// @param value the new value set to the parameter - function updateParam(string calldata key, bytes calldata value) external view override onlyInit onlyGov { - revert UnsupportedGovParam(key); - } -} diff --git a/contracts/StakeHub.sol b/contracts/StakeHub.sol index 19a7d9682..1d7038fbb 100644 --- a/contracts/StakeHub.sol +++ b/contracts/StakeHub.sol @@ -91,7 +91,7 @@ contract StakeHub is IStakeHub, System, IParamSubscriber { operators[HASH_AGENT_ADDR] = true; operators[BTC_AGENT_ADDR] = true; operators[BTC_STAKE_ADDR] = true; - operators[BTCLST_STAKE_ADDR] = true; + // operators[BTCLST_STAKE_ADDR] = true; alreadyInit = true; diff --git a/contracts/System.sol b/contracts/System.sol index 30b972ae1..4e5b68c37 100644 --- a/contracts/System.sol +++ b/contracts/System.sol @@ -26,9 +26,9 @@ contract System { address public constant HASH_AGENT_ADDR = 0x0000000000000000000000000000000000001012; address public constant BTC_AGENT_ADDR = 0x0000000000000000000000000000000000001013; address public constant BTC_STAKE_ADDR = 0x0000000000000000000000000000000000001014; - address public constant BTCLST_STAKE_ADDR = 0x0000000000000000000000000000000000001015; + // 0x0000000000000000000000000000000000001015 is deprecated address public constant CONFIGURATION_ADDR = 0x0000000000000000000000000000000000001016; - address public constant BTCLST_TOKEN_ADDR = 0x0000000000000000000000000000000000010001; + // 0x0000000000000000000000000000000000010001 is deprecated; modifier onlyCoinbase() { diff --git a/contracts/System.template b/contracts/System.template index bcc4a6ba3..8bc6fd9f7 100644 --- a/contracts/System.template +++ b/contracts/System.template @@ -23,10 +23,8 @@ contract System { address public STAKE_HUB_ADDR; address public BTC_STAKE_ADDR; address public BTC_AGENT_ADDR; - address public BTCLST_STAKE_ADDR; address public CORE_AGENT_ADDR; address public HASH_AGENT_ADDR; - address public BTCLST_TOKEN_ADDR; address public CONFIGURATION_ADDR; struct SystemContractAddr { @@ -43,10 +41,8 @@ contract System { address stakeHub; address btcStake; address btcAgent; - address btcLSTStake; address coreAgent; address hashAgent; - address btcLstToken; address configurationContract; } @@ -65,10 +61,8 @@ contract System { STAKE_HUB_ADDR = systemContractAddr.stakeHub; BTC_STAKE_ADDR = systemContractAddr.btcStake; BTC_AGENT_ADDR = systemContractAddr.btcAgent; - BTCLST_STAKE_ADDR = systemContractAddr.btcLSTStake; CORE_AGENT_ADDR = systemContractAddr.coreAgent; HASH_AGENT_ADDR = systemContractAddr.hashAgent; - BTCLST_TOKEN_ADDR = systemContractAddr.btcLstToken; CONFIGURATION_ADDR = systemContractAddr.configurationContract; } @@ -93,9 +87,9 @@ contract System { address public constant HASH_AGENT_ADDR = 0x0000000000000000000000000000000000001012; address public constant BTC_AGENT_ADDR = 0x0000000000000000000000000000000000001013; address public constant BTC_STAKE_ADDR = 0x0000000000000000000000000000000000001014; - address public constant BTCLST_STAKE_ADDR = 0x0000000000000000000000000000000000001015; + // 0x0000000000000000000000000000000000001015 is deprecated address public constant CONFIGURATION_ADDR = 0x0000000000000000000000000000000000001016; - address public constant BTCLST_TOKEN_ADDR = 0x0000000000000000000000000000000000010001; + // 0x0000000000000000000000000000000000010001 is deprecated; {% endif %} modifier onlyCoinbase() { {% if not mock %} diff --git a/contracts/interface/IBitcoinLSTToken.sol b/contracts/interface/IBitcoinLSTToken.sol deleted file mode 100644 index 6f0ac0f28..000000000 --- a/contracts/interface/IBitcoinLSTToken.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache2.0 -pragma solidity 0.8.4; - -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; - -interface IBitcoinLSTToken is IERC20 { - function mint(address delegator, uint256 amount) external; - function burn(address delegator, uint256 amount) external; -} \ No newline at end of file diff --git a/generate-genesis.js b/generate-genesis.js index 44bc064f5..d9f76ba08 100644 --- a/generate-genesis.js +++ b/generate-genesis.js @@ -154,16 +154,6 @@ Promise.all([ "contracts/BitcoinStake.sol", "BitcoinStake" ), - compileContract( - "bitcoinLSTstake", - "contracts/BitcoinLSTStake.sol", - "BitcoinLSTStake" - ), - compileContract( - "bitcoinLSTtoken", - "contracts/BitcoinLSTToken.sol", - "BitcoinLSTToken" - ), compileContract( "configuration", "contracts/Configuration.sol", From 382de5aa89fb3e0f6a4d14ad9ef9075b7ae74749 Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Wed, 9 Jul 2025 17:05:59 +0800 Subject: [PATCH 09/27] add test cases for bep127 & bep131 --- contracts/mock/CandidateHubMock.sol | 19 +- contracts/mock/SlashIndicatorMock.sol | 13 +- contracts/mock/ValidatorSetMock.sol | 25 +- tests/common.py | 72 +- tests/test_candidate_hub.py | 238 +++- tests/test_claim_reward.py | 1269 +++++++++++++++++- tests/test_slash.py | 11 +- tests/test_slash_indicator.py | 277 +++- tests/test_stake_hub_unit.py | 1 - tests/test_validator_set.py | 1699 ++++++++++++++++++++++++- 10 files changed, 3569 insertions(+), 55 deletions(-) diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index ec8757105..d3e25a250 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -135,10 +135,19 @@ contract CandidateHubMock is CandidateHub { function getValidatorsMock( address[] memory candidateList, uint256[] memory scoreList, - uint256 count + uint256 count, + uint256 sortedCount ) public view returns (address[] memory validatorList) { - return getValidators(candidateList, scoreList, count); + return getValidators(candidateList, scoreList, count, sortedCount); } + function getAlternateCountMock( + uint256 maxAlternateCount, + uint256 count, + uint256 candidateSize + ) public view returns (uint256) { + return getAlternateCount(maxAlternateCount, count, candidateSize); + } + function cleanMock() public { ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); @@ -334,4 +343,10 @@ contract CandidateHubMock is CandidateHub { changeStatus(candidateSet[i], statusList[i]); } } + function setMaxAlternateCount(uint256 _maxAlternateCount) external { + maxAlternateCount = _maxAlternateCount; + } + function mockGetAlternateCount(uint256 maxAlternateCount, uint256 count, uint256 candidateSize) public pure returns (uint256) { + return getAlternateCount(maxAlternateCount, count, candidateSize); + } } diff --git a/contracts/mock/SlashIndicatorMock.sol b/contracts/mock/SlashIndicatorMock.sol index 23692cce1..f047128b1 100644 --- a/contracts/mock/SlashIndicatorMock.sol +++ b/contracts/mock/SlashIndicatorMock.sol @@ -74,6 +74,9 @@ contract SlashIndicatorMock is SlashIndicator { // verifyBLSSignature(evidence.voteB, evidence.voteAddr), "verify signature failed"); (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getValidatorsAndVoteAddresses(); + if (voteAddrs.length <= 1) { + return; + } for (uint256 i; i < voteAddrs.length; ++i) { if (BytesLib.equal(voteAddrs[i], evidence.voteAddr)) { ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportFinalityViolation); @@ -82,5 +85,13 @@ contract SlashIndicatorMock is SlashIndicator { } } } - + function setMisdemeanorThreshold(uint256 _misdemeanorThreshold) external { + misdemeanorThreshold = _misdemeanorThreshold; + } + function setFelonyThreshold(uint256 _felonyThreshold) external { + felonyThreshold = _felonyThreshold; + } + function mockSlashWithBlockCount(address validator, uint256 blockCount) external { + slashWithBlockCount(validator, blockCount); + } } diff --git a/contracts/mock/ValidatorSetMock.sol b/contracts/mock/ValidatorSetMock.sol index d034dc1eb..1c0b74bdf 100644 --- a/contracts/mock/ValidatorSetMock.sol +++ b/contracts/mock/ValidatorSetMock.sol @@ -89,9 +89,9 @@ contract ValidatorSetMock is ValidatorSet { for (i = 0; i < currentLength; ++i) { if (i >= lastLength) { - currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0)); + currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0, 0)); } else { - currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0); + currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0, 0); } currentValidatorSetMap[consensusAddrList[i]] = i + 1; } @@ -181,4 +181,25 @@ contract ValidatorSetMock is ValidatorSet { totalInCome = 0; return operateAddressList; } + function setValidatorCount(uint256 _validatorCount) external { + validatorCount = _validatorCount; + } + function clearCurrentValidatorSet() external { + for (uint i = 0; i < currentValidatorSet.length; i++) { + delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; + } + delete currentValidatorSet; + } + function getCurrentValidatorSet() external view returns (Validator[] memory) { + return currentValidatorSet; + } + function setMaintainSlashPercent(uint256 _maintainSlashPercent) external { + maintainSlashPercent = _maintainSlashPercent; + } + function mockUpdateRankedValidatorList(address[] calldata consensusAddrList) external { + updateRankedValidatorList(consensusAddrList); + } + function getRankedValidatorList() external view returns (address[] memory) { + return rankedValidatorList; + } } diff --git a/tests/common.py b/tests/common.py index 94186110c..a458a1478 100644 --- a/tests/common.py +++ b/tests/common.py @@ -28,7 +28,33 @@ def get_candidate(operator=None): return CandidateHubMock[0].candidateSet(idx - 1).dict() -def turn_round(miners: list = None, tx_fee=100, round_count=1,weights=None): +def chain_deposit(miners, tx_fee=100,deposit_count=1): + if isinstance(miners, list): + for miner in miners: + for _ in range(deposit_count): + tx = ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) + print(f'chain_deposit>>>>>>>>>>deposit_count{deposit_count}:', tx.events) + else: + for _ in range(deposit_count): + tx = ValidatorSetMock[0].deposit(miners, {"value": tx_fee, "from": accounts[99]}) + print(f'chain_deposit>>>>>>>>>>deposit_count{deposit_count}:',tx.events) + + + +def chain_vote(miners, weights): + if isinstance(miners, list): + for miner in miners: + ValidatorSetMock[0].vote(miner, weights, {"from": accounts[99]}) + else: + ValidatorSetMock[0].vote(miners, weights, {"from": accounts[99]}) + + +def chain_get_validator_consensus(): + consensus, vote_addresses = ValidatorSetMock[0].getValidatorsAndVoteAddresses() + print(f'chain_get_validator_consensus>>>>>>>>consensus:{consensus}') + return consensus + +def turn_round(miners: list = None, tx_fee=100, round_count=1, weights=None): if miners is None: miners = [] if weights is None: @@ -54,6 +80,10 @@ def execute_proposal(target, value, signature, calldata, msg): GovHubMock[0].execute(proposal_id) return proposal_id +def expect_validator_consensus(consensuses): + assert len(chain_get_validator_consensus()) == len(consensuses) + for consensus in chain_get_validator_consensus(): + assert consensus in consensuses def register_relayer(relayer_address=None): if relayer_address is None: @@ -80,9 +110,11 @@ def stake_hub_claim_reward(account): if isinstance(account, list): for i in account: tx = StakeHubMock[0].claimReward({'from': i}) + print(f'stake_hub_claim_reward>>>>>>>>account:{i}', tx.events) else: tx = StakeHubMock[0].claimReward({'from': account}) + print(f'stake_hub_claim_reward>>>>>>>>account:{account}', tx.events) return tx @@ -94,3 +126,41 @@ def claim_stake_and_relay_reward(account): else: tx0 = stake_hub_claim_reward(account) return tx0 + + +def slash_validator(consensus,slash_type='felony'): + tx =None + slash_indicator = SlashIndicatorMock[0] + if slash_type == 'felony': + slash_threshold = slash_indicator.felonyThreshold() + for _ in range(slash_threshold): + tx = slash_indicator.slash(consensus) + assert 'validatorFelony' in tx.events + elif slash_type == 'minor': + slash_threshold = slash_indicator.misdemeanorThreshold() + for _ in range(slash_threshold): + tx = slash_indicator.slash(consensus) + assert 'validatorMisdemeanor' in tx.events + return tx + +def refuse_delegate(operator): + tx = CandidateHubMock[0].refuseDelegate({'from': operator}) + print(f'refuse_delegate>>>>>>>>operator:{operator}', tx.events) + return tx + +def accept_delegate(operator): + tx = CandidateHubMock[0].acceptDelegate({'from': operator}) + print(f'accept_delegate>>>>>>>>operator:{operator}', tx.events) + return tx + +def enter_maintenance(operator): + tx = ValidatorSetMock[0].enterMaintenance({'from': operator}) + assert 'validatorEnterMaintenance' in tx.events + print(f'enter_maintenance>>>>>>>>operator:{operator}', tx.events) + return tx + +def exit_maintenance(operator): + tx = ValidatorSetMock[0].exitMaintenance({'from': operator}) + assert 'validatorExitMaintenance' in tx.events + print(f'exit_maintenance>>>>>>>>operator:{operator}', tx.events) + return tx diff --git a/tests/test_candidate_hub.py b/tests/test_candidate_hub.py index 1c5daa115..69f885b08 100644 --- a/tests/test_candidate_hub.py +++ b/tests/test_candidate_hub.py @@ -279,7 +279,7 @@ def test_turnround_update_voteaddrlist_success(candidate_hub, validator_set, sla assert validator_set.getValidatorsAndVoteAddresses()[1] == vote_address_list turn_round(consensuses) assert validator_set.currentValidatorSet(0) == [operators[0], consensuses[0], operators[0], 1000, 0, - vote_address_list[0], 0] + vote_address_list[0], 0, 0] @pytest.mark.parametrize("times", [ @@ -491,6 +491,7 @@ def test_registration_index_correct_after_success(candidate_hub, required_margin assert candidate_hub.candidateSet(1)['operateAddr'] == accounts[2] +# updateParam def test_only_gov_can_call(candidate_hub, required_margin): value = padding_left(Web3.to_hex(candidate_hub.dues() + 10), 64) with brownie.reverts("the msg sender must be governance contract"): @@ -592,6 +593,23 @@ def test_governance_param_error(candidate_hub, required_margin): candidate_hub.updateParam("error_key", value) +# updateParam - maxAlternateCount +@pytest.mark.parametrize("maxAlternateCount", [0, 1, 2, 6, 7]) +def test_govern_max_alternate_count_success(candidate_hub, maxAlternateCount): + value = padding_left(Web3.to_hex(maxAlternateCount), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("maxAlternateCount", value) + assert candidate_hub.maxAlternateCount() == maxAlternateCount + + +def test_max_alternate_count_out_of_range(candidate_hub): + value = padding_left(Web3.to_hex(candidate_hub.validatorCount() // 3 + 1), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + with brownie.reverts( + f"OutOfBounds: maxAlternateCount, {candidate_hub.validatorCount() // 3 + 1}, 0, {candidate_hub.validatorCount() // 3}"): + candidate_hub.updateParam("maxAlternateCount", value) + + def test_refuse_delegate_success(candidate_hub, set_candidate): operators, consensuses = set_candidate turn_round() @@ -1050,8 +1068,9 @@ def test_get_validators(candidate_hub): (candidates[:10], score_list1[:10], indexes[:10], 21, 10), (candidates[:10], score_list2[:10], indexes[:10], 21, 10), ] + sorted_count = 0 for candidate_list, score_list, index_list, count, expect_count in tests: - validator_list = candidate_hub.getValidatorsMock(candidate_list, score_list, count) + validator_list = candidate_hub.getValidatorsMock(candidate_list, score_list, count, sorted_count) index_list.sort(key=lambda e: score_list[e], reverse=True) for i in range(expect_count): flag = False @@ -1063,6 +1082,110 @@ def test_get_validators(candidate_hub): assert len(validator_list) == expect_count +def test_get_validators_single_candidate(candidate_hub): + candidates = [accounts[1]] + scores = [123] + result = candidate_hub.getValidatorsMock(candidates, scores, 1, 0) + assert result == [accounts[1]] + result2 = candidate_hub.getValidatorsMock(candidates, scores, 3, 0) + assert result2 == [accounts[1]] + with brownie.reverts(): + result3 = candidate_hub.getValidatorsMock(candidates, scores, 3, 1) + + +def test_get_validators_basic(candidate_hub): + candidates = [accounts[1], accounts[2], accounts[3]] + scores = [100, 200, 150] + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 0) + assert result == candidates + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 2) + assert result == [accounts[2], accounts[3], accounts[1]] + + +def test_get_validators_more_candidates(candidate_hub): + candidates = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5]] + scores = [100, 200, 150, 300, 250] + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 0) + assert result == [accounts[4], accounts[5], accounts[2]] + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 2) + assert result == [accounts[4], accounts[5], accounts[2]] + + +def test_get_validators_candidates_less_than_count(candidate_hub): + candidates = [accounts[1], accounts[2]] + scores = [100, 200] + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 0) + assert set(result) == set(candidates) + assert len(result) == 2 + result2 = candidate_hub.getValidatorsMock(candidates, scores, 3, 1) + assert result2 == [accounts[2], accounts[1]] + assert len(result2) == 2 + + +def test_get_validators_mixed_order_with_sorted_count(candidate_hub): + candidates = [random_address() for _ in range(10)] + scores = [3000, 2000, 1100, 8000, 1000, 7000, 4000, 10000, 5000, 6000] + max_alternate_count = 12 + count = 8 + get_alternate_count = candidate_hub.getAlternateCountMock(max_alternate_count, count, len(candidates)) + assert get_alternate_count == 2 + result = candidate_hub.getValidatorsMock(candidates, scores, count + get_alternate_count, get_alternate_count) + assert result[-2] == candidates[2] + assert result[-1] == candidates[4] + sorted_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True) + expected = [candidates[i] for i in sorted_indices[:8]] + assert result[:-2] != expected[:-2] + + +def test_get_validators_sorted_count_eq_count(candidate_hub): + candidates = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5]] + scores = [500, 400, 300, 200, 100] + with brownie.reverts("count should be greater than sortedCount"): + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 3) + + +def test_get_validators_with_sorted_count_equal_scores(candidate_hub): + candidates = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5]] + scores = [200, 300, 200, 300, 100] + result = candidate_hub.getValidatorsMock(candidates, scores, 4, 2) + assert len(result) == 4 + assert result[0] in [accounts[2], accounts[4]] + assert result[1] in [accounts[2], accounts[4]] + assert result[2] in [accounts[1], accounts[3]] + assert result[3] in [accounts[1], accounts[3]] + + +def test_get_validators_with_sorted_count_zero_scores(candidate_hub): + candidates = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5]] + scores = [0, 0, 300, 0, 200] + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 1) + assert result == [accounts[3], accounts[5], accounts[4]] + + +def test_get_validators_many_candidates_with_sorted_count(candidate_hub): + candidates = [accounts[i] for i in range(1, 31)] + scores = [1000 - i * 10 for i in range(30)] + random.shuffle(scores) + canditates_dict = {accounts[i]: scores[i - 1] for i in range(1, 31)} + + result = candidate_hub.getValidatorsMock(candidates, scores, 21, 10) + sorted_canditates = sorted(canditates_dict.items(), key=lambda x: x[1], reverse=True) + for i in sorted_canditates[:11]: + assert i[0] in result[:11] + assert result[11:] == [i[0] for i in sorted_canditates[11:21]] + for validator in result: + assert validator in candidates + + +def test_get_validators_with_equal_scores(candidate_hub): + candidates = [accounts[1], accounts[2], accounts[3], accounts[4]] + scores = [200, 200, 150, 200] + result = candidate_hub.getValidatorsMock(candidates, scores, 3, 0) + assert len(result) == 3 + for addr in result: + assert addr in [accounts[1], accounts[2], accounts[4]] + + def test_jail_validator(candidate_hub, validator_set, required_margin): fee_address = random_address() @@ -1454,6 +1577,62 @@ def test_edit_commission_rate_same_round_multiple_times(candidate_hub, accounts) 500 - max_change - 1, {'from': operator}) +def test_remove_candidate_agent_map_index(candidate_hub, slash_indicator): + operators = [] + consensuses = [] + for operator in accounts[5:10]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + turn_round() + agents = [accounts[10], accounts[11], accounts[12], accounts[13], accounts[14]] + candidate_hub.setDues(1e18) + for i in range(1, 5): + candidate_hub.updateAgent(agents[i], {'from': operators[i]}) + for i in range(5): + assert candidate_hub.operateMap(operators[i]) == i + 1 + assert candidate_hub.getConsensusMap(consensuses[i]) == i + 1 + for i in range(1, 4): + assert candidate_hub.agentMap(agents[i]) == i + 1 + assert candidate_hub.agentMap(agents[0]) == 0 + felony_threshold = slash_indicator.felonyThreshold() + for _ in range(felony_threshold): + tx = slash_indicator.slash(consensuses[0]) + candidate_hub.editVoteAddress(random_vote_address(), {'from': agents[-1]}) + result = [0, 2, 3, 4, 1] + for r in range(1, 5): + assert candidate_hub.operateMap(operators[r]) == result[r] + assert candidate_hub.getConsensusMap(consensuses[r]) == result[r] + assert candidate_hub.agentMap(agents[r]) == result[r] + assert candidate_hub.agentMap(agents[0]) == 0 + assert candidate_hub.operateMap(operators[0]) == 0 + assert candidate_hub.getConsensusMap(consensuses[0]) == 0 + + +def test_remove_candidate_agent_map_index_with_all_agents(candidate_hub, slash_indicator): + operators = [] + consensuses = [] + for operator in accounts[5:12]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + agents = [accounts[13], accounts[14], accounts[15], accounts[16], accounts[17], accounts[18], accounts[19]] + for i in range(7): + candidate_hub.updateAgent(agents[i], {'from': operators[i]}) + for i in range(7): + assert candidate_hub.operateMap(operators[i]) == i + 1 + assert candidate_hub.getConsensusMap(consensuses[i]) == i + 1 + assert candidate_hub.agentMap(agents[i]) == i + 1 + candidate_hub.unregister({'from': operators[2]}) + candidate_list = candidate_hub.getCandidates() + assert len(candidate_list) == 6 + assert candidate_hub.operateMap(operators[-1]) == 3 + assert candidate_hub.getConsensusMap(consensuses[-1]) == 3 + assert candidate_hub.agentMap(agents[-1]) == 3 + vote_addr = random_vote_address() + candidate_hub.editVoteAddress(vote_addr, {'from': agents[-1]}) + assert candidate_hub.candidateSet(2).dict()['voteAddr'] == vote_addr + assert candidate_hub.candidateSet(2).dict()['operateAddr'] == operators[-1] + + # editVoteAddress def test_edit_vote_address_permission(candidate_hub, accounts): with brownie.reverts("candidate does not exist"): @@ -1636,3 +1815,58 @@ def test_candidate_update_and_turn_round(candidate_hub, accounts, validator_set, tx = stake_hub_claim_reward(accounts[0]) assert 'claimedReward' in tx.events turn_round(consensuses) + + +# getAlternateCount +def test_get_alternate_count_basic_scenarios(candidate_hub): + # candidateSize <= validatorCount, return 0 + alternate_count = candidate_hub.mockGetAlternateCount(5, 3, 2) + assert alternate_count == 0 + + # candidateSize = validatorCount, return 0 + alternate_count = candidate_hub.mockGetAlternateCount(5, 3, 3) + assert alternate_count == 0 + + # candidateSize < validatorCount + maxAlternateCount, return candidateSize - validatorCount + alternate_count = candidate_hub.mockGetAlternateCount(5, 3, 5) + assert alternate_count == 2 # 5 - 3 = 2 + + # candidateSize >= validatorCount + maxAlternateCount, return maxAlternateCount + alternate_count = candidate_hub.mockGetAlternateCount(5, 3, 10) + assert alternate_count == 5 + + +def test_get_alternate_count_edge_cases(candidate_hub): + # all parameters are 0 + alternate_count = candidate_hub.mockGetAlternateCount(0, 0, 0) + assert alternate_count == 0 + + # maxAlternateCount is 0 + alternate_count = candidate_hub.mockGetAlternateCount(0, 3, 5) + assert alternate_count == 0 + + # validatorCount is 0 + alternate_count = candidate_hub.mockGetAlternateCount(5, 0, 3) + assert alternate_count == 3 + + # candidateSize is 0 + alternate_count = candidate_hub.mockGetAlternateCount(5, 3, 0) + assert alternate_count == 0 + + +def test_get_alternate_count_typical_scenarios(candidate_hub): + # candidateSize = validatorCount, return 0 + alternate_count = candidate_hub.mockGetAlternateCount(5, 10, 10) + assert alternate_count == 0 + + # candidateSize > validatorCount, return candidateSize - validatorCount + alternate_count = candidate_hub.mockGetAlternateCount(5, 10, 12) + assert alternate_count == 2 + + # candidateSize > validatorCount + maxAlternateCount, return maxAlternateCount + alternate_count = candidate_hub.mockGetAlternateCount(5, 10, 20) + assert alternate_count == 5 + + # candidateSize < validatorCount, return 0 + alternate_count = candidate_hub.mockGetAlternateCount(5, 10, 8) + assert alternate_count == 0 diff --git a/tests/test_claim_reward.py b/tests/test_claim_reward.py index 888599080..3992f480e 100644 --- a/tests/test_claim_reward.py +++ b/tests/test_claim_reward.py @@ -3,8 +3,7 @@ import rlp from .calc_reward import set_delegate, parse_delegation, Discount, set_btc_lst_delegate -from .common import register_candidate, turn_round, stake_hub_claim_reward, set_round_tag, claim_stake_and_relay_reward, \ - execute_proposal +from .common import * from eth_utils import to_bytes from .delegate import * from .utils import * @@ -46,7 +45,7 @@ def init_system_reward_balance(system_reward): def set_block_reward(validator_set, candidate_hub, btc_stake, stake_hub, pledge_agent, btc_lst_stake, gov_hub, btc_agent, system_reward, core_agent): global BLOCK_REWARD, FEE, DELEGATE_VALUE, TOTAL_REWARD, MIN_INIT_DELEGATE_VALUE - global BTC_STAKE, STAKE_HUB, CANDIDATE_HUB, BTC_LST_STAKE, PLEDGE_AGENT + global BTC_STAKE, STAKE_HUB, CANDIDATE_HUB, BTC_LST_STAKE, PLEDGE_AGENT, block_reward FEE = FEE * 100 block_reward = validator_set.blockReward() block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() @@ -82,6 +81,11 @@ def set_candidate(): return operators, consensuses +def set_validator_count(validator_count=6, alternate_count=2): + CandidateHubMock[0].setValidatorCount(validator_count) + CandidateHubMock[0].setMaxAlternateCount(alternate_count) + + def add_candidates(count=25): start_count = 10 operators = [] @@ -106,6 +110,48 @@ def mock_btc_stake_lock_time(timestamp, stake_round=None): return timestamp, end_round +def setup_validators_and_delegates(validator_count=9, alternate_count=3, candidate_count=15): + validator_set = ValidatorSetMock[0] + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + validator_set.setMaintainSlashPercent(30) + set_validator_count(validator_count=validator_count, alternate_count=alternate_count) + operators, consensuses = add_candidates(candidate_count) + return operators, consensuses + + +def delegate_validator(operators, valudator_index=None, delegator=None, delegate_amount=None, btc_amount=1000): + if delegate_amount is None: + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + if valudator_index is None: + valudator_index = [0, 6] + if delegator is None: + delegator = accounts[0:2] + if isinstance(valudator_index, list): + for i in range(valudator_index[0], valudator_index[1]): + if type(delegator) == list: + for acc in delegator: + delegate_coin_success(operators[i], acc, delegate_amount) + delegate_btc_success(operators[i], acc, btc_amount, LOCK_SCRIPT, relay=acc) + else: + delegate_coin_success(operators[i], delegator, delegate_amount) + delegate_btc_success(operators[i], delegator, btc_amount, LOCK_SCRIPT, relay=delegator) + else: + if type(delegator) == list: + for acc in delegator: + delegate_coin_success(operators[valudator_index], acc, delegate_amount) + delegate_btc_success(operators[valudator_index], acc, btc_amount, LOCK_SCRIPT, relay=acc) + else: + delegate_coin_success(operators[valudator_index], delegator, delegate_amount) + delegate_btc_success(operators[valudator_index], delegator, btc_amount, LOCK_SCRIPT, relay=delegator) + + +def expect_validator_consensus(consensuses): + assert len(chain_get_validator_consensus()) == len(consensuses) + for consensus in chain_get_validator_consensus(): + assert consensus in consensuses + + @pytest.mark.skip(reason="the data migration part has been removed, skip it.") def test_successful_upgrade_after_round_switch(pledge_agent, candidate_hub): init_round_tag, timestamp = mock_current_round() @@ -1106,3 +1152,1220 @@ def old_turn_round(miners: list = None, tx_fee=100, round_count=1): tx = CandidateHubMock[0].turnRoundOld() chain.sleep(1) return tx + + +# bep127 & bep131 +def test_bep127_alternate_replace_after_major_offense(candidate_hub, validator_set): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + set_validator_count() + operators, consensuses = add_candidates(8) + for i in range(6): + for acc in accounts[0:2]: + delegate_coin_success(operators[i], acc, delegate_amount) + delegate_coin_success(operators[6], accounts[2], 1000) + delegate_coin_success(operators[6], accounts[3], 1000) + delegate_coin_success(operators[7], accounts[4], 1000) + delegate_coin_success(operators[7], accounts[5], 1000) + turn_round() + enter_maintenance(operators[0]) + slash_validator(consensuses[1]) + chain_deposit(consensuses[7], deposit_count=1) + tx = turn_round(chain_get_validator_consensus()) + tracker2 = get_tracker(accounts[2]) + tracker4 = get_tracker(accounts[4]) + stake_hub_claim_reward(accounts[2]) + stake_hub_claim_reward(accounts[4]) + reward = block_reward + 100 + validator6_reward = reward * 90 // 100 + validator7_reward = reward * 2 * 90 // 100 + assert tracker2.delta() == validator6_reward // 4 + assert tracker4.delta() == validator7_reward // 4 + + +def test_bep127_major_offense_alternate_replace_and_repeat(slash_indicator): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + set_validator_count() + operators, consensuses = add_candidates(8) + for i in range(6): + for acc in accounts[0:2]: + delegate_coin_success(operators[i], acc, delegate_amount) + delegate_btc_success(operators[i], acc, 1000, LOCK_SCRIPT, relay=acc) + delegate_coin_success(operators[6], accounts[2], 1000) + delegate_coin_success(operators[6], accounts[3], 1000) + delegate_btc_success(operators[6], accounts[2], 1000, LOCK_SCRIPT, relay=accounts[2]) + delegate_btc_success(operators[6], accounts[3], 1000, LOCK_SCRIPT, relay=accounts[3]) + delegate_coin_success(operators[7], accounts[4], 1000) + delegate_coin_success(operators[7], accounts[5], 1000) + turn_round() + assert chain_get_validator_consensus() == consensuses[:6] + slash_validator(consensuses[0]) + expect_validator_consensus( + [consensuses[1], consensuses[2], consensuses[3], consensuses[4], consensuses[5], consensuses[6]]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:6]) + stake_hub_claim_reward(accounts[:6]) + assert trackers[2].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[4].delta() == 0 + + +def test_bep127_major_offense_alternate_replace_until_no_alternate_then_claim_reward(): + set_validator_count() + tx_fee = 10000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(8) + delegate_validator(operators) + delegate_coin_success(operators[6], accounts[2], 1000) + delegate_coin_success(operators[6], accounts[3], 1000) + delegate_coin_success(operators[7], accounts[4], 1000) + delegate_coin_success(operators[7], accounts[5], 1000) + turn_round() + assert chain_get_validator_consensus() == consensuses[:6] + slash_validator(consensuses[0]) + chain_deposit(consensuses[6], deposit_count=1, tx_fee=tx_fee) + slash_validator(consensuses[6]) + chain_deposit(consensuses[7], deposit_count=2, tx_fee=tx_fee) + slash_validator(consensuses[7]) + assert len(chain_get_validator_consensus()) == 5 + block_validators = chain_get_validator_consensus() + expect_validator_consensus([consensuses[1], consensuses[2], consensuses[3], consensuses[4], consensuses[5]]) + turn_round(block_validators) + tracker2 = get_tracker(accounts[2]) + tracker4 = get_tracker(accounts[4]) + stake_hub_claim_reward(accounts[2]) + stake_hub_claim_reward(accounts[4]) + assert tracker2.delta() == 0 + assert tracker4.delta() == 0 + turn_round(chain_get_validator_consensus()) + + +@pytest.mark.parametrize("slash_type", ["minor", "felony"]) +def test_bep127_minor_offense_enter_maintenance_alternate_replace(slash_type, validator_set): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + set_validator_count() + btc_amount = 1000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(8) + delegate_validator(operators, btc_amount=1000) + delegate_validator(operators, [6, 7], accounts[2:4], delegate_amount - 1, btc_amount - 1) + delegate_validator(operators, [7, 8], accounts[4:6], delegate_amount - 1, btc_amount) + turn_round() + assert chain_get_validator_consensus() == consensuses[:6] + tx = slash_validator(consensuses[2], slash_type=slash_type) + if slash_type == 'minor': + assert 'validatorEnterMaintenance' in tx.events + else: + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[2]) + assert consensuses[7] in chain_get_validator_consensus() + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:6]) + stake_hub_claim_reward(accounts[:6]) + assert trackers[4].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[5].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[2].delta() == 0 + assert trackers[3].delta() == 0 + assert trackers[0].delta() > 0 + assert trackers[1].delta() > 0 + + +def test_bep127_minor_then_felony_alternate_replace(validator_set, candidate_hub): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + validator_set.setMaintainSlashPercent(30) + btc_amount = 1000 + set_validator_count() + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(8) + delegate_validator(operators) + delegate_validator(operators, [6, 7], accounts[2:4], delegate_amount - 1, btc_amount) + delegate_validator(operators, [7, 8], accounts[4:6], delegate_amount - 2, btc_amount) + delegate_coin_success(operators[5], accounts[6], 10000) + turn_round() + expect_validator_consensus(consensuses[:6]) + tx = slash_validator(consensuses[5], slash_type="minor") + assert 'validatorEnterMaintenance' in tx.events + chain.mine(300) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:7]) + stake_hub_claim_reward(accounts[:7]) + assert trackers[2].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[3].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[6].delta() == 0 + turn_round(chain_get_validator_consensus()) + + +@pytest.mark.parametrize("slash_type", ["minor", "felony"]) +def test_bep127_minor_no_alternate_replace(validator_set, slash_type): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + validator_set.setMaintainSlashPercent(30) + btc_amount = 1000 + set_validator_count() + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(8) + delegate_validator(operators) + delegate_validator(operators, [6, 7], accounts[2:4], delegate_amount - 1, btc_amount) + delegate_validator(operators, [7, 8], accounts[4:6], delegate_amount - 1, btc_amount) + delegate_coin_success(operators[2], accounts[3], 10000) + turn_round() + slash_validator(consensuses[2], slash_type=slash_type) + turn_round([consensuses[0], consensuses[1], consensuses[3], consensuses[4], consensuses[5]]) + trackers = get_trackers(accounts[:6]) + stake_hub_claim_reward(accounts[:6]) + assert trackers[0].delta() > 0 + assert trackers[3].delta() == 0 + + +def test_bep127_alternate_replace_with_maintenance_and_felony(validator_set): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + validator_set.setMaintainSlashPercent(30) + btc_amount = 1000 + set_validator_count(validator_count=9, alternate_count=3) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(15) + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + delegate_validator(operators, [9, 11], accounts[2], delegate_amount - 1, btc_amount) + delegate_validator(operators, [11, 12], accounts[4], delegate_amount - 2, btc_amount) + delegate_validator(operators, [12, 15], accounts[5], delegate_amount - 3, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + + enter_maintenance(operators[2]) + enter_maintenance(operators[3]) + expect_validator_consensus([consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8, 9, 10]]) + turn_round(chain_get_validator_consensus()) + slash_validator(consensuses[2], slash_type="felony") + slash_validator(consensuses[3], slash_type="felony") + chain_deposit(consensuses[9], deposit_count=3) + chain_deposit(consensuses[10], deposit_count=3) + enter_maintenance(operators[9]) + with brownie.reverts("can not enter Temporary Maintenance"): + enter_maintenance(operators[10]) + slash_validator(consensuses[10], slash_type="felony") + expect_validator_consensus([consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8, 11]]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:6]) + stake_hub_claim_reward(accounts[:6]) + assert trackers[1].delta() > 0 + assert trackers[2].delta() > 0 + assert trackers[4].delta() > 0 + assert trackers[5].delta() == 0 + turn_round(chain_get_validator_consensus()) + expect_validator_consensus([consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8, 9, 11]]) + validators = validator_set.getValidators() + assert len(validators) == 12 + for c in [consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14]]: + assert c in validators + turn_round(chain_get_validator_consensus()) + + +def test_bep127_all_alternates_in_maintenance(validator_set): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + validator_set.setMaintainSlashPercent(30) + btc_amount = 1000 + set_validator_count(validator_count=9, alternate_count=3) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(15) + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + delegate_validator(operators, [9, 12], accounts[2], delegate_amount - 1, btc_amount) + delegate_validator(operators, [12, 15], accounts[4], delegate_amount - 2, btc_amount) + delegate_coin_success(operators[2], accounts[3], 10000) + turn_round() + expect_validator_consensus(consensuses[:9]) + enter_maintenance(operators[2]) + enter_maintenance(operators[3]) + expect_validator_consensus([consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8, 9, 10]]) + turn_round(chain_get_validator_consensus()) + enter_maintenance(operators[9]) + enter_maintenance(operators[10]) + enter_maintenance(operators[11]) + + slash_validator(consensuses[2], slash_type="felony") + slash_validator(consensuses[3], slash_type="felony") + expect_validator_consensus([consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8]]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:6]) + stake_hub_claim_reward(accounts[:6]) + assert trackers[1].delta() > 0 + assert trackers[3].delta() == 0 + validators = validator_set.getValidators() + assert len(validators) == 12 + turn_round(chain_get_validator_consensus()) + + +def test_bep127_alternate_high_score_in_maintenance(validator_set): + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + validator_set.setMaintainSlashPercent(30) + btc_amount = 1000 + set_validator_count(validator_count=9, alternate_count=3) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(15) + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + delegate_validator(operators, 2, accounts[6], delegate_amount - 1, btc_amount) + delegate_validator(operators, 3, accounts[6], delegate_amount - 1, btc_amount) + delegate_validator(operators, 9, accounts[2], delegate_amount - 1, btc_amount) + delegate_validator(operators, [10, 12], accounts[3], delegate_amount - 2, btc_amount) + delegate_validator(operators, 12, accounts[4], delegate_amount - 3, btc_amount) + delegate_validator(operators, [13, 15], accounts[5], delegate_amount - 4, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + enter_maintenance(operators[2]) + enter_maintenance(operators[3]) + slash_validator(consensuses[2], slash_type="felony") + slash_validator(consensuses[3], slash_type="felony") + enter_maintenance(operators[9]) + expect_validator_consensus([consensuses[i] for i in [0, 1, 4, 5, 6, 7, 8, 10, 11]]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:7]) + stake_hub_claim_reward(accounts[:7]) + assert trackers[6].delta() == 0 + turn_round(chain_get_validator_consensus()) + + +def test_bep127_validator_maintenance_and_return(validator_set): + btc_amount = 1000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = setup_validators_and_delegates() + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + delegate_validator(operators, 2, accounts[6], delegate_amount, btc_amount) + delegate_validator(operators, 9, accounts[2], delegate_amount - 1, btc_amount) + delegate_validator(operators, [10, 12], accounts[3], delegate_amount - 2, btc_amount) + delegate_validator(operators, 12, accounts[4], delegate_amount - 3, btc_amount) + delegate_validator(operators, [13, 15], accounts[5], delegate_amount - 4, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + enter_maintenance(operators[2]) + expect_validator_consensus([consensuses[i] for i in [0, 1, 3, 4, 5, 6, 7, 8, 9]]) + chain_deposit(consensuses[9], deposit_count=3) + exit_maintenance(operators[2]) + expect_validator_consensus(consensuses[:9]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:7]) + stake_hub_claim_reward(accounts[:7]) + assert trackers[6].delta() > 0 + assert trackers[2].delta() == TOTAL_REWARD * 3 - 2 + + +@pytest.mark.parametrize("slash_type", ["minor", "felony", "active"]) +def test_bep127_validator_maintenance_with_minor_slash(validator_set, slash_type): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates() + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + delegate_validator(operators, 9, accounts[6], delegate_amount - 1, btc_amount) + delegate_validator(operators, 10, accounts[3], delegate_amount - 2, btc_amount) + delegate_validator(operators, 11, accounts[7], delegate_amount - 2, btc_amount) + delegate_validator(operators, 12, accounts[4], delegate_amount - 3, btc_amount) + delegate_validator(operators, [13, 15], accounts[5], delegate_amount - 4, btc_amount) + delegate_validator(operators, 2, accounts[2], delegate_amount, btc_amount) + tx = turn_round() + expect_validator_consensus(consensuses[:9]) + chain_deposit(consensuses[2], deposit_count=3) + enter_maintenance(operators[2]) + validator_consensus = consensuses[:9] + chain_deposit(consensuses[9], deposit_count=1) + chain_deposit(consensuses[2], deposit_count=1) + event_name = None + total_reward = block_reward + 100 + validator_actual_reward = total_reward * 4 // 11 + total_reward + if slash_type == "minor": + chain.mine(80) + event_name = 'validatorMisdemeanor' + account2_reward = TOTAL_REWARD // 2 - 1 + account3_reward = (validator_actual_reward - total_reward) * 90 // 100 // 2 - 1 + account6_reward = validator_actual_reward * 90 // 100 // 2 - 1 + elif slash_type == "felony": + chain.mine(200) + event_name = 'validatorFelony' + validator_consensus = [consensuses[i] for i in [0, 1, 3, 4, 5, 6, 7, 8, 9]] + account2_reward = 0 + account3_reward = (validator_actual_reward - total_reward) * 90 // 100 // 2 - 1 + account6_reward = (validator_actual_reward * 90 // 100 // 2 - 1) + TOTAL_REWARD + else: + tx = exit_maintenance(operators[2]) + assert "validatorMisdemeanor" not in tx.events + assert "validatorFelony" not in tx.events + account2_reward = (total_reward * 5 * 90 / 100) // 4 + account3_reward = 0 + account6_reward = TOTAL_REWARD - 2 + if slash_type != "active": + tx = exit_maintenance(operators[2]) + assert event_name in tx.events + expect_validator_consensus(validator_consensus) + tx = turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:8]) + stake_hub_claim_reward(accounts[:8]) + assert trackers[2].delta() == account2_reward + assert trackers[3].delta() == account3_reward + assert trackers[6].delta() == account6_reward + + +def test_bep127_validator_multiple_minor_slash(validator_set, slash_indicator): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates() + for i in range(0, 9): + delegate_validator(operators, i, accounts[1], delegate_amount - i, btc_amount) + delegate_validator(operators, 9, accounts[2], delegate_amount + 1, btc_amount) + delegate_validator(operators, 10, accounts[3], delegate_amount + 2, btc_amount) + delegate_validator(operators, 11, accounts[4], delegate_amount + 3, btc_amount) + delegate_validator(operators, 12, accounts[5], delegate_amount + 4, btc_amount) + delegate_validator(operators, 13, accounts[6], delegate_amount + 5, btc_amount) + delegate_validator(operators, 14, accounts[7], delegate_amount + 5, btc_amount) + + tx = turn_round() + expect_validator_consensus([consensuses[i] for i in [14, 13, 12, 11, 10, 9, 0, 1, 2]]) + slash_indicator.setFelonyThreshold(100) + chain_deposit(consensuses[9], deposit_count=3) + slash_validator(consensuses[9], slash_type="minor") + chain_deposit(consensuses[9], deposit_count=3) + slash_validator(consensuses[9], slash_type="minor") + chain_deposit(consensuses[9], deposit_count=3) + slash_validator(consensuses[9], slash_type="minor") + expect_validator_consensus([consensuses[i] for i in [14, 13, 12, 11, 10, 0, 1, 2, 3]]) + tx = turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:8]) + stake_hub_claim_reward(accounts[:8]) + total_reward = 30100 + assert trackers[2].delta() == 0 + assert trackers[7].delta() == (total_reward * 9 // 11 + total_reward) * 90 // 100 // 2 - 1 + + +@pytest.mark.parametrize("slash_type", ["minor", "felony", "active"]) +def test_bep127_validator_exit_maintenance_with_minor_or_felony_slash(validator_set, slash_indicator, slash_type): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates() + delegate_validator(operators, [0, 9], accounts[1], delegate_amount - 1, btc_amount) + for i, acc in enumerate(accounts[2:8], start=9): + delegate_validator(operators, i, acc, delegate_amount - (i - 8), btc_amount) + delegate_validator(operators, 2, accounts[8], delegate_amount, btc_amount) + turn_round() + slash_indicator.setFelonyThreshold(6) + expect_validator_consensus(consensuses[:9]) + slash_validator(consensuses[2], slash_type="minor") + chain_deposit(consensuses[2], deposit_count=3) + event_name = 'test' + actual_reward = 0 + if slash_type == "minor": + chain.mine(80) + event_name = 'validatorMisdemeanor' + elif slash_type == "felony": + chain.mine(200) + event_name = 'validatorFelony' + elif slash_type == "active": + chain.mine(1) + actual_reward = TOTAL_REWARD * 3 // 2 + tx = turn_round(chain_get_validator_consensus()) + if slash_type == "active": + assert 'validatorMisdemeanor' not in tx.events + assert 'validatorFelony' not in tx.events + else: + assert event_name in tx.events + trackers = get_trackers(accounts[:9]) + stake_hub_claim_reward(accounts[:9]) + assert trackers[8].delta() == actual_reward + turn_round(chain_get_validator_consensus()) + + +def test_bep127_validator_exit_maintenance_without_slash(): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates() + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + for i, acc in enumerate(accounts[2:8], start=9): + delegate_validator(operators, i, acc, delegate_amount - (i - 8), btc_amount) + delegate_validator(operators, 2, accounts[8], delegate_amount, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + + enter_maintenance(operators[2]) + chain_deposit(consensuses[2], deposit_count=3) + chain.mine(2) + tx = turn_round(chain_get_validator_consensus()) + assert 'validatorMisdemeanor' not in tx.events + assert 'validatorFelony' not in tx.events + + trackers = get_trackers(accounts[:9]) + stake_hub_claim_reward(accounts[:9]) + assert trackers[8].delta() == TOTAL_REWARD * 3 // 2 + turn_round(chain_get_validator_consensus()) + + +@pytest.mark.parametrize("is_exit_maintenance", [True, False]) +def test_bep127_validator_exit_maintenance_and_get_slashed(is_exit_maintenance): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates() + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + for i, acc in enumerate(accounts[2:8], start=9): + delegate_validator(operators, i, acc, delegate_amount - (i - 8), btc_amount) + delegate_validator(operators, 2, accounts[8], delegate_amount, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + enter_maintenance(operators[2]) + chain_deposit(consensuses[2], deposit_count=3) + chain.mine(3) + actual_reward = TOTAL_REWARD * 3 // 2 + if is_exit_maintenance: + exit_maintenance(operators[2]) + actual_reward += TOTAL_REWARD // 2 + chain.mine(500) + tx = turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:9]) + stake_hub_claim_reward(accounts[:9]) + assert trackers[8].delta() == actual_reward + turn_round(chain_get_validator_consensus()) + + +@pytest.mark.parametrize("is_maintenance", [True, False]) +@pytest.mark.parametrize("slash_type", ["minor", "felony"]) +def test_maintenance_and_blocking_then_stop_slashed_no_reward(slash_type, is_maintenance, slash_indicator): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates() + delegate_validator(operators, [0, 9], accounts[1], delegate_amount, btc_amount) + for i, acc in enumerate(accounts[2:8], start=9): + delegate_validator(operators, i, acc, delegate_amount - (i - 8), btc_amount) + delegate_validator(operators, 2, accounts[8], delegate_amount, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + slash_indicator.setFelonyThreshold(6) + if is_maintenance: + enter_maintenance(operators[2]) + else: + slash_validator(consensuses[2], slash_type='minor') + chain_deposit(consensuses[2], deposit_count=3) + chain.mine(3) + if slash_type == "minor": + chain.mine(80) + event_name = 'validatorMisdemeanor' + elif slash_type == "felony": + chain.mine(200) + event_name = 'validatorFelony' + tx = turn_round(chain_get_validator_consensus()) + assert event_name in tx.events + trackers = get_trackers(accounts[:9]) + stake_hub_claim_reward(accounts[:9]) + assert trackers[8].delta() == 0 + turn_round(consensuses) + + +def test_not_enough_alternate_next_round_add_and_reward(validator_set): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=9, + alternate_count=3, + candidate_count=15) + for i in range(14): + delegate_validator(operators, i, accounts[0], delegate_amount - i, btc_amount) + delegate_validator(operators, 14, accounts[1], delegate_amount - 14, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:9]) + enter_maintenance(operators[0]) + enter_maintenance(operators[1]) + enter_maintenance(operators[2]) + chain.mine(400) + chain_get_validator_consensus() + expect_validator_consensus([consensuses[i] for i in [3, 4, 5, 6, 7, 8, 9, 10, 11]]) + tx = turn_round(chain_get_validator_consensus()) + expect_validator_consensus([consensuses[i] for i in [3, 4, 5, 6, 7, 8, 9, 10, 11]]) + validator_set_list = validator_set.getValidators() + for i in [consensuses[i] for i in [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]]: + assert i in validator_set_list + assert 'validatorFelony' in tx.events + slash_validator(consensuses[3], slash_type="felony") + slash_validator(consensuses[4], slash_type="felony") + slash_validator(consensuses[5], slash_type="felony") + slash_validator(consensuses[6], slash_type="felony") + slash_validator(consensuses[7], slash_type="felony") + expect_validator_consensus([consensuses[i] for i in [8, 9, 10, 11, 12, 13, 14]]) + tx = turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:9]) + stake_hub_claim_reward(accounts[:9]) + assert trackers[1].delta() == TOTAL_REWARD - 2 + turn_round(chain_get_validator_consensus()) + + +def test_validator_enter_maintenance_and_slash_workflow(validator_set, slash_indicator): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=3, + alternate_count=2, + candidate_count=7) + for i in range(3): + delegate_validator(operators, i, accounts[i], delegate_amount, btc_amount) + delegate_validator(operators, 0, accounts[6], delegate_amount - 1, btc_amount) + delegate_validator(operators, 3, accounts[3], delegate_amount - 1, btc_amount) + delegate_validator(operators, 4, accounts[4], delegate_amount - 2, btc_amount) + delegate_validator(operators, 5, accounts[5], delegate_amount - 3, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:3]) + enter_maintenance(operators[0]) + slash_validator(consensuses[0], slash_type="minor") + for _ in range(2): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' in tx.events + expect_validator_consensus(consensuses[1:4]) + tx = turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[:7]) + stake_hub_claim_reward(accounts[:7]) + assert trackers[1].delta() > 0 + assert trackers[2].delta() > 0 + assert trackers[4].delta() == 0 + assert trackers[6].delta() == 0 + turn_round(chain_get_validator_consensus()) + + +def test_upgrade_with_validator_minor_and_felony(validator_set, candidate_hub, slash_indicator): + validator_set.setMaintainSlashPercent(0) + set_validator_count(validator_count=31, alternate_count=0) + validator_set = ValidatorSetMock[0] + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + btc_amount = 1000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(40) + for i in range(36): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + candidate_hub.unregister({'from': operators[-1]}) + candidate_hub.unregister({'from': operators[-2]}) + candidate_hub.unregister({'from': operators[-3]}) + candidate_hub.unregister({'from': operators[-4]}) + turn_round() + set_validator_count(validator_count=31, alternate_count=7) + validator_set.setValidatorCount(0) + validator_set.setMaintainSlashPercent(10) + validators = validator_set.getValidators() + assert validator_set.validatorCount() == 0 + assert validators == consensuses[:31] + expect_validator_consensus(consensuses[:31]) + with brownie.reverts("can not enter Temporary Maintenance"): + enter_maintenance(operators[0]) + slash_validator(consensuses[0], slash_type="minor") + slash_validator(consensuses[30], slash_type="felony") + expect_validator_consensus(consensuses[:30]) + turn_round(chain_get_validator_consensus()) + assert len(validator_set.getValidators()) == 35 + actual_deposit = consensuses[:30] + actual_deposit.append(consensuses[31]) + expect_validator_consensus(actual_deposit) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[50:86]) + stake_hub_claim_reward(accounts[50:86]) + for i in range(len(trackers)): + if i == 30 or i > 31: + assert trackers[i].delta() == 0 + else: + assert trackers[i].delta() > 0 + turn_round(chain_get_validator_consensus()) + + +def test_upgrade_with_no_validator_minor_and_felony(validator_set, candidate_hub, slash_indicator): + validator_set.setMaintainSlashPercent(0) + set_validator_count(validator_count=31, alternate_count=0) + validator_set = ValidatorSetMock[0] + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + btc_amount = 1000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(40) + for i in range(4, 40): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + for i in range(4): + candidate_hub.unregister({'from': operators[i]}) + turn_round() + set_validator_count(validator_count=31, alternate_count=3) + validator_set.setValidatorCount(0) + validator_set.setMaintainSlashPercent(10) + validators = validator_set.getValidators() + assert validator_set.validatorCount() == 0 + assert len(validators) == len(consensuses[4:35]) + for i in consensuses[4:35]: + assert i in validators + expect_validator_consensus(consensuses[4:35]) + turn_round(chain_get_validator_consensus()) + validators = validator_set.getValidators() + assert validator_set.validatorCount() == 31 + assert len(validators) == len(consensuses[4:38]) + for i in consensuses[4:38]: + assert i in validators + expect_validator_consensus(consensuses[4:35]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[50:90]) + stake_hub_claim_reward(accounts[50:90]) + for i in range(len(trackers)): + if i > 34 or i < 4: + assert trackers[i].delta() == 0 + else: + assert trackers[i].delta() > 0 + turn_round(chain_get_validator_consensus()) + + +def test_new_validator_join_and_unregister(candidate_hub, validator_set): + validator_count = 5 + alternate_count = 2 + candidate_count = 8 + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + set_validator_count(validator_count=validator_count, alternate_count=alternate_count) + operators, consensuses = add_candidates(candidate_count) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + for i in range(candidate_count): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[58 + i], delegate_amount - i, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:5]) + new_operator = [accounts[70], accounts[71]] + new_consensus = [] + new_consensus.append(register_candidate(operator=new_operator[0])) + new_consensus.append(register_candidate(operator=new_operator[1])) + delegate_validator(new_operator, 0, accounts[72], delegate_amount + 1, btc_amount) + delegate_validator(new_operator, 0, accounts[73], delegate_amount + 1, btc_amount) + delegate_validator(new_operator, 1, accounts[74], delegate_amount - 3, btc_amount) + delegate_validator(new_operator, 1, accounts[75], delegate_amount - 3, btc_amount) + turn_round(chain_get_validator_consensus()) + new_consensus_list = consensuses[:4] + new_consensus_list.append(new_consensus[0]) + expect_validator_consensus(new_consensus_list) + validators = validator_set.getValidators() + assert new_consensus[0] in validator_set.getValidators() + assert new_consensus[1] in validator_set.getValidators() + chain_deposit(new_consensus[0], deposit_count=1) + expect_validator_consensus(new_consensus_list) + turn_round(chain_get_validator_consensus()) + tracker = get_tracker(accounts[72]) + stake_hub_claim_reward(accounts[72]) + assert tracker.delta() == TOTAL_REWARD - 1 + candidate_hub.refuseDelegate({'from': new_operator[0]}) + expect_validator_consensus(new_consensus_list) + turn_round(chain_get_validator_consensus()) + expect_validator_consensus(consensuses[:4] + new_consensus[1:]) + candidate_hub.unregister({'from': new_operator[0]}) + chain_deposit(new_consensus[1], deposit_count=1) + expect_validator_consensus(consensuses[:4] + new_consensus[1:]) + turn_round(chain_get_validator_consensus()) + tracker1 = get_tracker(accounts[74]) + stake_hub_claim_reward(accounts[72]) + stake_hub_claim_reward(accounts[74]) + assert tracker.delta() == TOTAL_REWARD // 2 - 1 + assert tracker1.delta() == TOTAL_REWARD - 1 + + +def test_major_offense_then_pay_fine_then_normal(candidate_hub, validator_set, slash_indicator): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + candidate_count = 7 + operators, consensuses = setup_validators_and_delegates(validator_count=3, alternate_count=5, + candidate_count=candidate_count) + for i in range(candidate_count): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[58 + i], delegate_amount - i, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:3]) + major_offense_operator = operators[0] + major_offense_consensus = consensuses[0] + slash_validator(major_offense_consensus, slash_type="felony") + expect_validator_consensus(consensuses[1:4]) + validators = validator_set.getValidators() + assert major_offense_consensus not in validators + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[50:50 + candidate_count]) + stake_hub_claim_reward(accounts[50:50 + candidate_count]) + assert trackers[0].delta() == 0 + assert trackers[1].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[2].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[3].delta() == TOTAL_REWARD // 2 - 1 + tx = candidate_hub.addMargin({'from': operators[0], 'value': slash_indicator.felonyDeposit() * 2}) + expect_validator_consensus(consensuses[1:4]) + turn_round(chain_get_validator_consensus()) + assert consensuses[0] not in validator_set.getValidators() + expect_validator_consensus(consensuses[1:4]) + + turn_round(chain_get_validator_consensus()) + assert consensuses[0] in validator_set.getValidators() + expect_validator_consensus(consensuses[0:3]) + stake_hub_claim_reward(accounts[50:50 + candidate_count]) + trackers[0].update_height() + trackers[1].update_height() + trackers[2].update_height() + trackers[3].update_height() + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50:50 + candidate_count]) + assert trackers[0].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[1].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[2].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[3].delta() == 0 + turn_round(chain_get_validator_consensus()) + + +def test_alternate_count_greater_than_validators_with_unregistered(candidate_hub, validator_set): + validator_count = 3 + alternate_count = 5 + candidate_count = 10 + operators, consensuses = setup_validators_and_delegates(validator_count=validator_count, + alternate_count=alternate_count, + candidate_count=candidate_count) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + + for i in range(candidate_count): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[61 + i], delegate_amount - i, btc_amount) + turn_round() + refuse_delegate(operators[3]) + expect_validator_consensus(consensuses[:3]) + turn_round(chain_get_validator_consensus()) + candidate_hub.unregister({'from': operators[3]}) + enter_maintenance(operators[4]) + enter_maintenance(operators[0]) + refuse_delegate(operators[0]) + refuse_delegate(operators[4]) + refuse_delegate(operators[5]) + refuse_delegate(operators[6]) + refuse_delegate(operators[7]) + refuse_delegate(operators[8]) + refuse_delegate(operators[9]) + expect_validator_consensus([consensuses[1], consensuses[2], consensuses[5]]) + turn_round(chain_get_validator_consensus()) + candidate_hub.unregister({'from': operators[0]}) + candidate_hub.unregister({'from': operators[4]}) + candidate_hub.unregister({'from': operators[5]}) + candidate_hub.unregister({'from': operators[6]}) + candidate_hub.unregister({'from': operators[7]}) + candidate_hub.unregister({'from': operators[8]}) + candidate_hub.unregister({'from': operators[9]}) + expect_validator_consensus([consensuses[1], consensuses[2]]) + turn_round(chain_get_validator_consensus(), round_count=2) + + +def test_refused_delegate_cannot_be_alternate(candidate_hub, validator_set): + validator_count = 3 + alternate_count = 3 + candidate_count = 8 + operators, consensuses = setup_validators_and_delegates(validator_count=validator_count, + alternate_count=alternate_count, + candidate_count=candidate_count) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + + for i in range(candidate_count): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[60 + i], delegate_amount - i, btc_amount) + turn_round() + refuse_delegate(operators[0]) + refuse_delegate(operators[3]) + refuse_delegate(operators[5]) + expect_validator_consensus(consensuses[:3]) + turn_round(chain_get_validator_consensus()) + expect_validator_consensus(consensuses[1:3] + consensuses[4:5]) + turn_round(chain_get_validator_consensus()) + +def test_replenish_and_validator_count_equals_total_validators(validator_set, candidate_hub): + validator_count = 10 + replenish_count = 5 + total_validators = validator_count + replenish_count + + set_validator_count(validator_count=validator_count, alternate_count=replenish_count) + operators, consensuses = add_candidates(total_validators) + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + + for i in range(total_validators): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + + turn_round() + validators = validator_set.getValidators() + assert len(validators) == total_validators + assert validator_set.validatorCount() == validator_count + expect_validator_consensus(consensuses[:validator_count]) + + enter_maintenance(operators[0]) + chain_deposit(consensuses[0], deposit_count=1) + expect_validator_consensus(consensuses[1:validator_count + 1]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[50:50 + total_validators]) + stake_hub_claim_reward(accounts[50:50 + total_validators]) + for i in range(len(trackers)): + if i > 10: + assert trackers[i].delta() == 0 + else: + assert trackers[i].delta() > 0 + turn_round(consensuses) + + +def test_edit_consensus_address_and_claim_reward(candidate_hub, validator_set): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + + for i in range(7): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) + + operator = operators[0] + old_consensus = consensuses[0] + new_consensus = accounts[90] + turn_round() + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[50:50 + 7]) + stake_hub_claim_reward(accounts[50:50 + 7]) + assert trackers[0].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[1].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[2].delta() == TOTAL_REWARD // 2 - 1 + expect_validator_consensus(consensuses[:5]) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50:50 + 7]) + trackers[0].update_height() + tx = candidate_hub.editConsensusAddress(new_consensus, {'from': operator}) + assert 'ConsensusAddressEdited' in tx.events + assert tx.events['ConsensusAddressEdited']['newConsensusAddr'] == new_consensus + validators = validator_set.getValidators() + assert new_consensus not in validators + assert old_consensus in chain_get_validator_consensus() + assert new_consensus not in chain_get_validator_consensus() + expect_validator_consensus(consensuses[:5]) + chain_deposit(old_consensus, deposit_count=2) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + assert trackers[0].delta() == TOTAL_REWARD + TOTAL_REWARD // 2 + expect_validator_consensus([new_consensus, consensuses[1], consensuses[2], consensuses[3], consensuses[4]]) + chain_deposit(old_consensus, deposit_count=3) + turn_round() + stake_hub_claim_reward(accounts[50]) + assert trackers[0].delta() == 0 + chain_deposit(new_consensus, deposit_count=2) + turn_round() + stake_hub_claim_reward(accounts[50]) + stake_hub_claim_reward(accounts[52]) + assert trackers[0].delta() == TOTAL_REWARD - 1 + expect_validator_consensus([new_consensus, consensuses[1], consensuses[2], consensuses[3], consensuses[4]]) + new_consensus1 = accounts[91] + tx = candidate_hub.editConsensusAddress(new_consensus1, {'from': operators[2]}) + turn_round(chain_get_validator_consensus()) + trackers[0].update_height() + trackers[2].update_height() + stake_hub_claim_reward(accounts[50]) + stake_hub_claim_reward(accounts[52]) + assert trackers[0].delta() == TOTAL_REWARD // 2 - 1 + assert trackers[2].delta() == TOTAL_REWARD // 2 - 1 + expect_validator_consensus([new_consensus, consensuses[1], new_consensus1, consensuses[3], consensuses[4]]) + turn_round(chain_get_validator_consensus()) + + +def test_edit_commission_rate_and_claim_reward(candidate_hub): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + for i in range(7): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) + agent = accounts[65] + turn_round() + expect_validator_consensus(consensuses[:5]) + turn_round(chain_get_validator_consensus()) + new_commission_rate = 400 + candidate_hub.updateAgent(agent, {'from': operators[0]}) + tx = candidate_hub.editCommissionRate(new_commission_rate, {'from': agent}) + assert 'CommissionRateEdited' in tx.events + chain_deposit(consensuses[0], deposit_count=1) + expect_validator_consensus(consensuses[:5]) + stake_hub_claim_reward(accounts[50]) + turn_round(chain_get_validator_consensus()) + tracker = get_tracker(accounts[50]) + stake_hub_claim_reward(accounts[50]) + stake_hub_claim_reward(accounts[51]) + assert tracker.delta() == TOTAL_REWARD - 1 + info = candidate_hub.getCandidate(operators[0]) + assert info['commissionThousandths'] == new_commission_rate + chain_deposit(consensuses[0], deposit_count=2) + turn_round(chain_get_validator_consensus()) + tracker1 = get_tracker(accounts[51]) + stake_hub_claim_reward(accounts[50]) + stake_hub_claim_reward(accounts[51]) + actual_reward = 30100 * 90 // 100 * 60 // 100 + assert tracker.delta() == actual_reward * 3 // 2 - 1 + assert tracker1.delta() == TOTAL_REWARD // 2 - 1 + expect_validator_consensus(consensuses[:5]) + turn_round(chain_get_validator_consensus()) + + +def test_edit_vote_address_and_claim_reward(candidate_hub): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + for i in range(7): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) + agent = accounts[65] + turn_round() + weights = [10, 20, 30, 60, 20] + expect_validator_consensus(consensuses[:5]) + tx = turn_round(chain_get_validator_consensus(), weights=weights) + stake_hub_claim_reward(accounts[50]) + vote_reward = 483 + expect_event(tx, "voteRewardTransfer", { + 'operateAddress': operators[0], + 'amount': vote_reward + }) + new_vote_address = random_vote_address() + candidate_hub.editVoteAddress(new_vote_address, {'from': operators[0]}) + expect_validator_consensus(consensuses[:5]) + tx = turn_round(chain_get_validator_consensus(), weights=weights) + expect_event(tx, "voteRewardTransfer", { + 'operateAddress': operators[0], + 'amount': vote_reward + }) + tracker = get_tracker(accounts[50]) + stake_hub_claim_reward(accounts[50]) + assert tracker.delta() == TOTAL_REWARD // 2 - 1 + turn_round(chain_get_validator_consensus()) + + +def test_edit_description_and_claim_reward(candidate_hub): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + for i in range(7): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:5]) + tx = turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + moniker = "Test Validator" + identity = "test-identity" + website = "https://test.com" + details = "Test validator details" + tx = candidate_hub.editDescription(moniker, identity, website, details, {'from': operators[0]}) + turn_round(chain_get_validator_consensus()) + tracker = get_tracker(accounts[50]) + stake_hub_claim_reward(accounts[50]) + assert tracker.delta() == TOTAL_REWARD // 2 - 1 + turn_round(chain_get_validator_consensus()) + tx = candidate_hub.editDescription('test1', identity, website, details, {'from': operators[0]}) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + assert tracker.delta() == (TOTAL_REWARD // 2 - 1) * 2 + turn_round(chain_get_validator_consensus()) + + +def test_edit_fee_address_and_claim_reward(candidate_hub): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + for i in range(7): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:5]) + tx = turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + new_fee_address = accounts[90] + candidate_hub.editFeeAddress(new_fee_address, {'from': operators[0]}) + tx = turn_round(chain_get_validator_consensus()) + expect_event(tx, "voteRewardTransfer", { + 'operateAddress': operators[0], + 'validator': operators[0], + }) + tracker = get_tracker(accounts[90]) + assert tracker.delta() == 0 + tx = turn_round(chain_get_validator_consensus(), weights=[10, 20, 30, 60, 20]) + vote_fee = 483 + expect_event(tx, "voteRewardTransfer", { + 'operateAddress': operators[0], + 'validator': new_fee_address, + 'amount': vote_fee + }) + assert tracker.delta() == TOTAL_REWARD + vote_fee - TOTAL_REWARD * 10 // 100 + turn_round(chain_get_validator_consensus()) + + +def test_bep127_refuse_and_redelegate(candidate_hub, validator_set): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=9, alternate_count=3, candidate_count=15) + for i in range(15): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[70 + i], delegate_amount - i, btc_amount) + turn_round() + trackers = get_trackers(accounts[50:50 + 15]) + refuse_delegate(operators[0]) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + trackers[0].update_height() + accept_delegate(operators[0]) + expect_validator_consensus(consensuses[1:10]) + turn_round(chain_get_validator_consensus()) + expect_validator_consensus(consensuses[0:9]) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + assert trackers[0].delta() == TOTAL_REWARD // 2 - 1 + + +def test_edit_agent_info_and_claim_reward(candidate_hub, validator_set): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + for i in range(7): + delegate_validator(operators, i, accounts[70 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[77 + i], delegate_amount - i, btc_amount) + turn_round() + validator_set.setValidatorCount(0) + moniker = "Test Validator" + identity = "test-identity" + website = "https://test.com" + details = "Test validator details" + candidate_hub.updateAgent(accounts[72], {'from': operators[0]}) + candidate_hub.editFeeAddress(accounts[72], {'from': operators[0]}) + candidate_hub.editVoteAddress(random_vote_address(), {'from': operators[0]}) + candidate_hub.editCommissionRate(550, {'from': operators[0]}) + candidate_hub.editConsensusAddress(accounts[72], {'from': operators[0]}) + candidate_hub.editDescription(moniker, identity, website, details, {'from': operators[0]}) + stake_hub_claim_reward(accounts[70]) + turn_round(chain_get_validator_consensus(), round_count=2) + + +def test_maintenance_validator_upgrade_coin_btc_no_effect(validator_set, candidate_hub, slash_indicator): + validator_set.setMaintainSlashPercent(0) + set_validator_count(validator_count=31, alternate_count=0) + validator_set = ValidatorSetMock[0] + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + btc_amount = 1000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(40) + for i in range(0, 40): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[1 + i], delegate_amount - i, btc_amount) + tx_id0 = delegate_btc_success(operators[1], accounts[91], btc_amount, LOCK_SCRIPT, relay=accounts[91]) + tx_id1 = delegate_btc_success(operators[3], accounts[92], btc_amount, LOCK_SCRIPT, relay=accounts[92]) + candidate_hub.unregister({'from': operators[-1]}) + candidate_hub.unregister({'from': operators[-2]}) + candidate_hub.unregister({'from': operators[-3]}) + turn_round() + set_validator_count(validator_count=31, alternate_count=3) + validator_set.setValidatorCount(0) + validator_set.setMaintainSlashPercent(10) + validators = validator_set.getValidators() + assert validator_set.validatorCount() == 0 + transfer_coin_success(operators[0], operators[2], accounts[50], delegate_amount) + transfer_coin_success(operators[2], operators[0], accounts[52], delegate_amount - 2) + transfer_btc_success(tx_id0, operators[2], accounts[91]) + delegate_btc_success(operators[4], accounts[93], btc_amount, LOCK_SCRIPT, relay=accounts[93]) + delegate_coin_success(operators[0], accounts[50], delegate_amount) + turn_round(chain_get_validator_consensus()) + trackers0 = get_tracker(accounts[50]) + trackers1 = get_tracker(accounts[91]) + trackers2 = get_tracker(accounts[92]) + trackers3 = get_tracker(accounts[93]) + stake_hub_claim_reward(accounts[50]) + stake_hub_claim_reward(accounts[91]) + stake_hub_claim_reward(accounts[92]) + assert trackers0.delta() == TOTAL_REWARD // 2 - 1 + assert trackers1.delta() == 0 + assert trackers2.delta() > 0 + enter_maintenance(operators[2]) + transfer_coin_success(operators[2], operators[0], accounts[50], delegate_amount // 2) + transfer_btc_success(tx_id0, operators[3], accounts[91]) + undelegate_coin_success(operators[0], accounts[50], delegate_amount // 4) + undelegate_coin_success(operators[2], accounts[50], delegate_amount // 4) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + stake_hub_claim_reward(accounts[91]) + stake_hub_claim_reward(accounts[92]) + stake_hub_claim_reward(accounts[93]) + assert trackers0.delta() > 0 + assert trackers1.delta() == 0 + assert trackers2.delta() > 0 + assert trackers3.delta() > 0 + turn_round(chain_get_validator_consensus(), round_count=2) + + +def test_update_consensus_address_block_produce_on_old_and_new(validator_set,candidate_hub): + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + btc_amount = 1000 + operators, consensuses = setup_validators_and_delegates(validator_count=5, + alternate_count=2, + candidate_count=7) + for i in range(7): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) + turn_round() + expect_validator_consensus(consensuses[:5]) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + new_consensus = accounts[90] + candidate_hub.editConsensusAddress(new_consensus, {'from': operators[0]}) + chain_deposit(new_consensus, deposit_count=10) + chain_deposit(consensuses[0], deposit_count=1) + turn_round(chain_get_validator_consensus()) + tracker = get_tracker(accounts[50]) + stake_hub_claim_reward(accounts[50]) + assert tracker.delta() == TOTAL_REWARD - 1 + chain_deposit(new_consensus, deposit_count=1) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[50]) + assert tracker.delta() == TOTAL_REWARD - 1 + +def test_validator_and_alternate_count_exceed_candidates(validator_set, candidate_hub, slash_indicator): + validator_set.setMaintainSlashPercent(0) + set_validator_count(validator_count=31, alternate_count=0) + validator_set = ValidatorSetMock[0] + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + btc_amount = 1000 + delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 + operators, consensuses = add_candidates(15) + for i in range(0, 15): + delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) + delegate_validator(operators, i, accounts[1 + i], delegate_amount - i, btc_amount) + tx_id0 = delegate_btc_success(operators[1], accounts[91], btc_amount, LOCK_SCRIPT, relay=accounts[91]) + tx_id1 = delegate_btc_success(operators[3], accounts[92], btc_amount, LOCK_SCRIPT, relay=accounts[92]) + candidate_hub.unregister({'from': operators[-1]}) + candidate_hub.unregister({'from': operators[-2]}) + candidate_hub.unregister({'from': operators[-3]}) + turn_round() + set_validator_count(validator_count=31, alternate_count=3) + validator_set.setValidatorCount(0) + validator_set.setMaintainSlashPercent(10) + validators = validator_set.getValidators() + assert validator_set.validatorCount() == 0 + transfer_coin_success(operators[0], operators[2], accounts[50], delegate_amount) + transfer_coin_success(operators[2], operators[0], accounts[52], delegate_amount - 2) + transfer_btc_success(tx_id0, operators[2], accounts[91]) + delegate_btc_success(operators[4], accounts[93], btc_amount, LOCK_SCRIPT, relay=accounts[93]) + delegate_coin_success(operators[0], accounts[50], delegate_amount) + turn_round(chain_get_validator_consensus()) + value = padding_left(Web3.to_hex(10), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("maxAlternateCount", value) + validatorCount = padding_left(Web3.to_hex(7), 64) + candidate_hub.updateParam("validatorCount", validatorCount) + alternate_count = candidate_hub.mockGetAlternateCount(10, 7, 12) + assert alternate_count == 5 + expect_validator_consensus(consensuses[:12]) + turn_round(chain_get_validator_consensus()) + trackers = get_trackers(accounts[50:65]) + stake_hub_claim_reward(accounts[50:65]) + assert trackers[0].delta() > 0 + turn_round(chain_get_validator_consensus()) diff --git a/tests/test_slash.py b/tests/test_slash.py index d63e8775d..89f137c26 100644 --- a/tests/test_slash.py +++ b/tests/test_slash.py @@ -139,7 +139,7 @@ def test_clean(slash_indicator, validator_set): for account, count in zip(slash_accounts, counts): assert slash_indicator.getSlashIndicator(account.address)[1] == max([count - decrease_value, 0]) - +# submitFinalityViolationEvidence def test_old_block_involved_failed(slash_indicator, validator_set, set_candidate): signature = os.urandom(96).hex() chain.mine(86400 + chain.height) @@ -154,6 +154,15 @@ def test_old_block_involved_failed(slash_indicator, validator_set, set_candidate with brownie.reverts(f"too old block involved"): slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) +def test_vote_addrs_length_le_1(slash_indicator): + register_candidate(operator=accounts[0]) + turn_round() + signature = os.urandom(96).hex() + vote_a = build_vote_data(100, random_btc_tx_id(), 150, random_btc_tx_id(), signature) + vote_b = build_vote_data(120, random_btc_tx_id(), 140, random_btc_tx_id(), signature) + vote_addr = random_vote_address() + slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + def test_identical_votes_failed(slash_indicator, validator_set, set_candidate): signature = os.urandom(96).hex() diff --git a/tests/test_slash_indicator.py b/tests/test_slash_indicator.py index b8e5d1f87..2941b290b 100644 --- a/tests/test_slash_indicator.py +++ b/tests/test_slash_indicator.py @@ -1,10 +1,9 @@ import pytest import brownie -from web3 import Web3 +from .common import * +from .delegate import delegate_coin_success +from .utils import * from eth_abi import encode -from brownie import * -from .utils import expect_event, padding_left, random_address, random_btc_tx_id, random_vote_address -from .common import execute_proposal @pytest.fixture(scope="module", autouse=True) @@ -17,8 +16,26 @@ def set_up(slash_indicator): encode(['string', 'bytes'], ['felonyThreshold', Web3.to_bytes(hexstr=hex_value)]), "update felonyThreshold" ) +@pytest.fixture(scope="module", autouse=True) +def deposit_for_reward(validator_set, gov_hub): + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + +@pytest.fixture() +def set_candidate_maintenance(candidate_hub): + candidate_hub.setValidatorCount(3) + operators = [] + consensuses = [] + for operator in accounts[5:10]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + for operator in operators[:3]: + delegate_coin_success(operator, accounts[0], 1e18) + delegate_coin_success(operator, accounts[1], 1e18) + return operators, consensuses + +# slash def test_slash(slash_indicator, validator_set): for slash_address, times, count in ( (random_address(), 1, 1), @@ -31,6 +48,32 @@ def test_slash(slash_indicator, validator_set): result = slash_indicator.getSlashIndicator(slash_address) assert result[1] == count +def test_slash_force_enter_maintenance(slash_indicator,candidate_hub, validator_set, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(5) + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + operator = operators[0] + turn_round() + slash_indicator.setMisdemeanorThreshold(2) + slash_indicator.setFelonyThreshold(5) + validator_set.deposit(validator, {"value": 10000, "from": accounts[99]}) + + for _ in range(2): + tx= slash_indicator.slash(validator) + assert 'validatorEnterMaintenance' in tx.events + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + old_enter_maintenance_height = validator['enterMaintenanceHeight'] + + result = slash_indicator.getSlashIndicator(consensuses[0]) + assert result[1] == 2 + slash_indicator.slash(consensuses[0]) + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorEnterMaintenance' not in tx.events + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] == old_enter_maintenance_height + + def test_double_sign_slash(slash_indicator, candidate_hub): tests = [( @@ -113,3 +156,229 @@ def test_ecrecovery_faild(slash_indicator, candidate_hub): num_bytes = num.to_bytes(65, byteorder='big') address = slash_indicator.mockEcrecovery(random_btc_tx_id(), num_bytes).return_value assert address == ZERO_ADDRESS + + +# exitMaintenanceSlash +def test_revert_when_caller_is_not_validator_contract(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + block_count = 5 + + with brownie.reverts("the msg sender must be validatorSet contract"): + slash_indicator.exitMaintenanceSlash(validator, block_count, {'from': operators[0]}) + + +def test_revert_when_contract_not_initialized(slash_indicator, validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + slash_indicator.setAlreadyInit(False) + validator = consensuses[0] + block_count = 5 + slash_indicator.exitMaintenanceSlash(validator, block_count, {'from': validator_set.address}) + + +@pytest.mark.parametrize("block_count", [2, 150]) +def test_exit_maintenance_slash_validator_success(slash_indicator, validator_set, block_count, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + tx = slash_indicator.exitMaintenanceSlash(validator, block_count, {'from': validator_set.address}) + if block_count == 2: + expect_event(tx, "validatorMisdemeanor") + result = slash_indicator.getSlashIndicator(validator) + assert result[1] == block_count + else: + expect_event(tx, "validatorFelony") + result = slash_indicator.getSlashIndicator(validator) + assert result[1] == 0 + expect_event(tx, "validatorSlashed", {'validator': validator, 'blockCount': block_count}) + turn_round(consensuses) + + +@pytest.mark.parametrize("block_count", [20, 120]) +def test_exit_maintenance_slash_existing_validator_success(slash_indicator, validator_set, block_count, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + turn_round() + slash_indicator.setMisdemeanorThreshold(50) + for i in range(30): + slash_indicator.slash(validator) + assert slash_indicator.getSlashIndicator(validator)[1] == 30 + tx = slash_indicator.exitMaintenanceSlash(validator, block_count, {'from': validator_set.address}) + if block_count == 20: + expect_event(tx, "validatorMisdemeanor") + else: + expect_event(tx, "validatorFelony") + + +def test_exit_maintenance_slash_felony_threshold_exceeded_success(slash_indicator, validator_set, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + slash_indicator.setMisdemeanorThreshold(5) + slash_indicator.setFelonyThreshold(10) + turn_round() + for _ in range(10): + tx = slash_indicator.slash(validator) + expect_event(tx, "validatorFelony") + assert slash_indicator.getSlashIndicator(validator)[1] == 0 + tx = slash_indicator.exitMaintenanceSlash(validator, 11, {'from': validator_set.address}) + assert len(tx.events) == 1 + expect_event(tx, "validatorSlashed", {'validator': validator, 'blockCount': 11}) + assert slash_indicator.getSlashIndicator(validator)[1] == 0 + tx = slash_indicator.exitMaintenanceSlash(validator, 5, {'from': validator_set.address}) + assert len(tx.events) == 1 + expect_event(tx, "validatorSlashed", {'validator': validator, 'blockCount': 5}) + + +def test_exit_maintenance_slash_misdemeanor_threshold_crossed_success(slash_indicator, candidate_hub, validator_set, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + slash_indicator.setMisdemeanorThreshold(40) + slash_indicator.setFelonyThreshold(150) + turn_round() + validator_set.deposit(validator, {"value": 10000, "from": accounts[99]}) + for _ in range(20): + slash_indicator.slash(validator) + tx = slash_indicator.exitMaintenanceSlash(validator, 61, {'from': validator_set.address}) + expect_event(tx, "validatorMisdemeanor", {'validator': operators[0], 'amount': 40000}) + turn_round(consensuses) + + +def test_exit_maintenance_slash_no_threshold_crossed_success(slash_indicator, validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + slash_indicator.setMisdemeanorThreshold(10) + slash_indicator.setFelonyThreshold(15) + turn_round() + tx = slash_indicator.exitMaintenanceSlash(validator, 2, {'from': validator_set.address}) + assert 'validatorMisdemeanor' not in tx.events + for _ in range(2): + slash_indicator.slash(validator) + tx = slash_indicator.exitMaintenanceSlash(validator, 2, {'from': validator_set.address}) + assert 'validatorMisdemeanor' not in tx.events + assert slash_indicator.getSlashIndicator(validator)[1] == 6 + + +def test_exit_maintenance_slash_zero_block_count_success(slash_indicator, validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + validator = consensuses[0] + turn_round() + + tx = slash_indicator.exitMaintenanceSlash(validator, 0, {'from': validator_set.address}) + + expect_event(tx, "validatorSlashed", {'validator': validator, 'blockCount': 0}) + + result = slash_indicator.getSlashIndicator(validator) + assert result[1] == 0 + + +# slashWithBlockCount +def test_slash_with_block_count_new_validator_success(slash_indicator, validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + block_count = 5 + + slash_indicator.mockSlashWithBlockCount(validator, block_count) + + result = slash_indicator.getSlashIndicator(validator) + assert result[0] == chain.height + assert result[1] == block_count + + assert slash_indicator.validators(0) == validator + + +def test_slash_with_block_count_existing_validator_success(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + + slash_indicator.mockSlashWithBlockCount(validator, 5) + result1 = slash_indicator.getSlashIndicator(validator) + assert result1[1] == 5 + + slash_indicator.mockSlashWithBlockCount(validator, 3) + result2 = slash_indicator.getSlashIndicator(validator) + assert result2[1] == 8 + + assert result2[0] == chain.height + + +def test_slash_with_block_count_multiple_validators_success(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + for i, validator in enumerate(consensuses[:3]): + tx = slash_indicator.mockSlashWithBlockCount(validator, i + 1) + result = slash_indicator.getSlashIndicator(validator) + assert result[1] == i + 1 + assert result[0] == chain.height + + assert slash_indicator.validators(0) == consensuses[0] + assert slash_indicator.validators(1) == consensuses[1] + assert slash_indicator.validators(2) == consensuses[2] + + +def test_slash_with_block_count_zero_block_count_success(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + + slash_indicator.mockSlashWithBlockCount(validator, 0) + + result = slash_indicator.getSlashIndicator(validator) + assert result[0] == chain.height + assert result[1] == 0 + + assert slash_indicator.validators(0) == validator + + +def test_slash_with_block_count_height_update_success(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + + slash_indicator.mockSlashWithBlockCount(validator, 1) + result1 = slash_indicator.getSlashIndicator(validator) + initial_height = result1[0] + + chain.mine(5) + + slash_indicator.mockSlashWithBlockCount(validator, 2) + result2 = slash_indicator.getSlashIndicator(validator) + + assert result2[0] > initial_height + assert result2[0] == chain.height + assert result2[1] == 3 # 1 + 2 = 3 + + +def test_slash_with_block_count_indicator_structure_success(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + slash_indicator.setMisdemeanorThreshold(20) + for _ in range(10): + slash_indicator.slash(validator) + slash_indicator.mockSlashWithBlockCount(validator, 10) + indicator = slash_indicator.indicators(validator) + assert indicator['height'] == chain.height + assert indicator['count'] == 20 + assert indicator['exist'] == True + + +def test_slash_with_block_count_accumulation_success(slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator = consensuses[0] + + for i in range(5): + slash_indicator.mockSlashWithBlockCount(validator, i + 1) + result = slash_indicator.getSlashIndicator(validator) + expected_count = sum(range(1, i + 2)) + assert result[1] == expected_count + assert result[0] == chain.height + final_result = slash_indicator.getSlashIndicator(validator) + assert final_result[1] == 15 diff --git a/tests/test_stake_hub_unit.py b/tests/test_stake_hub_unit.py index 6dd0096e0..8f15b1e4b 100644 --- a/tests/test_stake_hub_unit.py +++ b/tests/test_stake_hub_unit.py @@ -855,7 +855,6 @@ def test_stake_hup_add_round_reward(stake_hub, validator_set, candidate_hub, cor if test['status'] == 'success': tx = validator_set.addRoundRewardMock(test['validators'], test['reward_list'], test['round'], {'from': accounts[0], 'value': value_sum}) - print(tx.events['roundReward']) for i in range(len(test['expect_round_reward'])): expect_event(tx, 'roundReward', test['expect_round_reward'][i], i) else: diff --git a/tests/test_validator_set.py b/tests/test_validator_set.py index ed4df79a7..71dbf585c 100644 --- a/tests/test_validator_set.py +++ b/tests/test_validator_set.py @@ -2,8 +2,8 @@ import brownie from web3 import Web3, constants -from .common import turn_round, register_candidate, get_current_round, execute_proposal -from .delegate import delegate_coin_success +from .common import * +from .delegate import * from .utils import * from eth_abi import encode @@ -16,6 +16,7 @@ ] init_validator_incomes = [0, 0, 0, 0, 0] random_address = "0x51BafF77eFF55ac97d170E7449b59b73E95e262e" +LOCK_SCRIPT = "0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac" account_tracker: AccountTracker = None system_reward_tracker: AccountTracker = None @@ -28,10 +29,10 @@ @pytest.fixture(scope="module", autouse=True) -def setup(system_reward, validator_set, pledge_agent, core_agent, stake_hub): +def setup(system_reward, validator_set, pledge_agent, core_agent, stake_hub, candidate_hub): global account_tracker, system_reward_tracker, validator_set_tracker, stake_hub_tracker global validator_set_instance - global BLOCK_REWARD, block_reward + global BLOCK_REWARD, block_reward, validator_count validator_set_instance = validator_set BLOCK_REWARD = validator_set.BLOCK_REWARD() account_tracker = get_tracker(accounts[0]) @@ -39,6 +40,10 @@ def setup(system_reward, validator_set, pledge_agent, core_agent, stake_hub): validator_set_tracker = get_tracker(validator_set) stake_hub_tracker = get_tracker(stake_hub) block_reward = validator_set.blockReward() + validator_count = validator_set.validatorCount() + candidate_hub.setMaxAlternateCount(1) + candidate_hub.setValidatorCount(3) + validator_set.setMaintainSlashPercent(30) @pytest.fixture() @@ -46,6 +51,19 @@ def deposit_for_reward(validator_set): accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) +@pytest.fixture() +def set_candidate_maintenance(candidate_hub): + operators = [] + consensuses = [] + for operator in accounts[5:10]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + for operator in operators[:3]: + delegate_coin_success(operator, accounts[0], 1e18) + delegate_coin_success(operator, accounts[1], 1e18) + return operators, consensuses + + @pytest.fixture(autouse=True) def clear_tracker(): account_tracker.balance() @@ -265,7 +283,7 @@ def test_vote_success(validator_set): weights = [10, 20, 30] validator_set.vote(consensuses, weights) for i in range(len(consensuses)): - validator = [operators[i], consensuses[i], operators[i], 1000, 0, vote_address_list[i], weights[i]] + validator = [operators[i], consensuses[i], operators[i], 1000, 0, vote_address_list[i], weights[i], 0] assert validator_set.getValidatorByConsensus(consensuses[i]) == validator @@ -334,7 +352,7 @@ def test_vote_address_contains_unregistered_failed(validator_set, candidate_hub, validator_set.getValidatorByConsensus(consensuses[0]) -def test_vote_array_length_change_success(validator_set, candidate_hub, set_candidate): +def test_vote_array_length_change_success(validator_set, set_candidate): operators, consensuses = set_candidate turn_round() weights0 = [10, 20, 30] @@ -348,14 +366,16 @@ def test_vote_array_length_change_success(validator_set, candidate_hub, set_cand assert validator_set.getValidatorByConsensus(consensuses[i])['voteWeight'] == actual_validator_fee[i] +# updateValidatorSet def test_update_failed_by_address_which_is_not_candidate(): with brownie.reverts("the msg sender must be candidate contract"): - validator_set_instance.updateValidatorSet([random_address], [random_address], [random_address], [100], []) + validator_set_instance.updateValidatorSet([random_address], [random_address], [random_address], [100], [], + validator_count) def test_update_failed_with_empty_validator_set(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], [], validator_count) assert validator_set_instance.getValidators() == init_validators @@ -363,17 +383,20 @@ def test_update_failed_with_addresses_of_different_length(): __fake_validator_set() with brownie.reverts("the numbers of consensusAddresses and commissionThousandthss should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address], [accounts[1].address], [accounts[2].address], - [accounts[3].address, accounts[4].address], [random_vote_address()]) + [accounts[3].address, accounts[4].address], [random_vote_address()], + validator_count) with brownie.reverts("the numbers of consensusAddresses and feeAddresses should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address], [accounts[1].address], [accounts[2].address, accounts[4].address], [accounts[3].address], - [random_vote_address()]) + [random_vote_address()], validator_count) with brownie.reverts("the numbers of consensusAddresses and operateAddresses should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address], [accounts[1].address, accounts[4].address], - [accounts[2].address], [accounts[3].address], [random_vote_address()]) + [accounts[2].address], [accounts[3].address], [random_vote_address()], + validator_count) with brownie.reverts("the numbers of consensusAddresses and operateAddresses should be equal"): validator_set_instance.updateValidatorSet([accounts[0].address, accounts[4].address], [accounts[1].address], - [accounts[2].address], [accounts[3].address], [random_vote_address()]) + [accounts[2].address], [accounts[3].address], [random_vote_address()], + validator_count) def test_update_failed_with_duplicate_consensus_address(): @@ -381,7 +404,7 @@ def test_update_failed_with_duplicate_consensus_address(): with brownie.reverts("duplicate consensus address"): validator_set_instance.updateValidatorSet([accounts[0], accounts[0]], [accounts[1], accounts[1]], [accounts[2], accounts[2]], [100, 100], - [random_vote_address(), random_vote_address()]) + [random_vote_address(), random_vote_address()], validator_count) def test_update_failed_with_commissionThousandths_out_of_range(): @@ -389,18 +412,18 @@ def test_update_failed_with_commissionThousandths_out_of_range(): with brownie.reverts("commissionThousandths out of bound"): validator_set_instance.updateValidatorSet([accounts[0], accounts[0]], [accounts[1], accounts[3]], [accounts[2], accounts[2]], [1000, 10000], - [random_vote_address(), random_vote_address()]) + [random_vote_address(), random_vote_address()], validator_count) def test_update_success(): __fake_validator_set() vote_address = random_vote_address() tx = validator_set_instance.updateValidatorSet([accounts[0]], [accounts[1]], [accounts[2]], [1000], - [vote_address]) + [vote_address], validator_count) expect_event(tx, "validatorSetUpdated") assert validator_set_instance.getValidators() == [accounts[1]] assert validator_set_instance.getValidatorByConsensus(accounts[1]) == [accounts[0], accounts[1], accounts[2], 1000, - 0, vote_address, 0] + 0, vote_address, 0, 0] def test_update_validator_set_default_vote_weight(set_candidate): @@ -409,13 +432,13 @@ def test_update_validator_set_default_vote_weight(set_candidate): __fake_validator_set() vote_address = random_vote_address() validator_set_instance.updateValidatorSet([operators[0]], [accounts[1]], [accounts[2]], [1000], - [vote_address]) + [vote_address], validator_count) assert validator_set_instance.getValidators() == [accounts[1]] validator = validator_set_instance.getValidatorByConsensus(accounts[1]) assert validator['voteWeight'] == 0 -def test_update_validator_set_add_new(set_candidate, validator_set): +def test_update_validator_set_add_new(validator_set, set_candidate): turn_round() __fake_validator_set() commission_thousandths = 400 @@ -426,12 +449,12 @@ def test_update_validator_set_add_new(set_candidate, validator_set): vote_addr_list = [random_vote_address() for _ in range(5)] commission_thousandths = [commission_thousandths for _ in range(5)] validator_set.updateValidatorSet(operate_addr_list, consensus_addr_list, fee_addr_list, commission_thousandths, - vote_addr_list) + vote_addr_list, validator_count) assert len(validator_set.getValidators()) == 5 for index, address in enumerate(consensus_addr_list): assert validator_set.getValidatorByConsensus(address) == [operate_addr_list[index], address, fee_addr_list[index], commission_thousandths[index], - 0, vote_addr_list[index], 0] + 0, vote_addr_list[index], 0, 0] def test_update_validator_set_modify_existing(validator_set): @@ -444,11 +467,11 @@ def test_update_validator_set_modify_existing(validator_set): vote_addr_list = [random_vote_address() for _ in range(5)] commission_thousandths = [commission_thousandths for _ in range(5)] validator_set.updateValidatorSet(operate_addr_list, consensus_addr_list, fee_addr_list, commission_thousandths, - vote_addr_list) + vote_addr_list, validator_count) assert len(validator_set.getValidators()) == 5 for index, address in enumerate(consensus_addr_list): validator = [operate_addr_list[index], address, fee_addr_list[index], commission_thousandths[index], 0, - vote_addr_list[index], 0] + vote_addr_list[index], 0, 0] assert validator_set.getValidatorByConsensus(address) == validator @@ -464,13 +487,17 @@ def test_vote_validator_count_mismatch(validator_set, legnth): commission_thousandths = [commission_thousandths for _ in range(5)] with brownie.reverts("the numbers of consensusAddresses and voteAddressed should be equal"): validator_set.updateValidatorSet(operate_addr_list, consensus_addr_list, fee_addr_list, commission_thousandths, - vote_addr_list) + vote_addr_list, validator_count) def test_validator_count_change_update(validator_set, candidate_hub, set_candidate): operators, consensuses = set_candidate + delegate_coin_success(operators[0], accounts[0], amount=1002) + delegate_coin_success(operators[1], accounts[0], amount=1001) + delegate_coin_success(operators[2], accounts[0], amount=1000) turn_round() assert len(validator_set.getValidators()) == 3 + delegate_coin_success(operators[0], accounts[0], amount=999) vote_address = random_vote_address() consensuses.append(register_candidate(operator=accounts[20], vote_address=vote_address)) operators.append(accounts[20]) @@ -480,17 +507,50 @@ def test_validator_count_change_update(validator_set, candidate_hub, set_candida # commission_thousandths is set to 1000 because there is no stake on the validator. commission_thousandths = 1000 assert validator_set.getValidatorByConsensus(consensuses[-1]) == [accounts[20], consensuses[-1], accounts[20], - commission_thousandths, 0, vote_address, 0] + commission_thousandths, 0, vote_address, 0, 0] candidate_hub.refuseDelegate({'from': operators[0]}) turn_round(consensuses) candidate_hub.unregister({'from': operators[0]}) turn_round(consensuses) assert validator_set.getValidatorByConsensus(consensuses[-1]) == [accounts[20], consensuses[-1], accounts[20], - commission_thousandths, 0, vote_address, 0] + commission_thousandths, 0, vote_address, 0, 0] with brownie.reverts("no match validator"): validator_set.getValidatorByConsensus(consensuses[0]) +def test_update_validator_set_updates_ranked_validator_list(validator_set): + operate_addr_list = [account for account in accounts[:5]] + consensus_addr_list = [account for account in accounts[5:10]] + fee_addr_list = [account for account in accounts[10:15]] + vote_addr_list = [random_vote_address() for _ in range(5)] + commission_thousandths = [400 for _ in range(5)] + validator_count = 5 + update_system_contract_address(validator_set, candidate_hub=accounts[0]) + validator_set.updateValidatorSet( + operate_addr_list, + consensus_addr_list, + fee_addr_list, + commission_thousandths, + vote_addr_list, + validator_count + ) + assert validator_set.getRankedValidatorList() == consensus_addr_list + new_consensus_addr_list = [account for account in accounts[15:20]] + new_operate_addr_list = [account for account in accounts[20:25]] + new_fee_addr_list = [account for account in accounts[25:30]] + new_vote_addr_list = [random_vote_address() for _ in range(5)] + validator_set.updateValidatorSet( + new_operate_addr_list, + new_consensus_addr_list, + new_fee_addr_list, + commission_thousandths, + new_vote_addr_list, + validator_count + ) + assert validator_set.getRankedValidatorList() == new_consensus_addr_list + + +# getValidatorsAndVoteAddresses def test_get_validators_and_vote_address_success(validator_set): operators = [] consensuses = [] @@ -502,6 +562,166 @@ def test_get_validators_and_vote_address_success(validator_set): assert validator_set.getValidatorsAndVoteAddresses() == [consensuses, vote_address_list] +def test_get_validators_and_vote_addresses_basic_functionality(validator_set, candidate_hub): + operators = [] + consensuses = [] + vote_address_list = [random_vote_address() for _ in range(10)] + for index, operator in enumerate(accounts[5:15]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + delegate_coin_success(operator, accounts[0], 1000 + index) + candidate_hub.setMaxAlternateCount(2) + candidate_hub.setValidatorCount(6) + turn_round() + assert len(validator_set.getCurrentValidatorSet()) == 8 + assert validator_set.getCurrentValidatorSet()[6]['consensusAddress'] == consensuses[-7] + assert validator_set.getCurrentValidatorSet()[7]['consensusAddress'] == consensuses[-8] + result = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs, vote_addrs = result + assert len(consensus_addrs) == len(vote_addrs) == 6 + assert set(consensuses[-6:]).issubset(set(consensus_addrs)) + + +def test_get_validators_and_vote_addresses_with_maintenance_validators(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + delegate_coin_success(operators[3], accounts[0], 5000) + delegate_coin_success(operators[4], accounts[0], 10000) + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + result = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs, vote_addrs = result + + working_validators = validator_set.getWorkingValidators() + assert len(consensus_addrs) == len(working_validators) == 3 + for addr in consensus_addrs: + assert addr != consensuses[0] + assert consensuses[4] in consensus_addrs + + +def test_get_validators_and_vote_addresses_after_validator_exit_maintenance(validator_set, candidate_hub): + operators = [] + consensuses = [] + vote_address_list = [random_vote_address() for _ in range(15)] + for index, operator in enumerate(accounts[5:20]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + delegate_coin_success(operator, accounts[0], 10000 - index) + candidate_hub.setMaxAlternateCount(3) + candidate_hub.setValidatorCount(9) + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.enterMaintenance({'from': operators[3]}) + validator_set.enterMaintenance({'from': operators[6]}) + result_before = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs_before, _ = result_before + + assert consensus_addrs_before[-3:] == [consensuses[9], consensuses[10], consensuses[11]] + for consensus in consensus_addrs_before[:-3]: + assert consensus in [consensuses[1], consensuses[2], consensuses[4], consensuses[5], consensuses[7], + consensuses[8]] + validator_set.exitMaintenance({'from': operators[0]}) + validator_set.exitMaintenance({'from': operators[6]}) + + result_after = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs_after, vote_addrs_after = result_after + for consensus in consensus_addrs_after: + assert consensus in [consensuses[0], consensuses[1], consensuses[2], consensuses[4], consensuses[5], + consensuses[6], consensuses[7], consensuses[8], consensuses[9]] + assert len(consensus_addrs_after) == 9 + + +def test_get_validators_and_vote_addresses_with_slashed_validators(validator_set, slash_indicator, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + slash_indicator.slash(consensuses[0]) + slash_indicator.slash(consensuses[0]) + + result_after = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs_after, _ = result_after + + assert consensuses[0] not in consensus_addrs_after + turn_round(consensuses) + slash_indicator.setFelonyThreshold(3) + for i in range(3): + slash_indicator.slash(consensuses[1]) + + result_final = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs_final, _ = result_final + assert consensuses[1] not in consensus_addrs_final + + +def test_get_validators_and_vote_addresses_validator_count_limit(validator_set): + operators = [] + consensuses = [] + vote_address_list = [random_vote_address() for _ in range(10)] + + for index, operator in enumerate(accounts[5:15]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + + turn_round() + + result = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs, vote_addrs = result + + validator_count = validator_set.validatorCount() + assert len(consensus_addrs) <= validator_count == 3 + + working_validators = validator_set.getWorkingValidators() + assert len(consensus_addrs) <= len(working_validators) == 4 + + +def test_get_validators_and_vote_addresses_empty_validator_set(validator_set): + turn_round() + validator_set.clearCurrentValidatorSet() + result = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs, vote_addrs = result + assert len(consensus_addrs) == 0 + assert len(vote_addrs) == 0 + + +def test_get_validators_and_vote_addresses_vote_address_correctness(validator_set): + operators = [] + consensuses = [] + specific_vote_addresses = [ + random_vote_address(), + random_vote_address(), + random_vote_address() + ] + + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=specific_vote_addresses[index])) + + turn_round() + + result = validator_set.getValidatorsAndVoteAddresses() + consensus_addrs, vote_addrs = result + + for i, consensus_addr in enumerate(consensus_addrs): + consensus_index = consensuses.index(consensus_addr) + expected_vote_addr = specific_vote_addresses[consensus_index] + assert vote_addrs[i] == expected_vote_addr + + +def test_get_validators_and_vote_addresses_validator_count_zero(validator_set, candidate_hub, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + delegate_coin_success(operators[3], accounts[0], 10000) + turn_round() + validator_set.setValidatorCount(0) + assert validator_set.validatorCount() == 0 + consensus_addrs, vote_addrs = validator_set.getValidatorsAndVoteAddresses() + working_validators = validator_set.getWorkingValidators() + assert len(consensus_addrs) == len(working_validators) + for addr in consensus_addrs: + assert addr in working_validators + assert len(consensus_addrs) == len(vote_addrs) + for consensus in consensuses[:4]: + assert consensus in consensus_addrs + + def test_only_gov_can_govern(validator_set): with brownie.reverts("the msg sender must be governance contract"): validator_set.updateParam('voteRewardPercent', @@ -543,7 +763,7 @@ def test_update_vote_reward_percent_success(validator_set, value): @pytest.mark.parametrize("value", [0, 1, 10, 50, 98, 99]) -def test_update_vote_reward_percent_success(validator_set, value): +def test_update_block_reward_incentive_percent_success(validator_set, value): __update_gov_address() hex_value = padding_left(Web3.to_hex(value), 64) validator_set.updateParam('blockRewardIncentivePercent', hex_value) @@ -566,7 +786,7 @@ def test_distribute_reward_failed_by_address_which_is_not_candidate(): def test_distribute_reward_success_with_empty_validators(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], [], validator_count) validator_set_instance.distributeReward(7) __contract_check(0, init_validator_incomes) __balance_check() @@ -617,7 +837,8 @@ def test_distribute_reward_success_with_commissionThousandths_500(): [validator['consensusAddress']], [validator['feeAddress']], [commission], - [validator['voteAddr']] + [validator['voteAddr']], + validator_count ) validator_set_instance.deposit(init_validators[0], {'value': value}) @@ -939,7 +1160,7 @@ def test_single_vote_distribution(validator_set, candidate_hub, set_candidate, d turn_round(consensuses) -def test_vote_new_validator_added(validator_set, set_candidate, deposit_for_reward): +def test_vote_new_validator_added(validator_set, candidate_hub, set_candidate, deposit_for_reward): new_block_reward = 20000 delegate_amount = 10000 validator_set.updateBlockReward(new_block_reward) @@ -950,7 +1171,7 @@ def test_vote_new_validator_added(validator_set, set_candidate, deposit_for_rewa turn_round() consensuses.append(register_candidate(operator=accounts[10])) operators.append(accounts[10]) - delegate_coin_success(accounts[10], accounts[99], delegate_amount) + delegate_coin_success(accounts[10], accounts[99], delegate_amount - 1) weights.append(30) tx = turn_round(consensuses, weights=weights, round_count=2, tx_fee=10000) total_vote_fee = 5400 @@ -1036,7 +1257,7 @@ def test_misdemeanor_failed_with_address_which_is_not_slash(): def test_misdemeanor_failed_with_after_set_empty_validator_set(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], [], validator_count) __update_slash_address() validator = validator_set_instance.getValidatorByConsensus(init_validators[0]).dict() tx = validator_set_instance.misdemeanor(init_validators[0]) @@ -1048,7 +1269,7 @@ def test_misdemeanor_failed_with_after_set_empty_validator_set(): def test_misdemeanor_return_empty_with_empty_validator_set_and_ZERO_ADDRESS(): __fake_validator_set() - validator_set_instance.updateValidatorSet([], [], [], [], []) + validator_set_instance.updateValidatorSet([], [], [], [], [], validator_count) __update_slash_address() assert validator_set_instance.misdemeanor.call(ZERO_ADDRESS) == () @@ -1056,7 +1277,7 @@ def test_misdemeanor_return_empty_with_empty_validator_set_and_ZERO_ADDRESS(): def test_misdemeanor_return_empty_with_only_one_validator_set_and_0_income(): __fake_validator_set() validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], - [random_vote_address()]) + [random_vote_address()], validator_count) __update_slash_address() assert validator_set_instance.misdemeanor.call(init_validators[0]) == () __contract_check(0, [0]) @@ -1065,7 +1286,7 @@ def test_misdemeanor_return_empty_with_only_one_validator_set_and_0_income(): def test_misdemeanor_return_empty_with_only_one_validator_set(): __fake_validator_set() validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], - [random_vote_address()]) + [random_vote_address()], validator_count) deposit_value = 1000000000 expect_event(validator_set_instance.deposit(init_validators[0], {'value': deposit_value}), "validatorDeposit", { @@ -1110,7 +1331,7 @@ def test_misdemeanor_return_empty_with_ZERO_ADDRESS_validator(): def test_felony_failed_with_one_validator_which_has_0_income(): __fake_validator_set() validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], - [random_vote_address()]) + [random_vote_address()], validator_count) __update_slash_address() assert validator_set_instance.felony.call(init_validators[0], felony_round, felony_deposit) == () __contract_check(0, [0]) @@ -1119,7 +1340,7 @@ def test_felony_failed_with_one_validator_which_has_0_income(): def test_felony_failed_with_one_validator_which_has_income(): __fake_validator_set() validator_set_instance.updateValidatorSet([init_validators[0]], [init_validators[0]], [init_validators[0]], [100], - [random_vote_address()]) + [random_vote_address()], validator_count) deposit_value = 1000000000 validator_set_instance.deposit(init_validators[0], {'value': deposit_value}) __update_slash_address() @@ -1137,7 +1358,7 @@ def test_felony_success_with_validator_set_which_has_0_income(candidate_hub): __fake_validator_set() validator_set_instance.updateValidatorSet([accounts[0], accounts[1]], [accounts[0], accounts[1]], [accounts[0], accounts[1]], [100, 100], - [random_vote_address(), random_vote_address()]) + [random_vote_address(), random_vote_address()], validator_count) __update_slash_address() candidate = candidate_hub.candidateSet(0).dict() @@ -1170,7 +1391,7 @@ def test_felony_success_with_validator_set_which_has_income(candidate_hub): __fake_validator_set() validator_set_instance.updateValidatorSet([accounts[0], accounts[1]], [accounts[0], accounts[1]], [accounts[0], accounts[1]], [100, 100], - [random_vote_address(), random_vote_address()]) + [random_vote_address(), random_vote_address()], validator_count) __update_slash_address() deposit_value = 1000000000 @@ -1238,3 +1459,1405 @@ def test_validator_contract_receive_ether(validator_set): tx1 = accounts[0].transfer(validator_set.address, 0) assert "received" not in tx1.events assert tracker0.delta() == 0 + + +# exitMaintenanceTurnRound +def test_revert_when_not_candidate_exit_maintenance_turn_round(validator_set): + with brownie.reverts("the msg sender must be candidate contract"): + validator_set.exitMaintenanceTurnRound() + + +def test_exit_maintenance_turn_round_no_maintenance_success(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + + for i in range(len(operators)): + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] == 0 + + turn_round() + + for i in range(len(operators)): + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] == 0 + + +def test_exit_maintenance_turn_round_with_maintenance_success(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.enterMaintenance({'from': operators[1]}) + validator0 = validator_set.getValidatorByConsensus(consensuses[0]) + validator1 = validator_set.getValidatorByConsensus(consensuses[1]) + assert validator0['enterMaintenanceHeight'] != 0 + assert validator1['enterMaintenanceHeight'] != 0 + turn_round(chain_get_validator_consensus()) + for i in range(len(operators)): + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] == 0 + + +@pytest.mark.parametrize("block_count", [20, 100]) +def test_exit_maintenance_turn_round_with_maintenance_slash(validator_set, candidate_hub, block_count, + set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.enterMaintenance({'from': operators[1]}) + chain.mine(block_count) + tx = turn_round(chain_get_validator_consensus()) + if block_count == 20: + assert 'validatorMisdemeanor' in tx.events + else: + assert 'validatorFelony' in tx.events + for i in range(len(operators[:2])): + if block_count == 20: + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] == 0 + else: + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[i]) + + +def test_exit_maintenance_turn_round_slash_then_exit(validator_set, slash_indicator, candidate_hub, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + slash_indicator.slash(consensuses[0], {'from': accounts[0]}) + slash_indicator.slash(consensuses[0], {'from': accounts[0]}) + assert slash_indicator.indicators(consensuses[0])['count'] == 2 + slash_indicator.setFelonyThreshold(8) + chain.mine(24) + tx = turn_round(chain_get_validator_consensus()) + expect_event(tx, "validatorSlashed", { + "validator": consensuses[0], + 'blockCount': 3 + }) + assert 'validatorMisdemeanor' in tx.events + + slash_indicator.slash(consensuses[1], {'from': accounts[1]}) + slash_indicator.slash(consensuses[1], {'from': accounts[1]}) + chain.mine(60) + tx = validator_set.exitMaintenanceTurnRound( + {'from': candidate_hub.address}) + expect_event(tx, "validatorSlashed", { + "validator": consensuses[1], + 'blockCount': 6 + }) + assert 'validatorFelony' in tx.events + + +def test_slash_during_maintenance(validator_set, slash_indicator, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + for i in range(2): + slash_indicator.slash(consensuses[0], {'from': accounts[0]}) + for i in range(2): + slash_indicator.slash(consensuses[0], {'from': accounts[0]}) + for i in range(2): + slash_indicator.slash(consensuses[1], {'from': accounts[0]}) + tx = turn_round(consensuses) + assert 'validatorMisdemeanor' not in tx.events + assert 'validatorFelony' not in tx.events + with brownie.reverts("no match validator"): + validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorByConsensus(consensuses[1]) + assert validator['enterMaintenanceHeight'] == 0 + turn_round(consensuses) + + +@pytest.mark.parametrize("operator_index", [-1, -2]) +def test_validator_apply_maintenance_success(validator_set, set_candidate_maintenance, operator_index, candidate_hub): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + tx1 = validator_set.enterMaintenance({'from': operators[operator_index]}) + validator_last = validator_set.getValidatorByConsensus(consensuses[operator_index]) + assert validator_last['enterMaintenanceHeight'] != 0 + assert 'validatorEnterMaintenance' in tx1.events + tx2 = validator_set.enterMaintenance({'from': operators[0]}) + validator_first = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator_first['enterMaintenanceHeight'] != 0 + assert 'validatorEnterMaintenance' in tx2.events + chain.mine(300) + tx = turn_round(consensuses) + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[operator_index]) + + +@pytest.mark.parametrize("maintain_index", [ + [0], + [0, 1, 2], + [0, 1, -1, 2, -3], + [0, 1, 2, 3, 4, 5, 6], + [0, 8, 9, 10], + [0, 10, 11], + [1, -2, -1, -3], + [-1, 1, 0, -3], + [0, -1, 1, -2, 3, -3, 4, -4], +] + ) +def test_multiple_validators_multiple_candidates_exit_maintenance(validator_set, candidate_hub, maintain_index): + candidate_hub.setMaxAlternateCount(9) + candidate_hub.setValidatorCount(11) + operators = [] + consensuses = [] + for operator in accounts[5:25]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + for operator in operators[:10]: + delegate_coin_success(operator, accounts[0], 1e18) + delegate_coin_success(operator, accounts[1], 1e18) + turn_round() + for i in maintain_index: + tx = validator_set.enterMaintenance({'from': operators[i]}) + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] != 0 + assert 'validatorEnterMaintenance' in tx.events + chain.mine(300) + tx = turn_round(consensuses) + maintain_address = [] + for i in maintain_index: + maintain_address.append(consensuses[i]) + with brownie.reverts("no match validator"): + validator_set.getValidatorByConsensus(consensuses[i]) + for consensus in consensuses: + if consensus not in maintain_address: + validator = validator_set.getValidatorByConsensus(consensus) + assert validator['enterMaintenanceHeight'] == 0 + assert validator['consensusAddress'] == consensus + turn_round(consensuses) + + +def test_exit_maintenance_with_rejected_and_queued_delegations(validator_set, slash_indicator, candidate_hub, + set_candidate_maintenance): + slash_indicator.setFelonyThreshold(100) + operators, consensuses = set_candidate_maintenance + delegate_coin_success(operators[3], accounts[1], 10000) + delegate_coin_success(operators[4], accounts[2], 9999) + candidate_hub.setValidatorCount(3) + candidate_hub.setMaxAlternateCount(2) + turn_round() + enter_maintenance(operators[0]) + slash_validator(consensuses[2]) + slash_validator(consensuses[1], 'minor') + refuse_delegate(operators[3]) + refuse_delegate(operators[0]) + working_validators = chain_get_validator_consensus() + assert len(working_validators) == 3 + for consensus in working_validators: + assert consensus in [consensuses[1], consensuses[3], consensuses[4]] + tx = turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[0]) + working_validators = chain_get_validator_consensus() + assert len(working_validators) == 2 + for consensus in working_validators: + assert consensus in [consensuses[1], consensuses[4]] + + +def test_exit_and_reenter_maintenance_then_exit_again(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + enter_maintenance(operators[0]) + chain.mine(20) + turn_round(chain_get_validator_consensus()) + enter_maintenance(operators[0]) + chain.mine(10) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[0]) + turn_round(chain_get_validator_consensus()) + + +def test_turn_round_with_validator_count_zero_and_various_operations(validator_set, candidate_hub, slash_indicator): + delegate_amount = 10000 + start_count = 10 + operators = [] + consensuses = [] + for i, operator in enumerate(accounts[start_count:start_count + 12]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + delegate_coin_success(operator, accounts[0], delegate_amount - i) + candidate_hub.setValidatorCount(6) + candidate_hub.setMaxAlternateCount(0) + turn_round() + validator_set.setValidatorCount(0) + expect_validator_consensus(consensuses[:6]) + tx = slash_validator(consensuses[0], slash_type='minor') + assert 'validatorMisdemeanor' in tx.events + assert 'validatorEnterMaintenance' not in tx.events + tx = slash_validator(consensuses[1], slash_type='felony') + assert 'validatorFelony' in tx.events + with brownie.reverts("can not enter Temporary Maintenance"): + enter_maintenance(operators[2]) + candidate_hub.unregister({'from': operators[-1]}) + refuse_delegate(operators[4]) + expect_validator_consensus([consensuses[0], consensuses[2], consensuses[3], consensuses[4], consensuses[5]]) + turn_round(chain_get_validator_consensus()) + expect_validator_consensus( + [consensuses[0], consensuses[2], consensuses[3], consensuses[5], consensuses[6], consensuses[7]]) + stake_hub_claim_reward(accounts[0]) + turn_round(chain_get_validator_consensus()) + + +# enterMaintenance +def test_enter_maintenance_non_validator(): + with brownie.reverts("not a validator"): + validator_set_instance.enterMaintenance({'from': accounts[99]}) + + +def test_enter_maintenance_success(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + candidate_hub.setValidatorCount(2) + for operator in operators[:2]: + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + assert len(validator_set.getCurrentValidatorSet()) == 3 + tx = validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[0] + }) + + +def test_enter_maintenance_already_in_maintenance(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + for operator in operators[:3]: + delegate_coin_success(operator, accounts[0], 1e18) + candidate_hub.setValidatorCount(3) + turn_round() + assert len(validator_set.getCurrentValidatorSet()) == 5 + validator_set.enterMaintenance({'from': operators[0]}) + assert validator_set.currentValidatorSet(0)['enterMaintenanceHeight'] != 0 + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operators[0]}) + + +def test_enter_maintenance_multiple_validators(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + for i in range(len(operators[:2])): + tx = validator_set.enterMaintenance({'from': operators[i]}) + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[i] + }) + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] != 0 + + +def test_enter_maintenance_after_exit(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + candidate_hub.setValidatorCount(2) + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + + __fake_validator_set() + validator_set.exitMaintenanceTurnRound() + + tx = validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[0] + }) + + +def test_revert_when_single_validator_enter_maintenance(validator_set, candidate_hub): + operator = accounts[5] + consensus = register_candidate(operator=operator) + delegate_coin_success(operator, accounts[0], 1e18) + + turn_round() + assert len(validator_set.getCurrentValidatorSet()) == 1 + + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operator}) + + +def test_revert_when_notenoughalternates_entermaintenance(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(3) + operators, consensuses = set_candidate_maintenance + for operator in operators[:3]: + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + assert len(validator_set.getCurrentValidatorSet()) == 5 + for i in range(len(operators[:2])): + validator_set.enterMaintenance({'from': operators[i]}) + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] != 0 + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operators[2]}) + turn_round(consensuses) + + +def test_revert_when_already_misdemeanor_enter_maintenance(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + slash_threshold = slash_indicator.misdemeanorThreshold() + for count in range(slash_threshold): + tx1 = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx1.events + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operators[0]}) + + +def test_revert_when_already_felony_enter_maintenance(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + slash_threshold = slash_indicator.felonyThreshold() + for count in range(slash_threshold): + tx1 = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' in tx1.events + with brownie.reverts("not a validator"): + validator_set.enterMaintenance({'from': operators[0]}) + + +def test_misdemeanor_apply_maintenance(validator_set, candidate_hub, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + candidate_hub.setValidatorCount(5) + turn_round() + slash_threshold = slash_indicator.misdemeanorThreshold() + for count in range(slash_threshold): + tx1 = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx1.events + assert 'validatorEnterMaintenance' not in tx1.events + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] == 0 + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operators[0]}) + + turn_round(consensuses) + + +def test_alternate_validator_apply_maintenance_success(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[4]}) + tx = validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + assert 'validatorEnterMaintenance' in tx.events + chain.mine(300) + turn_round(consensuses) + + +def test_revert_when_candidate_apply_maintenance_rejected(validator_set, set_candidate_maintenance, candidate_hub): + operators, consensuses = set_candidate_maintenance + candidate_hub.setMaxAlternateCount(0) + turn_round() + with brownie.reverts("not a validator"): + validator_set.enterMaintenance({'from': operators[-1]}) + + +def test_enter_maintenance_when_get_working_count_zero(validator_set, candidate_hub, set_candidate_maintenance, + slash_indicator): + operators, consensuses = set_candidate_maintenance + delegate_coin_success(operators[0], accounts[0], 1000) + candidate_hub.setValidatorCount(1) + candidate_hub.setMaxAlternateCount(0) + # If only one validator, felony does not remove it. + slash_threshold = slash_indicator.felonyThreshold() + for _ in range(slash_threshold): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' not in tx.events + + turn_round() + assert validator_set.getWorkingCount() == 1 + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operators[0]}) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(accounts[0]) + + +def test_slash_accumulates_during_and_after_maintenance(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + delegate_coin_success(operators[0], accounts[0], 1e18) + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + + tx = slash_indicator.slash(consensuses[0]) + tx = slash_indicator.slash(consensuses[0]) + chain.mine(21) + indicators = slash_indicator.getIndicators() + tx2 = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorFelony' in tx2.events + assert tx2.events['validatorSlashed']['blockCount'] == 2 + + +def test_working_count_excludes_felony_validators(validator_set, candidate_hub, set_candidate_maintenance, + slash_indicator): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round(consensuses) + initial_working_count = validator_set.getWorkingCount() + assert initial_working_count == 5 + enter_maintenance(operators[0]) + enter_maintenance(operators[1]) + slash_threshold = slash_indicator.felonyThreshold() + for _ in range(slash_threshold): + slash_indicator.slash(consensuses[2]) + slash_indicator.slash(consensuses[1]) + slash_indicator.slash(consensuses[4]) + tx = exit_maintenance(operators[0]) + assert tx.events['validatorSlashed']['blockCount'] == 2 + + +def test_upgrade_validator_cannot_enter_maintenance(validator_set, slash_indicator, candidate_hub, + set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.setValidatorCount(0) + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operators[0]}) + slash_threshold = slash_indicator.misdemeanorThreshold() + for i in range(slash_threshold): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx.events + assert 'validatorEnterMaintenance' not in tx.events + turn_round(consensuses) + + +def test_maintenance_with_less_alternate(validator_set, candidate_hub): + candidate_hub.setValidatorCount(4) + candidate_hub.setMaxAlternateCount(5) + operators = [] + consensuses = [] + for i in range(5): + operators.append(accounts[i]) + consensuses.append(register_candidate(operator=accounts[i])) + turn_round() + enter_maintenance(operators[0]) + with brownie.reverts("can not enter Temporary Maintenance"): + enter_maintenance(operators[1]) + with brownie.reverts("can not enter Temporary Maintenance"): + enter_maintenance(operators[2]) + with brownie.reverts("can not enter Temporary Maintenance"): + enter_maintenance(operators[3]) + turn_round(chain_get_validator_consensus()) + enter_maintenance(operators[1]) + turn_round(chain_get_validator_consensus()) + enter_maintenance(operators[2]) + turn_round(chain_get_validator_consensus()) + enter_maintenance(operators[3]) + +def test_delegate_and_transfer_during_maintenance(validator_set, candidate_hub, set_candidate_maintenance): + stake_manager = StakeManager() + stake_manager.set_lp_rates() + stake_manager.set_tlp_rates() + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + operators, consensuses = set_candidate_maintenance + turn_round() + operator = operators[0] + delegator = accounts[20] + btc_delegator = accounts[21] + delegate_amount = 100000 + btc_amount = 1000 + tx = validator_set.enterMaintenance({'from': operator}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + delegate_coin_success(operator, delegator, delegate_amount) + txid = delegate_btc_success(operator, btc_delegator, btc_amount, LOCK_SCRIPT, relay=btc_delegator) + transfer_coin_success(operator, operators[1], delegator, delegate_amount // 2) + transfer_btc_success(txid,operators[2],btc_delegator) + undelegate_coin_success(operator, delegator, delegate_amount // 4) + delegate_coin_success(operator, delegator, delegate_amount // 10) + txid2 = delegate_btc_success(operator, btc_delegator, btc_amount // 10, LOCK_SCRIPT, relay=btc_delegator) + transfer_coin_success(operator, operators[1], delegator, delegate_amount // 20) + transfer_btc_success(txid2, operators[2], btc_delegator) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(delegator) + turn_round(chain_get_validator_consensus()) + stake_hub_claim_reward(delegator) + turn_round(chain_get_validator_consensus()) + +def test_validator_with_no_delegation_cannot_enter_maintenance(validator_set, candidate_hub, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + candidate_hub.refuseDelegate({'from': operators[0]}) + + validator_set.enterMaintenance({'from': operators[0]}) + + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + + +def test_new_validator_apply_maintenance_success(validator_set, set_candidate_maintenance, candidate_hub): + operators, consensuses = set_candidate_maintenance + turn_round() + operator = accounts[12] + consensus = register_candidate(operator=operator) + delegate_coin_success(operator, accounts[0], 1e18) + with brownie.reverts("not a validator"): + validator_set.enterMaintenance({'from': operator}) + + +# exitMaintenance +def test_maintenance_state_check_success(validator_set, set_candidate_maintenance): + validator_set.setMaintainSlashPercent(30) + operators, consensuses = set_candidate_maintenance + for operator in operators[:3]: + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] == 0 + + validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + maintenance_height = validator['enterMaintenanceHeight'] + assert maintenance_height != 0 + chain.mine(105) + tx = validator_set.exitMaintenance({'from': operators[0]}) + with brownie.reverts('no match validator'): + validator_set.getValidatorByConsensus(consensuses[0]) + expect_event(tx, "validatorExitMaintenance", { + 'validator': consensuses[0] + }) + assert 'validatorFelony' in tx.events + expect_event(tx, "validatorSlashed", { + 'validator': consensuses[0], + 'blockCount': 10 + }) + + +def test_exitmaintenance_success(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.deposit(consensuses[0], {"value": 100, "from": accounts[99]}) + validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + chain.mine(40) + tx = validator_set.exitMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] == 0 + expect_event(tx, "validatorExitMaintenance", { + 'validator': consensuses[0] + }) + expect_event(tx, "validatorSlashed", { + 'validator': consensuses[0], + 'blockCount': 3 + }) + expect_event(tx, "validatorMisdemeanor", { + 'amount': 100 + }) + turn_round(consensuses) + + +def test_revert_when_notvalidator_exitmaintenance(validator_set): + with brownie.reverts("not a validator"): + validator_set.exitMaintenance({'from': accounts[99]}) + + +def test_revert_when_notinmaintenance_exitmaintenance(validator_set, set_candidate): + operators, consensuses = set_candidate + turn_round() + + with brownie.reverts("not in Temporary Maintenance"): + validator_set.exitMaintenance({'from': operators[0]}) + + +def test_revert_when_alreadyexited_exitmaintenance(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + + validator_set.exitMaintenance({'from': operators[0]}) + + with brownie.reverts("not in Temporary Maintenance"): + validator_set.exitMaintenance({'from': operators[0]}) + + +def test_success_multiple_validators_exitmaintenance(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + for i in range(2): + validator_set.enterMaintenance({'from': operators[i]}) + + for i in range(2): + tx = validator_set.exitMaintenance({'from': operators[i]}) + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] == 0 + expect_event(tx, "validatorExitMaintenance", { + 'validator': consensuses[i] + }) + + +def test_revert_when_removedvalidator_exitmaintenance(validator_set, candidate_hub, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + + with brownie.reverts("not a validator"): + validator_set.exitMaintenance({'from': operators[0]}) + + +def test_revert_when_previous_felony_exit_maintenance(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + + slash_threshold = slash_indicator.felonyThreshold() + for _ in range(slash_threshold): + slash_indicator.slash(consensuses[0]) + with brownie.reverts("not a validator"): + validator_set.exitMaintenance({'from': operators[0]}) + + +def test_second_misdemeanor_after_exit(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.deposit(consensuses[0], {"value": 100, "from": accounts[99]}) + slash_threshold = slash_indicator.misdemeanorThreshold() + for _ in range(slash_threshold): + tx = slash_indicator.slash(consensuses[0]) + slash_indicator.setMisdemeanorThreshold(1) + assert tx.events['validatorMisdemeanor']['amount'] == 100 + assert 'validatorEnterMaintenance' in tx.events + validator_set.deposit(consensuses[0], {"value": 1000, "from": accounts[99]}) + chain.mine(13) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorMisdemeanor' in tx.events + assert tx.events['validatorMisdemeanor']['amount'] == 1000 + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' in tx.events + turn_round(consensuses) + + +def test_revert_when_previous_slash_and_missed_blocks(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + slash_indicator.slash(consensuses[0]) + tx = validator_set.enterMaintenance({'from': operators[0]}) + chain.mine(15) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorMisdemeanor' in tx.events + slash_indicator.slash(consensuses[0]) + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' in tx.events + turn_round(consensuses) + + +def test_slash_count_after_percent_change(validator_set, set_candidate_maintenance): + operators, _ = set_candidate_maintenance + turn_round() + validator_set.setMaintainSlashPercent(50) + validator_set.enterMaintenance({'from': operators[0]}) + chain.mine(100) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert tx.events['validatorSlashed']['blockCount'] == 16 + + +def test_no_slash_when_maintain_slash_percent_zero(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.setMaintainSlashPercent(0) + validator_set.enterMaintenance({'from': operators[0]}) + chain.mine(2000) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorSlashed' not in tx.events + assert 'validatorMisdemeanor' not in tx.events + assert 'validatorFelony' not in tx.events + + +def test_clear_unpunished_blocks_in_maintenance(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + chain.mine(1) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorSlashed' not in tx.events + + +def test_slash_count_exclude_maintenance_validators(validator_set, slash_indicator, candidate_hub, + set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + slash_indicator.slash(consensuses[0]) + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx.events + assert 'validatorEnterMaintenance' in tx.events + validator_set.setValidatorCount(21) + chain.mine(100) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert tx.events['validatorSlashed']['blockCount'] == 100 // 5 * 30 / 100 + turn_round(consensuses) + + +def test_felony_after_misdemeanor_in_maintenance(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + + misdemeanor_threshold = slash_indicator.misdemeanorThreshold() + for _ in range(misdemeanor_threshold): + slash_indicator.slash(consensuses[0]) + for _ in range(misdemeanor_threshold): + slash_indicator.slash(consensuses[0]) + with brownie.reverts("not a validator"): + tx = validator_set.exitMaintenance({'from': operators[0]}) + + +def test_misdemeanor_to_felony_then_exit_maintenance(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + misdemeanor_threshold = slash_indicator.misdemeanorThreshold() + for _ in range(misdemeanor_threshold): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx.events + assert 'validatorEnterMaintenance' in tx.events + for _ in range(misdemeanor_threshold): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' in tx.events + with brownie.reverts("not a validator"): + validator_set.exitMaintenance({'from': operators[0]}) + tx = turn_round(consensuses) + assert len(tx.events['roundReward']) == 3 + + +def test_misdemeanor_with_missed_blocks(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.deposit(consensuses[0], {"value": 1000, "from": accounts[99]}) + for _ in range(slash_indicator.misdemeanorThreshold()): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx.events + assert 'validatorEnterMaintenance' in tx.events + slash_indicator.setFelonyThreshold(5) + slash_indicator.slash(consensuses[0]) + slash_indicator.slash(consensuses[0]) + chain.mine(10) + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorFelony' in tx.events + turn_round(consensuses) + + +def test_revert_when_maintenance_then_felony(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + for _ in range(slash_indicator.felonyThreshold()): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorFelony' in tx.events + + with brownie.reverts("not a validator"): + validator_set.exitMaintenance({'from': operators[0]}) + + +def test_maintenance_then_misdemeanor_exit(validator_set, set_candidate_maintenance, slash_indicator): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.deposit(consensuses[0], {"value": 1000, "from": accounts[99]}) + for _ in range(slash_indicator.misdemeanorThreshold()): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx.events + tx = validator_set.exitMaintenance({'from': operators[0]}) + assert 'validatorExitMaintenance' in tx.events + turn_round(consensuses) + + +@pytest.mark.parametrize("blcok", [50, 100]) +def test_slash_then_maintenance_then_felony(validator_set, set_candidate_maintenance, slash_indicator, blcok): + operators, consensuses = set_candidate_maintenance + turn_round() + slash_indicator.setFelonyThreshold(10) + slash_indicator.setMisdemeanorThreshold(5) + + validator_set.deposit(consensuses[0], {"value": 1000, "from": accounts[99]}) + slash_indicator.slash(consensuses[0]) + slash_indicator.slash(consensuses[0]) + validator_set.enterMaintenance({'from': operators[0]}) + chain.mine(blcok) + tx = validator_set.exitMaintenance({'from': operators[0]}) + if blcok == 100: + assert 'validatorFelony' in tx.events + else: + assert 'validatorMisdemeanor' in tx.events + turn_round(consensuses) + + +# canEnterMaintenance +def test_can_enter_maintenance_normal_case(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + index = validator_set.getValidatorIndexFromOps(operators[0]) + assert validator_set.canEnterMaintenance(index) + + +def test_revert_when_already_in_maintenance(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + index = validator_set.getValidatorIndexFromOps(operators[0]) + assert not validator_set.canEnterMaintenance(index) + + +def test_revert_when_single_validator(validator_set, candidate_hub): + operator = accounts[5] + consensus = register_candidate(operator=operator) + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + + index = validator_set.getValidatorIndexFromOps(operator) + assert not validator_set.canEnterMaintenance(index) + + +def test_revert_when_working_count_insufficient(validator_set, candidate_hub, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + candidate_hub.setValidatorCount(6) + turn_round() + index = validator_set.getValidatorIndexFromOps(operators[0]) + assert not validator_set.canEnterMaintenance(index) + + +def test_can_enter_maintenance_after_exit(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.exitMaintenance({'from': operators[0]}) + + index = validator_set.getValidatorIndexFromOps(operators[0]) + assert validator_set.canEnterMaintenance(index) + + +def test_revert_when_validator_count_equals_working(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.enterMaintenance({'from': operators[1]}) + + index = validator_set.getValidatorIndexFromOps(operators[2]) + assert not validator_set.canEnterMaintenance(index) + + +def test_can_enter_maintenance_with_sufficient_working(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + + index = validator_set.getValidatorIndexFromOps(operators[1]) + assert validator_set.canEnterMaintenance(index) + + +def test_revert_when_invalid_index(validator_set): + assert not validator_set.canEnterMaintenance(0) + with brownie.reverts("Index out of range"): + validator_set.canEnterMaintenance(999) + + +# enterMaintenance-slash +def test_revert_when_not_slash_contract(validator_set): + with brownie.reverts("the msg sender must be slash contract"): + validator_set.enterMaintenance(init_validators[0]) + + +def test_validator_not_in_set_success(validator_set): + __update_slash_address() + non_validator_address = accounts[0] + + initial_validators = validator_set.getValidators() + + tx = validator_set.enterMaintenance(non_validator_address) + + assert validator_set.getValidators() == initial_validators + assert 'validatorEnterMaintenance' not in tx.events + + +def test_validator_already_in_maintenance_success(validator_set, set_candidate_maintenance): + __update_slash_address() + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + + tx = validator_set.enterMaintenance(consensuses[0]) + + validator_after = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator_after['enterMaintenanceHeight'] == validator['enterMaintenanceHeight'] + assert 'validatorEnterMaintenance' not in tx.events + + +def test_only_one_working_validator_success(validator_set): + __update_slash_address() + operator = accounts[5] + consensus = register_candidate(operator=operator) + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + + assert len(validator_set.getCurrentValidatorSet()) == 1 + + tx = validator_set.enterMaintenance(consensus) + + validator = validator_set.getValidatorByConsensus(consensus) + assert validator['enterMaintenanceHeight'] == 0 + assert 'validatorEnterMaintenance' not in tx.events + + +def test_working_count_equals_validator_count_success(validator_set, candidate_hub, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + candidate_hub.setValidatorCount(len(operators)) + turn_round() + __update_slash_address() + assert validator_set.getWorkingCount() == len(operators) + tx = validator_set.enterMaintenance(consensuses[0]) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] == 0 + assert 'validatorEnterMaintenance' not in tx.events + + +def test_validator_can_enter_maintenance_success(validator_set, set_candidate_maintenance): + __update_slash_address() + operators, consensuses = set_candidate_maintenance + turn_round() + assert len(validator_set.getCurrentValidatorSet()) == 4 + assert validator_set.getWorkingCount() == 4 + assert validator_set.validatorCount() == 3 + tx = validator_set.enterMaintenance(consensuses[0]) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[0] + }) + + +def test_multiple_validators_enter_maintenance_success(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + __update_slash_address() + operators, consensuses = set_candidate_maintenance + turn_round() + for i in range(2): + tx = validator_set.enterMaintenance(consensuses[i]) + validator = validator_set.getValidatorByConsensus(consensuses[i]) + assert validator['enterMaintenanceHeight'] != 0 + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[i] + }) + + +def test_validator_after_exit_maintenance_success(validator_set, set_candidate_maintenance): + __update_slash_address() + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance(consensuses[0]) + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + + validator_set.exitMaintenance({'from': operators[0]}) + validator_after_exit = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator_after_exit['enterMaintenanceHeight'] == 0 + + tx = validator_set.enterMaintenance(consensuses[0]) + validator_after_enter = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator_after_enter['enterMaintenanceHeight'] != 0 + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[0] + }) + + +def test_zero_address_success(validator_set): + __update_slash_address() + + initial_validators = validator_set.getValidators() + + tx = validator_set.enterMaintenance(ZERO_ADDRESS) + + assert validator_set.getValidators() == initial_validators + assert 'validatorEnterMaintenance' not in tx.events + + +def test_validator_has_income_success(validator_set, set_candidate_maintenance): + __update_slash_address() + operators, consensuses = set_candidate_maintenance + turn_round() + + deposit_value = 1000000000 + validator_set.deposit(consensuses[0], {'value': deposit_value}) + assert validator_set.getIncoming(consensuses[0]) == deposit_value + + tx = validator_set.enterMaintenance(consensuses[0]) + + validator = validator_set.getValidatorByConsensus(consensuses[0]) + assert validator['enterMaintenanceHeight'] != 0 + assert validator_set.getIncoming(consensuses[0]) == deposit_value + expect_event(tx, "validatorEnterMaintenance", { + 'validator': consensuses[0] + }) + + +# getValidatorIndexFromOps +def test_get_validator_index_success(validator_set): + first_validator = validator_set.currentValidatorSet(0) + operate_address = first_validator['operateAddress'] + + index = validator_set.getValidatorIndexFromOps(operate_address) + + assert index == 1 + + +def test_get_validator_index_multiple_success(validator_set): + validator_count = len(validator_set.getCurrentValidatorSet()) + for i in range(validator_count): + validator = validator_set.currentValidatorSet(i) + operate_address = validator['operateAddress'] + index = validator_set.getValidatorIndexFromOps(operate_address) + assert index == i + 1 + + +def test_get_validator_index_not_found_success(validator_set): + non_existent_address = accounts[99] + + index = validator_set.getValidatorIndexFromOps(non_existent_address) + + assert index == 0 + + +def test_get_validator_index_consensus_address_success(validator_set): + first_validator = validator_set.currentValidatorSet(0) + consensus_address = first_validator['consensusAddress'] + + index = validator_set.getValidatorIndexFromOps(consensus_address) + + assert index == 0 + + +def test_get_validator_index_after_removal_success(validator_set, candidate_hub, set_candidate): + operators, consensuses = set_candidate + turn_round() + + first_operator = operators[0] + index_before = validator_set.getValidatorIndexFromOps(first_operator) + assert index_before > 0 + + candidate_hub.refuseDelegate({'from': first_operator}) + turn_round() + candidate_hub.unregister({'from': first_operator}) + + index_after = validator_set.getValidatorIndexFromOps(first_operator) + assert index_after == 0 + + +# update maintainSlashPercent +def test_revert_when_not_gov_update_maintain_slash_percent(validator_set): + hex_value = padding_left(Web3.to_hex(50), 64) + with brownie.reverts("the msg sender must be governance contract"): + validator_set.updateParam('maintainSlashPercent', hex_value) + + +def test_revert_when_invalid_param_length_maintain_slash_percent(validator_set): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(50), 62) + with brownie.reverts("MismatchParamLength: maintainSlashPercent"): + validator_set.updateParam('maintainSlashPercent', hex_value) + + +@pytest.mark.parametrize("value", [101, 102]) +def test_revert_when_maintain_slash_percent_exceeds_max(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + with brownie.reverts(f"OutOfBounds: maintainSlashPercent, {value}, 0, 100"): + validator_set.updateParam('maintainSlashPercent', hex_value) + + +@pytest.mark.parametrize("value", [0, 1, 10, 50, 98, 99, 100]) +def test_update_maintain_slash_percent_success(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + + tx = validator_set.updateParam('maintainSlashPercent', hex_value) + + assert validator_set.maintainSlashPercent() == value + + +# updateRankedValidatorList +def test_update_ranked_validator_list_basic_success(validator_set, set_candidate_maintenance): + turn_round() + assert len(validator_set.getRankedValidatorList()) == 4 + new_consensus_list = [accounts[10], accounts[11], accounts[12]] + validator_set.mockUpdateRankedValidatorList(new_consensus_list) + + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 3 + assert updated_list[0] == accounts[10] + assert updated_list[1] == accounts[11] + assert updated_list[2] == accounts[12] + + +def test_update_ranked_validator_list_length_changes_success(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + long_list = [accounts[10], accounts[11], accounts[12], accounts[13], accounts[14]] + validator_set.mockUpdateRankedValidatorList(long_list) + assert len(validator_set.getRankedValidatorList()) == 5 + + short_list = [accounts[20], accounts[21]] + tx = validator_set.mockUpdateRankedValidatorList(short_list) + + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 2 + assert updated_list[0] == accounts[20] + assert updated_list[1] == accounts[21] + + longer_list = [accounts[30], accounts[31], accounts[32], accounts[33]] + tx = validator_set.mockUpdateRankedValidatorList(longer_list) + + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 4 + assert updated_list[0] == accounts[30] + assert updated_list[1] == accounts[31] + assert updated_list[2] == accounts[32] + assert updated_list[3] == accounts[33] + + +def test_update_ranked_validator_list_edge_cases_success(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + empty_list = [] + tx = validator_set.mockUpdateRankedValidatorList(empty_list) + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 0 + single_element_list = [accounts[99]] + tx = validator_set.mockUpdateRankedValidatorList(single_element_list) + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 1 + assert updated_list[0] == accounts[99] + + large_list = [accounts[i] for i in range(10, 20)] + tx = validator_set.mockUpdateRankedValidatorList(large_list) + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 10 + for i in range(10): + assert updated_list[i] == accounts[10 + i] + + +def test_update_ranked_validator_list_special_addresses_success(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + duplicate_list = [accounts[10], accounts[10], accounts[11], accounts[11], accounts[12]] + tx = validator_set.mockUpdateRankedValidatorList(duplicate_list) + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 5 + assert updated_list[0] == accounts[10] + assert updated_list[1] == accounts[10] + assert updated_list[2] == accounts[11] + assert updated_list[3] == accounts[11] + assert updated_list[4] == accounts[12] + + zero_address_list = [accounts[10], "0x0000000000000000000000000000000000000000", accounts[11]] + tx = validator_set.mockUpdateRankedValidatorList(zero_address_list) + updated_list = validator_set.getRankedValidatorList() + assert len(updated_list) == 3 + assert updated_list[0] == accounts[10] + assert updated_list[1] == "0x0000000000000000000000000000000000000000" + assert updated_list[2] == accounts[11] + + +def test_update_ranked_validator_list_multiple_updates_success(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + list1 = [accounts[10], accounts[11]] + tx1 = validator_set.mockUpdateRankedValidatorList(list1) + assert len(validator_set.getRankedValidatorList()) == 2 + + list2 = [accounts[20], accounts[21], accounts[22]] + tx2 = validator_set.mockUpdateRankedValidatorList(list2) + assert len(validator_set.getRankedValidatorList()) == 3 + + list3 = [accounts[30]] + tx3 = validator_set.mockUpdateRankedValidatorList(list3) + + final_list = validator_set.getRankedValidatorList() + assert len(final_list) == 1 + assert final_list[0] == accounts[30] + + +# getWorkingCount +def test_get_working_count_basic(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + assert validator_set.getWorkingCount() == 5 + + validator_set.enterMaintenance({'from': operators[0]}) + assert validator_set.getWorkingCount() == 4 + + validator_set.enterMaintenance({'from': operators[1]}) + assert validator_set.getWorkingCount() == 3 + + +def test_get_working_count_after_exit(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + assert validator_set.getWorkingCount() == 4 + + validator_set.exitMaintenance({'from': operators[0]}) + assert validator_set.getWorkingCount() == 5 + + +def test_get_working_count_edge_case(validator_set, candidate_hub): + operator = accounts[5] + consensus = register_candidate(operator=operator) + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + + assert validator_set.getWorkingCount() == 1 + + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operator}) + + assert validator_set.getWorkingCount() == 1 + + +def test_get_working_count_after_misdemeanor_slash(validator_set, candidate_hub, slash_indicator, + set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + assert validator_set.getWorkingCount() == 5 + + for _ in range(2): + slash_indicator.slash(consensuses[0]) + assert validator_set.getWorkingCount() == 4 + for _ in range(2): + slash_indicator.slash(consensuses[1]) + assert validator_set.getWorkingCount() == 3 + slash_indicator.setFelonyThreshold(3) + for _ in range(3): + slash_indicator.slash(consensuses[2]) + assert validator_set.getWorkingCount() == 2 + + +# getWorkingValidators +def test_get_working_validators_basic(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + delegate_coin_success(operators[3], accounts[0], 1e17) + delegate_coin_success(operators[4], accounts[0], 1e16) + turn_round() + + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 5 + assert working_validators == consensuses + + validator_set.enterMaintenance({'from': operators[0]}) + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 4 + assert consensuses[0] not in working_validators + for i in range(1, 5): + assert consensuses[i] in working_validators + + +def test_get_working_validators_after_exit(validator_set, candidate_hub, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.enterMaintenance({'from': operators[-1]}) + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 3 + assert consensuses[0] not in working_validators + assert consensuses[-1] not in working_validators + + validator_set.exitMaintenance({'from': operators[-1]}) + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 4 + assert consensuses[0] not in working_validators + assert consensuses[-1] in working_validators + + +def test_get_working_validators_after_slash(validator_set, candidate_hub, slash_indicator, set_candidate_maintenance): + candidate_hub.setMaxAlternateCount(2) + operators, consensuses = set_candidate_maintenance + turn_round() + + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 5 + + for _ in range(2): + slash_indicator.slash(consensuses[0]) + + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 4 + assert consensuses[0] not in working_validators + + slash_indicator.setFelonyThreshold(3) + for _ in range(3): + slash_indicator.slash(consensuses[1]) + + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 3 + assert consensuses[0] not in working_validators + assert consensuses[1] not in working_validators + + +def test_get_working_validators_edge_case(validator_set, candidate_hub): + operator = accounts[5] + consensus = register_candidate(operator=operator) + delegate_coin_success(operator, accounts[0], 1e18) + turn_round() + + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 1 + assert working_validators[0] == consensus + with brownie.reverts("can not enter Temporary Maintenance"): + validator_set.enterMaintenance({'from': operator}) + + working_validators = validator_set.getWorkingValidators() + assert len(working_validators) == 1 + assert working_validators[0] == consensus + + +def test_get_working_validators_consistency(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + + working_count = validator_set.getWorkingCount() + working_validators = validator_set.getWorkingValidators() + assert working_count == len(working_validators) + + validator_set.enterMaintenance({'from': operators[0]}) + working_count = validator_set.getWorkingCount() + working_validators = validator_set.getWorkingValidators() + assert working_count == len(working_validators) + + validator_set.exitMaintenance({'from': operators[0]}) + working_count = validator_set.getWorkingCount() + working_validators = validator_set.getWorkingValidators() + assert working_count == len(working_validators) From 1ee6f4def3527aebeb48807abcc6a84c05dbab6e Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Fri, 25 Jul 2025 21:14:18 +0800 Subject: [PATCH 10/27] Remove BitcoinLST test cases --- contracts/mock/BitcoinAgentMock.sol | 6 +- contracts/mock/BitcoinLSTStakeMock.sol | 73 - tests/common.py | 2 - tests/conftest.py | 21 +- tests/delegate.py | 52 - tests/test_bitcoin_lst_token.py | 161 -- tests/test_btc_agent.py | 334 ++-- tests/test_btc_lst.py | 2415 ------------------------ tests/test_btc_stake.py | 2 +- tests/test_claim_reward.py | 670 +------ tests/test_delegate_btc.py | 2 +- tests/test_delegate_btc_lst.py | 937 --------- tests/test_dual_staking.py | 183 +- tests/test_hash_power_agent.py | 2 +- tests/test_pledge_agent.py | 40 +- tests/test_stake_hub_unit.py | 196 +- tests/test_system_reward.py | 4 +- tests/utils.py | 9 +- 18 files changed, 291 insertions(+), 4818 deletions(-) delete mode 100644 contracts/mock/BitcoinLSTStakeMock.sol delete mode 100644 tests/test_bitcoin_lst_token.py delete mode 100644 tests/test_btc_lst.py delete mode 100644 tests/test_delegate_btc_lst.py diff --git a/contracts/mock/BitcoinAgentMock.sol b/contracts/mock/BitcoinAgentMock.sol index edec7f7be..6561897d1 100644 --- a/contracts/mock/BitcoinAgentMock.sol +++ b/contracts/mock/BitcoinAgentMock.sol @@ -15,11 +15,7 @@ contract BitcoinAgentMock is BitcoinAgent { function setCandidateMap(address agent, uint256 value, uint256 value1) external { candidateMap[agent] = StakeAmount(value, value1); } - - function setPercentage(uint256 value) external { - lstGradePercentage = value; - } - + function setInitLpRates(uint32 value1, uint32 value01, uint32 value2, uint32 value02, uint32 value3, uint32 value03, uint32 value4, uint32 value04) external { while (grades.length > 0) { grades.pop(); diff --git a/contracts/mock/BitcoinLSTStakeMock.sol b/contracts/mock/BitcoinLSTStakeMock.sol deleted file mode 100644 index 5e2adb7fc..000000000 --- a/contracts/mock/BitcoinLSTStakeMock.sol +++ /dev/null @@ -1,73 +0,0 @@ -pragma solidity 0.8.4; - -import "../BitcoinAgent.sol"; -import {BitcoinLSTStake} from "../BitcoinLSTStake.sol"; - -contract BitcoinLSTStakeMock is BitcoinLSTStake { - - function developmentInit() external { - utxoFee = INIT_UTXO_FEE / 100; - } - - - function setInitRound(uint256 value) external { - initRound = value; - } - - function setWallet(bytes memory pkscript) external { - _addWallet(pkscript); - } - - function setRoundTag(uint256 value) external { - roundTag = value; - } - - function setBtcLstRewardMap(address delegator, uint256 reward, uint256 accStakedAmount) external { - rewardMap[delegator] = Reward(reward, accStakedAmount); - } - - - function setUtxoFee(uint64 value) external { - utxoFee = value; - } - - function setStakedAmount(uint64 value) external { - stakedAmount = value; - } - - function setRealtimeAmount(uint64 value) external { - realtimeAmount = value; - } - - function getRedeemMap(bytes32 value) external view returns (uint256) { - return redeemMap[value]; - } - - function getWalletMap(bytes32 value) external view returns (uint256) { - return walletMap[value]; - } - - - function getAccruedRewardPerBTCMap(uint256 round) external view returns (uint256) { - uint256 reward = accruedRewardPerBTCMap[round]; - return reward; - } - - function setAccruedRewardPerBTCMap(uint256 round, uint256 value) external { - accruedRewardPerBTCMap[round] = value; - } - - - function getRedeemRequestsLength() external view returns (uint) { - return redeemRequests.length; - } - - function mockBuildPkScript(bytes32 whash, uint32 addrType) external view returns (bytes memory pkscript) { - pkscript = _buildPkScript(whash, addrType); - } - - function mockExtractPkScriptAddr(bytes memory pkScript) external view returns (bytes32 whash, uint32 addrType) { - return _extractPkScriptAddr(pkScript); - } - -} diff --git a/tests/common.py b/tests/common.py index a458a1478..fd4ec44ee 100644 --- a/tests/common.py +++ b/tests/common.py @@ -100,8 +100,6 @@ def set_round_tag(round_tag): CandidateHubMock[0].setRoundTag(round_tag) BitcoinStakeMock[0].setRoundTag(round_tag) CoreAgentMock[0].setRoundTag(round_tag) - BitcoinLSTStakeMock[0].setRoundTag(round_tag) - BitcoinLSTStakeMock[0].setInitRound(round_tag) PledgeAgentMock[0].setRoundTag(round_tag) diff --git a/tests/conftest.py b/tests/conftest.py index 9e601f348..4e1cbff0f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -153,20 +153,6 @@ def btc_agent(accounts): c.init() return c - -@pytest.fixture(scope="module") -def btc_lst_stake(accounts): - c = accounts[0].deploy(BitcoinLSTStakeMock) - return c - - -@pytest.fixture(scope="module") -def lst_token(accounts): - c = accounts[0].deploy(BitcoinLSTToken) - c.init() - return c - - @pytest.fixture(scope="module") def hash_power_agent(accounts): c = accounts[0].deploy(HashPowerAgentMock) @@ -203,16 +189,13 @@ def set_system_contract_address( stake_hub, btc_stake, btc_agent, - btc_lst_stake, core_agent, hash_power_agent, - lst_token, configuration ): contracts = [ validator_set, slash_indicator, system_reward, btc_light_client, relay_hub, candidate_hub, gov_hub, - pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, btc_lst_stake, core_agent, hash_power_agent, - lst_token, configuration + pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, core_agent, hash_power_agent, configuration ] args = encode(['address'] * len(contracts), [c.address for c in contracts]) @@ -224,12 +207,10 @@ def set_system_contract_address( # init after set system contract system_reward.init() btc_stake.init() - btc_lst_stake.init() stake_hub.init() if is_development: btc_stake.developmentInit() - btc_lst_stake.developmentInit() stake_hub.developmentInit() diff --git a/tests/delegate.py b/tests/delegate.py index 43f5bfd8d..76f0565f8 100644 --- a/tests/delegate.py +++ b/tests/delegate.py @@ -216,7 +216,6 @@ def set_last_round_tag(stake_round, time0=None): CandidateHubMock[0].setRoundTag(current_round) BitcoinStakeMock[0].setRoundTag(current_round) CoreAgentMock[0].setRoundTag(current_round) - BitcoinLSTStakeMock[0].setInitRound(current_round) return end_round0, current_round @@ -303,42 +302,7 @@ def delegate_power_success(candidate, delegator, value=1, stake_round=0): BtcLightClientMock[0].setMiners(stake_round, candidate, [delegator] * value) -def delegate_btc_lst_success(delegator, btc_amount, lock_script, percentage=5000, relay=None): - BitcoinAgentMock[0].setPercentage(percentage) - delegator_asset = BitcoinLSTToken[0].balanceOf(delegator) - btc_tx0 = build_btc_lst_tx(delegator, int(btc_amount), lock_script) - if relay is None: - relay = accounts[0] - tx = BitcoinLSTStakeMock[0].delegate(btc_tx0, 1, [], 0, lock_script, {"from": relay}) - assert 'delegated' in tx.events - delegator_asset += btc_amount - assert BitcoinLSTToken[0].balanceOf(delegator) == delegator_asset - tx_id = get_transaction_txid(btc_tx0) - return tx_id - - -def transfer_btc_lst_success(delegator, amount, to): - from_asset0 = BitcoinLSTToken[0].balanceOf(delegator) - to_asset = BitcoinLSTToken[0].balanceOf(to) - BitcoinLSTToken[0].transfer(to, amount, {"from": delegator}) - from_asset = from_asset0 - amount - to_asset += amount - if delegator == to: - to_asset = from_asset0 - from_asset = from_asset0 - assert BitcoinLSTToken[0].balanceOf(delegator) == from_asset - assert BitcoinLSTToken[0].balanceOf(to) == to_asset - - -def redeem_btc_lst_success(delegator, amount, pkscript): - UTXO_FEE = 100 - tx = BitcoinLSTStakeMock[0].redeem(amount, pkscript, {"from": delegator}) - assert tx.events['redeemed']['amount'] == amount - UTXO_FEE - return tx - - # mock delegate -# agent, delegator, btc_amount, lock_script def mock_delegate_btc_success(agent, delegator, btc_amount, lock_time=None, block_timestamp=0, output_index=0, tx_id=None): if tx_id is None: @@ -662,28 +626,12 @@ def set_stake_hub_delegator_map(account, change_round, rewards=None): rewards = [] StakeHubMock[0].setDelegatorMap(account, change_round, rewards) - @staticmethod - def add_wallet(script): - update_system_contract_address(BitcoinLSTStakeMock[0], gov_hub=accounts[10]) - BitcoinLSTStakeMock[0].updateParam('add', script, {'from': accounts[10]}) - update_system_contract_address(BitcoinLSTStakeMock[0], gov_hub=GovHubMock[0]) - - @staticmethod - def remove_wallet(script): - update_system_contract_address(BitcoinLSTStakeMock[0], gov_hub=accounts[10]) - BitcoinLSTStakeMock[0].updateParam('remove', script, {'from': accounts[10]}) - update_system_contract_address(BitcoinLSTStakeMock[0], gov_hub=GovHubMock[0]) - class RoundRewardManager: @staticmethod def mock_core_reward_map(delegator, reward, acc_stake_amount): BitcoinAgentMock[0].setCoreRewardMap(delegator, reward, acc_stake_amount) - @staticmethod - def mock_btc_lst_reward_map(delegator, reward, delegate_amount): - BitcoinLSTStakeMock[0].setBtcLstRewardMap(delegator, reward, delegate_amount) - @staticmethod def mock_btc_reward_map(delegator, reward, unclaimed_reward, delegate_amount): BitcoinStakeMock[0].setBtcRewardMap(delegator, reward, unclaimed_reward, delegate_amount) diff --git a/tests/test_bitcoin_lst_token.py b/tests/test_bitcoin_lst_token.py deleted file mode 100644 index 343623516..000000000 --- a/tests/test_bitcoin_lst_token.py +++ /dev/null @@ -1,161 +0,0 @@ -import brownie -from .utils import * - -MINT_VALUE = 100000 -BURN_VALUE = 50000 -TRANSFER_VALUE = 60000 - - -def test_bitcoin_lst_token_init_once_only(lst_token): - assert lst_token.alreadyInit() is True - with brownie.reverts("the contract already init"): - lst_token.init() - - -def test_total_supply(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[0], MINT_VALUE) - lst_token.mint(accounts[1], MINT_VALUE) - amount = lst_token.totalSupply() - assert amount == MINT_VALUE * 2 - - -def test_balanceOf(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[0], MINT_VALUE) - lst_token.mint(accounts[1], MINT_VALUE) - amount0 = lst_token.balanceOf(accounts[0]) - amount1 = lst_token.balanceOf(accounts[1]) - assert amount0 == amount1 == MINT_VALUE - - -def test_approve_success(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[2], MINT_VALUE) - lst_token.approve(accounts[1], MINT_VALUE, {'from': accounts[2]}) - assert lst_token.allowance(accounts[2], accounts[1]) == MINT_VALUE - transfer_amount = 1000 - lst_token.transfer(accounts[3], transfer_amount, {'from': accounts[2]}) - lst_token.transferFrom(accounts[2], accounts[4], transfer_amount * 2, {'from': accounts[1]}) - amount0 = lst_token.balanceOf(accounts[3]) - amount1 = lst_token.balanceOf(accounts[4]) - assert amount0 == transfer_amount - assert amount1 == transfer_amount * 2 - - -def test_increase_allowance_success(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.approve(accounts[1], MINT_VALUE) - lst_token.increaseAllowance(accounts[1], MINT_VALUE) - assert lst_token.allowance(accounts[0], accounts[1]) == MINT_VALUE * 2 - - -def test_decrease_allowance_success(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.approve(accounts[1], MINT_VALUE * 2) - lst_token.increaseAllowance(accounts[1], MINT_VALUE) - lst_token.increaseAllowance(accounts[1], MINT_VALUE) - assert lst_token.allowance(accounts[0], accounts[1]) == MINT_VALUE * 4 - lst_token.decreaseAllowance(accounts[1], MINT_VALUE) - assert lst_token.allowance(accounts[0], accounts[1]) == MINT_VALUE * 3 - - -def test_bitcoin_lst_token_mint(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[0], MINT_VALUE) - amount = lst_token.balanceOf(accounts[0]) - assert amount == MINT_VALUE - - -def test_revert_on_mint_with_zero_address(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - with brownie.reverts("ERC20: mint to the zero address"): - lst_token.mint(ZERO_ADDRESS, MINT_VALUE) - amount = lst_token.balanceOf(ZERO_ADDRESS) - assert amount == 0 - - -def test_bitcoin_lst_token_NoBtcLSTStake_mint(lst_token): - with brownie.reverts("only invoked by bitcoin lst stake"): - lst_token.mint(accounts[0], MINT_VALUE) - amount = lst_token.balanceOf(accounts[0]) - assert amount == 0 - - -def test_bitcoin_lst_token_burn(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[0], MINT_VALUE) - lst_token.burn(accounts[0], BURN_VALUE) - amount = lst_token.balanceOf(accounts[0]) - assert amount == MINT_VALUE - BURN_VALUE - - -def test_revert_on_burn_with_zero_address(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - with brownie.reverts("ERC20: burn from the zero address"): - lst_token.burn(ZERO_ADDRESS, MINT_VALUE) - - -def test_bitcoin_lst_token_NoBtcLSTStake_burns(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[0], MINT_VALUE) - update_system_contract_address(lst_token, btc_lst_stake=accounts[1]) - with brownie.reverts("only invoked by bitcoin lst stake"): - lst_token.burn(accounts[0], BURN_VALUE) - - -def test_bitcoin_lst_token_transfer(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[0], MINT_VALUE) - lst_token.transfer(accounts[1], TRANSFER_VALUE) - amount = lst_token.balanceOf(accounts[1]) - assert amount == TRANSFER_VALUE - - -def test_bitcoin_lst_token_gov_updateParam(lst_token): - update_system_contract_address(lst_token, gov_hub=accounts[0]) - with brownie.reverts("UnsupportedGovParam: test"): - lst_token.updateParam("test", "0x00") - - -def test_bitcoin_lst_token_nogov_updateParam(lst_token): - with brownie.reverts("the msg sender must be governance contract"): - lst_token.updateParam("test", "0x00") - - -def test_bitcoin_lst_token_allow_transferFrom(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[1], MINT_VALUE) - lst_token.allowance(accounts[1], accounts[0]) - lst_token.approve(accounts[0], TRANSFER_VALUE, {"from": accounts[1]}) - lst_token.transferFrom(accounts[1], accounts[2], TRANSFER_VALUE, {'from': accounts[0]}) - with brownie.reverts("ERC20: transfer amount exceeds balance"): - lst_token.transfer(accounts[2], TRANSFER_VALUE, {'from': accounts[0]}) - amount = lst_token.balanceOf(accounts[2]) - assert amount == TRANSFER_VALUE - - -def test_transferFrom_without_approval(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[1], MINT_VALUE) - with brownie.reverts("ERC20: insufficient allowance"): - lst_token.transferFrom(accounts[1], accounts[2], TRANSFER_VALUE, {'from': accounts[0]}) - amount = lst_token.balanceOf(accounts[2]) - assert amount == 0 - - -def test_transfer_amount_exceeds_approved_limit(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[1], MINT_VALUE) - lst_token.approve(accounts[0], TRANSFER_VALUE, {"from": accounts[1]}) - with brownie.reverts("ERC20: insufficient allowance"): - lst_token.transferFrom(accounts[1], accounts[2], TRANSFER_VALUE * 2, {'from': accounts[0]}) - amount = lst_token.balanceOf(accounts[2]) - assert amount == 0 - - -def test_insufficient_assets_during_transfer(lst_token): - update_system_contract_address(lst_token, btc_lst_stake=accounts[0]) - lst_token.mint(accounts[1], MINT_VALUE) - with brownie.reverts("ERC20: insufficient allowance"): - lst_token.transferFrom(accounts[1], accounts[2], TRANSFER_VALUE) diff --git a/tests/test_btc_agent.py b/tests/test_btc_agent.py index 9e75f5d1f..0c99d9004 100644 --- a/tests/test_btc_agent.py +++ b/tests/test_btc_agent.py @@ -1,11 +1,8 @@ import pytest import brownie import rlp -from web3 import Web3, constants -from brownie import * -from .constant import Utils -from .delegate import delegate_btc_lst_success, delegate_btc_success, set_block_time_stamp, StakeManager, \ - RoundRewardManager +from web3 import constants +from .delegate import * from .utils import expect_event, padding_left, update_system_contract_address from .common import turn_round, get_current_round, register_candidate, set_round_tag @@ -14,31 +11,26 @@ # BTC delegation-related LOCK_SCRIPT = "0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac" LOCK_TIME = 1736956800 -# BTCLST delegation-related -BTC_LST_VALUE = 200 -BTCLST_LOCK_SCRIPT = "0xa914cdf3d02dd323c14bea0bed94962496c80c09334487" -BTCLST_REDEEM_SCRIPT = "0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287" stake_manager = StakeManager() round_reward_manager = RoundRewardManager() @pytest.fixture(scope="module", autouse=True) -def set_up(btc_stake, stake_hub, btc_agent, core_agent, btc_lst_stake, hash_power_agent, validator_set, gov_hub): - global BTC_STAKE, STAKE_HUB, BTC_AGENT, CORE_AGENT, BTC_LST_STAKE, HASH_POWER_AGENT, TOTAL_REWARD +def set_up(btc_stake, stake_hub, btc_agent, core_agent, hash_power_agent, validator_set, gov_hub): + global BTC_STAKE, STAKE_HUB, BTC_AGENT, CORE_AGENT, HASH_POWER_AGENT, TOTAL_REWARD BTC_STAKE = btc_stake STAKE_HUB = stake_hub BTC_AGENT = btc_agent CORE_AGENT = core_agent - BTC_LST_STAKE = btc_lst_stake HASH_POWER_AGENT = hash_power_agent btc_agent.setAssetWeight(1) block_reward = validator_set.blockReward() block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() total_block_reward = block_reward + TX_FEE - BLOCK_REWARD = total_block_reward * ((100 - block_reward_incentive_percent) / 100) + BLOCK_REWARD = total_block_reward * \ + ((100 - block_reward_incentive_percent) / 100) TOTAL_REWARD = BLOCK_REWARD // 2 set_block_time_stamp(150, LOCK_TIME) - btc_lst_stake.updateParam('add', BTCLST_LOCK_SCRIPT, {'from': gov_hub.address}) @pytest.fixture(scope="module", autouse=True) @@ -89,42 +81,34 @@ def test_only_pledge_agent_can_call(btc_agent): btc_agent._initializeFromPledgeAgent(candidates, amounts) -def test_distribute_reward_success(btc_agent, btc_stake, btc_lst_stake): +@pytest.mark.parametrize('lst_btc_amount', [0, 4000]) +def test_distribute_reward_success(btc_agent, btc_stake, lst_btc_amount): history_reward = 200 turn_round() round_tag = get_current_round() candidates = accounts[:3] btc_amount = 1000 - lst_btc_amount = 4000 rewards = [10000, 20000, 30000] set_round_tag(round_tag - 1) round_tag += 2 - btc_lst_stake.setRoundTag(round_tag) btc_stake.setRoundTag(round_tag) update_system_contract_address(btc_agent, stake_hub=accounts[0]) for c in candidates: btc_agent.setCandidateMap(c, lst_btc_amount, btc_amount) btc_stake.setCandidateMap(c, btc_amount, btc_amount, [round_tag - 1]) btc_stake.setAccruedRewardPerBTCMap(c, round_tag - 1, history_reward) - btc_lst_stake.setAccruedRewardPerBTCMap(round_tag - 1, history_reward) - btc_lst_stake.setStakedAmount(lst_btc_amount) btc_agent.distributeReward(candidates, rewards, 0) - btc_reward = sum(rewards) for index, c in enumerate(candidates): reward = rewards[index] - lst_btc_reward = reward * lst_btc_amount / (lst_btc_amount + btc_amount) - btc_reward -= lst_btc_reward assert btc_stake.accruedRewardPerBTCMap(c, - round_tag) == history_reward + lst_btc_reward * Utils.BTC_DECIMAL // lst_btc_amount - assert btc_lst_stake.getAccruedRewardPerBTCMap( - round_tag) == history_reward + btc_reward * Utils.BTC_DECIMAL // btc_amount + round_tag) == history_reward + reward * Utils.BTC_DECIMAL // btc_amount def test_validators_and_reward_list_length_mismatch_failed(btc_agent): candidates = accounts[:3] rewards = [10000, 20000] update_system_contract_address(btc_agent, stake_hub=accounts[0]) - with brownie.reverts("the length of validators and rewardList should be equal"): + with brownie.reverts(): btc_agent.distributeReward(candidates, rewards, 0) @@ -135,35 +119,49 @@ def test_only_stake_hub_can_call_distribute_reward(btc_agent): btc_agent.distributeReward(candidates, rewards, 0) -def test_reward_zero_with_staked_tokens(btc_agent, btc_stake, btc_lst_stake): +def test_reward_not_zero_with_zero_staked_tokens(btc_agent, btc_stake): turn_round() round_tag = get_current_round() candidates = accounts[:3] rewards = [10000, 20000, 30000] set_round_tag(round_tag - 1) round_tag += 2 - btc_lst_stake.setRoundTag(round_tag) btc_stake.setRoundTag(round_tag) update_system_contract_address(btc_agent, stake_hub=accounts[0]) btc_agent.distributeReward(candidates, rewards, 0) - assert btc_stake.accruedRewardPerBTCMap(candidates[0], get_current_round()) == 0 + assert btc_stake.accruedRewardPerBTCMap( + candidates[0], get_current_round()) == 0 -def test_distribute_reward_reverts(btc_agent, btc_stake, btc_lst_stake): +def test_reward_zero_with_staked_tokens(btc_agent, btc_stake): candidates = accounts[:3] - rewards = [10000, 20000, 30000] + rewards = [0, 0, 0] + round_tag = get_current_round() + 2 + btc_stake.setRoundTag(round_tag) + update_system_contract_address(btc_agent, stake_hub=accounts[0]) + btc_agent.distributeReward(candidates, rewards, 0) + for index, c in enumerate(candidates): + assert btc_stake.accruedRewardPerBTCMap(c, round_tag) == 0 + + +def test_reward_zero_with_nonzero_stake(btc_agent, btc_stake): + turn_round() + btc_amount = 2000 + candidates = accounts[:3] + rewards = [0, 0, 0] round_tag = get_current_round() + 2 - btc_lst_stake.setRoundTag(round_tag) btc_stake.setRoundTag(round_tag) update_system_contract_address(btc_agent, stake_hub=accounts[0]) + for c in candidates: + btc_agent.setCandidateMap(c, 0, btc_amount) + btc_stake.setCandidateMap(c, btc_amount, btc_amount, [round_tag - 1]) + btc_stake.setAccruedRewardPerBTCMap(c, round_tag - 1, 100) btc_agent.distributeReward(candidates, rewards, 0) for index, c in enumerate(candidates): assert btc_stake.accruedRewardPerBTCMap(c, round_tag) == 0 - assert btc_lst_stake.getAccruedRewardPerBTCMap(round_tag) == 0 -def test_get_stake_amounts_success(btc_agent, btc_stake, btc_lst_stake, set_candidate): - lst_amount = 6000 +def test_get_stake_amounts_success(btc_agent, btc_stake, set_candidate): btc_amount = 3000 operators, consensuses = set_candidate turn_round() @@ -171,56 +169,53 @@ def test_get_stake_amounts_success(btc_agent, btc_stake, btc_lst_stake, set_cand btc_agent.getStakeAmounts(operators, 0) for o in operators: btc_stake.setCandidateMap(o, btc_amount, btc_amount, []) - btc_lst_stake.setRealtimeAmount(lst_amount) - amounts, total_amount = btc_agent.getStakeAmounts(operators, 0).return_value - lst_validator_amount = lst_amount // 3 - amount = lst_validator_amount + btc_amount - assert amounts == [amount, amount, amount] - assert total_amount == amount * 3 + amounts, total_amount = btc_agent.getStakeAmounts( + operators, 0).return_value + assert amounts == [btc_amount, btc_amount, btc_amount] + assert total_amount == btc_amount * 3 -def test_query_address_empty(btc_agent, btc_stake, btc_lst_stake, set_candidate): +def test_query_address_empty(btc_agent, btc_stake, set_candidate): operators = [] update_system_contract_address(btc_agent, stake_hub=accounts[0]) - amounts, total_amount = btc_agent.getStakeAmounts(operators, 0).return_value + amounts, total_amount = btc_agent.getStakeAmounts( + operators, 0).return_value assert sum(amounts) == total_amount == 0 -def test_query_address_zero(btc_agent, btc_stake, btc_lst_stake, set_candidate): +def test_query_address_zero(btc_agent, btc_stake, set_candidate): operators = ZERO_ADDRESS turn_round() update_system_contract_address(btc_agent, stake_hub=accounts[0]) - amounts, total_amount = btc_agent.getStakeAmounts([operators], 0).return_value + amounts, total_amount = btc_agent.getStakeAmounts( + [operators], 0).return_value assert sum(amounts) == total_amount == 0 -def test_only_stake_hub_can_call(btc_agent, btc_stake, btc_lst_stake, set_candidate): +def test_only_stake_hub_can_call(btc_agent, btc_stake, set_candidate): operators, consensuses = set_candidate turn_round() with brownie.reverts("the msg sender must be stake hub contract"): btc_agent.getStakeAmounts(operators, 0) -def test_set_new_round_success(btc_agent, btc_stake, btc_lst_stake, set_candidate): - lst_amount = 6000 +def test_set_new_round_success(btc_agent, btc_stake, set_candidate): btc_amount = 3000 operators, consensuses = set_candidate round_tag = 7 - assert btc_stake.roundTag() == btc_lst_stake.roundTag() == round_tag + assert btc_stake.roundTag() == round_tag turn_round() round_tag += 1 for o in operators: btc_stake.setCandidateMap(o, 0, btc_amount, []) - btc_lst_stake.setRealtimeAmount(lst_amount) update_system_contract_address(btc_agent, stake_hub=accounts[0]) btc_agent.setNewRound(operators, get_current_round()) for op in operators: assert btc_stake.candidateMap(op) == [btc_amount, btc_amount] - assert btc_lst_stake.stakedAmount() == lst_amount - assert btc_stake.roundTag() == btc_lst_stake.roundTag() == round_tag + assert btc_stake.roundTag() == round_tag -def test_only_stake_hub_can_call_set_new_round(btc_agent, btc_stake, btc_lst_stake, set_candidate): +def test_only_stake_hub_can_call_set_new_round(btc_agent, btc_stake, set_candidate): with brownie.reverts("the msg sender must be stake hub contract"): btc_agent.setNewRound(accounts[:3], get_current_round()) @@ -240,8 +235,10 @@ def test_reward_claiming_after_grade_update(btc_agent, grade): rate = 5000 stake_manager.set_is_stake_hub_active(grade) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward(accounts[0], core_reward, get_current_round() - 1, False).return_value + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, 0, btc_amount) + reward = btc_agent.claimReward( + accounts[0], core_reward, get_current_round() - 1, False).return_value if grade: float_reward = -btc_reward // 2 actual_reward = btc_reward * rate // Utils.DENOMINATOR @@ -251,16 +248,6 @@ def test_reward_claiming_after_grade_update(btc_agent, grade): assert reward == [actual_reward, float_reward, btc_amount] -def test_no_extra_bonus_for_btc_lst_state(btc_stake, btc_agent, set_candidate, stake_hub): - lst_reward = 10000 - lst_amount = 100 - core_reward = 100000 - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], lst_reward, lst_amount) - claim = btc_agent.claimReward.call(accounts[0], core_reward, get_current_round() - 1, False) - assert claim == [lst_reward, 0, lst_amount] - - def test_grades_not_set(btc_agent): stake_manager.set_lp_rates() btc_reward = 20000 @@ -268,8 +255,10 @@ def test_grades_not_set(btc_agent): core_reward = 10000 stake_manager.set_is_stake_hub_active(True) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward(accounts[0], core_reward, get_current_round() - 1, False).return_value + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, 0, btc_amount) + reward = btc_agent.claimReward( + accounts[0], core_reward, get_current_round() - 1, False).return_value assert reward[0] == btc_reward @@ -286,8 +275,10 @@ def test_grades_length_mismatch(btc_agent, grades): core_reward = 50000 stake_manager.set_is_stake_hub_active(True) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward(accounts[0], core_reward, get_current_round() - 1, False).return_value + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, 0, btc_amount) + reward = btc_agent.claimReward( + accounts[0], core_reward, get_current_round() - 1, False).return_value assert reward[0] == btc_reward // 2 @@ -306,10 +297,13 @@ def test_update_core_amount_and_claim_reward(btc_agent, tests): unclaimed_reward = actual_btc_reward - btc_reward stake_manager.set_is_stake_hub_active(True) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward.call(accounts[0], core_amount, get_current_round() - 2, False) + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, 0, btc_amount) + reward = btc_agent.claimReward.call( + accounts[0], core_amount, get_current_round() - 2, False) assert reward == [actual_btc_reward, unclaimed_reward, btc_amount] - tx = btc_agent.claimReward(accounts[0], core_amount, get_current_round() - 2, False) + tx = btc_agent.claimReward( + accounts[0], core_amount, get_current_round() - 2, False) dual_staking_rate = actual_btc_reward expect_event(tx, 'storedBtcReward', { 'delegator': accounts[0], @@ -334,7 +328,6 @@ def test_unclaimed_reward_record_correct(btc_agent, set_candidate, tests, claim) stake_manager.set_is_stake_hub_active(True) operators, consensuses = set_candidate turn_round() - delegate_btc_lst_success(accounts[0], BTC_LST_VALUE, BTCLST_LOCK_SCRIPT, percentage=0) turn_round(consensuses, round_count=2) btc_reward = tests[1] btc_amount = tests[2] @@ -342,15 +335,17 @@ def test_unclaimed_reward_record_correct(btc_agent, set_candidate, tests, claim) actual_btc_reward = tests[4] unclaimed_reward = actual_btc_reward - btc_reward update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward.call(accounts[0], core_amount, get_current_round() - 1, False) - unclaimed_reward -= TOTAL_REWARD * 3 - assert reward == [actual_btc_reward, unclaimed_reward, btc_amount + BTC_LST_VALUE] - tx = btc_agent.claimReward(accounts[0], core_amount, get_current_round() - 1, claim) + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, 0, btc_amount) + reward = btc_agent.claimReward.call( + accounts[0], core_amount, get_current_round() - 1, False) + assert reward == [actual_btc_reward, unclaimed_reward, btc_amount] + tx = btc_agent.claimReward( + accounts[0], core_amount, get_current_round() - 1, claim) dual_staking_rate = tests[0][-1][-1] - event_name = ['storedBtcReward', 'storedBtcLstReward'] + event_name = ['storedBtcReward'] if claim: - event_name = ['claimedBtcReward', 'claimedBtcLstReward'] + event_name = ['claimedBtcReward'] expect_event(tx, event_name[0], { 'amount': actual_btc_reward, 'unclaimedAmount': 0, @@ -358,43 +353,32 @@ def test_unclaimed_reward_record_correct(btc_agent, set_candidate, tests, claim) 'accStakedAmount': btc_amount, 'dualStakingRate': dual_staking_rate }) - expect_event(tx, event_name[1], { - 'amount': 0, - 'unclaimedAmount': 0, - 'floatReward': 0 - TOTAL_REWARD * 3, - 'accStakedAmount': BTC_LST_VALUE, - 'percent': 0 - }) @pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 4000, 'btc_lst_unclaimed': 5000}, - {'btc_reward': 3000, 'btc_lst_reward': 2000, 'duration_unclaimed': 0, 'btc_lst_unclaimed': 1000}, - {'btc_reward': 3000, 'btc_lst_reward': 0, 'duration_unclaimed': 1000, 'btc_lst_unclaimed': 0}, - {'btc_reward': 3000, 'btc_lst_reward': 2000, 'duration_unclaimed': 4000, 'btc_lst_unclaimed': 1000}, - {'btc_reward': 3000, 'btc_lst_reward': 2000, 'duration_unclaimed': 1000, 'btc_lst_unclaimed': 1000}, - {'btc_reward': 20000, 'btc_lst_reward': 2000, 'duration_unclaimed': 8000, 'btc_lst_unclaimed': 1000}, - {'btc_reward': 5000, 'btc_lst_reward': 0, 'duration_unclaimed': 6000, 'btc_lst_unclaimed': 0}, - {'btc_reward': 5000, 'btc_lst_reward': 12000, 'duration_unclaimed': 0, 'btc_lst_unclaimed': 6000}, - {'btc_reward': 5000, 'btc_lst_reward': 0, 'duration_unclaimed': 0, 'btc_lst_unclaimed': 0} + {'btc_reward': 10000, 'duration_unclaimed': 4000}, + {'btc_reward': 3000, 'duration_unclaimed': 0}, + {'btc_reward': 3000, 'duration_unclaimed': 1000}, + {'btc_reward': 3000, 'duration_unclaimed': 4000}, + {'btc_reward': 3000, 'duration_unclaimed': 1000}, + {'btc_reward': 20000, 'duration_unclaimed': 8000}, + {'btc_reward': 5000, 'duration_unclaimed': 6000}, + {'btc_reward': 5000, 'duration_unclaimed': 0}, + {'btc_reward': 5000, 'duration_unclaimed': 0} ]) -def test_claim_reward_with_additional_bonus(btc_agent, btc_lst_stake, tests): +def test_claim_reward_with_additional_bonus(btc_agent, tests): stake_manager.set_lp_rates([[0, 20000]]) btc_reward = tests['btc_reward'] additional_bonus = btc_reward * 2 - btc_reward - btc_agent.setPercentage(5000) duration_unclaimed = tests['duration_unclaimed'] - btc_lst_unclaimed = tests['btc_lst_unclaimed'] stake_manager.set_is_stake_hub_active(True) turn_round() - btc_lst_reward = tests['btc_lst_reward'] - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_lst_reward, 0) + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, duration_unclaimed, 100) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - reward = btc_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, False) - actual_reward = btc_reward + btc_lst_reward // 2 + additional_bonus - if btc_lst_unclaimed > 0: - additional_bonus -= btc_lst_unclaimed + reward = btc_agent.claimReward.call( + accounts[0], 0, get_current_round() - 1, False) + actual_reward = btc_reward + additional_bonus if duration_unclaimed > 0: additional_bonus -= duration_unclaimed assert reward == [actual_reward, additional_bonus, 100] @@ -402,46 +386,40 @@ def test_claim_reward_with_additional_bonus(btc_agent, btc_lst_stake, tests): @pytest.mark.parametrize("claim", [True, False]) @pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 4000, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'expect_reward': 24000, 'expect_surplus': 0}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 3000, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'expect_reward': 24000, 'expect_surplus': 1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 5000, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'expect_reward': 24000, 'expect_surplus': -1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'expect_reward': 24000, 'expect_surplus': 4000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 15000, - 'btc_percentage': 9000, 'expect_reward': 24000, 'expect_surplus': 2000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 9000, - 'btc_percentage': 15000, 'expect_reward': 24000, 'expect_surplus': 4000} + {'btc_reward': 10000, 'duration_unclaimed': 4000, 'btc_percentage': 12000, 'expect_reward': 12000, + 'expect_surplus': -2000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 12000, 'expect_reward': 12000, + 'expect_surplus': 1000}, + {'btc_reward': 10000, 'duration_unclaimed': 10000, 'btc_percentage': 18000, 'expect_reward': 18000, + 'expect_surplus': -2000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 14000, 'expect_reward': 14000, + 'expect_surplus': 4000}, + {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 9000, 'expect_reward': 9000, + 'expect_surplus': -3000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 15000, 'expect_reward': 15000, + 'expect_surplus': 5000} ]) -def test_btc_extra_pool_reward(btc_agent, btc_lst_stake, tests, claim): +def test_btc_extra_pool_reward(btc_agent, tests, claim): btc_reward = tests['btc_reward'] duration_unclaimed = tests['duration_unclaimed'] - btc_lst_percentage = tests['btc_lst_percentage'] btc_percentage = tests['btc_percentage'] stake_manager.set_lp_rates([[0, btc_percentage]]) - btc_agent.setPercentage(btc_lst_percentage) stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR additional_bonus = actual_btc_reward - btc_reward - btc_lst_reward = tests['btc_lst_reward'] - actual_btc_lst_reward = btc_lst_reward * btc_lst_percentage // Utils.DENOMINATOR btc_acc_amount = 1100 - btc_lst_acc_amount = 1200 - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, btc_acc_amount) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_lst_reward, btc_lst_acc_amount) + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, duration_unclaimed, btc_acc_amount) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - reward = btc_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, False) + reward = btc_agent.claimReward.call( + accounts[0], 0, get_current_round() - 1, False) if duration_unclaimed > 0: additional_bonus -= duration_unclaimed - if btc_lst_percentage != Utils.DENOMINATOR: - additional_bonus += (actual_btc_lst_reward - btc_lst_reward) - actual_reward = actual_btc_reward + actual_btc_lst_reward + actual_reward = actual_btc_reward assert actual_reward == tests['expect_reward'] assert additional_bonus == tests['expect_surplus'] - assert reward == [actual_reward, additional_bonus, btc_acc_amount + btc_lst_acc_amount] + assert reward == [actual_reward, additional_bonus, btc_acc_amount] tx = btc_agent.claimReward(accounts[0], 0, get_current_round() - 1, claim) event_name = ['storedBtcReward', 'storedBtcLstReward'] if claim: @@ -454,64 +432,51 @@ def test_btc_extra_pool_reward(btc_agent, btc_lst_stake, tests, claim): 'accStakedAmount': btc_acc_amount, 'dualStakingRate': btc_percentage }) - expect_event(tx, event_name[1], { - 'delegator': accounts[0].address, - 'amount': actual_btc_lst_reward, - 'unclaimedAmount': 0, - 'floatReward': actual_btc_lst_reward - btc_lst_reward, - 'accStakedAmount': btc_lst_acc_amount, - 'percent': btc_lst_percentage - }) @pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 8000, - 'btc_percentage': 8000, 'expect_reward': 16000, 'expect_surplus': -4000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 8000, - 'btc_percentage': 8000, 'expect_reward': 16000, 'expect_surplus': -5000}, - {'btc_reward': 3000, 'btc_lst_reward': 2000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 0, - 'btc_percentage': 0, 'expect_reward': 0, 'expect_surplus': -6000}, - {'btc_reward': 20000, 'btc_lst_reward': 10000, 'duration_unclaimed': 3000, 'btc_lst_percentage': 10000, - 'btc_percentage': 10000, 'expect_reward': 30000, 'expect_surplus': -3000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 12000, - 'btc_percentage': 7000, 'expect_reward': 19000, 'expect_surplus': -1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 12000, - 'btc_percentage': 7000, 'expect_reward': 19000, 'expect_surplus': -3000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 8000, - 'btc_percentage': 12000, 'expect_reward': 20000, 'expect_surplus': -1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 7000, - 'btc_percentage': 12000, 'expect_reward': 19000, 'expect_surplus': -2000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 15000, - 'btc_percentage': 3000, 'expect_reward': 18000, 'expect_surplus': -4000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 5000, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'expect_reward': 24000, 'expect_surplus': -1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 0, - 'btc_percentage': 12000, 'expect_reward': 12000, 'expect_surplus': -9000}, - {'btc_reward': 20000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 12000, - 'btc_percentage': 0, 'expect_reward': 12000, 'expect_surplus': -18000} + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 8000, 'expect_reward': 8000, + 'expect_surplus': -2000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 8000, 'expect_reward': 8000, + 'expect_surplus': -3000}, + {'btc_reward': 3000, 'duration_unclaimed': 1000, 'btc_percentage': 0, 'expect_reward': 0, 'expect_surplus': -4000}, + {'btc_reward': 20000, 'duration_unclaimed': 3000, 'btc_percentage': 10000, 'expect_reward': 20000, + 'expect_surplus': -3000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 7000, 'expect_reward': 7000, + 'expect_surplus': -3000}, + {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 9000, 'expect_reward': 9000, + 'expect_surplus': -3000}, + {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 12000, 'expect_reward': 12000, + 'expect_surplus': 0}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'expect_reward': 12000, + 'expect_surplus': -1000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 12000, 'expect_reward': 12000, + 'expect_surplus': 1000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 12000, 'expect_reward': 12000, + 'expect_surplus': 2000}, + {'btc_reward': 20000, 'duration_unclaimed': 0, 'btc_percentage': 0, 'expect_reward': 0, 'expect_surplus': -20000}, + {'btc_reward': 20000, 'duration_unclaimed': 2000, 'btc_percentage': 0, 'expect_reward': 0, + 'expect_surplus': -22000}, + {'btc_reward': 20000, 'duration_unclaimed': 2000, 'btc_percentage': 5000, 'expect_reward': 10000, + 'expect_surplus': -12000} ]) -def test_btc_add_reward_to_pool(btc_agent, btc_lst_stake, tests): +def test_btc_add_reward_to_pool(btc_agent, tests): btc_reward = tests['btc_reward'] duration_unclaimed = tests['duration_unclaimed'] - btc_lst_percentage = tests['btc_lst_percentage'] btc_percentage = tests['btc_percentage'] stake_manager.set_lp_rates([[0, btc_percentage]]) - btc_agent.setPercentage(btc_lst_percentage) stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR additional_bonus = actual_btc_reward - btc_reward - btc_lst_reward = tests['btc_lst_reward'] - actual_btc_lst_reward = btc_lst_reward * btc_lst_percentage // Utils.DENOMINATOR - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_lst_reward, 0) + round_reward_manager.mock_btc_reward_map( + accounts[0], btc_reward, duration_unclaimed, 100) update_system_contract_address(btc_agent, stake_hub=accounts[0]) - reward = btc_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, False) + reward = btc_agent.claimReward.call( + accounts[0], 0, get_current_round() - 1, False) if duration_unclaimed > 0: additional_bonus -= duration_unclaimed - if btc_lst_percentage != Utils.DENOMINATOR: - additional_bonus += (actual_btc_lst_reward - btc_lst_reward) - actual_reward = actual_btc_reward + actual_btc_lst_reward + actual_reward = actual_btc_reward assert actual_reward == tests['expect_reward'] assert additional_bonus == tests['expect_surplus'] assert reward == [actual_reward, additional_bonus, 100] @@ -521,27 +486,11 @@ def test_get_acc_stake_amount_success(btc_agent, set_candidate): btc_value = 200 operators, consensuses = set_candidate turn_round() - delegate_btc_lst_success(accounts[0], BTC_LST_VALUE, BTCLST_LOCK_SCRIPT) delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) update_system_contract_address(btc_agent, stake_hub=accounts[0]) _, _, acc_staked_amount = btc_agent.claimReward(accounts[0], 100, get_current_round() - 1, False).return_value - assert acc_staked_amount == BTC_LST_VALUE + btc_value - - -@pytest.mark.parametrize("percentage", [400, 1000, 8800, 10000]) -def test_lst_claim_reward_percentage_change(btc_agent, btc_stake, btc_lst_stake, set_candidate, percentage): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_LST_VALUE, BTCLST_LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_agent, gov_hub=accounts[0], stake_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(percentage)), 64) - btc_agent.updateParam('lstGradePercentage', hex_value, {'from': accounts[0]}) - return_value = btc_agent.claimReward(accounts[0], 0, get_current_round() - 1, False).return_value - btc_lst_reward = TOTAL_REWARD * 3 - claimed_reward = btc_lst_reward * percentage // Utils.DENOMINATOR - assert return_value == [claimed_reward, -(btc_lst_reward - claimed_reward), BTC_LST_VALUE] + assert acc_staked_amount == btc_value def test_get_grades(btc_agent, btc_stake): @@ -695,6 +644,7 @@ def test_update_param_percentage_length_error(btc_agent): btc_agent.updateParam('lstGradePercentage', hex_value) +@pytest.mark.skip(reason="deprecated") def test_revert_on_percentage_zero(btc_agent): percentage = 0 update_system_contract_address(btc_agent, gov_hub=accounts[0]) @@ -702,7 +652,7 @@ def test_revert_on_percentage_zero(btc_agent): with brownie.reverts(f"OutOfBounds: lstGradePercentage, {percentage}, 1, 100000"): btc_agent.updateParam('lstGradePercentage', hex_value) - +@pytest.mark.skip(reason="deprecated") def test_revert_on_percentage_exceeding_limit(btc_agent): percentage = 100001 update_system_contract_address(btc_agent, gov_hub=accounts[0]) diff --git a/tests/test_btc_lst.py b/tests/test_btc_lst.py deleted file mode 100644 index 7e23f3093..000000000 --- a/tests/test_btc_lst.py +++ /dev/null @@ -1,2415 +0,0 @@ -import brownie -import pytest -from web3 import constants -from .common import * -from .delegate import * -from .utils import * - -BTC_VALUE = 2000 -TX_FEE = 100 -public_key = "025615000708918f33f8743b2284558ac9d89e7b8d0df0d692ed48859eea73de93" -LOCK_SCRIPT = "0xa914cdf3d02dd323c14bea0bed94962496c80c09334487" -REDEEM_SCRIPT = "0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287" -LOCK_TIME = 1736956800 -FEE = 0 -TOTAL_REWARD = 0 -UTXO_FEE = 100 -btc_script = get_btc_script() -btc_delegate = BtcStake() -stake_manager = StakeManager() - - -@pytest.fixture(scope="module", autouse=True) -def deposit_for_reward(validator_set, gov_hub): - accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) - accounts[99].transfer(gov_hub.address, Web3.to_wei(100000, 'ether')) - - -@pytest.fixture(scope="module", autouse=True) -def set_relayer_register(relay_hub): - for account in accounts[:3]: - relay_hub.setRelayerRegister(account.address, True) - - -@pytest.fixture(scope="module", autouse=True) -def set_block_reward(validator_set, candidate_hub, btc_agent, btc_light_client, btc_stake, stake_hub, core_agent, - btc_lst_stake, - gov_hub): - global FEE, BTC_REWARD, TOTAL_REWARD, GOV_HUB - global BTC_STAKE, STAKE_HUB, CORE_AGENT, BTC_LST_STAKE, BTC_LIGHT_CLIENT - FEE = FEE * 100 - block_reward = validator_set.blockReward() - block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() - total_block_reward = block_reward + TX_FEE - actual_block_reward = total_block_reward * ((100 - block_reward_incentive_percent) / 100) - TOTAL_REWARD = actual_block_reward // 2 - BTC_STAKE = btc_stake - STAKE_HUB = stake_hub - CORE_AGENT = core_agent - BTC_LIGHT_CLIENT = btc_light_client - BTC_LST_STAKE = btc_lst_stake - candidate_hub.setControlRoundTimeTag(True) - # The default staking time is 150 days - set_block_time_stamp(150, LOCK_TIME) - btc_lst_stake.updateParam('add', LOCK_SCRIPT, {'from': gov_hub.address}) - btc_agent.setPercentage(Utils.DENOMINATOR // 2) - GOV_HUB = gov_hub - - -@pytest.fixture() -def set_candidate(): - operators = [] - consensuses = [] - for operator in accounts[5:8]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - return operators, consensuses - - -def test_btc_lst_init_once_only(btc_lst_stake): - with brownie.reverts("the contract already init"): - btc_lst_stake.init() - - -def test_delegate_lst_btc_p2sh_script_success(btc_lst_stake, lst_token, gov_hub, set_candidate, stake_hub, btc_agent): - lock_script = '0xa91454f0594a167b8226a2f4905e70f272fee9f5360387' - btc_lst_stake.updateParam('add', lock_script, {'from': gov_hub.address}) - btc_agent.setPercentage(Utils.DENOMINATOR) - operators, consensuses = set_candidate - turn_round() - tx_id = '0x' + reverse_by_bytes('23a4ee8926da03aa9097caccbb9788787946d369b097e3238b0d1bf347b577bb') - btc_tx = ( - '01000000016f6187d0c4a17fd74bc61f1f16f15a94f13f22f13e0d4d455dabceebe317368e020000006a47304402206d78735d73a7f01e7b7a0d2a2b2a96e9bd9e14baa32f59eb0802701f7a1b05950220539eff3f30dcf7bd98f322c1d8e77406ff63ad64889b22566cc8fefc3b98c8d401210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff03' - 'd00700000000000017a91454f0594a167b8226a2f4905e70f272fee9f5360387' - '00000000000000001e6a1c5341542b0204589fb29aac15b9a4b7f17c3385939b007540f4d7910111eec3' - '00000000001976a914e1c5ba4d1fef0a3c7806603de565929684f9c2b188ac00000000') - - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[0]}) - expect_event(tx, 'Transfer', { - 'to': accounts[0], - 'value': BTC_VALUE - }) - expect_event(tx, 'delegated', { - 'txid': tx_id, - 'delegator': accounts[0], - 'amount': BTC_VALUE - }) - btc_tx_map = __get_btc_tx_map(tx_id) - assert btc_tx_map == [BTC_VALUE, 0, 1] - turn_round() - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - tx = stake_hub_claim_reward(accounts[0]) - expect_event(tx, 'claimedBtcLstReward', { - 'delegator': accounts[0], - 'amount': TOTAL_REWARD * 3, - 'unclaimedAmount': 0, - 'floatReward': 0, - 'accStakedAmount': BTC_VALUE, - 'percent': Utils.DENOMINATOR, - }) - assert tracker.delta() == TOTAL_REWARD * 3 - - -def test_delegate_lst_btc_p2pkh_script_success(btc_lst_stake, lst_token, gov_hub, set_candidate, stake_hub, btc_agent): - operators, consensuses = set_candidate - turn_round() - lock_script = '0x76a914cdf3d02dd323c14bea0bed94962496c80c09334488ac' - btc_lst_stake.updateParam('add', lock_script, {'from': gov_hub.address}) - tx_id = '0x' + reverse_by_bytes('27f28cedfa18b1cd8cb05e916986c88afbe34d3a211707e35485fd6b89db4379') - btc_tx = ( - '0100000001bb77b547f31b0d8b23e397b069d34679788897bbccca9790aa03da2689eea423020000006a47304402205eeecc0a79a2016fb59de8752eaeb282af8ce68771ec27c8778c6aef7c71f6710220497e910cf619695ce966c18ab4c0118cc80907d24a8c0c6275701e56ca0337de01210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff03d007' - '0000000000001976a914cdf3d02dd323c14bea0bed94962496c80c09334488ac' - '00000000000000001e6a1c5341542b0204589fb29aac15b9a4b7f17c3385939b007540f4d79101cbe3c3' - '00000000001976a914e1c5ba4d1fef0a3c7806603de565929684f9c2b188ac00000000') - - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[0]}) - expect_event(tx, 'Transfer', { - 'to': accounts[0], - 'value': BTC_VALUE - }) - expect_event(tx, 'delegated', { - 'txid': tx_id, - 'delegator': accounts[0], - 'amount': BTC_VALUE - }) - turn_round() - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - - -def test_delegate_lst_btc_p2wpkh_script_success(btc_lst_stake, lst_token, gov_hub, set_candidate, stake_hub, btc_agent): - operators, consensuses = set_candidate - turn_round() - lock_script = '0x0014cdf3d02dd323c14bea0bed94962496c80c093344' - btc_lst_stake.updateParam('add', lock_script, {'from': gov_hub.address}) - tx_id = '0x' + reverse_by_bytes('63e868b4cae7df3045c73ab65f0052d5a5e119ce4dbb452b5720b58938f91131') - btc_tx = ( - '01000000017943db896bfd8554e30717213a4de3fb8ac88669915eb08ccdb118faed8cf227020000006a47304402200255151b40013b656c752dd85ab6ea27487e8f5f658cb44b6db231609c3b416c02202a4e3682b64a2d0acae87a526968a7330d6e05f71e5190faae4efbc83a86da2701210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff03d00700' - '0000000000160014cdf3d02dd323c14bea0bed94962496c80c093344' - '00000000000000001e6a1c5341542b0204589fb29aac15b9a4b7f17c3385939b007540f4d791018bd9c3' - '00000000001976a914e1c5ba4d1fef0a3c7806603de565929684f9c2b188ac00000000') - - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[0]}) - expect_event(tx, 'Transfer', { - 'to': accounts[0], - 'value': BTC_VALUE - }) - expect_event(tx, 'delegated', { - 'txid': tx_id, - 'delegator': accounts[0], - 'amount': BTC_VALUE - }) - turn_round() - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - - -def test_delegate_lst_btc_p2wsh_script_success(btc_lst_stake, lst_token, gov_hub, set_candidate, stake_hub, btc_agent): - operators, consensuses = set_candidate - turn_round() - lock_script = '0x00201dc72212f4defaec092ffe941f4d62f64f139910c4bf60d6a306f43762c4de55' - btc_lst_stake.updateParam('add', lock_script, {'from': gov_hub.address}) - btc_tx = ( - '01000000011ba59f247e7aaba7d7aa3360e718dcdcc43d83e499573dc64f1e4152ac000436020000006a47304402205540f1066ded451bf2775118d005f5920e4c37387f0a781bd7508bc3fd2853790220010c0670459192484b588655c04305ab305b57c816832ad5c3e9e1cffde7243001210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff02d0070000000000002200201dc72212f4defaec092ffe941f4d62f64f139910c4bf60d6a306f43762c4de55' - '00000000000000001e6a1c5341542b0204589fb29aac15b9a4b7f17c3385939b007540f4d7910100000000') - - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[0]}) - expect_event(tx, 'Transfer', { - 'to': accounts[0], - 'value': BTC_VALUE - }) - expect_event(tx, 'delegated', { - 'delegator': accounts[0], - 'amount': BTC_VALUE - }) - turn_round() - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - - -def test_delegate_lst_btc_p2tr_script_success(btc_lst_stake, lst_token, gov_hub, set_candidate, stake_hub, btc_agent): - operators, consensuses = set_candidate - turn_round() - lock_script = '0x51201dc72212f4defaec092ffe941f4d62f64f139910c4bf60d6a306f43762c4de55' - btc_lst_stake.updateParam('add', lock_script, {'from': gov_hub.address}) - btc_tx = ( - '01000000011ba59f247e7aaba7d7aa3360e718dcdcc43d83e499573dc64f1e4152ac000436020000006a47304402205540f1066ded451bf2775118d005f5920e4c37387f0a781bd7508bc3fd2853790220010c0670459192484b588655c04305ab305b57c816832ad5c3e9e1cffde7243001210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff02d007000000000000' - '2251201dc72212f4defaec092ffe941f4d62f64f139910c4bf60d6a306f43762c4de55' - '00000000000000001e6a1c5341542b0204589fb29aac15b9a4b7f17c3385939b007540f4d7910100000000') - - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[0]}) - expect_event(tx, 'delegated', { - 'delegator': accounts[0], - 'amount': BTC_VALUE - }) - turn_round() - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - - -def test_btc_tx_delegate_lock_script_type_mismatch(btc_lst_stake, lst_token, set_candidate): - turn_round() - btc_tx, _ = __create_btc_lst_delegate(accounts[0], BTC_VALUE) - btc_lst_script_p2wsh = __create_btc_lst_staking_script(script_type='p2wsh') - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - # p2wsh lock scirp - btc_lst_stake.updateParam('add', btc_lst_script_p2wsh) - with brownie.reverts("staked value is zero"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, btc_lst_script_p2wsh, {"from": accounts[1]}) - - -def test_duplicate_btc_tx_stake(btc_lst_stake, lst_token, set_candidate): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - assert 'delegated' in tx.events - with brownie.reverts("btc tx is already delegated."): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_revert_due_to_unconfirmed_btc_tx(btc_lst_stake, lst_token, set_candidate, btc_light_client): - btc_light_client.setCheckResult(False, 0) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE) - with brownie.reverts("btc tx isn't confirmed"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_lock_script_not_found_reverts(btc_lst_stake, lst_token, set_candidate, btc_light_client): - lock_script = '0xa91454f0594a167b8226a2f4905e70f272fee9f5360383' - with brownie.reverts("Wallet not found"): - btc_lst_stake.delegate('0x00', 1, [], 0, lock_script, {"from": accounts[1]}) - - -@pytest.mark.parametrize("round_count", [0, 1, 2, 3]) -def test_lock_script_wallet_inactive_reverts(btc_lst_stake, set_candidate, round_count): - stake_manager.add_wallet(REDEEM_SCRIPT) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('remove', LOCK_SCRIPT) - turn_round(round_count=round_count) - with brownie.reverts("wallet inactive"): - btc_lst_stake.delegate('0x00', 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - - -@pytest.mark.parametrize("lock_script", [ - ['0xa914cdf3d02dd323c14bea0bed94962496c80c09334487', '0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287'], - ['0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287', '0xa914cdf3d02dd323c14bea0bed94962496c80c09334487'], - ['0x00148bca50f394ed7e7f750213f287accc32c2431cf5', '0xa914cdf3d02dd323c14bea0bed94962496c80c09334487'], - ['0xa914cdf3d02dd323c14bea0bed94962496c80c09334487', '0x00148bca50f394ed7e7f750213f287accc32c2431cf5'], - ['0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287', '0x00148bca50f394ed7e7f750213f287accc32c2431cf5'] -]) -def test_btclst_delegate_script_error(set_candidate, lst_token, btc_lst_stake, lock_script): - stake_manager.add_wallet(lock_script[1]) - turn_round() - btc_tx0 = build_btc_lst_tx(accounts[0], int(BTC_VALUE), lock_script[0]) - with brownie.reverts("staked value is zero"): - btc_lst_stake.delegate(btc_tx0, 1, [], 0, lock_script[1], {"from": accounts[0]}) - turn_round() - - -def test_delete_wallet_after_stake(btc_lst_stake, set_candidate): - operators, consensuses = set_candidate - turn_round() - stake_manager.add_wallet(REDEEM_SCRIPT) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - delegate_btc_lst_success(accounts[1], BTC_VALUE, LOCK_SCRIPT) - btc_lst_stake.updateParam('remove', LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[1]) - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - stake_manager.add_wallet(LOCK_SCRIPT) - turn_round(consensuses) - stake_hub_claim_reward(accounts[1]) - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 4 - - -def test_delete_multisig_wallet(btc_lst_stake, set_candidate): - turn_round() - lst_script0 = random_btc_lst_lock_script() - lst_script1 = random_btc_lst_lock_script() - stake_manager.add_wallet(REDEEM_SCRIPT) - stake_manager.add_wallet(lst_script0) - stake_manager.add_wallet(lst_script1) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - stake_manager.remove_wallet(lst_script0) - wallet_index = btc_lst_stake.getWalletMap(keccak_hash256(lst_script0)) - assert wallet_index == 3 - assert btc_lst_stake.wallets(wallet_index - 1)['status'] == 2 - stake_manager.add_wallet(lst_script0) - wallet_index = btc_lst_stake.getWalletMap(keccak_hash256(lst_script0)) - assert wallet_index == 3 - assert btc_lst_stake.wallets(wallet_index - 1)['status'] == 1 - - -def test_stake_to_removed_wallet(btc_lst_stake, set_candidate): - turn_round() - lst_script0 = random_btc_lst_lock_script() - lst_script1 = random_btc_lst_lock_script() - stake_manager.add_wallet(REDEEM_SCRIPT) - stake_manager.add_wallet(lst_script0) - stake_manager.add_wallet(lst_script1) - stake_manager.remove_wallet(lst_script0) - with brownie.reverts("wallet inactive"): - delegate_btc_lst_success(accounts[0], BTC_VALUE, lst_script0) - - -def test_op_return_length_error_causes_failure(btc_lst_stake, lst_token): - op_length0 = hex(27).replace('0x', '') - op_length1 = hex(25).replace('0x', '') - btc_tx = ( - '01000000016f6187d0c4a17fd74bc61f1f16f15a94f13f22f13e0d4d455dabceebe317368e020000006a47304402206d78735d73a7f01e7b7a0d2a2b2a96e9bd9e14baa32f59eb0802701f7a1b05950220539eff3f30dcf7bd98f322c1d8e77406ff63ad64889b22566cc8fefc3b98c8d401210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff03d007' - '00000000000017a91454f0594a167b8226a2f4905e70f272fee9f5360387' - f'0000000000000000{op_length0}6a{op_length1}53410204589fb29aac15b9a4b7f17c3385939b007540f4d791' - f'11eec300000000001976a914e1c5ba4d1fef0a3c7806603de565929684f9c2b188ac00000000') - with brownie.reverts("payload length is too small"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - - -def test_btc_lst_opreturn_too_long(btc_lst_stake, lst_token): - lock_script = '0x0014cdf3d02dd323c14bea0bed94962496c80c093344' - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('add', lock_script) - op_length0 = hex(33).replace('0x', '') - op_length1 = hex(31).replace('0x', '') - btc_tx = ( - '01000000017943db896bfd8554e30717213a4de3fb8ac88669915eb08ccdb118faed8cf227020000006a47304402200255151b40013b656c752dd85ab6ea27487e8f5f658cb44b6db231609c3b416c02202a4e3682b64a2d0acae87a526968a7330d6e05f71e5190faae4efbc83a86da2701210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732' - 'fffffffff03d00700' - '0000000000160014cdf3d02dd323c14bea0bed94962496c80c093344' - '0000000000000000' - f'{op_length0}6a{op_length1}5341542b0204589fb29aac15b9a4b7f17c3385939b007540f4d79101' - f'890001' - '8bd9c300000000001976a914e1c5ba4d1fef0a3c7806603de565929684f9c2b188ac00000000') - - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[0]}) - assert 'delegated' in tx.events - - -def test_stake_failed_on_wrong_magic(btc_lst_stake, lst_token, set_candidate): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE, magic='5341542c') - with brownie.reverts("wrong magic"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_stake_failed_on_wrong_chain_id(btc_lst_stake, lst_token): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE, chain_id=1116) - with brownie.reverts("wrong chain id"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_failed_stake_with_invalid_version(btc_lst_stake, lst_token): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE, version=1) - with brownie.reverts("unsupported sat+ version in btc staking"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_btc_lst_stake_address_zero(btc_lst_stake, lst_token, set_candidate, btc_light_client): - operators, consensuses = set_candidate - turn_round() - btc_tx, lock_script = __create_btc_lst_delegate(constants.ADDRESS_ZERO, BTC_VALUE) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[2]}) - assert 'Transfer' not in tx.events - assert btc_lst_stake.realtimeAmount() == 0 - turn_round(consensuses, round_count=2) - tx = stake_hub_claim_reward(constants.ADDRESS_ZERO) - assert tx.events['claimedBtcLstReward']['amount'] == 0 - - -def test_btc_stake_with_zero_amount_fails(btc_lst_stake, lst_token): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], 0) - with brownie.reverts("staked value is zero"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_stake_without_opreturn_success(btc_lst_stake, lst_token): - btc_tx = ( - "010000000103ad821b58ab29c462009c53bf427fb121446e27f3c2cc2b11c1070dabb5bea0020000006a473044022041b7d5402a979672ec75021f7ad4d337347a63dc3135a175c02e07ed360f7d5d02207491acdc2d521596b1dcadf554038be17a2630e99b35e2d19f73acd1e5469f7b01210270e4215fbe540cab09ac91c9586eba4fc797537859489f4a23d3e22356f1732" - "fffffffff026c0700000000000017a914cdf3d02dd323c14bea0bed94962496c80c09334487159b9e" - "00000000001976a914e1c5ba4d1fef0a3c7806603de565929684f9c2b188ac00000000") - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - tx_id = get_transaction_txid(btc_tx) - __check_btc_lst_tx_map_info(tx_id, { - 'amount': 1900, - 'outputIndex': 0, - 'blockHeight': 1, - }) - - -def test_address_mismatch_leads_to_zero_stake_value(btc_lst_stake, lst_token): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE) - # P2WPKH lock script generated from the public key '023821629dad3e7bad594d183f27bfca34511bedb319aec33faea6f71c2c821fe8' - error_script = '0x0014047b9ba09367c1b213b5ba2184fba3fababcdc02' - btc_lst_stake.updateParam('add', error_script) - with brownie.reverts("staked value is zero"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, error_script, {"from": accounts[1]}) - - -def test_invalid_relay_cannot_be_moved(btc_lst_stake, lst_token, set_candidate, btc_light_client): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE) - with brownie.reverts("only delegator or relayer can submit the BTC transaction"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[4]}) - - -def test_delegator_successfully_handles_relay(btc_lst_stake, lst_token, set_candidate, btc_light_client): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[4], BTC_VALUE) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[4]}) - assert 'delegated' in tx.events - - -def test_successful_handling_of_legal_relay(btc_lst_stake, lst_token, set_candidate, btc_light_client): - btc_tx, lock_script = __create_btc_lst_delegate(accounts[4], BTC_VALUE) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[2]}) - assert 'delegated' in tx.events - - -@pytest.mark.parametrize("btc_amount", [100, 199, 200, 201]) -def test_revert_on_btc_amount_too_small(btc_lst_stake, btc_amount): - utxo_fee = UTXO_FEE * 2 - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], btc_amount) - if btc_amount >= utxo_fee: - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - assert 'delegated' in tx.events - else: - with brownie.reverts("btc amount is too small"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_multisig_wallet_initiate_staking(btc_lst_stake, stake_hub, set_candidate, lst_token, gov_hub): - btc_tx_info = {} - btc_lst_stake.updateParam('add', REDEEM_SCRIPT, {'from': gov_hub.address}) - inputs = [build_input()] - outputs = [build_output(BTC_VALUE + 1, LOCK_SCRIPT), - build_btc_lst_stake_opreturn(accounts[0]), - build_output(BTC_VALUE + 2, LOCK_SCRIPT)] - generate_btc_transaction_info(btc_tx_info, inputs, outputs) - btc_tx = build_btc_transaction(btc_tx_info) - turn_round() - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - assert tx.events['delegated']['amount'] == BTC_VALUE + 2 - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE + 2 - - -def test_delegate_includes_existing_rewards(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - turn_round() - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE, script_type='p2tr') - btc_lst_stake.updateParam('add', lock_script) - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - turn_round() - turn_round(consensuses) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE // 2) - btc_lst_reward0, acc_staked_amount = btc_lst_stake.rewardMap(accounts[0]) - assert btc_lst_reward0 == 0 - assert acc_staked_amount == 0 - assert btc_lst_stake.userStakeInfo(accounts[0])[1] == BTC_VALUE - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - # "Staked on all validators" - assert btc_lst_stake.userStakeInfo(accounts[0])[1] == BTC_VALUE + BTC_VALUE // 2 - btc_lst_reward, acc_staked_amount = btc_lst_stake.rewardMap(accounts[0]) - assert btc_lst_reward == TOTAL_REWARD * 3 - assert btc_lst_stake.realtimeAmount() == BTC_VALUE + BTC_VALUE // 2 - assert acc_staked_amount == BTC_VALUE - - -def test_multisig_wallet_self_staking_reverts(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - turn_round() - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, LOCK_SCRIPT, tx_id) - with brownie.reverts("should not delegate from whitelisted multisig wallets"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - turn_round(consensuses) - - -def test_cross_multisig_wallet_staking(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - turn_round() - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE, script_type='p2tr') - btc_lst_stake.updateParam('add', lock_script) - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, lock_script, tx_id) - with brownie.reverts("should not delegate from whitelisted multisig wallets"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - turn_round(consensuses) - - -def test_multisig_wallet_multi_input_self_staking_reverts(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([random_btc_tx_id()], [tx_id]), - set_op_return([accounts[0]])) - with brownie.reverts("should not delegate from whitelisted multisig wallets"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - turn_round(consensuses) - - -def test_staking_with_valid_non_staked_vout_from_same_txid(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - turn_round() - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, LOCK_SCRIPT, tx_id, vout=1) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - assert 'delegated' in tx.events - turn_round(consensuses) - - -def test_non_staked_utxo_self_staking_by_multisig_wallet(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT], [BTC_VALUE * 3, LOCK_SCRIPT])) - btc_lst_stake.delegate(btc_tx, 1, [], 2, LOCK_SCRIPT) - tx_id = get_transaction_txid(btc_tx) - turn_round() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 1], [random_btc_tx_id()]), - set_op_return([accounts[0]])) - with brownie.reverts("should not delegate from whitelisted multisig wallets"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - turn_round(consensuses) - - -def test_non_staked_utxo_staking_from_multisig_to_another_multisig(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - lock_script0 = random_btc_lst_lock_script() - lock_script1 = random_btc_lst_lock_script() - btc_lst_stake.updateParam('add', lock_script0) - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script1], [BTC_VALUE * 3, lock_script0]) - ) - btc_lst_stake.delegate(btc_tx, 1, [], 2, lock_script0) - tx_id = get_transaction_txid(btc_tx) - turn_round() - btc_tx = btc_delegate.build_btc_lst(set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 1], [random_btc_tx_id()]), - set_op_return([accounts[0]]) - ) - with brownie.reverts("should not delegate from whitelisted multisig wallets"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - turn_round(consensuses) - - -def test_successful_staking_with_non_staked_utxos_from_different_vouts(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - lock_script0 = random_btc_lst_lock_script() - lock_script1 = random_btc_lst_lock_script() - btc_lst_stake.updateParam('add', lock_script0) - btc_tx = btc_delegate.build_btc_lst(set_outputs([BTC_VALUE, lock_script1], [BTC_VALUE * 3, lock_script0])) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 2, lock_script0) - assert 'delegated' in tx.events - tx_id = get_transaction_txid(btc_tx) - turn_round() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([random_btc_tx_id(), 1], [tx_id, 0]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - assert 'Transfer' in tx.events - turn_round(consensuses) - - -def test_staking_with_change_vout_after_transaction(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - lock_script0 = random_btc_lst_lock_script() - lock_script1 = random_btc_lst_lock_script() - btc_lst_stake.updateParam('add', lock_script1) - delegate_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script1], [BTC_VALUE * 3, lock_script0]), [], - set_op_return([accounts[0]])) - tx = btc_lst_stake.delegate(delegate_btc_tx0, 1, [], 0, lock_script1, {"from": accounts[1]}) - assert 'delegated' in tx.events - turn_round() - delegate_btc_tx1 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([get_transaction_txid(delegate_btc_tx0), 1]), - set_op_return([accounts[0]])) - tx = btc_lst_stake.delegate(delegate_btc_tx1, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - assert 'delegated' in tx.events - turn_round(consensuses) - - -def test_stake_success_with_multiple_opreturns(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), [], - set_op_return([accounts[0]], [accounts[1]]) - ) - tx = btc_lst_stake.delegate(delegate_btc_tx0, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - expect_event(tx, 'delegated', { - 'amount': BTC_VALUE, - }) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[1]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - - -def test_stake_success_with_no_validators(btc_lst_stake, stake_hub): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(round_count=3) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - - -def test_delegate_non_zero_valid_stake(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - opreturn=set_op_return([constants.ADDRESS_ZERO]) - ) - tx = btc_lst_stake.delegate(delegate_btc_tx0, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - expect_event(tx, 'delegated', { - 'amount': BTC_VALUE, - }) - assert 'Transfer' not in tx.events - turn_round(consensuses, round_count=2) - tx = stake_hub_claim_reward(constants.ADDRESS_ZERO) - assert len(tx.events) == 4 - assert tx.events['claimedBtcLstReward']['amount'] == 0 - - -@pytest.mark.skip(reason="skip gas-related tests") -def test_multi_output_gas_consumption(btc_lst_stake, stake_hub, set_candidate): - turn_round() - output = [] - out_put_count = 168 - gas_limit = 400000000 - for i in range(out_put_count): - if i < out_put_count - 1: - output.append([BTC_VALUE, REDEEM_SCRIPT]) - else: - output.append([BTC_VALUE, LOCK_SCRIPT]) - delegate_btc_tx0 = btc_delegate.build_btc_lst( - output, - opreturn=set_op_return([constants.ADDRESS_ZERO]) - ) - tx = btc_lst_stake.delegate(delegate_btc_tx0, 1, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) - assert tx.gas_used < gas_limit - - -@pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'redeem', 'transfer']) -def test_paused_state_prevents_operations(btc_lst_stake, lst_token, set_candidate, tests): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - UTXO_FEE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([random_btc_tx_id(), 0]) - ) - btc_lst_stake.updateParam('paused', 1) - if tests == 'delegate': - with brownie.reverts("Pausable: paused"): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - elif tests == 'undelegate': - with brownie.reverts("Pausable: paused"): - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - elif tests == 'redeem': - with brownie.reverts("Pausable: paused"): - redeem_btc_lst_success(accounts[0], BTC_VALUE, random_btc_lst_lock_script()) - elif tests == 'transfer': - btc_lst_stake.updateParam('paused', 0) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_lst_stake.updateParam('paused', 1) - with brownie.reverts("call lstStake.onTokenTransfer failed."): - lst_token.transfer(accounts[1], BTC_VALUE, {"from": accounts[0]}) - - -@pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'redeem', 'transfer']) -def test_successful_call_after_unpause(btc_lst_stake, lst_token, set_candidate, tests): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - UTXO_FEE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([random_btc_tx_id(), 0]) - ) - btc_lst_stake.updateParam('paused', 1) - with brownie.reverts("Pausable: paused"): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_lst_stake.updateParam('paused', 0) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - if tests == 'delegate': - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - elif tests == 'undelegate': - with brownie.reverts("input must from stake wallet."): - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - elif tests == 'redeem': - redeem_btc_lst_success(accounts[0], BTC_VALUE, random_btc_lst_lock_script()) - elif tests == 'transfer': - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - - -def test_stake_with_inactive_wallet_address(btc_lst_stake, lst_token, set_candidate): - stake_manager.add_wallet(REDEEM_SCRIPT) - stake_manager.remove_wallet(REDEEM_SCRIPT) - with brownie.reverts("wallet inactive"): - delegate_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - - -@pytest.mark.parametrize("utxo_fee", [1000, 3000, 5000, 100000]) -def test_stake_after_utxo_fee_update(btc_lst_stake, utxo_fee): - btc_amount = 6001 - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(utxo_fee)), 16) - btc_lst_stake.updateParam('utxoFee', hex_value) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], btc_amount) - if btc_amount >= utxo_fee * 2: - tx = btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - assert 'delegated' in tx.events - else: - with brownie.reverts("btc amount is too small"): - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - - -def test_lst_not_minted(btc_lst_stake): - btc_value = 10000 - btc_amount = 6000 - # BTCLST staking transactions have been generated - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], btc_amount) - failed_tx_id = get_transaction_txid(btc_tx) - valid_tx_id = delegate_btc_lst_success(accounts[1], btc_value, lock_script) - # The staking failed - user_address = random_btc_lst_lock_script() - # Scenario 1 - # input must from stake wallet. - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([btc_amount, user_address]), - set_inputs([failed_tx_id, 0]) - ) - # Because this ratio is not in the scope of our record - with brownie.reverts("input must from stake wallet."): - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - - # Scenario 2 - new_lock_script = random_btc_lst_lock_script() - stake_manager.add_wallet(new_lock_script) - # Transfer to another MultiSig wallet address - transfer_tx = btc_delegate.build_btc_lst( - set_outputs([btc_amount, new_lock_script]), - set_inputs([failed_tx_id, 0], [valid_tx_id, 0]) - ) - with brownie.reverts("should not delegate from whitelisted multisig wallets"): - btc_lst_stake.delegate(transfer_tx, 1, [], 0, new_lock_script, {"from": accounts[1]}) - - # Scenario 3 - new_lock_script = random_btc_lst_lock_script() - stake_manager.add_wallet(new_lock_script) - # Transfer to another MultiSig wallet address - # UTXOs must be picked - transfer_to_wallet_tx = btc_delegate.build_btc_lst( - set_outputs([btc_amount // 3, new_lock_script]), - set_inputs([failed_tx_id, 0]) - ) - to_wallet_tx_id = get_transaction_txid(transfer_to_wallet_tx) - tx = btc_lst_stake.delegate(transfer_to_wallet_tx, 1, [], 0, new_lock_script, {"from": accounts[1]}) - new_tx_id = delegate_btc_lst_success(accounts[1], btc_value, new_lock_script) - # MultiSig wallet B transfers money directly to the user - transfer_to_user_tx = btc_delegate.build_btc_lst( - set_outputs([btc_amount, user_address], [btc_value, new_lock_script]), - set_inputs([to_wallet_tx_id, 0], [new_tx_id, 0]) - ) - to_user_tx_id = get_transaction_txid(transfer_to_user_tx) - tx = btc_lst_stake.undelegate(transfer_to_user_tx, 1, [], 1) - __check_btc_lst_tx_map_info(to_user_tx_id, { - 'amount': btc_value, - 'outputIndex': 1, - 'blockHeight': 1, - }) - expect_event(tx, 'undelegatedOverflow', { - 'txid': to_user_tx_id, - 'outputIndex': 0, - 'expectAmount': 0, - 'actualAmount': btc_amount, - 'pkscript': user_address, - }) - - -@pytest.mark.parametrize("claim", [True, False]) -def test_view_event_after_btcLst_stake_success(btc_lst_stake, set_candidate, claim): - btc_value = 10000 - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - if claim: - tx = stake_hub_claim_reward(accounts[0]) - event_name = 'claimedBtcLstReward' - else: - tx = delegate_coin_success(operators[0], accounts[0], btc_value) - event_name = 'storedBtcLstReward' - assert tx.events[event_name]['delegator'] == accounts[0] - assert tx.events[event_name]['amount'] == TOTAL_REWARD * 3 // 2 - - -@pytest.mark.parametrize("script", ['p2sh', 'p2tr', 'p2wsh', 'p2wpkh', 'p2pkh']) -def test_extract_pk_script_address_success(btc_lst_stake, lst_token, set_candidate, script): - delegate_script = __create_btc_lst_staking_script(script_type=script) - add_hash, add_type = btc_lst_stake.mockExtractPkScriptAddr(delegate_script) - actual_hash, actual_type = btc_script.get_script_hash(delegate_script) - assert [add_hash, add_type] == [actual_hash, actual_type] - - -@pytest.mark.parametrize("script", ['p2sh', 'p2tr', 'p2wsh', 'p2wpkh', 'p2pkh']) -def test_build_pk_script_success(btc_lst_stake, lst_token, set_candidate, script): - delegate_script = __create_btc_lst_staking_script(script_type=script) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('add', delegate_script) - delegate_btc_lst_success(accounts[0], BTC_VALUE, delegate_script) - btc_lst_stake.redeem(BTC_VALUE, delegate_script) - redeem_requests = btc_lst_stake.redeemRequests(0) - pk_script = btc_lst_stake.mockBuildPkScript(redeem_requests['hash'], redeem_requests['addrType']) - assert delegate_script == pk_script - - -@pytest.mark.parametrize("script", ['p2sh', 'p2tr', 'p2wsh', 'p2wpkh', 'p2pkh']) -def test_undelegate_different_script_types(btc_lst_stake, lst_token, set_candidate, script): - delegate_script = __create_btc_lst_staking_script(script_type=script) - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_lst_stake.redeem(BTC_VALUE, delegate_script) - redeem_requests = btc_lst_stake.redeemRequests(0) - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - UTXO_FEE, delegate_script], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 0]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert 'undelegated' in tx.events - expect_event(tx, 'undelegated', { - 'pkscript': delegate_script - }) - pk_script = btc_lst_stake.mockBuildPkScript(redeem_requests['hash'], redeem_requests['addrType']) - assert delegate_script == pk_script - - -def test_lst_btc_undelegate_success(btc_lst_stake, lst_token, set_candidate): - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_lst_stake.redeem(BTC_VALUE, LOCK_SCRIPT) - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - UTXO_FEE, LOCK_SCRIPT], [BTC_VALUE * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 0]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - tx_id = get_transaction_txid(redeem_btc_tx) - expect_event(tx, 'undelegated', { - 'txid': tx_id, - 'outputIndex': 0, - 'amount': BTC_VALUE - UTXO_FEE, - 'pkscript': LOCK_SCRIPT, - }) - assert lst_token.balanceOf(accounts[0]) == 0 - assert btc_lst_stake.realtimeAmount() == 0 - - -def test_undelegate_success_with_zero_utxo_fee(btc_lst_stake, lst_token, set_candidate): - btc_lst_stake.setUtxoFee(0) - btc_tx, lock_script = __create_btc_lst_delegate(accounts[0], BTC_VALUE) - tx_id = get_transaction_txid(btc_tx) - btc_lst_stake.delegate(btc_tx, 1, [], 0, lock_script, {"from": accounts[1]}) - turn_round() - btc_lst_stake.redeem(BTC_VALUE, lock_script) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, lock_script, tx_id, 0) - assert btc_lst_stake.getRedeemRequestsLength() == 1 - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - expect_event(tx, 'undelegated', { - 'txid': get_transaction_txid(redeem_btc_tx), - 'outputIndex': 0, - 'amount': BTC_VALUE, - 'pkscript': lock_script, - }) - assert btc_lst_stake.getRedeemRequestsLength() == 0 - - -def test_revert_on_undelegate_with_unconfirmed_btc_tx(btc_lst_stake, lst_token, btc_light_client, set_candidate): - btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_light_client.setCheckResult(False, 0) - with brownie.reverts("btc tx not confirmed"): - btc_lst_stake.undelegate(btc_tx, 0, [], 0) - - -def test_undelegate_no_redeem_record_in_script(btc_lst_stake, lst_token, btc_light_client, set_candidate): - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, REDEEM_SCRIPT, tx_id) - tx = btc_lst_stake.undelegate(btc_tx, 0, [], 0) - tx_id = get_transaction_txid(btc_tx) - expect_event(tx, 'undelegatedOverflow', { - 'txid': tx_id, - 'outputIndex': 0, - 'expectAmount': 0, - 'actualAmount': BTC_VALUE, - 'pkscript': REDEEM_SCRIPT, - }) - - -def test_btc_transaction_amount_exceeds_redeem_amount(btc_lst_stake, lst_token, set_candidate): - redeem_amount = BTC_VALUE - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(redeem_amount, LOCK_SCRIPT) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, LOCK_SCRIPT, tx_id, 0) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - expect_event(tx, 'undelegatedOverflow', { - 'txid': get_transaction_txid(redeem_btc_tx), - 'outputIndex': 0, - 'expectAmount': BTC_VALUE - UTXO_FEE, - 'actualAmount': BTC_VALUE, - 'pkscript': LOCK_SCRIPT, - }) - assert 'undelegated' in tx.events - turn_round() - assert lst_token.balanceOf(accounts[0]) == 0 - - -def test_undelegate_with_multiple_redeem_info(btc_lst_stake, lst_token, set_candidate): - btc_amount0 = BTC_VALUE - btc_amount2 = BTC_VALUE * 2 - delegate_btc_lst_success(accounts[0], btc_amount0, LOCK_SCRIPT) - tx_id = delegate_btc_lst_success(accounts[1], btc_amount2 * 5, LOCK_SCRIPT) - turn_round() - scripts = [] - last_index = 0 - for t in ['p2sh', 'p2pkh', 'p2wsh', 'p2wpkh']: - scripts.append(__create_btc_lst_staking_script(script_type=t)) - for index, s in enumerate(scripts): - account = accounts[1] - if index == 0: - account = accounts[index] - btc_lst_stake.redeem(btc_amount0 + index, s, {'from': account}) - last_index = index - redeem_tx_value = btc_amount0 - UTXO_FEE - redeem_btc_tx = build_btc_lst_tx(accounts[0], redeem_tx_value + 1, scripts[1], tx_id) - assert btc_lst_stake.getRedeemRequestsLength() == 4 - for index, script in enumerate(scripts): - redeem_map_key = keccak_hash256(script) - redeem_index = btc_lst_stake.getRedeemMap(redeem_map_key) - 1 - assert redeem_index == index - _, _, amount = __get_redeem_requests(redeem_index) - assert amount == redeem_tx_value + index - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0, {'from': accounts[2]}) - redeem_index = 2 - assert btc_lst_stake.getRedeemRequestsLength() == 3 - assert btc_lst_stake.getRedeemMap(keccak_hash256(scripts[-1])) == redeem_index - script_hash, addr_type = btc_script.get_script_hash(scripts[-1]) - __check_redeem_requests(redeem_index - 1, { - 'hash': script_hash, - 'addrType': addr_type, - 'amount': redeem_tx_value + last_index, - }) - - -def test_undelegate_with_partial_redeem_amount(btc_lst_stake, lst_token, set_candidate): - btc_amount0 = BTC_VALUE - tx_id = delegate_btc_lst_success(accounts[0], btc_amount0 * 2, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(btc_amount0 * 2, REDEEM_SCRIPT) - redeem_tx_value = btc_amount0 - UTXO_FEE - redeem_btc_tx = build_btc_lst_tx(accounts[0], redeem_tx_value, REDEEM_SCRIPT, tx_id) - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0, {'from': accounts[2]}) - script_hash, addr_type = btc_script.get_script_hash(REDEEM_SCRIPT) - __check_redeem_requests(0, { - 'hash': script_hash, - 'addrType': addr_type, - 'amount': btc_amount0, - }) - - -def test_failed_undelegate_with_incorrect_redeem_script(btc_lst_stake, lst_token, set_candidate): - btc_amount0 = BTC_VALUE - tx_id = delegate_btc_lst_success(accounts[0], btc_amount0 * 2, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(btc_amount0 * 2, REDEEM_SCRIPT) - redeem_tx_value = btc_amount0 - FEE - error_redeem_script = btc_script.k2_btc_lst_script(public_key, 'p2wsh') - redeem_btc_tx = build_btc_lst_tx(accounts[0], redeem_tx_value, error_redeem_script, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0, {'from': accounts[2]}) - assert 'undelegatedOverflow' in tx.events - - -def test_undelegate_with_different_wallet_address(btc_lst_stake, lst_token, set_candidate): - btc_amount0 = BTC_VALUE - tx_id0 = delegate_btc_lst_success(accounts[0], btc_amount0 * 2, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(btc_amount0 * 2, REDEEM_SCRIPT) - redeem_tx_value = btc_amount0 - FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([redeem_tx_value, LOCK_SCRIPT], [redeem_tx_value * 3, LOCK_SCRIPT]), - set_inputs([tx_id0]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - assert 'undelegated' not in tx.events - tx_id = get_transaction_txid(redeem_btc_tx0) - __check_btc_lst_tx_map_info(tx_id, { - "amount": (BTC_VALUE - FEE) * 3, - "outputIndex": 2, - "blockHeight": 1 - }) - - -def test_single_tx_can_settle_only_once(btc_lst_stake, lst_token, set_candidate): - btc_amount0 = BTC_VALUE - tx_id = delegate_btc_lst_success(accounts[0], btc_amount0 * 2, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(btc_amount0, REDEEM_SCRIPT) - redeem_tx_value = btc_amount0 - FEE - redeem_btc_tx0 = build_btc_lst_tx(accounts[0], redeem_tx_value, REDEEM_SCRIPT, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - assert 'undelegated' in tx.events - btc_lst_stake.redeem(btc_amount0, REDEEM_SCRIPT, {'from': accounts[0]}) - with brownie.reverts("btc tx is already undelegated."): - btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - - -def test_unsuccessful_writeoff_still_counts(btc_lst_stake, lst_token, set_candidate): - btc_amount0 = BTC_VALUE - tx_id = delegate_btc_lst_success(accounts[0], btc_amount0 * 2, LOCK_SCRIPT) - turn_round() - redeem_tx_value = btc_amount0 - FEE - redeem_btc_tx0 = build_btc_lst_tx(accounts[0], redeem_tx_value, REDEEM_SCRIPT, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - assert 'undelegatedOverflow' in tx.events - btc_lst_stake.redeem(btc_amount0, REDEEM_SCRIPT, {'from': accounts[0]}) - with brownie.reverts("btc tx is already undelegated."): - btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - - -def test_tx_input_not_from_wallet_address(btc_lst_stake, lst_token, set_candidate, gov_hub): - btc_amount0 = BTC_VALUE - delegate_btc_lst_success(accounts[0], btc_amount0 * 2, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - redeem_tx_value = btc_amount0 - FEE - redeem_btc_tx0 = build_btc_lst_tx(accounts[0], redeem_tx_value, LOCK_SCRIPT) - with brownie.reverts("input must from stake wallet."): - btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - - -def test_stake_and_undelegate_with_multiple_vouts(btc_lst_stake, lst_token, set_candidate): - lock_script0 = random_btc_lst_lock_script() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = get_transaction_txid(btc_tx) - - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - undelegate_amount = BTC_VALUE - FEE - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([undelegate_amount, lock_script0], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 2]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - assert 'undelegated' in tx.events - tx_id = get_transaction_txid(redeem_btc_tx0) - __check_btc_lst_tx_map_info(tx_id, { - "amount": 0, - "outputIndex": 0, - "blockHeight": 1 - }) - - -def test_outpoint_index_not_equal_to_stake_vout_index(btc_lst_stake, lst_token, set_candidate): - lock_script0 = random_btc_lst_lock_script() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - undelegate_amount = BTC_VALUE - FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([undelegate_amount, lock_script0], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 0]), - set_op_return([accounts[0]]) - ) - with brownie.reverts("input must from stake wallet."): - btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - - -def test_undelegate_with_multiple_inputs_success(btc_lst_stake, lst_token, set_candidate): - lock_script0 = __create_btc_lst_staking_script('p2pkh') - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - undelegate_amount = BTC_VALUE - FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([undelegate_amount, lock_script0], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 0], [tx_id, 2]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - tx_id = get_transaction_txid(redeem_btc_tx0) - assert 'undelegated' in tx.events - __check_btc_lst_tx_map_info(tx_id, { - "amount": 0, - "outputIndex": 0, - "blockHeight": 1 - }) - - -def test_input_tx_id_incorrect(btc_lst_stake, lst_token, set_candidate): - lock_script0 = __create_btc_lst_staking_script('p2wpkh') - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - undelegate_amount = BTC_VALUE - FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([undelegate_amount, lock_script0], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([random_btc_tx_id(), 2], [tx_id, 2]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - tx_id = get_transaction_txid(redeem_btc_tx0) - expect_event(tx, 'undelegated', { - 'amount': undelegate_amount * 3 - }) - __check_btc_lst_tx_map_info(tx_id, { - "amount": 0, - "outputIndex": 0, - "blockHeight": 1 - }) - - -def test_single_tx_settles_multiple_redeems(btc_lst_stake, lst_token, set_candidate): - lock_script0 = random_btc_lst_lock_script() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE * 2, REDEEM_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - undelegate_amount = BTC_VALUE - UTXO_FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 0], [tx_id, 2]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - tx_id = get_transaction_txid(redeem_btc_tx0) - assert 'undelegated' in tx.events - expect_event(tx, 'undelegatedOverflow', - { - 'outputIndex': 0, - 'expectAmount': BTC_VALUE - UTXO_FEE, - 'actualAmount': BTC_VALUE, - }) - expect_event(tx, 'undelegatedOverflow', { - 'outputIndex': 2, - 'expectAmount': BTC_VALUE * 2 - UTXO_FEE, - 'actualAmount': (BTC_VALUE - UTXO_FEE) * 3 - }, idx=1) - __check_btc_lst_tx_map_info(tx_id, { - "amount": 0, - "outputIndex": 0, - "blockHeight": 1 - }) - - -def test_vout_with_two_wallet_addresses(btc_lst_stake, lst_token, set_candidate, gov_hub): - btc_lst_stake.updateParam('add', REDEEM_SCRIPT, {'from': gov_hub.address}) - lock_script0 = random_btc_lst_lock_script() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - undelegate_amount = BTC_VALUE - FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([tx_id, 2]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - tx_id = get_transaction_txid(redeem_btc_tx0) - assert 'undelegated' not in tx.events - __check_btc_lst_tx_map_info(tx_id, { - "amount": (BTC_VALUE - FEE) * 3, - "outputIndex": 1, - "blockHeight": 1 - }) - - -def test_redeem_with_two_inputs_for_two_transactions(btc_lst_stake, lst_token, set_candidate, gov_hub): - btc_lst_stake.updateParam('add', REDEEM_SCRIPT, {'from': gov_hub.address}) - lock_script0 = random_btc_lst_lock_script() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id0 = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id1 = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE * 2, REDEEM_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - undelegate_amount = BTC_VALUE - UTXO_FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [undelegate_amount * 3, REDEEM_SCRIPT]), - set_inputs([tx_id0, 2], [tx_id1, 1]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - expect_event(tx, 'undelegatedOverflow', - { - 'outputIndex': 0, - 'expectAmount': BTC_VALUE - UTXO_FEE, - 'actualAmount': BTC_VALUE, - }) - expect_event(tx, 'undelegatedOverflow', { - 'outputIndex': 2, - 'expectAmount': BTC_VALUE * 2 - UTXO_FEE, - 'actualAmount': (BTC_VALUE - UTXO_FEE) * 3 - }, idx=1) - tx_id = get_transaction_txid(redeem_btc_tx0) - __check_btc_lst_tx_map_info(tx_id, { - "amount": 0, - "outputIndex": 0, - "blockHeight": 1 - }) - - -def test_undelegate_with_change_address_utxo(btc_lst_stake, lst_token, set_candidate, gov_hub): - btc_lst_stake.updateParam('add', REDEEM_SCRIPT, {'from': gov_hub.address}) - lock_script0 = random_btc_lst_lock_script() - btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, lock_script0], [BTC_VALUE * 3, LOCK_SCRIPT]), - [], - set_op_return([accounts[0]]) - ) - tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.delegate(btc_tx, 1, [], 0, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT) - undelegate_amount = BTC_VALUE - UTXO_FEE - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT], [undelegate_amount * 3, LOCK_SCRIPT]), - set_inputs([tx_id, 2]) - ) - redeem_tx_id = btc_delegate.get_btc_tx_id() - btc_lst_stake.undelegate(redeem_btc_tx0, 2, [], 0, {'from': accounts[2]}) - __check_btc_lst_tx_map_info(redeem_tx_id, { - "amount": (BTC_VALUE - UTXO_FEE) * 3, - "outputIndex": 1, - "blockHeight": 2 - }) - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT) - redeem_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT]), - set_inputs([redeem_tx_id, 1]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx0, 1, [], 0, {'from': accounts[2]}) - expect_event(tx, 'undelegatedOverflow', { - 'outputIndex': 0, - 'expectAmount': BTC_VALUE - UTXO_FEE - BTC_VALUE // 2, - 'actualAmount': BTC_VALUE - }) - - -def test_undelegate_non_staked_utxo(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - address0 = random_btc_lst_lock_script() - transfer_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE + 1, address0], [BTC_VALUE + 2, LOCK_SCRIPT]) - ) - tx_id = get_transaction_txid(transfer_btc_tx) - btc_lst_stake.delegate(transfer_btc_tx, 1, [], 2, LOCK_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - turn_round() - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT], [BTC_VALUE * 3, address0]), - set_inputs([tx_id, 1]), - set_op_return([accounts[0]])) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 1, [], 0, {'from': accounts[2]}) - expect_event(tx, 'undelegated', { - 'outputIndex': 0, - 'amount': BTC_VALUE, - 'pkscript': REDEEM_SCRIPT, - }) - turn_round(consensuses) - - -def test_undelegate_non_staked_utxo_different_vouts(btc_lst_stake, stake_hub, set_candidate): - operators, consensuses = set_candidate - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - address0 = random_btc_lst_lock_script() - transfer_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE + 1, LOCK_SCRIPT], [BTC_VALUE + 2, address0]), - opreturn=set_op_return([constants.ADDRESS_ZERO]) - ) - tx_id = get_transaction_txid(transfer_btc_tx) - btc_lst_stake.delegate(transfer_btc_tx, 1, [], 2, LOCK_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - turn_round() - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT], [BTC_VALUE * 3, address0]), - set_inputs([tx_id, 0]), - set_op_return([accounts[0]]) - ) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 1, [], 0, {'from': accounts[2]}) - assert 'undelegated' in tx.events - turn_round(consensuses) - - -def test_multiple_inputs_with_non_staked_utxo_undelegate(btc_lst_stake, set_candidate): - operators, consensuses = set_candidate - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - address0 = random_btc_lst_lock_script() - transfer_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE + 1, address0], [BTC_VALUE + 2, LOCK_SCRIPT]) - ) - tx_id = get_transaction_txid(transfer_btc_tx) - btc_lst_stake.delegate(transfer_btc_tx, 1, [], 2, LOCK_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - turn_round() - redeem_btc_tx = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT], [BTC_VALUE * 3, address0]), - set_inputs([random_btc_tx_id(), 1], [tx_id, 1]), - set_op_return([accounts[0]])) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 1, [], 0, {'from': accounts[2]}) - assert 'undelegated' in tx.events - turn_round(consensuses) - - -def test_handle_tx_out_success(btc_lst_stake, set_candidate): - operators, consensuses = set_candidate - lock_script0 = random_btc_lst_lock_script() - transfer_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - 1, lock_script0], [BTC_VALUE, LOCK_SCRIPT]) - ) - transfer_btc_tx1 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - 1, LOCK_SCRIPT], [BTC_VALUE, lock_script0]) - ) - tx_id0 = get_transaction_txid(transfer_btc_tx0) - tx_id1 = get_transaction_txid(transfer_btc_tx1) - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, LOCK_SCRIPT) - btc_lst_stake.delegate(transfer_btc_tx1, 2, [], 4, LOCK_SCRIPT) - __check_btc_lst_tx_map_info(tx_id0, { - 'amount': BTC_VALUE, - 'outputIndex': 1, - 'blockHeight': 1, }) - __check_btc_lst_tx_map_info(tx_id1, { - 'amount': BTC_VALUE - 1, - 'outputIndex': 0, - 'blockHeight': 2, - }) - turn_round(consensuses) - - -def test_revert_when_tx_lacks_wallet_script(btc_lst_stake, set_candidate): - operators, consensuses = set_candidate - lock_script0 = random_btc_lst_lock_script() - lock_script1 = random_btc_lst_lock_script() - transfer_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - 1, lock_script1], [BTC_VALUE, lock_script0])) - get_transaction_txid(transfer_btc_tx0) - with brownie.reverts("staked value is zero"): - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, LOCK_SCRIPT) - turn_round(consensuses) - - -def test_revert_when_handle_tx_out_with_non_wallet_script(btc_lst_stake, set_candidate): - lock_script0 = random_btc_lst_lock_script() - lock_script1 = random_btc_lst_lock_script() - transfer_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE - 1, lock_script1], [BTC_VALUE, lock_script0])) - with brownie.reverts("Wallet not found"): - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, lock_script0) - - -def test_transfer_to_two_wallet_addresses(btc_lst_stake, set_candidate): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - lock_script0 = random_btc_lst_lock_script() - lock_script1 = random_btc_lst_lock_script() - btc_lst_stake.updateParam('add', lock_script0) - transfer_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 2, lock_script0], [BTC_VALUE * 3, lock_script1]) - ) - tx_id = get_transaction_txid(transfer_btc_tx0) - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, LOCK_SCRIPT) - __check_btc_lst_tx_map_info(tx_id, { - 'amount': BTC_VALUE, - 'outputIndex': 0 - }) - with brownie.reverts("btc tx is already delegated."): - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, lock_script0) - - -def test_vout_pair_with_identical_script(btc_lst_stake, set_candidate): - operators, consensuses = set_candidate - transfer_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, LOCK_SCRIPT], [BTC_VALUE * 2, LOCK_SCRIPT], [BTC_VALUE * 3, LOCK_SCRIPT]) - ) - tx_id = get_transaction_txid(transfer_btc_tx0) - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, LOCK_SCRIPT) - __check_btc_lst_tx_map_info(tx_id, { - 'amount': BTC_VALUE * 3, - 'outputIndex': 2 - }) - turn_round(consensuses) - - -def test_handle_tx_out_unconfirmed_transaction_revert(btc_lst_stake, btc_light_client, set_candidate): - transfer_btc_tx0 = btc_delegate.build_btc_lst(set_outputs([BTC_VALUE, LOCK_SCRIPT])) - btc_light_client.setCheckResult(False, 0) - with brownie.reverts("btc tx isn't confirmed"): - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, LOCK_SCRIPT) - - -@pytest.mark.parametrize("btc_amount", [1, 100, 1200, 13000, 1e8, 10e8]) -def test_stake_no_opreturn_different_amount(btc_lst_stake, set_candidate, btc_amount): - operators, consensuses = set_candidate - transfer_btc_tx0 = btc_delegate.build_btc_lst( - set_outputs([BTC_VALUE, REDEEM_SCRIPT], [BTC_VALUE * 2, REDEEM_SCRIPT], [int(btc_amount), LOCK_SCRIPT]) - ) - tx_id = get_transaction_txid(transfer_btc_tx0) - btc_lst_stake.delegate(transfer_btc_tx0, 1, [], 3, LOCK_SCRIPT) - __check_btc_lst_tx_map_info(tx_id, { - 'amount': int(btc_amount), - 'outputIndex': 2 - }) - turn_round(consensuses) - - -def test_distribute_reward_success(btc_lst_stake, btc_agent): - validators = accounts[:3] - reward_list = [1000, 20000, 30000] - stake_amount = 10000 - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - round_tag = get_current_round() - round_tag += 1 - btc_lst_stake.setRoundTag(round_tag) - btc_lst_stake.setStakedAmount(stake_amount) - btc_lst_stake.distributeReward(validators, reward_list) - sum_reward = sum(reward_list) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag) - round_reward = sum_reward * Utils.BTC_DECIMAL // stake_amount - assert accrued_reward == round_reward - btc_lst_stake.setRoundTag(round_tag + 1) - tx = btc_lst_stake.distributeReward(validators, reward_list) - expect_event(tx, 'rewardUpdated', { - 'round': round_tag + 1, - 'value': round_reward * 2, - }) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag + 1) - assert accrued_reward == round_reward * 2 - - -def test_distribute_reward_with_no_stakes(btc_lst_stake, btc_agent): - validators = accounts[:3] - reward_list = [1000, 20000, 30000] - history_amount = 5000 - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - round_tag = get_current_round() - round_tag += 2 - btc_lst_stake.setRoundTag(round_tag) - btc_lst_stake.setAccruedRewardPerBTCMap(round_tag - 1, history_amount) - btc_lst_stake.distributeReward(validators, reward_list) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag) - assert accrued_reward == history_amount - - -def test_no_reward_in_init_round(btc_lst_stake, btc_agent): - validators = accounts[:3] - reward_list = [1000, 20000, 30000] - stake_amount = 10000 - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - round_tag = get_current_round() - round_tag += 1 - btc_lst_stake.setRoundTag(round_tag) - btc_lst_stake.setStakedAmount(stake_amount) - btc_lst_stake.setAccruedRewardPerBTCMap(round_tag - 1, 5000) - btc_lst_stake.distributeReward(validators, reward_list) - sum_reward = sum(reward_list) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag) - round_reward = sum_reward * Utils.BTC_DECIMAL // stake_amount - assert accrued_reward == round_reward - btc_lst_stake.setRoundTag(round_tag + 1) - btc_lst_stake.distributeReward(validators, reward_list) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag + 1) - assert accrued_reward == round_reward * 2 - - -def test_no_reward_generated_in_current_round(btc_lst_stake, btc_agent): - validators = accounts[:3] - reward_list = [1000, 20000, 30000] - stake_amount = 10000 - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - round_tag = get_current_round() - round_tag += 10 - btc_lst_stake.setRoundTag(round_tag) - btc_lst_stake.setStakedAmount(stake_amount) - history_reward0 = 50000 - btc_lst_stake.setAccruedRewardPerBTCMap(round_tag - 2, history_reward0) - btc_lst_stake.setAccruedRewardPerBTCMap(round_tag - 3, history_reward0 * 10) - btc_lst_stake.distributeReward(validators, reward_list) - sum_reward = sum(reward_list) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag) - round_reward = sum_reward * Utils.BTC_DECIMAL // stake_amount - assert accrued_reward == round_reward + history_reward0 - btc_lst_stake.setRoundTag(round_tag + 1) - btc_lst_stake.distributeReward(validators, reward_list) - accrued_reward = btc_lst_stake.getAccruedRewardPerBTCMap(round_tag + 1) - assert accrued_reward == round_reward * 2 + history_reward0 - - -def test_distribute_reward_only_btc_agent_can_call(btc_lst_stake, btc_agent): - validators = accounts[:3] - reward_list = [1000, 20000, 30000] - with brownie.reverts("the msg sender must be bitcoin agent contract"): - btc_lst_stake.distributeReward(validators, reward_list) - - -def test_get_btc_lst_stake_amounts_success(btc_agent, btc_stake, btc_lst_stake, set_candidate): - lst_amount = 6000 - operators, consensuses = set_candidate - turn_round() - btc_lst_stake.setRealtimeAmount(lst_amount) - amounts = btc_lst_stake.getStakeAmounts(operators) - lst_validator_amount = lst_amount // 3 - assert amounts == [lst_validator_amount, lst_validator_amount, lst_validator_amount] - - -def test_no_validators_on_stake_amount_query(btc_agent, btc_stake, btc_lst_stake, set_candidate): - lst_amount = 6000 - turn_round() - btc_lst_stake.setRealtimeAmount(lst_amount) - amounts = btc_lst_stake.getStakeAmounts([]) - assert amounts == () - - -def test_inactive_validators_query_stake_amount(btc_agent, btc_stake, btc_lst_stake, set_candidate): - lst_amount = 6000 - operators, consensuses = set_candidate - operators.append(accounts[0]) - operators.append(accounts[1]) - turn_round() - btc_lst_stake.setRealtimeAmount(lst_amount) - amounts = btc_lst_stake.getStakeAmounts(operators) - lst_validator_amount = lst_amount // 3 - assert amounts == [lst_validator_amount, lst_validator_amount, lst_validator_amount, 0, 0] - amounts = btc_lst_stake.getStakeAmounts(accounts[:3]) - assert amounts == [0, 0, 0] - - -def test_set_btc_lst_new_round_success(btc_agent, btc_stake, btc_lst_stake, set_candidate): - staked_amount = 2200 - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - btc_lst_stake.setRealtimeAmount(staked_amount) - round_tag = 10 - btc_lst_stake.setNewRound([], round_tag) - assert btc_lst_stake.stakedAmount() == staked_amount - assert btc_lst_stake.roundTag() == round_tag - - -def test_set_new_round_only_btc_agent_can_call(btc_agent, btc_stake, btc_lst_stake, set_candidate): - round_tag = 10 - with brownie.reverts("the msg sender must be bitcoin agent contract"): - btc_lst_stake.setNewRound([], round_tag) - - -def test_btc_lst_claim_reward_success(btc_agent, btc_stake, btc_lst_stake, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - return_value = btc_lst_stake.claimReward(accounts[0], 0, False).return_value - claimed_reward = TOTAL_REWARD * 3 - unclaimed_reward = 0 - assert return_value == [claimed_reward, unclaimed_reward, BTC_VALUE] - assert btc_lst_stake.rewardMap(accounts[0]) == [0, 0] - - -def test_clear_reward_map(btc_agent, btc_stake, btc_lst_stake): - turn_round() - btc_lst_stake.setBtcLstRewardMap(accounts[0], 0, 1000) - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - btc_lst_stake.claimReward(accounts[0], 0, False) - assert btc_lst_stake.rewardMap(accounts[0]) == [0, 0] - - -def test_lst_claim_reward_only_btc_agent_can_call(btc_agent, btc_stake, btc_lst_stake, set_candidate): - with brownie.reverts("the msg sender must be bitcoin agent contract"): - btc_lst_stake.claimReward(accounts[0], 0, False) - - -@pytest.mark.parametrize('round_count', [0, 1]) -@pytest.mark.parametrize("tests", [ - [6000, 'delegate', 'transfer', 'claim'], - [2000, 'transfer', 'transfer', 'claim'], - [2000, 'transfer', 'redeem', 'delegate'], - [6000, 'delegate', 'redeem', 'claim'], - [4000, 'delegate', 'redeem', 'transfer', 'claim'], - [2000, 'redeem', 'redeem', 'delegate'], -]) -def test_get_btc_lst_acc_stake_amount_success(btc_lst_stake, btc_agent, set_candidate, round_count, tests): - operators, consensuses = set_candidate - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - for i in tests: - if i == 'delegate': - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - elif i == 'transfer': - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - elif i == 'redeem': - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - elif i == 'claim': - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses, round_count=round_count) - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_lst_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - expect_stake_amount = tests[0] - if round_count == 0: - expect_stake_amount = 0 - assert acc_staked_amount == expect_stake_amount - - -@pytest.mark.parametrize("tests", [ - [12000, 'delegate', 'transfer', 'claim'], - [14000, 'delegate', 'delegate', 'transfer'], - [0, 'transfer', 'transfer', 'redeem'], - [8000, 'delegate', 'redeem', 'transfer', 'claim'], - [6000, 'redeem', 'redeem', 'delegate'], -]) -def test_multi_round_btc_lst_acc_amount(btc_lst_stake, btc_agent, set_candidate, tests): - operators, consensuses = set_candidate - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - for i in tests: - if i == 'delegate': - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - elif i == 'transfer': - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - elif i == 'redeem': - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - elif i == 'claim': - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_lst_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - expect_stake_amount = tests[0] - assert acc_staked_amount == expect_stake_amount - - -def test_check_acc_stake_amount_after_btc_lst_redeem(btc_lst_stake, btc_agent, set_candidate): - operators, consensuses = set_candidate - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - turn_round(consensuses, round_count=3) - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_lst_stake.claimReward(accounts[0], 0, False).return_value - assert acc_staked_amount == 0 - - -@pytest.mark.parametrize('paused', [0, 1]) -def test_claim_reward_when_paused(btc_lst_stake, btc_agent, set_candidate, paused): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - actual_reward = TOTAL_REWARD * 3 - if paused: - btc_lst_stake.updateParam('paused', paused) - actual_reward = 0 - update_system_contract_address(btc_lst_stake, btc_agent=accounts[0]) - tx = btc_lst_stake.claimReward(accounts[0], 0, False) - reward, _, _ = tx.return_value - assert reward == actual_reward - - -def test_p2sh_lock_script_with_p2sh_redeem_script(btc_lst_stake, lst_token, set_candidate): - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE - assert btc_lst_stake.realtimeAmount() == BTC_VALUE - redeem_script = __create_btc_lst_staking_script(script_type='p2sh') - tx = btc_lst_stake.redeem(BTC_VALUE, redeem_script) - expect_event(tx, 'Transfer', { - 'from': accounts[0], - 'value': BTC_VALUE - }) - expect_event(tx, 'redeemed', { - 'delegator': accounts[0], - 'utxoFee': UTXO_FEE, - 'amount': BTC_VALUE - UTXO_FEE, - 'pkscript': redeem_script - }) - script_hash, addr_type = btc_script.get_script_hash(redeem_script) - __check_redeem_requests(0, { - 'hash': script_hash, - 'addrType': addr_type, - 'amount': BTC_VALUE - UTXO_FEE, - - }) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE - UTXO_FEE, redeem_script, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert 'undelegated' in tx.events - assert lst_token.balanceOf(accounts[0]) == 0 - assert btc_lst_stake.realtimeAmount() == 0 - - -def test_p2tr_lock_script_with_p2wsh_redeem_script(btc_lst_stake, lst_token): - delegate_lock_script = __create_btc_lst_staking_script(script_type='p2tr') - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('add', delegate_lock_script) - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, delegate_lock_script) - turn_round() - # Lock script is P2TR, redeem script is P2WSH - redeem_script = __create_btc_lst_staking_script(script_type='p2wsh') - tx = btc_lst_stake.redeem(BTC_VALUE, redeem_script) - expect_event(tx, 'redeemed', { - 'pkscript': redeem_script - }) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE - FEE, redeem_script, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert 'undelegated' in tx.events - assert lst_token.balanceOf(accounts[0]) == 0 - assert btc_lst_stake.getRedeemRequestsLength() == 0 - - -def test_p2sh_lock_script_with_p2tr_redeem_script(btc_lst_stake, lst_token): - delegate_lock_script = __create_btc_lst_staking_script(script_type='p2sh') - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('add', delegate_lock_script) - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, delegate_lock_script) - turn_round() - # Lock script is P2SH, redeem script is P2TR - redeem_script = __create_btc_lst_staking_script(script_type='p2tr') - tx = redeem_btc_lst_success(accounts[0], BTC_VALUE, redeem_script) - expect_event(tx, 'redeemed', { - 'pkscript': redeem_script - }) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE - FEE, redeem_script, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert 'undelegated' in tx.events - assert lst_token.balanceOf(accounts[0]) == 0 - assert btc_lst_stake.getRedeemRequestsLength() == 0 - assert btc_lst_stake.realtimeAmount() == 0 - - -def test_p2pkh_lock_script_with_p2wpkh_redeem_script(btc_lst_stake, lst_token): - delegate_script = __create_btc_lst_staking_script(script_type='p2pkh') - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('add', delegate_script) - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, delegate_script) - turn_round() - # Lock script is P2PKH, redeem script is P2WPKH - redeem_script = __create_btc_lst_staking_script(script_type='p2wpkh') - tx = redeem_btc_lst_success(accounts[0], BTC_VALUE, redeem_script) - expect_event(tx, 'redeemed', { - 'pkscript': redeem_script - }) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE - FEE, redeem_script, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert 'undelegated' in tx.events - assert lst_token.balanceOf(accounts[0]) == 0 - assert btc_lst_stake.getRedeemRequestsLength() == 0 - - -def test_p2wsh_lock_script_with_p2pkh_redeem_script(btc_lst_stake, lst_token): - delegate_script = __create_btc_lst_staking_script(script_type='p2wsh') - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('add', delegate_script) - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, delegate_script) - turn_round() - # Lock script is P2WSH, redeem script is P2PKH - redeem_script = __create_btc_lst_staking_script(script_type='p2pkh') - tx = btc_lst_stake.redeem(0, redeem_script) - expect_event(tx, 'redeemed', { - 'pkscript': redeem_script, - 'amount': BTC_VALUE - UTXO_FEE - }) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE - UTXO_FEE, redeem_script, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert 'undelegated' in tx.events - assert lst_token.balanceOf(accounts[0]) == 0 - assert btc_lst_stake.getRedeemRequestsLength() == 0 - - -@pytest.mark.parametrize("redeem_script", [ - pytest.param('0xa924047b9ba09367c1b213b5ba2184fba3fababcdc0287', id="p2sh format error"), - pytest.param('0x76a914047b9ba09367c1b213b5ba2184fba3fababcdc0288aa', id="p2pkh format error"), - pytest.param('0x0013047b9ba09367c1b213b5ba2184fba3fababcdc02', id="p2wpkh format error"), - pytest.param('0x00223d1b55fe81a7ca9718d8c909876b74550782a5a748f49b6c2e519de73342cb16', id="p2wsh format error"), - pytest.param('0x51303d1b55fe81a7ca9718d8c909876b74550782a5a748f49b6c2e519de73342cb16', id="p2tr format error")]) -def test_redeem_failed_due_to_invalid_pkscript(btc_lst_stake, lst_token, set_candidate, redeem_script): - with brownie.reverts("invalid pkscript"): - btc_lst_stake.redeem(BTC_VALUE, redeem_script) - - -@pytest.mark.parametrize("redeem_amount", [100, 199, 200, 201]) -def test_revert_on_redeem_amount_too_small(btc_lst_stake, lst_token, redeem_amount, set_candidate): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - if redeem_amount < UTXO_FEE * 2: - with brownie.reverts("The redeem amount is too small"): - btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - else: - tx = btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - - -def test_revert_on_redeem_amount_exceeding_stake(btc_lst_stake, lst_token, set_candidate): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - with brownie.reverts("Not enough btc token"): - btc_lst_stake.redeem(BTC_VALUE + 1, REDEEM_SCRIPT) - - -def test_default_full_redeem_when_amount_is_zero(btc_lst_stake, lst_token, set_candidate): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - tx = btc_lst_stake.redeem(0, REDEEM_SCRIPT) - assert tx.events['Transfer']['value'] == BTC_VALUE - - -def test_multiple_redeems_with_same_pkscript(btc_lst_stake, lst_token, set_candidate): - delegate_amount = BTC_VALUE * 5 - delegate_btc_lst_success(accounts[0], delegate_amount, LOCK_SCRIPT) - turn_round() - assert btc_lst_stake.userStakeInfo(accounts[0])[1] == delegate_amount - btc_lst_stake.redeem(BTC_VALUE, REDEEM_SCRIPT) - redeem_amount = BTC_VALUE - UTXO_FEE - assert btc_lst_stake.getRedeemMap(keccak_hash256(REDEEM_SCRIPT)) == 1 - script_hash, addr_type = btc_script.get_script_hash(REDEEM_SCRIPT) - __check_redeem_requests(0, { - 'hash': script_hash, - 'addrType': addr_type, - 'amount': redeem_amount - }) - btc_lst_stake.redeem(BTC_VALUE * 2, REDEEM_SCRIPT) - __check_redeem_requests(0, { - 'hash': script_hash, - 'addrType': addr_type, - 'amount': redeem_amount + BTC_VALUE * 2 - UTXO_FEE - }) - realtime_amount = delegate_amount - BTC_VALUE * 3 - assert btc_lst_stake.userStakeInfo(accounts[0])[1] == realtime_amount - assert btc_lst_stake.userStakeInfo(accounts[0])[2] == realtime_amount - tx = btc_lst_stake.redeem(BTC_VALUE, REDEEM_SCRIPT) - assert tx.events['Transfer']['value'] == BTC_VALUE - - -def test_no_rewards_generated_after_redeem(btc_lst_stake, lst_token, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(0, REDEEM_SCRIPT) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - tx = stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - assert 'claimedReward' not in tx.events - - -def test_partial_redeem_btc_stake_success(btc_lst_stake, lst_token, set_candidate): - operators, consensuses = set_candidate - turn_round() - tx_id = delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - btc_lst_stake.redeem(BTC_VALUE // 2, REDEEM_SCRIPT) - redeem_btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT, tx_id) - tx = btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - expect_event(tx, 'undelegated', { - 'outputIndex': 0, - 'amount': BTC_VALUE // 2, - 'pkscript': REDEEM_SCRIPT, - }) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 3 // 4 - FEE - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - - -def test_redeem_without_pledge_reverts(btc_lst_stake, lst_token, set_candidate): - delegate_btc_lst_success(accounts[1], BTC_VALUE, LOCK_SCRIPT) - with brownie.reverts("Not enough btc token"): - btc_lst_stake.redeem(BTC_VALUE // 2, REDEEM_SCRIPT) - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -@pytest.mark.parametrize("redeem_value", [0, 1, -1]) -def test_btc_redeem_exceeds_single_limit(btc_lst_stake, lst_token, set_candidate, redeem_value): - utxo_fee = 100 - btc_value = 15e8 - redeem_amount = 10e8 - delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - if redeem_value > 0: - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(redeem_amount + utxo_fee + redeem_value, REDEEM_SCRIPT) - else: - tx = btc_lst_stake.redeem(redeem_amount + utxo_fee + redeem_value, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -def test_btc_redeem_in_multiple_parts(btc_lst_stake, lst_token, set_candidate): - btc_value = 15e8 - redeem_amount = 2e8 - delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - for i in range(5): - tx = btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -@pytest.mark.parametrize("redeem_value", [9e8, 10e8, 11e8, 14e8]) -def test_redeem_exceeds_after_multiple_stakes(btc_lst_stake, lst_token, set_candidate, redeem_value): - btc_value = 3e8 - for i in range(5): - delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - if redeem_value > 10e8: - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(redeem_value, REDEEM_SCRIPT) - else: - tx = btc_lst_stake.redeem(redeem_value, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -def test_exceed_redeem_after_multiple_stakes_and_redeems(btc_lst_stake, lst_token, set_candidate): - btc_value = 3e8 - redeem_amount = 2e8 - for i in range(5): - delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - for i in range(5): - tx = btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -@pytest.mark.parametrize("part", [True, False]) -def test_recalculate_total_redeem_amount_after_undelegate(btc_lst_stake, part): - btc_value = 5e8 - redeem_amount = 2e8 - uxto_fee = 100 - tx_id = None - for i in range(5): - tx_id = delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - for i in range(5): - tx = btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - - redeemed = int(redeem_amount * 5) - (uxto_fee * 5) - __check_redeem_requests(0, { - 'amount': redeemed - }) - if part: - redeem_btc_tx = build_btc_lst_tx(accounts[0], int(redeem_amount), REDEEM_SCRIPT, tx_id, 0) - redeemed -= redeem_amount - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - __check_redeem_requests(0, { - 'amount': redeemed - }) - else: - redeem_btc_tx = build_btc_lst_tx(accounts[0], int(redeem_amount * 5), REDEEM_SCRIPT, tx_id, 0) - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - assert btc_lst_stake.getRedeemRequestsLength() == 0 - btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - if part: - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - else: - tx = btc_lst_stake.redeem(redeem_amount, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(9e8, REDEEM_SCRIPT) - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -def test_full_clear_after_undelegate(btc_lst_stake): - btc_value = 20e8 - redeem_amount0 = 2e8 - redeem_amount1 = 10e8 - tx_id = delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - btc_lst_stake.redeem(redeem_amount0, REDEEM_SCRIPT) - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(redeem_amount1, REDEEM_SCRIPT) - redeem_btc_tx = build_btc_lst_tx(accounts[0], redeem_amount0, REDEEM_SCRIPT, tx_id, 0) - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - tx = btc_lst_stake.redeem(redeem_amount1, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -@pytest.mark.parametrize("burn_btc_limit", [1000, 1e6, 20e8, 100e8]) -def test_redeem_after_modifying_btc_limit(btc_lst_stake, burn_btc_limit): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(burn_btc_limit)), 64) - btc_lst_stake.updateParam('burnBTCLimit', hex_value) - btc_value = 210e8 - tx_id = delegate_btc_lst_success(accounts[0], btc_value, LOCK_SCRIPT) - btc_lst_stake.redeem(burn_btc_limit + UTXO_FEE, REDEEM_SCRIPT) - with brownie.reverts("The cumulative burn amount has reached the upper limit"): - btc_lst_stake.redeem(burn_btc_limit, REDEEM_SCRIPT) - redeem_btc_tx = build_btc_lst_tx(accounts[0], btc_value, REDEEM_SCRIPT, tx_id, 0) - btc_lst_stake.undelegate(redeem_btc_tx, 0, [], 0) - tx = btc_lst_stake.redeem(burn_btc_limit + UTXO_FEE, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - - -def test_on_token_transfer_success(btc_agent, btc_lst_stake, lst_token, set_candidate): - btc_amount = BTC_VALUE * 2 - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], btc_amount, LOCK_SCRIPT, percentage=Utils.DENOMINATOR) - turn_round() - turn_round(consensuses) - update_system_contract_address(btc_lst_stake, lst_token=accounts[0]) - __check_user_stake_info(accounts[0], { - 'realtimeAmount': BTC_VALUE * 2, - 'stakedAmount': 0 - }) - btc_lst_stake.onTokenTransfer(accounts[0], accounts[1], btc_amount // 2) - __check_user_stake_info(accounts[0], { - 'realtimeAmount': BTC_VALUE, - 'stakedAmount': BTC_VALUE - }) - __check_user_stake_info(accounts[1], { - 'realtimeAmount': BTC_VALUE, - 'stakedAmount': 0 - }) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[0]) - stake_hub_claim_reward(accounts[1]) - assert tracker0.delta() == TOTAL_REWARD * 3 - FEE - assert tracker1.delta() == 0 - turn_round(consensuses) - __check_user_stake_info(accounts[1], { - 'realtimeAmount': BTC_VALUE, - 'stakedAmount': 0 - }) - stake_hub_claim_reward(accounts[0]) - stake_hub_claim_reward(accounts[1]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - assert tracker1.delta() == 0 - turn_round(consensuses) - __check_user_stake_info(accounts[1], { - 'realtimeAmount': BTC_VALUE, - 'stakedAmount': BTC_VALUE - }) - stake_hub_claim_reward(accounts[0]) - stake_hub_claim_reward(accounts[1]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - assert tracker1.delta() == TOTAL_REWARD * 3 // 2 - - -def test_only_lst_token_can_call_on_token_transfer(btc_lst_stake, lst_token, set_candidate): - with brownie.reverts("only btc lst token can call this function"): - btc_lst_stake.onTokenTransfer(accounts[0], accounts[1], BTC_VALUE) - - -def test_revert_on_lst_token_transfer_with_zero_from_address(btc_lst_stake, lst_token, set_candidate): - update_system_contract_address(btc_lst_stake, lst_token=accounts[0]) - with brownie.reverts("invalid sender"): - btc_lst_stake.onTokenTransfer(constants.ADDRESS_ZERO, accounts[1], BTC_VALUE) - - -def test_revert_on_lst_token_transfer_with_zero_to_address(btc_lst_stake, lst_token, set_candidate): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - update_system_contract_address(btc_lst_stake, lst_token=accounts[0]) - with brownie.reverts("invalid receiver"): - btc_lst_stake.onTokenTransfer(accounts[0], constants.ADDRESS_ZERO, BTC_VALUE) - - -def test_lst_token_transfer_amount_exceeds_balance(btc_lst_stake, lst_token, set_candidate): - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - update_system_contract_address(btc_lst_stake, lst_token=accounts[0]) - with brownie.reverts("Insufficient balance"): - btc_lst_stake.onTokenTransfer(accounts[0], constants.ADDRESS_ZERO, BTC_VALUE + 1) - - -def test_get_wallets_success(btc_lst_stake): - stake_manager.add_wallet(REDEEM_SCRIPT) - hash1, type1 = BtcScript.get_script_hash(LOCK_SCRIPT) - hash2, type2 = BtcScript.get_script_hash(REDEEM_SCRIPT) - assert btc_lst_stake.getWallets() == [[hash1, type1, 1], [hash2, type2, 1]] - - -@pytest.mark.parametrize("script", ['p2sh', 'p2pkh', 'p2wpkh', 'p2wsh', 'p2tr']) -def test_add_wallet_success(btc_lst_stake, script): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - script = __create_btc_lst_staking_script(script) - btc_lst_stake.updateParam('add', script) - wallet_index = btc_lst_stake.getWalletMap(keccak_hash256(script)) - wallets = btc_lst_stake.wallets(wallet_index - 1) - assert wallets[0] == btc_script.get_script_hash(script)[0] - assert wallets[1] == btc_script.get_script_hash(script)[1] - assert wallets[2] == 1 - - -def test_duplicate_add_wallet_calls(btc_lst_stake): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - script_info = btc_script.get_script_hash(LOCK_SCRIPT) - assert btc_lst_stake.wallets(0) == [script_info[0], script_info[1], 1] - tx = btc_lst_stake.updateParam('add', LOCK_SCRIPT) - expect_event(tx, 'addedWallet', { - '_hash': script_info[0], - '_type': script_info[1] - }) - assert btc_lst_stake.wallets(0) == [script_info[0], script_info[1], 1] - - -@pytest.mark.parametrize("script", [ - pytest.param('0xa924047b9ba09367c1b213b5ba2184fba3fababcdc0287', id="p2sh format error"), - pytest.param('0x76a914047b9ba09367c1b213b5ba2184fba3fababcdc0288aa', id="p2pkh format error"), - pytest.param('0x0013047b9ba09367c1b213b5ba2184fba3fababcdc02', id="p2wpkh format error"), - pytest.param('0x00223d1b55fe81a7ca9718d8c909876b74550782a5a748f49b6c2e519de73342cb16', id="p2wsh format error"), - pytest.param('0x51303d1b55fe81a7ca9718d8c909876b74550782a5a748f49b6c2e519de73342cb16', id="p2tr format error")]) -def test_revert_on_invalid_btc_wallet(btc_lst_stake, script): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - with brownie.reverts("Invalid BTC wallet"): - btc_lst_stake.updateParam('add', script) - - -def test_re_add_deleted_btc_wallet(btc_lst_stake): - stake_manager.add_wallet(REDEEM_SCRIPT) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('remove', LOCK_SCRIPT) - wallets = btc_lst_stake.wallets(0) - assert wallets[2] == 2 - btc_lst_stake.updateParam('add', LOCK_SCRIPT) - wallets = btc_lst_stake.wallets(0) - assert wallets[2] == 1 - btc_lst_stake.updateParam('remove', REDEEM_SCRIPT) - wallets = btc_lst_stake.wallets(1) - assert wallets[2] == 2 - btc_lst_stake.updateParam('add', REDEEM_SCRIPT) - wallets = btc_lst_stake.wallets(1) - assert wallets[2] == 1 - - -@pytest.mark.parametrize("script", ['p2sh', 'p2pkh', 'p2wpkh', 'p2wsh', 'p2tr']) -def test_remove_wallet(btc_lst_stake, script): - if script == 'p2sh': - stake_manager.add_wallet(REDEEM_SCRIPT) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - stake_script = __create_btc_lst_staking_script(script) - btc_lst_stake.updateParam('add', stake_script) - wallet_index = btc_lst_stake.getWalletMap(keccak_hash256(stake_script)) - actual_wallet_index = 2 - if script == 'p2sh': - actual_wallet_index = 1 - assert wallet_index == actual_wallet_index - wallets = btc_lst_stake.wallets(wallet_index - 1) - assert wallets[2] == 1 - tx = btc_lst_stake.updateParam('remove', stake_script) - wallets = btc_lst_stake.wallets(wallet_index - 1) - assert wallets[2] == 2 - script_info = btc_script.get_script_hash(stake_script) - expect_event(tx, 'removedWallet', { - '_hash': script_info[0], - '_type': script_info[1] - }) - - -def test_revert_on_deleting_unadded_btc_wallet(btc_lst_stake): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - stake_script = __create_btc_lst_staking_script('p2tr') - with brownie.reverts("Wallet not found"): - btc_lst_stake.updateParam('remove', stake_script) - - -def test_keep_at_least_one_valid_multisig_address(btc_lst_stake): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - with brownie.reverts("Wallet empty"): - btc_lst_stake.updateParam('remove', LOCK_SCRIPT) - stake_manager.add_wallet(REDEEM_SCRIPT) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('remove', REDEEM_SCRIPT) - with brownie.reverts("Wallet empty"): - btc_lst_stake.updateParam('remove', LOCK_SCRIPT) - btc_lst_script = random_btc_lst_lock_script() - stake_manager.add_wallet(btc_lst_script) - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - tx = btc_lst_stake.updateParam('remove', LOCK_SCRIPT) - assert 'removedWallet' in tx.events - - -def test_duplicate_stake_address_remove_and_add(btc_lst_stake): - stake_manager.add_wallet(REDEEM_SCRIPT) - wallet_index = btc_lst_stake.getWalletMap(keccak_hash256(REDEEM_SCRIPT)) - actual_wallet_index = 2 - assert wallet_index == actual_wallet_index - wallets = btc_lst_stake.wallets(actual_wallet_index - 1) - script_hash, script_type = BtcScript.get_script_hash(REDEEM_SCRIPT) - assert wallets == [script_hash, script_type, 1] - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('remove', REDEEM_SCRIPT) - wallets = btc_lst_stake.wallets(actual_wallet_index - 1) - assert wallets == [script_hash, script_type, 2] - stake_manager.add_wallet(REDEEM_SCRIPT) - assert wallet_index == actual_wallet_index - wallets = btc_lst_stake.wallets(actual_wallet_index - 1) - assert wallets == [script_hash, script_type, 1] - btc_lst_script = random_btc_lst_lock_script() - stake_manager.add_wallet(btc_lst_script) - script_hash, script_type = BtcScript.get_script_hash(btc_lst_script) - wallet_index = btc_lst_stake.getWalletMap(keccak_hash256(btc_lst_script)) - assert wallet_index == 3 - wallets = btc_lst_stake.wallets(wallet_index - 1) - assert wallets == [script_hash, script_type, 1] - - -@pytest.mark.parametrize("paused", [0, 1]) -def test_update_paused_success(btc_lst_stake, paused): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - if paused: - btc_lst_stake.updateParam('paused', 1) - paused_ = True - else: - btc_lst_stake.updateParam('paused', 1) - btc_lst_stake.updateParam('paused', 0) - paused_ = False - assert btc_lst_stake.paused() is paused_ - - -def test_update_paused_length_error(btc_lst_stake): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(1), 64) - with brownie.reverts("MismatchParamLength: paused"): - btc_lst_stake.updateParam('paused', hex_value) - - -@pytest.mark.parametrize("paused", [2, 3, 4]) -def test_update_paused_range_error(btc_lst_stake, paused): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - with brownie.reverts(f"OutOfBounds: paused, {paused}, 0, 1"): - btc_lst_stake.updateParam('paused', paused) - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -@pytest.mark.parametrize("burn_btc_limit", [1e5, 2e8, 10e8, 100e8]) -def test_update_burn_btc_limit_success(btc_lst_stake, burn_btc_limit): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(burn_btc_limit)), 64) - btc_lst_stake.updateParam('burnBTCLimit', hex_value) - assert btc_lst_stake.burnBTCLimit() == int(burn_btc_limit) - - -@pytest.mark.skip(reason="remove the skip for the burnBTCLimit function") -def test_update_burn_btc_limit_param_length_error(btc_lst_stake): - burn_btc_limit = 10e8 - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(burn_btc_limit)), 62) - with brownie.reverts("MismatchParamLength: burnBTCLimit"): - btc_lst_stake.updateParam('burnBTCLimit', hex_value) - - -@pytest.mark.parametrize("utxo_fee", [1e4, 1e5, 1e8, 1e10, 1e12]) -def test_update_utxo_fee_success(btc_lst_stake, utxo_fee): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(utxo_fee)), 16) - btc_lst_stake.updateParam('utxoFee', hex_value) - assert btc_lst_stake.utxoFee() == utxo_fee - - -def test_utxo_fee_length_incorrect(btc_lst_stake): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(1e8)), 32) - with brownie.reverts("MismatchParamLength: utxoFee"): - btc_lst_stake.updateParam('utxoFee', hex_value) - - -def test_utxo_fee_too_low(btc_lst_stake): - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(1e2)), 16) - with brownie.reverts("OutOfBounds: utxoFee, 100, 1000, 18446744073709551615"): - btc_lst_stake.updateParam('utxoFee', hex_value) - - -def test_governance_param_not_exist(btc_lst_stake): - test_value = 10e8 - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - with brownie.reverts("UnsupportedGovParam: test"): - btc_lst_stake.updateParam('test', int(test_value)) - - -def test_update_param_only_callable_by_gov_hub(btc_lst_stake): - hex_value = padding_left(Web3.to_hex(0), 64) - with brownie.reverts("the msg sender must be governance contract"): - btc_lst_stake.updateParam('burnBTCLimit', hex_value) - - -def __create_btc_lst_staking_script(script_type='p2sh', script_public_key=None): - if script_public_key is None: - script_public_key = public_key - lock_scrip = btc_script.k2_btc_lst_script(script_public_key, script_type) - return lock_scrip - - -def __create_btc_lst_delegate(delegator, amount, script_public_key=None, script_type='p2sh', fee=1, version=2, - chain_id=1112, magic='5341542b', input_tx_id=None, vout=0): - pay_address = __create_btc_lst_staking_script(script_type, script_public_key) - btc_tx = build_btc_lst_tx(delegator, amount, pay_address, input_tx_id, vout, fee, version, chain_id, magic) - return btc_tx, pay_address - - -def __get_redeem_requests(index): - redeem_request = BTC_LST_STAKE.redeemRequests(index) - return redeem_request - - -def __get_btc_tx_map(tx_id): - bt = BTC_LST_STAKE.btcTxMap(tx_id) - return bt - - -def __check_user_stake_info(delegator, result: dict): - user_info = BTC_LST_STAKE.userStakeInfo(delegator) - for r in result: - assert result[r] == user_info[r] - - -def __check_redeem_requests(redeem_index, result: dict): - redeem_request = BTC_LST_STAKE.redeemRequests(redeem_index) - for i in result: - assert redeem_request[i] == result[i] - - -def __get_btc_lst_tx_map_info(tx_id): - data = BTC_LST_STAKE.btcTxMap(tx_id) - return data - - -def __check_btc_lst_tx_map_info(tx_id, result: dict): - data = __get_btc_lst_tx_map_info(tx_id) - for i in result: - assert data[i] == result[i] diff --git a/tests/test_btc_stake.py b/tests/test_btc_stake.py index 9582696c7..3b6bd5c7b 100644 --- a/tests/test_btc_stake.py +++ b/tests/test_btc_stake.py @@ -99,7 +99,7 @@ def test_initialize_from_pledge_agent_success(btc_stake): assert c[1] == realtime_amounts[index] -def test_delegate_btc_with_lock_time_in_tx(btc_stake, set_candidate, stake_hub, btc_lst_stake, btc_agent): +def test_delegate_btc_with_lock_time_in_tx(btc_stake, set_candidate, stake_hub, btc_agent): turn_round() operators, consensuses = set_candidate btc_amount = BTC_VALUE diff --git a/tests/test_claim_reward.py b/tests/test_claim_reward.py index 3992f480e..0ee0a7982 100644 --- a/tests/test_claim_reward.py +++ b/tests/test_claim_reward.py @@ -14,7 +14,6 @@ COIN_VALUE = 10000 BTC_VALUE = 200 POWER_VALUE = 20 -BTC_LST_VALUE = 600 TX_FEE = 100 FEE = 0 MONTH = 30 @@ -22,9 +21,6 @@ # BTC delegation-related LOCK_SCRIPT = "0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac" LOCK_TIME = 1736956800 -# BTCLST delegation-related -BTCLST_LOCK_SCRIPT = "0xa914cdf3d02dd323c14bea0bed94962496c80c09334487" -BTCLST_REDEEM_SCRIPT = "0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287" stake_manager = StakeManager() @@ -43,9 +39,9 @@ def init_system_reward_balance(system_reward): @pytest.fixture(scope="module", autouse=True) def set_block_reward(validator_set, candidate_hub, btc_stake, stake_hub, pledge_agent, - btc_lst_stake, gov_hub, btc_agent, system_reward, core_agent): + gov_hub, btc_agent, system_reward, core_agent): global BLOCK_REWARD, FEE, DELEGATE_VALUE, TOTAL_REWARD, MIN_INIT_DELEGATE_VALUE - global BTC_STAKE, STAKE_HUB, CANDIDATE_HUB, BTC_LST_STAKE, PLEDGE_AGENT, block_reward + global BTC_STAKE, STAKE_HUB, CANDIDATE_HUB, PLEDGE_AGENT, block_reward FEE = FEE * 100 block_reward = validator_set.blockReward() block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() @@ -66,7 +62,6 @@ def set_block_reward(validator_set, candidate_hub, btc_stake, stake_hub, pledge_ btc_agent.setInitLpRates(*lp_rates) btc_stake.setIsActive(True) btc_agent.setIsActive(True) - BTC_LST_STAKE = btc_lst_stake PLEDGE_AGENT = pledge_agent system_reward.setOperator(stake_hub.address) @@ -152,525 +147,6 @@ def expect_validator_consensus(consensuses): assert consensus in consensuses -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_successful_upgrade_after_round_switch(pledge_agent, candidate_hub): - init_round_tag, timestamp = mock_current_round() - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - mock_btc_stake_lock_time(timestamp) - old_turn_round() - set_round_tag(init_round_tag) - operators, consensuses = add_candidates() - candidate_hub.setValidatorCount(21) - old_turn_round() - tx_ids = [] - lock_time, end_round = mock_btc_stake_lock_time(timestamp, 5) - for index, op in enumerate(operators): - old_delegate_coin_success(op, accounts[0], DELEGATE_VALUE + index) - for index, op in enumerate(operators[0:10]): - tx_id = old_delegate_btc_success(BTC_VALUE + index, op, accounts[0], lock_time) - tx_ids.append(tx_id) - lock_time, end_round = mock_btc_stake_lock_time(timestamp, 3) - delegate_coin_success(operators[0], accounts[0], DELEGATE_VALUE) - delegate_coin_success(operators[7], accounts[0], DELEGATE_VALUE) - for index, op in enumerate(operators[10:12]): - btc_amount = BTC_VALUE + (index + 10) - tx_id = old_delegate_btc_success(btc_amount, op, accounts[1], lock_time) - tx_ids.append(tx_id) - old_delegate_coin_success(operators[8], accounts[0], DELEGATE_VALUE) - old_delegate_coin_success(operators[12], accounts[1], DELEGATE_VALUE) - delegate_power_success(operators[0], accounts[2], 1) - old_turn_round(consensuses) - delegate_power_success(operators[0], accounts[2], 1) - old_turn_round(consensuses) - delegate_power_success(operators[0], accounts[2], 1) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data(tx_ids) - delegate_coin_success(operators[0], accounts[0], DELEGATE_VALUE) - delegate_btc_lst_success(accounts[0], BTC_LST_VALUE, BTCLST_LOCK_SCRIPT) - delegate_power_success(operators[0], accounts[0], 1) - delegate_btc_success(operators[2], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - delegate_btc_lst_success(accounts[0], BTC_LST_VALUE, BTCLST_LOCK_SCRIPT) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[2]) - turn_round(consensuses) - tx = stake_hub_claim_reward(accounts[0]) - assert 'claimedReward' in tx.events - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_multiple_stakes_after_upgrade(set_candidate): - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - btc_value = 100 - btc_lst_value = 1000 - delegate_amount = 600000 - power_value = 1 - operators, consensuses = set_candidate - old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data(tx_ids) - old_claim_reward_success(operators, accounts[0]) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) - turn_round(consensuses) - redeem_btc_lst_success(accounts[0], btc_lst_value // 2, BTCLST_LOCK_SCRIPT) - transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount) - undelegate_coin_success(operators[2], accounts[0], delegate_amount // 2) - transfer_btc_success(tx_ids[0], operators[1], accounts[0]) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount * 2, delegate_amount // 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)] - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)], - }, { - "address": operators[2], - }], BLOCK_REWARD // 2, { - accounts[0]: set_btc_lst_delegate(btc_lst_value, btc_lst_value // 2)}, state_map={'core_lp': 4}) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - turn_round(consensuses) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_cancel_and_transfer_stakes_after_upgrade(set_candidate, slash_indicator): - btc_value = 100 - btc_lst_value = 1000 - delegate_amount = 600000 - power_value = 1 - operators, consensuses = set_candidate - old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data(tx_ids) - old_claim_reward_success(operators, accounts[0]) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) - turn_round(consensuses) - redeem_btc_lst_success(accounts[0], btc_lst_value // 2, BTCLST_LOCK_SCRIPT) - transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount) - undelegate_coin_success(operators[2], accounts[0], delegate_amount) - transfer_btc_success(tx_ids[0], operators[1], accounts[0]) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount * 2, delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)] - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)], - }, { - "address": operators[2], - }], BLOCK_REWARD // 2, { - accounts[0]: set_btc_lst_delegate(btc_lst_value, btc_lst_value // 2)}, state_map={'core_lp': 4}) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - slash_threshold = slash_indicator.felonyThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[0]) - slash_indicator.slash(consensuses[1]) - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 - turn_round(consensuses) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_new_validator_join_after_upgrade(slash_indicator): - btc_value = 100 - btc_lst_value = 1000 - delegate_amount = 600000 - power_value = 1 - operators = [] - consensuses = [] - for operator in accounts[5:7]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data(tx_ids) - old_claim_reward_success(operators, accounts[0]) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) - turn_round(consensuses) - for operator in accounts[8:9]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - redeem_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - delegate_coin_success(operators[2], accounts[0], delegate_amount) - delegate_coin_success(operators[2], accounts[1], delegate_amount) - delegate_btc_success(operators[2], accounts[0], btc_value, LOCK_SCRIPT) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount * 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)] - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)], - }], BLOCK_REWARD // 2, { - accounts[0]: set_btc_lst_delegate(btc_lst_value, btc_lst_value)}, state_map={'core_lp': 4}) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount * 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, stake_duration=MONTH), - set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)] - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)], - }, { - "address": operators[2], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, stake_duration=MONTH)] - }], BLOCK_REWARD // 2, state_map={'core_lp': 4}) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount * 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, stake_duration=MONTH), - set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)], - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - }, { - "address": operators[2], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, stake_duration=MONTH)] - }], BLOCK_REWARD // 2, {accounts[0]: set_btc_lst_delegate(btc_lst_value)}, state_map={'core_lp': 4}) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - turn_round(consensuses) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_no_btc_lst_stake_after_upgrade(set_candidate): - btc_value = 100 - delegate_amount = 600000 - power_value = 1 - operators, consensuses = set_candidate - old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data(tx_ids) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - delegate_power_success(operators[2], accounts[0], power_value) - turn_round(consensuses) - transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount) - undelegate_coin_success(operators[2], accounts[0], delegate_amount // 2) - old_undelegate_coin_success(operators[1], accounts[0], delegate_amount // 2, False) - transfer_coin_success(operators[1], operators[0], accounts[0], delegate_amount // 2) - transfer_btc_success(tx_ids[0], operators[1], accounts[0]) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount * 2, delegate_amount // 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value, btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)] - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount, delegate_amount // 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - "power": [set_delegate(accounts[2], power_value)], - }, { - "address": operators[2], - "power": [set_delegate(accounts[0], power_value)] - }], BLOCK_REWARD // 2, state_map={'core_lp': 4}) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - turn_round(consensuses) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_stake_in_next_round_after_upgrade(set_candidate): - btc_value = 100 - delegate_amount = 600000 - operators, consensuses = set_candidate - old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data(tx_ids) - turn_round(consensuses, round_count=3) - old_claim_reward_success(operators, accounts[0]) - transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount) - undelegate_coin_success(operators[2], accounts[0], delegate_amount // 2) - old_undelegate_coin_success(operators[1], accounts[0], delegate_amount // 2, False) - transfer_coin_success(operators[1], operators[0], accounts[0], delegate_amount // 2) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount, delegate_amount // 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount, delegate_amount // 2), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)], - }, { - "address": operators[1] - }], BLOCK_REWARD // 2, state_map={'core_lp': 4}) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - turn_round(consensuses) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("round_count", [0, 1, 2]) -def test_claim_rewards_after_becoming_validator_post_upgrade(pledge_agent, validator_set, slash_indicator, - candidate_hub, round_count): - operators = [] - consensuses = [] - for operator in accounts[5:11]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - for op in operators[:4]: - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE) - for op in operators: - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - old_turn_round() - candidate_hub.refuseDelegate({'from': operators[0]}) - old_turn_round() - candidate_hub.unregister({'from': operators[0]}) - slash_threshold = slash_indicator.felonyThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[2]) - candidate_hub.refuseDelegate({'from': operators[1]}) - old_turn_round(consensuses) - candidate_hub.refuseDelegate({'from': operators[3]}) - old_turn_round(consensuses) - assert len(validator_set.getValidatorOps()) == 2 - init_hybrid_score_mock() - pledge_agent.moveCandidateData([operators[0]]) - turn_round(consensuses, round_count=round_count) - operators.append(operators[0]) - consensuses.append(register_candidate(operator=operators[0])) - candidate_hub.acceptDelegate({'from': operators[1]}) - candidate_hub.acceptDelegate({'from': operators[3]}) - required_margin = 100000000001 - candidate_hub.addMargin({'value': required_margin, 'from': operators[2]}) - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses) - assert len(validator_set.getValidatorOps()) == 6 - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 * 4 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("round_count", [0, 1, 2, 5, 6]) -def test_data_migration_between_different_validator_states(pledge_agent, validator_set, slash_indicator, - candidate_hub, round_count): - candidate_hub.setValidatorCount(3) - operators = [] - consensuses = [] - tx_ids = [] - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 2) - for operator in accounts[5:15]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - for op in operators: - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE) - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - for op in operators[:5]: - tx_id0 = old_delegate_btc_success(BTC_VALUE, op, accounts[0]) - tx_ids.append(tx_id0) - for op in operators[:8]: - tx_id1 = old_delegate_btc_success(BTC_VALUE, op, accounts[1], LOCK_TIME) - tx_ids.append(tx_id1) - for op in operators[:9]: - delegate_power_success(op, accounts[0]) - old_turn_round() - candidate_hub.refuseDelegate({'from': operators[0]}) - old_turn_round() - for i in range(6, 10): - candidate_hub.unregister({'from': operators[i]}) - slash_threshold = slash_indicator.felonyThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[2]) - slash_threshold = slash_indicator.misdemeanorThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[3]) - candidate_hub.refuseDelegate({'from': operators[1]}) - old_turn_round(consensuses) - init_hybrid_score_mock() - pledge_agent.moveCandidateData(operators) - move_btc_data(tx_ids) - turn_round(consensuses, round_count=round_count) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - turn_round(consensuses, round_count=2) - tx = stake_hub_claim_reward(accounts[0]) - assert 'claimedReward' in tx.events - tx = stake_hub_claim_reward(accounts[1]) - assert 'claimedReward' in tx.events - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_btclst_claim_extra_rewards_after_upgrade(set_candidate): - btc_lst_value = 1000 - delegate_amount = 600000 - power_value = 1 - operators, consensuses = set_candidate - old_turn_round() - for op in operators[:2]: - for account in accounts[:2]: - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - old_turn_round(consensuses) - init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT, Utils.DENOMINATOR * 2) - turn_round(consensuses) - redeem_btc_lst_success(accounts[0], btc_lst_value // 2, BTCLST_LOCK_SCRIPT) - transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount) - transfer_coin_success(operators[2], operators[1], accounts[0], delegate_amount) - transfer_coin_success(operators[1], operators[0], accounts[0], delegate_amount) - undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount, delegate_amount // 2), - set_delegate(accounts[1], delegate_amount)], - "power": [set_delegate(accounts[2], power_value)] - }, - { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), set_delegate(accounts[1], delegate_amount)], - "power": [set_delegate(accounts[2], power_value)], - }, { - "address": operators[2], - }], BLOCK_REWARD // 2, { - accounts[0]: set_btc_lst_delegate(btc_lst_value, btc_lst_value // 2)}, state_map={ - 'core_lp': 4, - 'percentage': Utils.DENOMINATOR * 2 - }) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - turn_round(consensuses) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_btc_data_migration_not_affected_by_stake_duration(set_candidate): - stake_manager.set_is_stake_hub_active(False) - stake_manager.set_tlp_rates([[0, 5000], [2 * Utils.MONTH_TIMESTAMP, 10000]]) - btc_value = 100 - operators, consensuses = set_candidate - old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - old_turn_round(consensuses) - init_hybrid_score_mock() - move_btc_data([tx_id]) - turn_round(consensuses) - delegate_btc_success(operators[1], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD - turn_round(consensuses, round_count=2) - trackers = get_trackers(accounts[:2]) - stake_hub_claim_reward(accounts[:2]) - assert trackers[0].delta() == TOTAL_REWARD * 2 - assert trackers[1].delta() == TOTAL_REWARD // 2 - - @pytest.mark.parametrize("hard_cap", [ [['coreHardcap', 2000], ['hashHardcap', 9000], ['btcHardcap', 10000]], [['coreHardcap', 3000], ['hashHardcap', 3000], ['btcHardcap', 3000]], @@ -686,8 +162,6 @@ def test_claim_reward_after_hardcap_update(stake_hub, hard_cap, set_candidate): delegate_coin_success(operators[0], accounts[0], COIN_VALUE) delegate_btc_success(operators[1], accounts[1], BTC_VALUE, LOCK_SCRIPT, relay=accounts[1]) delegate_power_success(operators[2], accounts[2], POWER_VALUE) - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], BTC_LST_VALUE, BTCLST_LOCK_SCRIPT) turn_round(consensuses, round_count=2, tx_fee=TX_FEE) _, unclaimed_rewards, account_rewards, _ = parse_delegation([{ "address": operators[0], @@ -699,8 +173,7 @@ def test_claim_reward_after_hardcap_update(stake_hub, hard_cap, set_candidate): "address": operators[2], "power": [set_delegate(accounts[2], POWER_VALUE)] } - ], BLOCK_REWARD // 2, btc_lst_stake={ - accounts[0]: set_btc_lst_delegate(BTC_LST_VALUE)}, + ], BLOCK_REWARD // 2, state_map={'core_lp': 4}, reward_cap={ 'coin': hard_cap[0][-1], @@ -938,11 +411,6 @@ def test_slash_after_upgrade(slash_indicator, candidate_hub, set_candidate, vali turn_round(consensuses, round_count=2) -def test_no_multisig_wallet_stake(btc_lst_stake): - with brownie.reverts("Wallet not found"): - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT) - - def test_stake_reward_claim_after_upgrade(stake_hub, system_reward, set_candidate, init_system_reward_balance): surplus = 50000 accounts[3].transfer(stake_hub.address, surplus) @@ -1011,128 +479,6 @@ def test_stake_reward_claim_after_upgrade(stake_hub, system_reward, set_candidat turn_round(consensuses) -@pytest.mark.parametrize("round", [0, 1, 2]) -@pytest.mark.parametrize("is_new_validator", [True, False]) -@pytest.mark.parametrize("is_slash", [True, False]) -@pytest.mark.parametrize("is_unregister", [True, False]) -@pytest.mark.parametrize("is_btc_lst", [True, False]) -def test_enable_lst_midway(slash_indicator, set_candidate, candidate_hub, round, is_new_validator, is_slash, - is_unregister, is_btc_lst, btc_lst_stake): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - btc_lst_value = 1000 - btc_value = 10000 - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[0], delegate_value) - delegate_coin_success(operators[0], accounts[1], delegate_value) - delegate_coin_success(operators[1], accounts[0], delegate_value) - delegate_coin_success(operators[1], accounts[1], delegate_value) - delegate_btc_success(operators[0], accounts[3], btc_value, LOCK_SCRIPT, relay=accounts[3]) - delegate_btc_success(operators[1], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) - turn_round(consensuses, round_count=2) - if is_new_validator: - for operator in accounts[10:12]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[0]) - delegate_btc_lst_success(accounts[1], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[1]) - delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) - delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) - turn_round(consensuses, round_count=round) - if is_btc_lst: - update_system_contract_address(btc_lst_stake, gov_hub=accounts[0]) - btc_lst_stake.updateParam('paused', 1) - with brownie.reverts("Pausable: paused"): - delegate_btc_lst_success(accounts[5], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[1]) - if is_slash: - slash_threshold = slash_indicator.felonyThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[1]) - turn_round(consensuses, round_count=round) - if is_unregister: - candidate_hub.refuseDelegate({'from': operators[2]}) - turn_round(consensuses) - candidate_hub.unregister({'from': operators[2]}) - delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) - turn_round(consensuses, round_count=round) - stake_hub_claim_reward(accounts[0:6]) - - -@pytest.mark.parametrize("round", [0, 1, 2]) -@pytest.mark.parametrize("is_new_validator", [True, False]) -@pytest.mark.parametrize("is_slash", [True, False]) -def test_disable_lst_after_enabling(slash_indicator, set_candidate, candidate_hub, round, is_new_validator, is_slash): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - btc_value = 10000 - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[0], delegate_value) - delegate_coin_success(operators[0], accounts[1], delegate_value) - delegate_coin_success(operators[1], accounts[0], delegate_value) - delegate_coin_success(operators[1], accounts[1], delegate_value) - delegate_btc_success(operators[0], accounts[3], btc_value, LOCK_SCRIPT, relay=accounts[3]) - delegate_btc_success(operators[1], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) - turn_round(consensuses, round_count=2) - if is_new_validator: - for operator in accounts[10:12]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) - delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) - turn_round(consensuses, round_count=round) - if is_slash: - slash_threshold = slash_indicator.felonyThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[1]) - turn_round(consensuses, round_count=round) - delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) - turn_round(consensuses, round_count=round) - stake_hub_claim_reward(accounts[0:6]) - - -@pytest.mark.parametrize("round", [0, 1, 2]) -@pytest.mark.parametrize("is_new_validator", [True, False]) -@pytest.mark.parametrize("is_unregister", [True, False]) -def test_btclst_transfer_success(slash_indicator, set_candidate, candidate_hub, round, is_new_validator, btc_lst_stake, - is_unregister): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - btc_lst_value = 1000 - btc_value = 10000 - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[0], delegate_value) - delegate_coin_success(operators[0], accounts[1], delegate_value) - delegate_coin_success(operators[1], accounts[0], delegate_value) - delegate_coin_success(operators[1], accounts[1], delegate_value) - delegate_coin_success(operators[2], accounts[1], delegate_value) - delegate_btc_success(operators[0], accounts[3], btc_value, LOCK_SCRIPT, relay=accounts[3]) - delegate_btc_success(operators[1], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) - delegate_btc_success(operators[2], accounts[4], btc_value, LOCK_SCRIPT, relay=accounts[4]) - turn_round(consensuses, round_count=2) - stake_manager.add_wallet(BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[0]) - delegate_btc_lst_success(accounts[1], btc_lst_value, BTCLST_LOCK_SCRIPT, relay=accounts[1]) - delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) - delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT, relay=accounts[1]) - turn_round(consensuses, round_count=round) - if is_new_validator: - for operator in accounts[10:12]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - transfer_btc_lst_success(accounts[0], btc_lst_value // 2, accounts[3]) - delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, relay=accounts[2]) - if is_unregister: - candidate_hub.refuseDelegate({'from': operators[2]}) - turn_round(consensuses) - candidate_hub.unregister({'from': operators[2]}) - turn_round(consensuses, round_count=round) - - redeem_btc_lst_success(accounts[3], btc_lst_value // 4, BTCLST_REDEEM_SCRIPT) - stake_hub_claim_reward(accounts[0:6]) - turn_round(consensuses, round_count=round) - - def init_hybrid_score_mock(): STAKE_HUB.initHybridScoreMock() set_round_tag(get_current_round()) @@ -1968,6 +1314,7 @@ def test_refused_delegate_cannot_be_alternate(candidate_hub, validator_set): expect_validator_consensus(consensuses[1:3] + consensuses[4:5]) turn_round(chain_get_validator_consensus()) + def test_replenish_and_validator_count_equals_total_validators(validator_set, candidate_hub): validator_count = 10 replenish_count = 5 @@ -2301,7 +1648,7 @@ def test_maintenance_validator_upgrade_coin_btc_no_effect(validator_set, candida turn_round(chain_get_validator_consensus(), round_count=2) -def test_update_consensus_address_block_produce_on_old_and_new(validator_set,candidate_hub): +def test_update_consensus_address_block_produce_on_old_and_new(validator_set, candidate_hub): delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 btc_amount = 1000 operators, consensuses = setup_validators_and_delegates(validator_count=5, @@ -2321,12 +1668,13 @@ def test_update_consensus_address_block_produce_on_old_and_new(validator_set,can turn_round(chain_get_validator_consensus()) tracker = get_tracker(accounts[50]) stake_hub_claim_reward(accounts[50]) - assert tracker.delta() == TOTAL_REWARD - 1 + assert tracker.delta() == TOTAL_REWARD - 1 chain_deposit(new_consensus, deposit_count=1) turn_round(chain_get_validator_consensus()) stake_hub_claim_reward(accounts[50]) - assert tracker.delta() == TOTAL_REWARD - 1 - + assert tracker.delta() == TOTAL_REWARD - 1 + + def test_validator_and_alternate_count_exceed_candidates(validator_set, candidate_hub, slash_indicator): validator_set.setMaintainSlashPercent(0) set_validator_count(validator_count=31, alternate_count=0) diff --git a/tests/test_delegate_btc.py b/tests/test_delegate_btc.py index 378deafe8..986636f98 100644 --- a/tests/test_delegate_btc.py +++ b/tests/test_delegate_btc.py @@ -77,7 +77,7 @@ def test_claim_rewards_from_multiple_validators(btc_stake): assert tracker0.delta() == sum_reward - FEE * 12 -def test_transfer_btc_between_different_validators(btc_stake, core_agent, candidate_hub, set_candidate, btc_lst_stake): +def test_transfer_btc_between_different_validators(btc_stake, core_agent, candidate_hub, set_candidate): operators, consensuses = set_candidate end_round, _ = set_last_round_tag(STAKE_ROUND) tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT, LOCK_SCRIPT) diff --git a/tests/test_delegate_btc_lst.py b/tests/test_delegate_btc_lst.py deleted file mode 100644 index 10a4c81ef..000000000 --- a/tests/test_delegate_btc_lst.py +++ /dev/null @@ -1,937 +0,0 @@ -import brownie -import pytest -from .calc_reward import parse_delegation, set_delegate, set_btc_lst_delegate -from .common import * -from .delegate import * -from .utils import * - -BLOCK_REWARD = 0 -BTC_VALUE = 2000 -TX_FEE = 100 -FEE = 0 -TOTAL_REWARD = 0 -UTXO_FEE = 100 -LOCK_TIME = 1736956800 -BTC_LOCK_SCRIPT = "0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac" -MIN_INIT_DELEGATE_VALUE = 20000 -LOCK_SCRIPT = "0xa914cdf3d02dd323c14bea0bed94962496c80c09334487" -REDEEM_SCRIPT = "0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287" - - -@pytest.fixture(scope="module", autouse=True) -def deposit_for_reward(validator_set, gov_hub): - accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) - accounts[99].transfer(gov_hub.address, Web3.to_wei(100000, 'ether')) - - -@pytest.fixture(scope="module", autouse=True) -def set_relayer_register(relay_hub): - for account in accounts[:3]: - relay_hub.setRelayerRegister(account.address, True) - - -@pytest.fixture(scope="module", autouse=True) -def set_block_reward(validator_set, candidate_hub, btc_light_client, btc_stake, stake_hub, core_agent, btc_lst_stake, - gov_hub, lst_token): - global BLOCK_REWARD, FEE, TOTAL_REWARD - global BTC_STAKE, STAKE_HUB, CORE_AGENT, BTC_LST_STAKE, BTC_LIGHT_CLIENT, BTC_LST_TOKEN - FEE = FEE * 100 - block_reward = validator_set.blockReward() - block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() - total_block_reward = block_reward + TX_FEE - BLOCK_REWARD = total_block_reward * ((100 - block_reward_incentive_percent) / 100) - TOTAL_REWARD = BLOCK_REWARD // 2 - BTC_STAKE = btc_stake - STAKE_HUB = stake_hub - CORE_AGENT = core_agent - BTC_LIGHT_CLIENT = btc_light_client - BTC_LST_STAKE = btc_lst_stake - candidate_hub.setControlRoundTimeTag(True) - # The default staking time is 150 days - BTC_LST_TOKEN = lst_token - set_block_time_stamp(150, LOCK_TIME) - btc_lst_stake.updateParam('add', LOCK_SCRIPT, {'from': gov_hub.address}) - - -@pytest.fixture() -def set_candidate(): - operators = [] - consensuses = [] - for operator in accounts[5:8]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - return operators, consensuses - - -@pytest.mark.parametrize("internal", [ - pytest.param(0, id="same round"), - pytest.param(1, id="adjacent rounds"), - pytest.param(2, id="spanning multiple rounds"), -]) -def test_delegate_btc_lst_success(set_candidate, lst_token, internal): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - turn_round(consensuses, round_count=internal) - tracker = get_tracker(accounts[0]) - assert lst_token.balanceOf(accounts[0]) == BTC_VALUE - claim_stake_and_relay_reward(accounts[0]) - actual_reward = 0 - if internal > 0: - actual_reward = TOTAL_REWARD * 3 * internal // 2 - assert tracker.delta() == actual_reward - - -@pytest.mark.parametrize("internal", [ - pytest.param(0, id="same round"), - pytest.param(1, id="adjacent rounds"), - pytest.param(2, id="spanning multiple rounds"), -]) -def test_redeem_btc_lst_success(set_candidate, lst_token, internal): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - turn_round(consensuses, round_count=internal) - tracker = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert lst_token.balanceOf(accounts[0]) == 0 - assert tracker.delta() == 0 - - -@pytest.mark.parametrize("internal", [ - pytest.param(0, id="same round"), - pytest.param(1, id="adjacent rounds"), - pytest.param(2, id="spanning multiple rounds"), -]) -def test_transfer_btc_lst_success(set_candidate, lst_token, internal): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses, round_count=internal) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - actual_reward = 0 - if internal > 1: - actual_reward = TOTAL_REWARD * 3 // 2 - assert tracker0.delta() == 0 - assert tracker1.delta() == actual_reward - - -def test_rewards_before_and_after_transfer(set_candidate, lst_token): - operators, consensuses = set_candidate - redeem_amount = BTC_VALUE // 4 - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - redeem_btc_lst_success(accounts[0], redeem_amount, REDEEM_SCRIPT) - transfer_btc_lst_success(accounts[0], redeem_amount, accounts[1]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 4 - assert tracker1.delta() == 0 - - -def test_partial_redeem_and_transfer(set_candidate, lst_token): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses, round_count=3) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - assert tracker1.delta() == TOTAL_REWARD * 3 * 2 // 2 - - -def test_multiple_currencies_stake_and_claim_reward(set_candidate, stake_hub, lst_token, btc_lst_stake): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - delegate_btc_success(operators[1], accounts[1], BTC_VALUE * 2, BTC_LOCK_SCRIPT) - delegate_power_success(operators[2], accounts[1], 200) - delegate_coin_success(operators[1], accounts[1], MIN_INIT_DELEGATE_VALUE) - delegate_btc_lst_success(accounts[2], BTC_VALUE * 2, LOCK_SCRIPT) - turn_round() - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - tracker2 = get_tracker(accounts[2]) - claim_stake_and_relay_reward(accounts[:3]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], MIN_INIT_DELEGATE_VALUE)], - }, { - "address": operators[1], - "coin": [set_delegate(accounts[1], MIN_INIT_DELEGATE_VALUE)], - "btc": [set_delegate(accounts[1], BTC_VALUE * 2)] - }, { - "address": operators[2], - "power": [set_delegate(accounts[1], 200)], - }], BLOCK_REWARD // 2, { - accounts[0]: set_btc_lst_delegate(BTC_VALUE), - accounts[2]: set_btc_lst_delegate(BTC_VALUE * 2)}) - assert tracker0.delta() == account_rewards[accounts[0]] - assert tracker1.delta() == account_rewards[accounts[1]] - FEE - assert tracker2.delta() == account_rewards[accounts[2]] - FEE - - -@pytest.mark.parametrize("validator_type", ['refuse', 'unregister', 'active']) -def test_no_reward_for_stake_to_invalid_validator(candidate_hub, validator_set, set_candidate, validator_type): - operators, consensuses = set_candidate - expect_reward = 0 - validator_count = 2 - if validator_type == 'refuse': - candidate_hub.refuseDelegate({'from': operators[0]}) - elif validator_type == 'unregister': - candidate_hub.unregister({'from': operators[0]}) - else: - expect_reward = TOTAL_REWARD // 2 - validator_count = 3 - turn_round() - validators = validator_set.getValidators() - assert len(validators) == validator_count - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD + expect_reward - - -def test_reward_claim_post_validator_reduction(candidate_hub, validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - candidate_hub.refuseDelegate({'from': operators[0]}) - turn_round(consensuses) - validators = validator_set.getValidators() - assert len(validators) == 2 - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 2 // 2 - - -def test_redeem_stake_on_refused_validator(candidate_hub, validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - candidate_hub.refuseDelegate({'from': operators[0]}) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], MIN_INIT_DELEGATE_VALUE)], - }, { - "address": operators[1] - }, { - "address": operators[2] - }], TOTAL_REWARD, {accounts[0]: set_btc_lst_delegate(BTC_VALUE)}) - assert tracker0.delta() == account_rewards[accounts[0]] - assert tracker1.delta() == account_rewards[accounts[1]] - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD // 2 - - -def test_redeem_and_claim_reward_after_transfer(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[2], MIN_INIT_DELEGATE_VALUE) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses) - redeem_btc_lst_success(accounts[1], BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - _, _, account_rewards, asset_unit_reward = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[2], MIN_INIT_DELEGATE_VALUE)], - }, { - "address": operators[1] - }, { - "address": operators[2] - }], TOTAL_REWARD, {accounts[1]: set_btc_lst_delegate(BTC_VALUE)}) - assert tracker0.delta() == 0 - assert tracker1.delta() == BTC_VALUE // 2 * asset_unit_reward['btc_lst'] // Utils.BTC_DECIMAL // 2 - redeem_btc_lst_success(accounts[1], BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[1]) - assert tracker1.delta() == 0 - - -@pytest.mark.parametrize("internal", [ - pytest.param(0, id="same round"), - pytest.param(1, id="adjacent rounds"), - pytest.param(2, id="spanning multiple rounds"), -]) -def test_transfer_post_multiple_round_reward_claim(validator_set, set_candidate, internal): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=internal) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses, round_count=2) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[1]) - assert tracker1.delta() == TOTAL_REWARD * 3 // 2 - - -@pytest.mark.parametrize("internal", [ - pytest.param(0, id="same round"), - pytest.param(1, id="adjacent rounds"), - pytest.param(2, id="spanning multiple rounds"), -]) -def test_redeem_post_multiple_round_reward_claim(validator_set, set_candidate, internal): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=internal) - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - _, _, account_rewards, asset_unit_reward = parse_delegation([{ - "address": operators[0], - }, { - "address": operators[1] - }, { - "address": operators[2] - }], TOTAL_REWARD, {accounts[0]: set_btc_lst_delegate(BTC_VALUE)}) - remain_btc = BTC_VALUE // 2 - btc_unit_reward = asset_unit_reward['btc_lst'] - if internal == 1: - actual_reward = remain_btc * btc_unit_reward // Utils.BTC_DECIMAL // 2 - elif internal == 2: - actual_reward = remain_btc * (btc_unit_reward + btc_unit_reward * 2) // Utils.BTC_DECIMAL // 2 - else: - actual_reward = 0 - claim_stake_and_relay_reward(accounts[0]) - assert tracker.delta() == actual_reward - - -def test_transfer_and_claim_reward_after_redeem(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT) - transfer_btc_lst_success(accounts[0], BTC_VALUE // 2, accounts[1]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == 0 - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker1.delta() == TOTAL_REWARD * 3 // 2 - - -def test_transfer_redeem_then_transfer_again(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses) - redeem_btc_lst_success(accounts[1], BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - transfer_btc_lst_success(accounts[1], BTC_VALUE // 2, accounts[0]) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == TOTAL_REWARD * 3 // 4 - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker1.delta() == 0 - - -def test_additional_stake_redeem_then_transfer(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - redeem_btc_lst_success(accounts[0], BTC_VALUE, REDEEM_SCRIPT) - turn_round(consensuses) - _, _, account_rewards, asset_unit_reward = parse_delegation([{ - "address": operators[0] - }, { - "address": operators[1] - }, { - "address": operators[2] - }], TOTAL_REWARD, {accounts[0]: set_btc_lst_delegate(BTC_VALUE * 3)}) - actual_reward = BTC_VALUE * 2 * asset_unit_reward['btc_lst'] // Utils.BTC_DECIMAL // 2 - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == actual_reward - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[0]) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[2]) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 3 // 4 - assert tracker0.delta() == actual_reward - - -def test_reward_claim_post_multiple_transfers_in_round(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - for i in range(6): - transfer_btc_lst_success(accounts[i], BTC_VALUE, accounts[i + 1]) - turn_round(consensuses) - tracker = get_tracker(accounts[6]) - claim_stake_and_relay_reward(accounts[6]) - assert tracker.delta() == 0 - - -def test_new_validator_post_stake_reward_claim(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - new_agent = accounts[8] - consensus = register_candidate(operator=new_agent) - operators.append(accounts[8]) - consensuses.append(consensus) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 4 // 2 - - -def test_stake_in_current_round_with_new_validator(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - new_agent = accounts[8] - consensus = register_candidate(operator=new_agent) - operators.append(accounts[8]) - consensuses.append(consensus) - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 3 // 2 - - -@pytest.mark.parametrize("percentage", [ - pytest.param(100, id="percentage is 100"), - pytest.param(500, id="percentage is 500"), - pytest.param(3000, id="percentage is 3000"), - pytest.param(8000, id="percentage is 8000") -]) -def test_claim_reward_after_percentage_update(btc_agent, set_candidate, percentage): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[0]) - btc_agent.setPercentage(percentage) - claim_stake_and_relay_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 3 * percentage // Utils.DENOMINATOR - - -def test_deduct_relay_fee_after_transfer(set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, relay=accounts[1]) - turn_round(consensuses) - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses, round_count=1) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - FEE - assert tracker1.delta() == FEE - - -def test_no_relay_fee_after_btc_lst_stake_and_transfer(set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, relay=accounts[0]) - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == TOTAL_REWARD * 3 // 2 - - -def test_porter_fee_deduction_success_over_multiple_rounds(set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, relay=accounts[1]) - turn_round(consensuses, round_count=3) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker1.delta() == FEE - - -def test_claim_reward_after_self_transfer(set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[0]) - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[0]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - transfer_btc_lst_success(accounts[0], BTC_VALUE // 2, accounts[1]) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 4 - - -def test_self_transfer(set_candidate, lst_token): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - tx = lst_token.transfer(accounts[0], BTC_VALUE, {"from": accounts[0]}) - expect_event(tx, 'Transfer', { - 'from': accounts[0], - 'to': accounts[0], - 'value': BTC_VALUE - }) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - - -@pytest.mark.parametrize("validator_state", ['minor', 'major']) -def test_btc_lst_stake_with_validator_slashed(set_candidate, slash_indicator, validator_state): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - tx0 = None - if validator_state == 'minor': - slash_threshold = slash_indicator.misdemeanorThreshold() - event_name = 'validatorMisdemeanor' - expect_reward = TOTAL_REWARD * 6 // 2 - else: - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - # no rewards for the current round if a major offense occurs - expect_reward = TOTAL_REWARD * 2 - for count in range(slash_threshold): - tx0 = slash_indicator.slash(consensuses[0]) - assert event_name in tx0.events - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == expect_reward - turn_round(consensuses) - - -@pytest.mark.parametrize("validator_state", ['minor', 'major']) -def test_validator_slashed_during_redeem(set_candidate, slash_indicator, validator_state): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, LOCK_SCRIPT) - tx0 = None - expect_reward = 0 - if validator_state == 'minor': - slash_threshold = slash_indicator.misdemeanorThreshold() - event_name = 'validatorMisdemeanor' - else: - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - # no rewards for the current round if a major offense occurs - for count in range(slash_threshold): - tx0 = slash_indicator.slash(consensuses[0]) - assert event_name in tx0.events - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == expect_reward - turn_round(consensuses) - - -@pytest.mark.parametrize("validator_state", ['minor', 'major']) -def test_transfer_with_validator_slashed(set_candidate, slash_indicator, validator_state): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE // 2, accounts[1]) - tx0 = None - if validator_state == 'minor': - slash_threshold = slash_indicator.misdemeanorThreshold() - event_name = 'validatorMisdemeanor' - expect_reward = TOTAL_REWARD * 3 // 2 - - else: - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - expect_reward = TOTAL_REWARD - for count in range(slash_threshold): - tx0 = slash_indicator.slash(consensuses[0]) - assert event_name in tx0.events - transfer_btc_lst_success(accounts[0], BTC_VALUE // 2, accounts[1]) - transfer_btc_lst_success(accounts[1], BTC_VALUE, accounts[2]) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - tracker2 = get_tracker(accounts[2]) - claim_stake_and_relay_reward(accounts[:3]) - assert tracker0.delta() == 0 - assert tracker1.delta() == 0 - assert tracker2.delta() == expect_reward - turn_round(consensuses) - - -def test_contract_stake_and_claim_reward(set_candidate, btc_agent, btc_lst_stake, stake_hub, relay_hub, lst_token): - operators, consensuses = set_candidate - turn_round() - btc_agent.setPercentage(Utils.DENOMINATOR // 2) - btc_lst_stake = delegateBtcLstProxy.deploy(btc_lst_stake.address, stake_hub.address, lst_token, - {'from': accounts[0]}) - btc_lst_stake.setReceiveState(True) - accounts[4].transfer(btc_lst_stake.address, Web3.to_wei(1, 'ether')) - relay_hub.setRelayerRegister(btc_lst_stake.address, True) - btc_tx = build_btc_lst_tx(accounts[0], BTC_VALUE, LOCK_SCRIPT) - tx = btc_lst_stake.delegateBtcLst(btc_tx, 1, [], 1, LOCK_SCRIPT) - assert 'delegated' in tx.events - turn_round() - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(btc_lst_stake) - btc_lst_stake.setReceiveState(True) - claim_stake_and_relay_reward([accounts[0], btc_lst_stake]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - FEE - assert tracker1.delta() == FEE - turn_round(consensuses) - - -def test_contract_stake_and_redeem(set_candidate, btc_agent, btc_lst_stake, stake_hub, relay_hub, lst_token): - operators, consensuses = set_candidate - turn_round() - btc_lst_stake = delegateBtcLstProxy.deploy(btc_lst_stake.address, stake_hub.address, lst_token.address, - {'from': accounts[0]}) - btc_tx = build_btc_lst_tx(btc_lst_stake.address, BTC_VALUE, LOCK_SCRIPT) - tx = btc_lst_stake.delegateBtcLst(btc_tx, 1, [], 1, LOCK_SCRIPT) - btc_agent.setPercentage(Utils.DENOMINATOR // 2) - assert 'delegated' in tx.events - turn_round() - tx = btc_lst_stake.redeemBtcLst(BTC_VALUE // 2, REDEEM_SCRIPT) - assert 'redeemed' in tx.events - turn_round(consensuses) - tx = btc_lst_stake.transferBtcLst(accounts[0].address, BTC_VALUE // 2) - assert tx.events['transferBtcLstSuccess'] - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(btc_lst_stake) - btc_lst_stake.setReceiveState(True) - btc_lst_stake.claimReward() - assert tracker1.delta() == TOTAL_REWARD * 3 // 4 - FEE - turn_round(consensuses, round_count=2) - btc_lst_stake.claimReward() - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - assert tracker1.delta() == 0 - turn_round(consensuses) - - -def test_claim_reward_after_multiple_stakes_and_operations(set_candidate, stake_hub, lst_token, btc_lst_stake): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - for i in range(2): - delegate_coin_success(operators[0], accounts[i + 1], MIN_INIT_DELEGATE_VALUE) - delegate_power_success(operators[0], accounts[1], 200) - tx_id = delegate_btc_success(operators[1], accounts[1], BTC_VALUE * 2, BTC_LOCK_SCRIPT, relay=accounts[1]) - delegate_power_success(operators[2], accounts[1], 200) - delegate_coin_success(operators[1], accounts[2], MIN_INIT_DELEGATE_VALUE) - delegate_btc_lst_success(accounts[2], BTC_VALUE * 2, LOCK_SCRIPT, relay=accounts[2]) - turn_round() - transfer_coin_success(operators[0], operators[1], accounts[1], MIN_INIT_DELEGATE_VALUE) - redeem_btc_lst_success(accounts[0], BTC_VALUE // 2, REDEEM_SCRIPT) - transfer_btc_success(tx_id, operators[2], accounts[1]) - undelegate_coin_success(operators[0], accounts[2], MIN_INIT_DELEGATE_VALUE // 4) - undelegate_coin_success(operators[1], accounts[2], MIN_INIT_DELEGATE_VALUE) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - tracker2 = get_tracker(accounts[2]) - claim_stake_and_relay_reward(accounts[:3]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], MIN_INIT_DELEGATE_VALUE), - set_delegate(accounts[2], MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE // 4)], - "power": [set_delegate(accounts[1], 200)] - }, { - "address": operators[1], - "coin": [set_delegate(accounts[2], MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE)], - "btc": [set_delegate(accounts[1], BTC_VALUE * 2, BTC_VALUE * 2)] - }, { - "address": operators[2], - "power": [set_delegate(accounts[1], 200)] - }], BLOCK_REWARD // 2, { - accounts[0]: set_btc_lst_delegate(BTC_VALUE, BTC_VALUE // 2), - accounts[2]: set_btc_lst_delegate(BTC_VALUE * 2)}) - assert tracker0.delta() == account_rewards[accounts[0]] - assert tracker1.delta() == account_rewards[accounts[1]] - assert tracker2.delta() == account_rewards[accounts[2]] - turn_round(consensuses) - - -def test_claim_reward_after_turnround_failure(set_candidate, candidate_hub, stake_hub, lst_token, btc_lst_stake): - operators, consensuses = set_candidate - block_times_tamp = 1723122315 - set_last_round_tag(2, block_times_tamp) - chain.mine(timestamp=block_times_tamp) - turn_round() - candidate_hub.setControlRoundTimeTag(False) - block_time = block_times_tamp - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - for i in range(2): - delegate_coin_success(operators[0], accounts[i + 1], MIN_INIT_DELEGATE_VALUE) - delegate_power_success(operators[0], accounts[1], 200) - delegate_btc_success(operators[1], accounts[1], BTC_VALUE * 2, BTC_LOCK_SCRIPT, relay=accounts[1]) - delegate_power_success(operators[2], accounts[1], 200) - delegate_coin_success(operators[1], accounts[2], MIN_INIT_DELEGATE_VALUE) - delegate_btc_lst_success(accounts[2], BTC_VALUE * 2, LOCK_SCRIPT, relay=accounts[2]) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL) - turn_round() - tracker0 = get_tracker(accounts[0]) - candidate_hub.setTurnroundFailed(True) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 2) - with brownie.reverts("turnRound failed"): - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - candidate_hub.setTurnroundFailed(False) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 3) - turn_round(consensuses) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 4) - turn_round(consensuses) - - -def test_claim_reward_after_round_failure(set_candidate, candidate_hub, stake_hub, lst_token, btc_lst_stake): - operators, consensuses = set_candidate - block_times_tamp = 1723122315 - set_last_round_tag(2, block_times_tamp) - chain.mine(timestamp=block_times_tamp) - turn_round() - candidate_hub.setControlRoundTimeTag(False) - block_time = block_times_tamp - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, percentage=Utils.DENOMINATOR) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL) - turn_round() - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 2) - turn_round(consensuses) - candidate_hub.setTurnroundFailed(True) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 3) - with brownie.reverts("turnRound failed"): - turn_round(consensuses) - candidate_hub.setTurnroundFailed(False) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 4) - turn_round(consensuses) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 5) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 * 4 - - -def test_retry_turnround_after_failure(set_candidate, candidate_hub, stake_hub, lst_token, btc_lst_stake): - operators, consensuses = set_candidate - block_times_tamp = 1723122315 - set_last_round_tag(2, block_times_tamp) - chain.mine(timestamp=block_times_tamp) - turn_round() - candidate_hub.setControlRoundTimeTag(False) - block_time = block_times_tamp - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL) - turn_round() - tracker0 = get_tracker(accounts[0]) - candidate_hub.setTurnroundFailed(True) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 2) - with brownie.reverts("turnRound failed"): - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - candidate_hub.setTurnroundFailed(False) - chain.mine(timestamp=block_time + Utils.ROUND_INTERVAL * 3) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 6 // 2 - - -@pytest.mark.parametrize("tests", [ - {'part': True, 'actual_reward0': [40635 // 4, 0], 'actual_reward1': [40635 // 4, 40635 // 4]}, - {'part': False, 'actual_reward0': [0, 0], 'actual_reward1': [0, 40635 // 2]} -]) -def test_claim_reward_after_btc_lst_approved_transfer(validator_set, lst_token, set_candidate, tests): - operators, consensuses = set_candidate - lst_amount = BTC_VALUE - turn_round() - delegate_btc_lst_success(accounts[0], lst_amount, LOCK_SCRIPT) - turn_round(consensuses) - lst_token.approve(accounts[2], lst_amount, {"from": accounts[0]}) - if tests['part']: - lst_amount = lst_amount // 2 - lst_token.transferFrom(accounts[0], accounts[1], lst_amount, {'from': accounts[2]}) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - tracker2 = get_tracker(accounts[2]) - claim_stake_and_relay_reward(accounts[:3]) - assert tracker0.delta() == tests['actual_reward0'][0] - assert tracker1.delta() == tests['actual_reward0'][1] - assert tracker2.delta() == 0 - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[:3]) - assert tracker0.delta() == tests['actual_reward1'][0] - assert tracker1.delta() == tests['actual_reward1'][1] - assert tracker2.delta() == 0 - - -def test_redeem_after_addition_in_current_round(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses) - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE * 2, REDEEM_SCRIPT) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE * 2 + BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 4 - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - redeem_btc_lst_success(accounts[0], BTC_VALUE * 2 + BTC_VALUE // 2, REDEEM_SCRIPT) - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == 0 - - -def test_transfer_after_addition_in_current_round(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, Utils.DENOMINATOR) - turn_round(consensuses) - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, Utils.DENOMINATOR) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT, Utils.DENOMINATOR) - transfer_btc_lst_success(accounts[0], BTC_VALUE * 2 + BTC_VALUE // 2, accounts[1]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - claim_stake_and_relay_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 4 - delegate_btc_lst_success(accounts[0], BTC_VALUE * 3, LOCK_SCRIPT, Utils.DENOMINATOR) - transfer_btc_lst_success(accounts[0], BTC_VALUE * 3 + BTC_VALUE // 2, accounts[1]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == 0 - tracker1.update_height() - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == TOTAL_REWARD * 3 - - -def test_immediate_transfer_after_stake(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, LOCK_SCRIPT, Utils.DENOMINATOR) - delegate_btc_lst_success(accounts[1], BTC_VALUE, LOCK_SCRIPT, Utils.DENOMINATOR) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == TOTAL_REWARD * 3 - - -def test_multiple_btc_lst_transfers(validator_set, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE * 2, LOCK_SCRIPT, Utils.DENOMINATOR) - delegate_btc_lst_success(accounts[1], BTC_VALUE * 2, LOCK_SCRIPT, Utils.DENOMINATOR) - turn_round(consensuses) - transfer_btc_lst_success(accounts[0], BTC_VALUE, accounts[1]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 2 // 2 - assert tracker1.delta() == TOTAL_REWARD * 3 // 2 - turn_round(consensuses) - claim_stake_and_relay_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 3 // 4 - assert tracker1.delta() == TOTAL_REWARD * 3 * 3 // 4 - - -def __get_redeem_requests(index): - redeem_request = BTC_LST_STAKE.redeemRequests(index) - return redeem_request - - -def __get_btc_tx_map(tx_id): - bt = BTC_LST_STAKE.btcTxMap(tx_id) - return bt - - -def __check_user_stake_info(delegator, result: dict): - user_info = BTC_LST_STAKE.userStakeInfo(delegator) - for r in result: - assert result[r] == user_info[r] - - -def __check_redeem_requests(redeem_index, result: dict): - redeem_request = BTC_LST_STAKE.redeemRequests(redeem_index) - for i in result: - assert redeem_request[i] == result[i] diff --git a/tests/test_dual_staking.py b/tests/test_dual_staking.py index dc3dfe66b..60a192121 100644 --- a/tests/test_dual_staking.py +++ b/tests/test_dual_staking.py @@ -23,9 +23,7 @@ # BTC delegation-related LOCK_TIME = 1736956800 LOCK_SCRIPT = "0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac" -# BTCLST delegation-related -BTCLST_LOCK_SCRIPT = "0xa914cdf3d02dd323c14bea0bed94962496c80c09334487" -BTCLST_REDEEM_SCRIPT = "0xa914047b9ba09367c1b213b5ba2184fba3fababcdc0287" + stake_manager = StakeManager() @@ -38,9 +36,9 @@ def deposit_for_reward(validator_set, gov_hub, system_reward): @pytest.fixture(scope="module", autouse=True) def set_block_reward(validator_set, candidate_hub, btc_light_client, btc_stake, stake_hub, core_agent, pledge_agent, - btc_lst_stake, gov_hub, hash_power_agent, btc_agent, system_reward): + gov_hub, hash_power_agent, btc_agent, system_reward): global BLOCK_REWARD, FEE, COIN_REWARD, BTC_REWARD_NO_POWER, COIN_REWARD_NO_POWER, BTC_REWARD, TOTAL_REWARD, HASH_POWER_AGENT, BTC_AGENT, stake_manager - global BTC_STAKE, STAKE_HUB, CORE_AGENT, BTC_LIGHT_CLIENT, MIN_INIT_DELEGATE_VALUE, CANDIDATE_HUB, BTC_LST_STAKE + global BTC_STAKE, STAKE_HUB, CORE_AGENT, BTC_LIGHT_CLIENT, MIN_INIT_DELEGATE_VALUE, CANDIDATE_HUB FEE = FEE * 100 block_reward = validator_set.blockReward() block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() @@ -67,9 +65,7 @@ def set_block_reward(validator_set, candidate_hub, btc_light_client, btc_stake, btc_agent.setIsActive(True) btc_agent.setAssetWeight(1) system_reward.setOperator(stake_hub.address) - BTC_LST_STAKE = btc_lst_stake HASH_POWER_AGENT = hash_power_agent - btc_lst_stake.updateParam('add', BTCLST_LOCK_SCRIPT, {'from': gov_hub.address}) @pytest.fixture(scope="module", autouse=True) @@ -232,35 +228,6 @@ def test_btc_reward_discount_by_core_stake_amount(btc_stake, candidate_hub, btc_ assert STAKE_HUB.surplus() == unclaimed_reward -@pytest.mark.parametrize("core_rate", [0, 4500, 8000, 11000, 12001, 17000]) -def test_btc_lst_reward_unaffected_by_core_amount(btc_stake, set_candidate, btc_lst_stake, core_rate): - stake_manager.set_is_stake_hub_active(True) - operators, consensuses = set_candidate - turn_round() - turn_round(consensuses, round_count=3) - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - btc_reward = TOTAL_REWARD * 3 - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == btc_reward // 2 - assert STAKE_HUB.surplus() == btc_reward - btc_reward // 2 - - -@pytest.mark.parametrize("percentage", [0, 1000, 4500, 5000, 6000, 7000, 10000]) -def test_update_btc_lst_percentage_and_claim(btc_stake, set_candidate, btc_lst_stake, percentage): - stake_manager.set_is_stake_hub_active(True) - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT, percentage) - turn_round(consensuses, round_count=2) - btc_reward = TOTAL_REWARD * 3 - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == btc_reward * percentage // Utils.DENOMINATOR - assert STAKE_HUB.surplus() == btc_reward - btc_reward * percentage // Utils.DENOMINATOR - - @pytest.mark.parametrize("core_rate", [0, 2001, 8000, 12000, 15000, 18000, 22001, 28000]) def test_core_hash_btc_rewards_discounted_by_core_ratio(btc_stake, candidate_hub, btc_light_client, set_candidate, core_agent, core_rate): @@ -535,48 +502,6 @@ def test_turn_round_core_rewards_without_core_stake(btc_stake, stake_hub, set_ca assert __calc_unclaimed_reward(TOTAL_REWARD, discount) * 2 == unclaimed_reward -@pytest.mark.parametrize("percentage", [400, 1000, 8800, 10000]) -def test_btc_lst_discount_by_percentage(btc_stake, set_candidate, btc_agent, percentage): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(percentage)), 64) - btc_agent.updateParam('lstGradePercentage', hex_value, {'from': accounts[0]}) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - btc_reward = TOTAL_REWARD * 3 - actual_reward = TOTAL_REWARD * 3 * percentage // Utils.DENOMINATOR - bonus = btc_reward - actual_reward - assert tracker.delta() == actual_reward - assert STAKE_HUB.surplus() == bonus - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert STAKE_HUB.surplus() == bonus * 2 - - -@pytest.mark.parametrize("percentage", [[12000, 8127], [20000, 40635]]) -def test_btc_lst_claim_extra_reward(btc_stake, set_candidate, btc_agent, percentage): - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(percentage[0])), 64) - btc_agent.updateParam('lstGradePercentage', hex_value, {'from': accounts[0]}) - tracker = get_tracker(accounts[0]) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['rewardTo']['amount'] == percentage[1] - actual_reward = TOTAL_REWARD * 3 * percentage[0] // Utils.DENOMINATOR - assert tracker.delta() == actual_reward - assert STAKE_HUB.surplus() == 0 - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == actual_reward - assert STAKE_HUB.surplus() == 0 - - def test_bonus_exclusive_to_btc_stake(btc_stake, btc_agent, set_candidate, stake_hub): stake_manager.set_is_stake_hub_active(True) stake_manager.set_lp_rates([[0, 20000]]) @@ -596,35 +521,20 @@ def test_bonus_exclusive_to_btc_stake(btc_stake, btc_agent, set_candidate, stake assert stake_hub.surplus() == 0 -def test_no_extra_bonus_for_btc_lst_state(btc_stake, btc_agent, set_candidate, stake_hub): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_lp_rates([[0, 20000]]) - operators, consensuses = set_candidate - turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT, percentage=Utils.DENOMINATOR) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[:3]) - assert tracker0.delta() == TOTAL_REWARD * 3 - assert stake_hub.surplus() == 0 - - -def test_btc_staking_reward_depleted(btc_stake, set_candidate, btc_lst_stake, stake_hub): +def test_btc_staking_reward_depleted(btc_stake, set_candidate, stake_hub): stake_manager.set_is_btc_stake_active(True) stake_manager.set_tlp_rates([[0, 0]]) operators, consensuses = set_candidate turn_round() delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round(consensuses, round_count=2) - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT, 0) - turn_round(consensuses, round_count=2) tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == 0 turn_round(consensuses) -def test_btc_expired_excluded_from_stake(btc_stake, set_candidate, btc_lst_stake): +def test_btc_expired_excluded_from_stake(btc_stake, set_candidate): delegate_amount = 10000 btc_amount = 1 stake_manager.set_lp_rates([[10000, 5000], [30000, 2000], [40000, 10000], [40001, 3000]]) @@ -646,7 +556,7 @@ def test_btc_expired_excluded_from_stake(btc_stake, set_candidate, btc_lst_stake @pytest.mark.parametrize("round", [0, 1, 2, 3]) -def test_acc_stake_amount_after_btc_transfer(btc_stake, set_candidate, btc_lst_stake, round): +def test_acc_stake_amount_after_btc_transfer(btc_stake, set_candidate, round): delegate_amount = 10000 btc_amount = 1 stake_manager.set_lp_rates([[10000, 5000], [30000, 2000], [40000, 10000], [40001, 3000]]) @@ -675,7 +585,7 @@ def test_acc_stake_amount_after_btc_transfer(btc_stake, set_candidate, btc_lst_s [([0, 1000], [2500, 5000], [10000, 10000], [10001, 3000]), 1000000, 1, 200], [([0, 1000], [2500, 10000], [5000, 5000], [10001, 3000]), 10000000, 20, 1000]] ) -def test_calc_stake_after_coin_unstake(btc_stake, stake_hub, set_candidate, btc_lst_stake, stake_amounts): +def test_calc_stake_after_coin_unstake(btc_stake, stake_hub, set_candidate, stake_amounts): delegate_amount = stake_amounts[1] power_value = stake_amounts[2] btc_value = stake_amounts[3] @@ -714,7 +624,7 @@ def test_calc_stake_after_coin_unstake(btc_stake, stake_hub, set_candidate, btc_ {'coinAmount': 0, 'btcAmount': 10e8, 'duration': 1, 'expect_reward_pool': 1.764e18, 'expect_btc_reward': 3.6e16}, ]) -def test_dual_staking_reward_in_current_round(btc_stake, btc_agent, stake_hub, validator_set, btc_lst_stake, +def test_dual_staking_reward_in_current_round(btc_stake, btc_agent, stake_hub, validator_set, set_candidate, tests, round_count): btc_agent.setAssetWeight(1e10) tx_fee = ONE_ETHER @@ -763,7 +673,7 @@ def test_dual_staking_reward_in_current_round(btc_stake, btc_agent, stake_hub, v {'delegator': ['delegateBtc', 'transferBtc'], 'expect_claim_reward': 1.35e18, 'expect_btc_reward': 6.75e18}, {'delegator': ['transferBtc', 'delegateBtc'], 'expect_claim_reward': 4.5e17, 'expect_btc_reward': 4.05e18}, ]) -def test_dual_staking_reward_after_rounds(btc_stake, btc_agent, stake_hub, validator_set, btc_lst_stake, +def test_dual_staking_reward_after_rounds(btc_stake, btc_agent, stake_hub, validator_set, set_candidate, tests): btc_agent.setAssetWeight(1e10) tx_fee = ONE_ETHER @@ -797,7 +707,7 @@ def test_dual_staking_reward_after_rounds(btc_stake, btc_agent, stake_hub, valid turn_round(consensuses, tx_fee=tx_fee) -def test_dual_staking_claim_success_after_btc_expiration(btc_stake, btc_agent, stake_hub, validator_set, btc_lst_stake, +def test_dual_staking_claim_success_after_btc_expiration(btc_stake, btc_agent, stake_hub, validator_set, set_candidate): btc_value = 100 stake_manager.set_is_stake_hub_active(True) @@ -842,7 +752,7 @@ def test_claim_reward_after_modify_asset_weight(btc_stake, btc_agent, {'rate': [[0, 5000], [2500, 10000], [10000, 1000], [10001, 3000]], 'coin': 10000, 'btc': 3, 'expect_reward': 6772 + 6772}, ]) -def test_cancel_immediately_after_transfer(btc_stake, stake_hub, set_candidate, btc_lst_stake, tests): +def test_cancel_immediately_after_transfer(btc_stake, stake_hub, set_candidate, tests): delegate_amount = tests['coin'] btc_value = tests['btc'] rates = tests['rate'] @@ -870,7 +780,7 @@ def test_cancel_immediately_after_transfer(btc_stake, stake_hub, set_candidate, ['transfer_btc', 13545, 13545 * 2 + 13545 // 2 * 2], ['claim', 13545 + 13545 // 2, 13545 * 2 + 13545 // 2 * 2] ]) -def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, core_agent, set_candidate, btc_lst_stake, +def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, core_agent, set_candidate, tests): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) @@ -911,8 +821,7 @@ def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, co assert tracker0.delta() == tests[2] -def test_staking_with_duration_discount_claim_reward_success(btc_stake, stake_hub, core_agent, set_candidate, - btc_lst_stake): +def test_staking_with_duration_discount_claim_reward_success(btc_stake, stake_hub, core_agent, set_candidate): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates([[0, 5000]]) @@ -949,7 +858,7 @@ def test_staking_with_duration_discount_claim_reward_success(btc_stake, stake_hu ['trb', 13545, 13545 * 2 + 13545 // 2 * 2], ['cl', 13545 + 13545 // 2, 13545 * 2 + 13545 // 2 * 2] ]) -def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agent, set_candidate, btc_lst_stake, tests): +def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agent, set_candidate, tests): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -987,7 +896,7 @@ def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agen assert tracker0.delta() == tests[2] -def test_dual_staking_multiple_rounds_operations_success(btc_stake, stake_hub, core_agent, btc_lst_stake): +def test_dual_staking_multiple_rounds_operations_success(btc_stake, stake_hub, core_agent): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -1020,7 +929,7 @@ def test_dual_staking_multiple_rounds_operations_success(btc_stake, stake_hub, c @pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'transfer', 'delegate_btc', 'transfer_btc', 'claim']) -def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_hub, core_agent, btc_lst_stake, tests): +def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_hub, core_agent, tests): stake_manager.set_lp_rates([[0, 1000], [3750, 2500], [3751, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -1065,7 +974,7 @@ def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_h turn_round(consensuses) -def test_calculate_reward_success(btc_stake, stake_hub, core_agent, btc_lst_stake): +def test_calculate_reward_success(btc_stake, stake_hub, core_agent): stake_manager.set_lp_rates([[0, 1000], [3750, 2500], [3751, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -1467,30 +1376,6 @@ def test_coin_staking_reward_settlement(set_candidate, pledge_agent, stake_hub): assert tracker.delta() * TOTAL_REWARD // 2 * 2 -def test_btclst_staking_reward_settlement(set_candidate, pledge_agent, stake_hub): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - delegate_btc_lst_success(accounts[0], delegate_amount, BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[1], delegate_amount, BTCLST_LOCK_SCRIPT) - turn_round(consensuses) - delegate_btc_lst_success(accounts[0], delegate_amount, BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[1], delegate_amount, BTCLST_LOCK_SCRIPT) - turn_round(consensuses) - delegate_btc_lst_success(accounts[0], delegate_amount, BTCLST_LOCK_SCRIPT) - delegate_btc_lst_success(accounts[1], delegate_amount, BTCLST_LOCK_SCRIPT) - turn_round(consensuses, round_count=3) - delegate_coin_success(operators[2], accounts[0], delegate_amount) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() * TOTAL_REWARD // 2 * 4 - turn_round(consensuses, round_count=2) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() * TOTAL_REWARD // 2 * 2 - - def test_unified_reward_settlement_for_different_validators(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -1950,7 +1835,7 @@ def test_change_round_diff_with_power_stake(stake_hub, set_candidate): assert trackers[0].delta() == account_reward -def test_change_round_diff_with_btcLst_stake(stake_hub, set_candidate): +def test_upgrade_claim_reward_with_diff_change_round_btcLst(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() stake_manager.set_lp_rates([[0, 10000]]) @@ -1958,7 +1843,6 @@ def test_change_round_diff_with_btcLst_stake(stake_hub, set_candidate): turn_round() delegate_amount = 500000 btc_value = 100 - btc_lst_value = 1000 for account in accounts[:2]: for op in operators[:2]: mock_delegate_coin_success(op, account, delegate_amount) @@ -1972,7 +1856,6 @@ def test_change_round_diff_with_btcLst_stake(stake_hub, set_candidate): delegate_power_success(operators[0], accounts[2]) delegate_power_success(operators[1], accounts[2]) turn_round(consensuses) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) trackers = get_trackers(accounts[:3]) account_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) turn_round(consensuses) @@ -1992,7 +1875,7 @@ def test_change_round_diff_with_btcLst_stake(stake_hub, set_candidate): }, { "address": operators[2] } - ], TOTAL_REWARD, {accounts[0]: set_btc_lst_delegate(btc_lst_value)}) + ], TOTAL_REWARD) assert trackers[0].delta() == account_rewards[accounts[0]] + account_reward turn_round(consensuses) tx = stake_hub_claim_reward(accounts[0]) @@ -2000,24 +1883,21 @@ def test_change_round_diff_with_btcLst_stake(stake_hub, set_candidate): assert tx.events['claimedCoinReward']['accStakedAmount'] == delegate_amount * 2 -def test_change_round_same_add_btcLst_stake(stake_hub, set_candidate): +def test_upgrade_same_change_round_claim_reward(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - btc_lst_value = 1000 for account in accounts[:2]: delegate_coin_success(operators[0], account, delegate_amount) turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) assert stake_hub.getDelegatorMap(accounts[0])[1][0] == TOTAL_REWARD // 2 delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT) delegate_power_success(operators[0], accounts[2]) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) turn_round(consensuses) - turn_round(consensuses) stake_manager.set_lp_rates([[4999, 1000], [5000, 10000], [5001, 1000]]) _, _, account_rewards, _ = parse_delegation([{ @@ -2030,7 +1910,7 @@ def test_change_round_same_add_btcLst_stake(stake_hub, set_candidate): }, { "address": operators[2] } - ], TOTAL_REWARD, {accounts[0]: set_btc_lst_delegate(btc_lst_value)}) + ], TOTAL_REWARD) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert tracker.delta() == account_rewards[accounts[0]] + TOTAL_REWARD // 2 * 2 @@ -2192,8 +2072,7 @@ def test_claim_reward_after_refund_surplus(stake_hub, system_reward, set_candida assert tracker.delta() == TOTAL_REWARD * 3 -def test_calculateReward_success(btc_stake, stake_hub, core_agent, set_candidate, - btc_lst_stake, ): +def test_calculateReward_success(btc_stake, stake_hub, core_agent, set_candidate, ): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [5001, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -2219,8 +2098,7 @@ def test_calculateReward_success(btc_stake, stake_hub, core_agent, set_candidate assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 -def test_calculateReward_repeated(btc_stake, stake_hub, core_agent, set_candidate, - btc_lst_stake, ): +def test_calculateReward_repeated(btc_stake, stake_hub, core_agent, set_candidate): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [5001, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -2250,8 +2128,7 @@ def test_calculateReward_repeated(btc_stake, stake_hub, core_agent, set_candidat assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 -def test_calculateReward_with_latest_stake(btc_stake, stake_hub, core_agent, set_candidate, - btc_lst_stake, ): +def test_calculateReward_with_latest_stake(btc_stake, stake_hub, core_agent, set_candidate): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [5001, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -2277,8 +2154,7 @@ def test_calculateReward_with_latest_stake(btc_stake, stake_hub, core_agent, set @pytest.mark.parametrize("onStakeChange", [True, False]) -def test_calculateReward_after_current_round_claim(btc_stake, stake_hub, core_agent, set_candidate, - btc_lst_stake, onStakeChange): +def test_calculateReward_after_current_round_claim(btc_stake, stake_hub, core_agent, set_candidate, onStakeChange): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -2311,8 +2187,7 @@ def test_calculateReward_after_current_round_claim(btc_stake, stake_hub, core_ag @pytest.mark.parametrize("round_count", [[2, 13545 // 2 * 2, 1693], [3, 13545 + 6772, 6772 // 2], [4, 13545 * 2 - 1, 6772]]) -def test_calculateReward_with_expired_btc(btc_stake, stake_hub, core_agent, set_candidate, - btc_lst_stake, round_count): +def test_calculateReward_with_expired_btc(btc_stake, stake_hub, core_agent, set_candidate, round_count): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [7500, 5000], [7501, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -2385,13 +2260,11 @@ def test_calculateReward_with_all_stakes(btc_stake, btc_agent, stake_hub, core_a turn_round() delegate_amount = 500000 btc_value = 100 - btc_lst_value = 1000 delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT) delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_coin_success(operators[0], accounts[1], delegate_amount) delegate_power_success(operators[0], accounts[2]) - delegate_btc_lst_success(accounts[0], btc_lst_value, BTCLST_LOCK_SCRIPT) turn_round(consensuses, round_count=2) tracker0 = get_tracker(accounts[0]) _, _, account_rewards, _ = parse_delegation([{ @@ -2403,7 +2276,7 @@ def test_calculateReward_with_all_stakes(btc_stake, btc_agent, stake_hub, core_a "address": operators[1] }, { "address": operators[2] - }], BLOCK_REWARD // 2, {accounts[0]: set_btc_lst_delegate(btc_lst_value)}) + }], BLOCK_REWARD // 2) stake_hub_calculate_reward(accounts[0]) stake_hub_calculate_reward(accounts[0]) stake_hub_claim_reward(accounts[0]) diff --git a/tests/test_hash_power_agent.py b/tests/test_hash_power_agent.py index dc8754658..f9232c48e 100644 --- a/tests/test_hash_power_agent.py +++ b/tests/test_hash_power_agent.py @@ -143,7 +143,7 @@ def test_get_power_stake_amounts_success(hash_power_agent, set_candidate): @pytest.mark.parametrize("round_count", [0, 1, 2, 3]) -def test_power_valid_for_one_round(btc_stake, set_candidate, hash_power_agent, btc_lst_stake, round_count): +def test_power_valid_for_one_round(btc_stake, set_candidate, hash_power_agent, round_count): power_value = 1 operators, consensuses = set_candidate delegate_power_success(operators[0], accounts[0], power_value) diff --git a/tests/test_pledge_agent.py b/tests/test_pledge_agent.py index 71ad27c49..715334341 100644 --- a/tests/test_pledge_agent.py +++ b/tests/test_pledge_agent.py @@ -37,8 +37,7 @@ def set_min_init_delegate_value(min_init_delegate_value): @pytest.fixture(scope="module", autouse=True) -def set_block_reward(validator_set, pledge_agent, stake_hub, core_agent, btc_stake, btc_lst_stake, gov_hub, - system_reward): +def set_block_reward(validator_set, pledge_agent, stake_hub, core_agent, btc_stake, gov_hub, system_reward): global BLOCK_REWARD, TOTAL_REWARD global PLEDGE_AGENT, STAKE_HUB, BTC_STAKE, CORE_AGENT block_reward = validator_set.blockReward() @@ -52,7 +51,6 @@ def set_block_reward(validator_set, pledge_agent, stake_hub, core_agent, btc_sta BTC_STAKE = btc_stake set_block_time_stamp(150, LOCK_TIME) system_reward.setOperator(stake_hub.address) - btc_lst_stake.updateParam('add', BTCLST_LOCK_SCRIPT, {'from': gov_hub.address}) @pytest.fixture() @@ -246,10 +244,10 @@ def test_claim_reward(pledge_agent, candidate_hub, agents_type: str): def test_claim_reward_success(btc_agent, pledge_agent, set_candidate): operators, consensuses = set_candidate turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT, percentage=Utils.DENOMINATOR) + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) - actual_reward = TOTAL_REWARD * 3 + actual_reward = TOTAL_REWARD pledge_agent.claimReward(operators) assert tracker.delta() == actual_reward - FEE @@ -257,7 +255,9 @@ def test_claim_reward_success(btc_agent, pledge_agent, set_candidate): def test_claim_reward_validator_address_empty(btc_agent, pledge_agent, set_candidate): operators, consensuses = set_candidate turn_round() - delegate_btc_lst_success(accounts[0], BTC_VALUE, BTCLST_LOCK_SCRIPT, percentage=Utils.DENOMINATOR) + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) + delegate_btc_success(operators[2], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) actual_reward = TOTAL_REWARD * 3 @@ -428,7 +428,7 @@ def test_move_btc_data_then_claim_btc_reward(pledge_agent, btc_stake, success): with brownie.reverts("btc tx not found"): pledge_agent.claimBtcReward(tx_ids) tx = stake_hub_claim_reward(accounts[0]) - assert len(tx.events) == 5 + assert len(tx.events) == 4 turn_round([consensus]) with brownie.reverts("btc tx not found"): pledge_agent.claimBtcReward(tx_ids) @@ -3294,7 +3294,6 @@ def test_cancel_stake_with_zero_amount(stake_hub, core_agent, set_candidate): def test_claim_reward_with_both_proxy_and_regular_staking(stake_hub, btc_stake, core_agent, set_candidate): delegate_amount = 10000 btc_amount = 1000 - btc_lst_amount = 200 power_value = 5 operators, consensuses = set_candidate turn_round() @@ -3303,7 +3302,6 @@ def test_claim_reward_with_both_proxy_and_regular_staking(stake_hub, btc_stake, delegate_btc_success(operators[0], accounts[0], btc_amount, LOCK_SCRIPT) tx_id = delegate_btc_success(operators[0], accounts[1], btc_amount, LOCK_SCRIPT, relay=accounts[1]) delegate_power_success(operators[0], accounts[3], power_value) - delegate_btc_lst_success(accounts[0], btc_lst_amount, BTCLST_LOCK_SCRIPT, percentage=Utils.DENOMINATOR) turn_round() old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) old_undelegate_coin_success(operators[0], accounts[0], 0, old=False) @@ -3329,7 +3327,7 @@ def test_claim_reward_with_both_proxy_and_regular_staking(stake_hub, btc_stake, }], TOTAL_REWARD, state_map={ 'btc_lst_gradeActive': 0, 'btc_gradeActive': 0 - }, btc_lst_stake={accounts[0]: set_btc_lst_delegate(btc_lst_amount)}) + }) for index, tracker in enumerate(trackers): assert tracker.delta() == account_rewards.get(tracker.address, 0) @@ -3947,28 +3945,6 @@ def test_move_core_data_gas_check(pledge_agent, set_candidate, validator_set, ca old_claim_reward_success(operators, accounts[0]) -def test_stake_lst_in_upgrade_round(core_agent, btc_lst_stake, set_candidate): - btc_value = 10000 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - delegate_btc_lst_success(accounts[0], btc_value, BTCLST_LOCK_SCRIPT) - assert btc_lst_stake.stakedAmount() == 0 - assert btc_lst_stake.realtimeAmount() == btc_value - - def __init_hybrid_score_mock(): STAKE_HUB.initHybridScoreMock() set_round_tag(get_current_round()) diff --git a/tests/test_stake_hub_unit.py b/tests/test_stake_hub_unit.py index 8f15b1e4b..db3c1139e 100644 --- a/tests/test_stake_hub_unit.py +++ b/tests/test_stake_hub_unit.py @@ -1,10 +1,7 @@ import pytest import brownie import rlp -from brownie import * -from .delegate import delegate_btc_success, delegate_coin_success, delegate_btc_lst_success -from .utils import * -from .common import register_candidate, turn_round, get_current_round, stake_hub_claim_reward +from .common import * from collections import OrderedDict from .delegate import * @@ -34,7 +31,7 @@ def set_candidate(): @pytest.fixture(scope="module", autouse=True) -def set_up(min_init_delegate_value, core_agent, candidate_hub, btc_lst_stake, btc_agent, hash_power_agent, +def set_up(min_init_delegate_value, core_agent, candidate_hub, btc_agent, hash_power_agent, btc_light_client, validator_set, stake_hub, btc_stake, system_reward, gov_hub): global MIN_INIT_DELEGATE_VALUE global CANDIDATE_REGISTER_MARGIN @@ -45,15 +42,13 @@ def set_up(min_init_delegate_value, core_agent, candidate_hub, btc_lst_stake, bt global actual_block_reward global COIN_REWARD global BLOCK_REWARD - global BTC_STAKE, STAKE_HUB, BTC_AGENT, CORE_AGENT, BTC_LST_STAKE, HASH_POWER_AGENT, TOTAL_REWARD, GOV_HUB + global BTC_STAKE, STAKE_HUB, BTC_AGENT, CORE_AGENT, HASH_POWER_AGENT, TOTAL_REWARD, GOV_HUB BTC_STAKE = btc_stake STAKE_HUB = stake_hub BTC_AGENT = btc_agent CORE_AGENT = core_agent - BTC_LST_STAKE = btc_lst_stake HASH_POWER_AGENT = hash_power_agent GOV_HUB = gov_hub - btc_agent.setAssetWeight(1) candidate_hub_instance = candidate_hub core_agent_instance = core_agent btc_light_client_instance = btc_light_client @@ -288,18 +283,17 @@ def test_only_candidate_can_call_set_new_round(stake_hub): stake_hub.setNewRound(accounts[:2], 100) -def test_set_new_round_success(stake_hub, core_agent, btc_lst_stake, btc_stake): +def test_set_new_round_success(stake_hub, core_agent, btc_stake): round_tag = 100 update_system_contract_address(stake_hub, candidate_hub=accounts[0]) stake_hub.setNewRound(accounts[:2], round_tag) - assert core_agent.roundTag() == btc_lst_stake.roundTag() == btc_stake.roundTag() == round_tag + assert core_agent.roundTag() == btc_stake.roundTag() == round_tag def __mock_stake_hub_reward(): accounts[3].transfer(STAKE_HUB, Web3.to_wei(1, 'ether')) -@pytest.mark.parametrize("claim", ['btc', 'lst_btc']) @pytest.mark.parametrize("tests", [ {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 0, 'actual_bonus': 0, 'claim_rewards': 10000}, {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 2000, 'actual_bonus': 0, 'claim_rewards': 8000}, @@ -317,9 +311,8 @@ def __mock_stake_hub_reward(): {'btc_reward': 10000, 'unclaimed_reward': 5000, 'reward_pool': 5000, 'actual_bonus': 0, 'claim_rewards': 0}, {'btc_reward': 10000, 'unclaimed_reward': 15000, 'reward_pool': 15000, 'actual_bonus': 20000, 'claim_rewards': 0} ]) -def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests, claim): +def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests): stake_manager.set_lp_rates([[0, 20000]]) - btc_agent.setPercentage(20000) stake_manager.set_is_stake_hub_active(True) btc_reward = tests['btc_reward'] unclaimed_reward = tests['unclaimed_reward'] @@ -328,11 +321,7 @@ def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests, claim): assert stake_hub.surplus() == reward_pool float_reward = btc_reward - unclaimed_reward __mock_stake_hub_reward() - if claim == 'btc': - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) - else: - round_reward_manager.mock_btc_reward_map(accounts[0], 0, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_reward, MIN_INIT_DELEGATE_VALUE) + round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) tracker = get_tracker(accounts[0]) tx = stake_hub.claimReward() if tests['claim_rewards'] > 0: @@ -345,7 +334,6 @@ def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests, claim): assert tracker.delta() == btc_reward * 2 -@pytest.mark.parametrize("claim", ['btc', 'lst_btc']) @pytest.mark.parametrize("tests", [ {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 0, 'actual_bonus': 5000}, {'btc_reward': 10000, 'unclaimed_reward': 3000, 'reward_pool': 0, 'actual_bonus': 8000}, @@ -354,9 +342,8 @@ def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests, claim): {'btc_reward': 10000, 'unclaimed_reward': 1000, 'reward_pool': 2000, 'actual_bonus': 8000}, {'btc_reward': 0, 'unclaimed_reward': 1000, 'reward_pool': 2000, 'actual_bonus': 3000} ]) -def test_btc_no_bonus(stake_hub, btc_agent, tests, claim): +def test_btc_no_bonus(stake_hub, btc_agent, tests): stake_manager.set_lp_rates([[0, 5000]]) - btc_agent.setPercentage(5000) stake_manager.set_is_stake_hub_active(True) btc_reward = tests['btc_reward'] actual_reward = btc_reward // 2 @@ -366,83 +353,79 @@ def test_btc_no_bonus(stake_hub, btc_agent, tests, claim): reward_pool += actual_reward reward_pool += unclaimed_reward __mock_stake_hub_reward() - if claim == 'btc': - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) - else: - round_reward_manager.mock_btc_reward_map(accounts[0], 0, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_reward, MIN_INIT_DELEGATE_VALUE) + round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) tracker = get_tracker(accounts[0]) - tx = stake_hub.claimReward() + stake_hub.claimReward() assert tracker.delta() == actual_reward assert stake_hub.surplus() == reward_pool assert reward_pool == tests['actual_bonus'] @pytest.mark.parametrize("tests", [ - {'surplus': 2000, 'duration_unclaimed': 3000, 'btc_lst_percentage': 10000, 'rewardTo': 5000, 'actual_surplus': 0}, - {'surplus': 2000, 'duration_unclaimed': 3000, 'btc_lst_percentage': 0, 'rewardTo': 0, 'actual_surplus': 5000}, - {'surplus': 0, 'duration_unclaimed': 0, 'btc_lst_percentage': 5000, 'rewardTo': 5000, 'actual_surplus': 0}, - {'surplus': 0, 'duration_unclaimed': 2000, 'btc_lst_percentage': 20000, 'rewardTo': 18000, 'actual_surplus': 0}, - {'surplus': 1000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 5000, 'rewardTo': 2000, 'actual_surplus': 0} + {'surplus': 2000, 'duration_unclaimed': 3000, 'rewardTo': 5000, 'actual_surplus': 0}, + {'surplus': 5000, 'duration_unclaimed': 15000, 'rewardTo': 0, 'actual_surplus': 10000}, + {'surplus': 5000, 'duration_unclaimed': 10000, 'rewardTo': 0, 'actual_surplus': 5000}, + {'surplus': 0, 'duration_unclaimed': 10000, 'rewardTo': 0, 'actual_surplus': 0}, + {'surplus': 10000, 'duration_unclaimed': 0, 'rewardTo': 0, 'actual_surplus': 0}, + {'surplus': 4000, 'duration_unclaimed': 6000, 'rewardTo': 0, 'actual_surplus': 0}, + {'surplus': 3000, 'duration_unclaimed': 6000, 'rewardTo': 1000, 'actual_surplus': 0}, + {'surplus': 0, 'duration_unclaimed': 0, 'rewardTo': 10000, 'actual_surplus': 0}, + {'surplus': 7000, 'duration_unclaimed': 6000, 'rewardTo': 0, 'actual_surplus': 3000} ]) -def test_get_rewards_from_systemReward(stake_hub, btc_agent, btc_lst_stake, tests): +def test_get_rewards_from_systemReward(stake_hub, btc_agent, tests): __mock_stake_hub_reward() stake_hub.setSurplus(tests['surplus']) btc_reward = 10000 duration_unclaimed = tests['duration_unclaimed'] - btc_lst_percentage = tests['btc_lst_percentage'] btc_percentage = 20000 stake_manager.set_lp_rates([[0, btc_percentage]]) - btc_agent.setPercentage(btc_lst_percentage) stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - btc_lst_reward = 10000 - actual_btc_lst_reward = btc_lst_reward * btc_lst_percentage // Utils.DENOMINATOR round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_lst_reward, 0) - actual_reward = actual_btc_reward + actual_btc_lst_reward tracker = get_tracker(accounts[0]) tx = stake_hub_claim_reward(accounts[0]) + print('dsaf', tx.events) if tests['rewardTo'] > 0: assert tx.events['rewardTo']['amount'] == tests['rewardTo'] else: assert 'rewardTo' not in tx.events assert stake_hub.surplus() == tests['actual_surplus'] - assert tracker.delta() == actual_reward + assert tracker.delta() == actual_btc_reward @pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 8000, - 'btc_percentage': 8000, 'surplus': 3000, 'expect_reward': 16000, 'expect_surplus': 7000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 12000, - 'btc_percentage': 6000, 'surplus': 3000, 'expect_reward': 18000, 'expect_surplus': 5000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 12000, - 'btc_percentage': 10000, 'surplus': 3000, 'expect_reward': 22000, 'expect_surplus': 1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 3000, 'btc_lst_percentage': 15000, - 'btc_percentage': 15000, 'surplus': 2000, 'expect_reward': 30000, 'expect_surplus': 0}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 10000, - 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 22000, 'expect_surplus': 0}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 5000, - 'btc_percentage': 5000, 'surplus': 0, 'expect_reward': 10000, 'expect_surplus': 12000} + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 8000, 'surplus': 3000, 'expect_reward': 8000, + 'expect_surplus': 5000}, + {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 6000, 'surplus': 3000, 'expect_reward': 6000, + 'expect_surplus': 9000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 10000, 'surplus': 3000, 'expect_reward': 10000, + 'expect_surplus': 3000}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 15000, 'surplus': 2000, 'expect_reward': 15000, + 'expect_surplus': 0}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, + 'expect_surplus': 1000}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 0, 'surplus': 2000, 'expect_reward': 0, + 'expect_surplus': 15000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 10000, 'surplus': 2000, 'expect_reward': 10000, + 'expect_surplus': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 20000, 'surplus': 2000, 'expect_reward': 20000, + 'expect_surplus': 0}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 5000, 'surplus': 0, 'expect_reward': 5000, + 'expect_surplus': 5000} ]) -def test_claim_reward_update_surplus(stake_hub, btc_agent, btc_lst_stake, tests): +def test_claim_reward_update_surplus(stake_hub, btc_agent, tests): __mock_stake_hub_reward() stake_hub.setSurplus(tests['surplus']) btc_reward = tests['btc_reward'] duration_unclaimed = tests['duration_unclaimed'] - btc_lst_percentage = tests['btc_lst_percentage'] btc_percentage = tests['btc_percentage'] stake_manager.set_lp_rates([[0, btc_percentage]]) - btc_agent.setPercentage(btc_lst_percentage) stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - btc_lst_reward = tests['btc_lst_reward'] - actual_btc_lst_reward = btc_lst_reward * btc_lst_percentage // Utils.DENOMINATOR round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_lst_reward, 0) - actual_reward = actual_btc_reward + actual_btc_lst_reward + actual_reward = actual_btc_reward tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert actual_reward == tests['expect_reward'] @@ -467,71 +450,84 @@ def test_only_pledge_agent_can_call(stake_hub): def test_proxy_claim_reward_success(stake_hub, btc_agent, pledge_agent, set_candidate): - fee = 0 delegate_amount = 1000000 operators, consensuses = set_candidate turn_round() delegate_coin_success(operators[0], accounts[2], delegate_amount) script, pay_address, timestamp = random_btc_lock_script() delegate_btc_success(operators[1], accounts[2], 100, script, timestamp, relay=accounts[2]) - btc_lst_scirpt = random_btc_lst_lock_script() - stake_manager.add_wallet(btc_lst_scirpt) - delegate_btc_lst_success(accounts[2], 200, btc_lst_scirpt, relay=accounts[2], percentage=Utils.DENOMINATOR) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) update_system_contract_address(stake_hub, pledge_agent=accounts[0]) stake_hub.proxyClaimReward(accounts[2]) - assert tracker.delta() == BLOCK_REWARD // 2 * 3 - 1 - fee * 2 + assert tracker.delta() == BLOCK_REWARD -def test_calculate_reward_success(stake_hub, btc_agent, core_agent, btc_lst_stake, btc_stake, hash_power_agent, +def test_calculate_reward_success(stake_hub, btc_agent, core_agent, btc_stake, hash_power_agent, set_candidate): accounts[3].transfer(stake_hub, Web3.to_wei(1, 'ether')) reward = 10000 - actual_rewards = [reward, reward, reward] + btc_reward = 6000 + actual_rewards = [reward, reward, btc_reward] core_agent.setCoreRewardMap(accounts[0], reward, 0) hash_power_agent.setPowerRewardMap(accounts[0], reward, 0) - btc_lst_stake.setBtcLstRewardMap(accounts[0], reward, 0) round_reward_manager.mock_btc_reward_map(accounts[0], reward, 0, MIN_INIT_DELEGATE_VALUE) btc_agent.setIsActive(True) - stake_manager.set_lp_rates([[0, 5000]]) - btc_agent.setPercentage(5000) + stake_manager.set_lp_rates([[0, btc_reward]]) stake_hub.setOperators(accounts[3], True) rewards = stake_hub.calculateRewardMock(accounts[0]).return_value assert rewards == actual_rewards - assert stake_hub.surplus() == reward + assert stake_hub.surplus() == reward - btc_reward @pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 8000, - 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 16000, 'expect_surplus': 4000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 8000, - 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 16000, 'expect_surplus': 5000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 0, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 24000, 'expect_surplus': 0, 'claim_rewards': 4000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 12000, - 'btc_percentage': 12000, 'surplus': 2000, 'expect_reward': 24000, 'expect_surplus': 0, 'claim_rewards': 1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 2000, 'btc_lst_percentage': 11000, - 'btc_percentage': 12000, 'surplus': 2000, 'expect_reward': 23000, 'expect_surplus': 1000}, - {'btc_reward': 10000, 'btc_lst_reward': 10000, 'duration_unclaimed': 1000, 'btc_lst_percentage': 5000, - 'btc_percentage': 12000, 'surplus': 2000, 'expect_reward': 17000, 'expect_surplus': 6000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 8000, + 'expect_surplus': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 8000, + 'expect_surplus': 3000}, + {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 8000, 'surplus': 2000, 'expect_reward': 8000, + 'expect_surplus': 6000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, + 'expect_surplus': 0, 'claim_rewards': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, + 'expect_surplus': 1000}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'surplus': 2000, 'expect_reward': 12000, + 'expect_surplus': 3000}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 20000, 'surplus': 2000, 'expect_reward': 20000, + 'expect_surplus': 0, 'claim_rewards': 5000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, + 'expect_surplus': 0, 'claim_rewards': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 10000, 'surplus': 1000, 'expect_reward': 10000, + 'expect_surplus': 1000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 10000, 'surplus': 1000, 'expect_reward': 10000, + 'expect_surplus': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 1000, 'expect_reward': 15000, + 'expect_surplus': 0, 'claim_rewards': 3000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, + 'expect_surplus': 0, 'claim_rewards': 4000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 6000, 'expect_reward': 15000, + 'expect_surplus': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 6000, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, + 'expect_surplus': 1000}, + {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, + 'expect_surplus': 0, 'claim_rewards': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 1000, 'expect_reward': 15000, + 'expect_surplus': 0, 'claim_rewards': 3000}, + {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 0, 'surplus': 1000, 'expect_reward': 0, + 'expect_surplus': 12000} + ]) -def test_calculate_reward_update_surplus(stake_hub, btc_agent, btc_lst_stake, tests): +def test_calculate_reward_update_surplus(stake_hub, btc_agent, tests): stake_hub.setSurplus(tests['surplus']) btc_reward = tests['btc_reward'] duration_unclaimed = tests['duration_unclaimed'] - btc_lst_percentage = tests['btc_lst_percentage'] btc_percentage = tests['btc_percentage'] stake_manager.set_lp_rates([[0, btc_percentage]]) - btc_agent.setPercentage(btc_lst_percentage) stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - btc_lst_reward = tests['btc_lst_reward'] - actual_btc_lst_reward = btc_lst_reward * btc_lst_percentage // Utils.DENOMINATOR round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_lst_reward, 0) - actual_reward = actual_btc_reward + actual_btc_lst_reward + actual_reward = actual_btc_reward tx = stake_hub.calculateRewardMock(accounts[0]) if tests.get('claim_rewards'): assert tx.events['rewardTo']['amount'] == tests['claim_rewards'] @@ -541,15 +537,13 @@ def test_calculate_reward_update_surplus(stake_hub, btc_agent, btc_lst_stake, te assert reward == [0, 0, actual_reward] -def test_system_reward_balance_insufficient(stake_hub, btc_agent, system_reward, btc_lst_stake): +def test_system_reward_balance_insufficient(stake_hub, btc_agent, system_reward): btc_reward = 100000e18 rate = 20000 stake_manager.set_lp_rates([[0, rate]]) - btc_agent.setPercentage(rate) stake_manager.set_is_stake_hub_active(True) turn_round() - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, 100) - round_reward_manager.mock_btc_lst_reward_map(accounts[0], btc_reward, 10) + round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward * 2, 0, 100) with brownie.reverts('SafeCast: value must be positive'): stake_hub.calculateRewardMock(accounts[0]) @@ -557,17 +551,19 @@ def test_system_reward_balance_insufficient(stake_hub, btc_agent, system_reward, @pytest.mark.parametrize("lp_rates", [ [(0, 1000), (1000, 5000), (30000, 10000)], [(0, 1000), (1000, 2000), (2000, 5000)], - [(0, 5000), (12000, 10000), (20000, 12000)] -]) -def test_claim_rewards_multiple_grades(stake_hub, core_agent, btc_lst_stake, hash_power_agent, btc_agent, lp_rates): + [(0, 5000), (12000, 10000), (20000, 12000)], + [(0, 2000), (8000, 2000), (10000, 5000)], + [(0, 2000), (10000, 5000), (12000, 2000)] +] +) +def test_claim_rewards_multiple_grades(stake_hub, core_agent, hash_power_agent, btc_agent, lp_rates): accounts[3].transfer(stake_hub, Web3.to_wei(1, 'ether')) reward = 10000 actual_rewards = [reward, reward, reward // 2] - core_agent.setCoreRewardMap(accounts[0], reward, 10000) - btc_lst_stake.setBtcLstRewardMap(accounts[0], reward, 1) + round_reward_manager.mock_btc_reward_map(accounts[0], reward, 0, 100) + core_agent.setCoreRewardMap(accounts[0], reward, 1000000) hash_power_agent.setPowerRewardMap(accounts[0], reward, 10) btc_agent.setIsActive(True) - btc_agent.setPercentage(5000) for lp in lp_rates: btc_agent.setLpRates(lp[0], lp[1]) rewards = stake_hub.calculateRewardMock(accounts[0]).return_value @@ -597,7 +593,7 @@ def test_get_delegator_success(stake_hub, set_candidate): assert delegator_map == [get_current_round(), [BLOCK_REWARD // 2, 0, 0]] -def test_onStakeChange_success(stake_hub, set_candidate, btc_lst_stake): +def test_onStakeChange_success(stake_hub, set_candidate): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [5001, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() diff --git a/tests/test_system_reward.py b/tests/test_system_reward.py index 204beecaa..c2a3a62ce 100644 --- a/tests/test_system_reward.py +++ b/tests/test_system_reward.py @@ -431,12 +431,12 @@ def test_whitelist_forbidden_funds_after_round_switch(stake_hub, burn, validator turn_round(consensuses) -def test_revert_when_whitelist_address_receives_funds(btc_lst_stake, system_reward, stake_hub, lst_token): +def test_revert_when_whitelist_address_receives_funds(system_reward, stake_hub): incentive_balance_cap = system_reward.incentiveBalanceCap() accounts[0].transfer(system_reward.address, incentive_balance_cap) turn_round() burn_reward = 10000000 - btc_lst_stake = delegateBtcLstProxy.deploy(btc_lst_stake.address, stake_hub.address, lst_token, + btc_lst_stake = delegateBtcLstProxy.deploy(accounts[1], stake_hub.address, accounts[2], {'from': accounts[0]}) __add_whitelist(btc_lst_stake, Utils.DENOMINATOR // 4) __add_whitelist(accounts[2], Utils.DENOMINATOR // 4) diff --git a/tests/utils.py b/tests/utils.py index 75a790640..bc6e55ecd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -185,10 +185,8 @@ def update_system_contract_address(update_contract, stake_hub=None, btc_stake=None, btc_agent=None, - btc_lst_stake=None, core_agent=None, hash_power_agent=None, - lst_token=None, configuration=None ): if candidate_hub is None: @@ -217,21 +215,16 @@ def update_system_contract_address(update_contract, btc_stake = BitcoinStakeMock[0] if btc_agent is None: btc_agent = BitcoinAgentMock[0] - if btc_lst_stake is None: - btc_lst_stake = BitcoinLSTStakeMock[0] if core_agent is None: core_agent = CoreAgentMock[0] if hash_power_agent is None: hash_power_agent = HashPowerAgentMock[0] - if lst_token is None: - lst_token = BitcoinLSTToken[0] if configuration is None: configuration = ConfigurationMock[0] contracts = [ validator_set, slash_indicator, system_reward, btc_light_client, relay_hub, candidate_hub, gov_hub, - pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, btc_lst_stake, core_agent, hash_power_agent, - lst_token, configuration + pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, core_agent, hash_power_agent, configuration ] args = encode(['address'] * len(contracts), [c.address for c in contracts]) getattr(update_contract, "updateContractAddr")(args) From 8ceaab928a349b878a114f384cd5f8084808db33 Mon Sep 17 00:00:00 2001 From: charles2023wood Date: Tue, 22 Jul 2025 20:28:49 +0800 Subject: [PATCH 11/27] initial impl - commission Signed-off-by: charles2023wood --- .gitignore | 2 +- abi/BitcoinAgent.abi | 2 +- abi/BitcoinStake.abi | 2 +- abi/BtcLightClient.abi | 2 +- abi/Burn.abi | 2 +- abi/CandidateHub.abi | 2 +- abi/Channel.abi | 1 + abi/Configuration.abi | 2 +- abi/Context.abi | 1 - abi/CoreAgent.abi | 2 +- abi/Foundation.abi | 2 +- abi/GovHub.abi | 2 +- abi/HashPowerAgent.abi | 2 +- abi/HashStakeAgent.abi | 1 - abi/PledgeAgent.abi | 2 +- abi/README.md | 2 +- abi/RelayerHub.abi | 2 +- abi/SatoshiPlusHelper.abi | 2 +- abi/SlashIndicator.abi | 2 +- abi/StakeHub.abi | 2 +- abi/SystemReward.abi | 2 +- abi/TeamVesting.abi | 1 - abi/ValidatorSet.abi | 2 +- contracts/BitcoinAgent.sol | 71 +++--- contracts/BitcoinStake.sol | 186 +++++++++------ contracts/Channel.sol | 284 +++++++++++++++++++++++ contracts/CoreAgent.sol | 204 +++++++++------- contracts/HashPowerAgent.sol | 21 +- contracts/PledgeAgent.sol | 11 +- contracts/StakeHub.sol | 40 ++-- contracts/System.sol | 16 +- contracts/System.template | 19 +- contracts/interface/IAgent.sol | 5 +- contracts/interface/IBitcoinStake.sol | 6 +- contracts/interface/IBtcAgent.sol | 21 ++ contracts/interface/IChannel.sol | 27 +++ contracts/interface/ICoreAgent.sol | 29 +++ contracts/lib/SatoshiPlusHelper.sol | 4 +- contracts/lib/SatoshiPlusHelper.template | 4 +- contracts/mock/BitcoinStakeMock.sol | 38 --- contracts/mock/PledgeAgentMock.sol | 66 ------ generate-abi.sh | 4 +- generate-genesis.js | 5 + 43 files changed, 727 insertions(+), 376 deletions(-) create mode 100644 abi/Channel.abi delete mode 100644 abi/Context.abi delete mode 100644 abi/HashStakeAgent.abi delete mode 100644 abi/TeamVesting.abi create mode 100644 contracts/Channel.sol create mode 100644 contracts/interface/IBtcAgent.sol create mode 100644 contracts/interface/IChannel.sol create mode 100644 contracts/interface/ICoreAgent.sol diff --git a/.gitignore b/.gitignore index d8a33d7e7..9525db3bb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ build/ .idea pids logs -.idea/ contracts/.prettierrc contracts/flattened/*.sol contracts/artifacts @@ -15,6 +14,7 @@ abi/Address.abi abi/BytesLib.abi abi/BytesToTypes.abi abi/BitcoinHelper.abi +abi/Context.abi abi/I*.abi abi/Memory.abi abi/Ownable.abi diff --git a/abi/BitcoinAgent.abi b/abi/BitcoinAgent.abi index 663a2e32f..a91e1f6cb 100644 --- a/abi/BitcoinAgent.abi +++ b/abi/BitcoinAgent.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"claimedBtcReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"storedBtcReward","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_CORE_BTC_CONVERSION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"lstStakeAmount","type":"uint256"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"coreAmount","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getGrades","outputs":[{"components":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct BitcoinAgent.DualStakingGrade[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gradeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"grades","outputs":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lstGradePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"claimedBtcReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedAmount","type":"uint256"},{"indexed":false,"internalType":"int256","name":"floatReward","type":"int256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"storedBtcReward","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"lstStakeAmount","type":"uint256"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"coreAmount","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"rate","type":"uint256"}],"name":"getGrade","outputs":[{"internalType":"uint256","name":"percentage","type":"uint256"},{"internalType":"uint256","name":"stakeRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGrades","outputs":[{"components":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct IBtcAgent.DualStakingGrade[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gradeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"grades","outputs":[{"internalType":"uint32","name":"stakeRate","type":"uint32"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lstGradePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/BitcoinStake.abi b/abi/BitcoinStake.abi index 7da693e52..c81808152 100644 --- a/abi/BitcoinStake.abi +++ b/abi/BitcoinStake.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"}],"name":"btcExpired","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"bool","name":"expired","type":"bool"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedReward","type":"uint256"}],"name":"claimedRewardPerTx","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"bytes","name":"script","type":"bytes"},{"indexed":false,"internalType":"uint32","name":"outputIndex","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"amount","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"delegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"bool","name":"expired","type":"bool"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unclaimedReward","type":"uint256"}],"name":"storedRewardPerTx","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"address","name":"sourceCandidate","type":"address"},{"indexed":false,"internalType":"address","name":"targetCandidate","type":"address"},{"indexed":false,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferredBtc","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"outpointHash","type":"bytes32"},{"indexed":true,"internalType":"uint32","name":"outpointIndex","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"usedTxid","type":"bytes32"}],"name":"undelegated","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardPerBTCMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcConfirmBlock","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"btcTxMap","outputs":[{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint32","name":"outputIndex","type":"uint32"},{"internalType":"uint64","name":"blockTimestamp","type":"uint64"},{"internalType":"uint32","name":"lockTime","type":"uint32"},{"internalType":"uint32","name":"usedHeight","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"rewardUnclaimed","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"btcTx","type":"bytes"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bytes","name":"script","type":"bytes"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getContinuousRewardEndRoundsByCandidate","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"agent","type":"address"}],"name":"getExpireValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGrades","outputs":[{"components":[{"internalType":"uint64","name":"lockDuration","type":"uint64"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct BitcoinStake.LockLengthGrade[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getTxIdsByDelegator","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gradeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"grades","outputs":[{"internalType":"uint64","name":"lockDuration","type":"uint64"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"prepare","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"receiptMap","outputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"round","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"unclaimedReward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"address","name":"targetCandidate","type":"address"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"btcTx","type":"bytes"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"undelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"uint256","name":"drRound","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"}],"name":"viewCollectReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"bool","name":"expired","type":"bool"},{"internalType":"uint256","name":"rewardUnclaimed","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"}],"name":"btcExpired","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"bool","name":"expired","type":"bool"},{"indexed":false,"internalType":"uint256","name":"lockLengthRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"claimedRewardBtcTx","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"bytes","name":"script","type":"bytes"},{"indexed":false,"internalType":"uint32","name":"outputIndex","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"amount","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"delegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"bool","name":"expired","type":"bool"},{"indexed":false,"internalType":"uint256","name":"lockLengthRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dualStakingRate","type":"uint256"}],"name":"storedRewardBtcTx","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txid","type":"bytes32"},{"indexed":false,"internalType":"address","name":"sourceCandidate","type":"address"},{"indexed":false,"internalType":"address","name":"targetCandidate","type":"address"},{"indexed":false,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferredBtc","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"outpointHash","type":"bytes32"},{"indexed":true,"internalType":"uint32","name":"outpointIndex","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"usedTxid","type":"bytes32"}],"name":"undelegated","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_CORE_BTC_CONVERSION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardPerBTCMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcConfirmBlock","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"btcTxMap","outputs":[{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint32","name":"outputIndex","type":"uint32"},{"internalType":"uint32","name":"channelId","type":"uint32"},{"internalType":"uint64","name":"blockTimestamp","type":"uint64"},{"internalType":"uint32","name":"lockTime","type":"uint32"},{"internalType":"uint32","name":"usedHeight","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"coreAmount","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"}],"name":"calculateRewards","outputs":[{"internalType":"uint256[]","name":"rewards","type":"uint256[]"},{"internalType":"int256[]","name":"floatRewards","type":"int256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"coreAmount","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"btcTx","type":"bytes"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bytes","name":"script","type":"bytes"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getContinuousRewardEndRoundsByCandidate","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"agent","type":"address"}],"name":"getExpireValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGrades","outputs":[{"components":[{"internalType":"uint64","name":"lockDuration","type":"uint64"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct BitcoinStake.LockLengthGrade[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getTxIdsByDelegator","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gradeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"grades","outputs":[{"internalType":"uint64","name":"lockDuration","type":"uint64"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"prepare","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"receiptMap","outputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"round","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"unclaimedReward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"address","name":"targetCandidate","type":"address"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"btcTx","type":"bytes"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"undelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/BtcLightClient.abi b/abi/BtcLightClient.abi index fcfe0cacf..dceee1bd3 100644 --- a/abi/BtcLightClient.abi +++ b/abi/BtcLightClient.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"miner","type":"address"}],"name":"AddMinerPower","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"rewardAddr","type":"address"},{"indexed":true,"internalType":"uint32","name":"height","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"bindingHash","type":"bytes32"}],"name":"StoreHeader","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"indexed":true,"internalType":"int256","name":"returnCode","type":"int256"}],"name":"StoreHeaderFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CALLER_COMPENSATION_MOLECULE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIRM_BLOCK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DIFFICULTY_ADJUSTMENT_INTERVAL","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_BLOCK_ALREADY_EXISTS","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_DIFFICULTY","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_MERKLE","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_NO_PREV_BLOCK","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_PROOF_OF_WORK","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_RETARGET","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_CHAIN_HEIGHT","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_CONSENSUS_STATE_BYTES","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REWARD_FOR_SYNC_HEADER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_STORE_BLOCK_GAS_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAXIMUM_WEIGHT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POWER_ROUND_GAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROUND_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_TIMESPAN","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_TIMESPAN_DIV_4","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_TIMESPAN_MUL_4","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNROUNDED_MAX_TARGET","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"adjustmentHashes","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"blockChain","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"count","type":"uint256"}],"name":"calculateRelayerWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callerCompensationMolecule","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"uint32","name":"confirmBlock","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"checkTxProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"uint32","name":"confirmBlock","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"checkTxProofAndGetTime","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"relayerAddr","type":"address"}],"name":"claimRelayerReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collectedRewardForHeaderRelayer","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"countInRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getAdjustmentHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getAdjustmentIndex","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getBits","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getCandidate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainTip","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainTipHeight","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getHeight","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getPrevHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getRewardAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"},{"internalType":"address","name":"candidate","type":"address"}],"name":"getRoundBlocks","outputs":[{"internalType":"bytes32[]","name":"blocks","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"}],"name":"getRoundCandidates","outputs":[{"internalType":"address[]","name":"candidates","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"},{"internalType":"address","name":"candidate","type":"address"}],"name":"getRoundMiners","outputs":[{"internalType":"address[]","name":"miners","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"},{"internalType":"address[]","name":"candidates","type":"address[]"}],"name":"getRoundPowers","outputs":[{"internalType":"uint256[]","name":"powers","type":"uint256[]"},{"internalType":"uint256","name":"totalPower","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getScore","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"btcHash","type":"bytes32"}],"name":"getSubmitter","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getTimestamp","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"headerRelayerAddressRecord","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"headerRelayersSubmitCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"heaviestBlock","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"height2HashMap","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"highScore","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initBlockHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"btcHash","type":"bytes32"}],"name":"isHeaderSynced","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"relayerRewardVault","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardForSyncHeader","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"storeBlockGasPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"blockBytes","type":"bytes"}],"name":"storeBlockHeader","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"submitters","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"miner","type":"address"}],"name":"AddMinerPower","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"rewardAddr","type":"address"},{"indexed":true,"internalType":"uint32","name":"height","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"bindingHash","type":"bytes32"}],"name":"StoreHeader","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"indexed":true,"internalType":"int256","name":"returnCode","type":"int256"}],"name":"StoreHeaderFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CALLER_COMPENSATION_MOLECULE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIRM_BLOCK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DIFFICULTY_ADJUSTMENT_INTERVAL","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_BLOCK_ALREADY_EXISTS","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_DIFFICULTY","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_MERKLE","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_NO_PREV_BLOCK","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_PROOF_OF_WORK","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_RETARGET","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_CHAIN_HEIGHT","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_CONSENSUS_STATE_BYTES","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REWARD_FOR_SYNC_HEADER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_STORE_BLOCK_GAS_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAXIMUM_WEIGHT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POWER_ROUND_GAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROUND_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_TIMESPAN","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_TIMESPAN_DIV_4","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_TIMESPAN_MUL_4","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNROUNDED_MAX_TARGET","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"adjustmentHashes","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"blockChain","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"count","type":"uint256"}],"name":"calculateRelayerWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callerCompensationMolecule","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"uint32","name":"confirmBlock","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"checkTxProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"txid","type":"bytes32"},{"internalType":"uint32","name":"blockHeight","type":"uint32"},{"internalType":"uint32","name":"confirmBlock","type":"uint32"},{"internalType":"bytes32[]","name":"nodes","type":"bytes32[]"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"checkTxProofAndGetTime","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"relayerAddr","type":"address"}],"name":"claimRelayerReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collectedRewardForHeaderRelayer","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"countInRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getAdjustmentHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getAdjustmentIndex","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getBits","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getCandidate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainTip","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainTipHeight","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getHeight","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getPrevHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getRewardAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"},{"internalType":"address","name":"candidate","type":"address"}],"name":"getRoundBlocks","outputs":[{"internalType":"bytes32[]","name":"blocks","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"}],"name":"getRoundCandidates","outputs":[{"internalType":"address[]","name":"candidates","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"},{"internalType":"address","name":"candidate","type":"address"}],"name":"getRoundMiners","outputs":[{"internalType":"address[]","name":"miners","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTimeTag","type":"uint256"},{"internalType":"address[]","name":"candidates","type":"address[]"}],"name":"getRoundPowers","outputs":[{"internalType":"uint256[]","name":"powers","type":"uint256[]"},{"internalType":"uint256","name":"totalPower","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getScore","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"btcHash","type":"bytes32"}],"name":"getSubmitter","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getTimestamp","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"headerRelayerAddressRecord","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"headerRelayersSubmitCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"heaviestBlock","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"height2HashMap","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"highScore","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initBlockHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"btcHash","type":"bytes32"}],"name":"isHeaderSynced","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"relayerRewardVault","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardForSyncHeader","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"storeBlockGasPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"blockBytes","type":"bytes"}],"name":"storeBlockHeader","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"submitters","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/Burn.abi b/abi/Burn.abi index af6b74d5b..12f402c59 100644 --- a/abi/Burn.abi +++ b/abi/Burn.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_CAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burn","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"burnCap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_CAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burn","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"burnCap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/CandidateHub.abi b/abi/CandidateHub.abi index 956a04f9e..ba0b6449b 100644 --- a/abi/CandidateHub.abi +++ b/abi/CandidateHub.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalMargin","type":"uint256"}],"name":"addedMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalMargin","type":"uint256"}],"name":"deductedMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":true,"internalType":"address","name":"consensusAddr","type":"address"},{"indexed":true,"internalType":"address","name":"feeAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"commissionThousandths","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"}],"name":"registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldStatus","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newStatus","type":"uint256"}],"name":"statusChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"}],"name":"turnedRound","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":true,"internalType":"address","name":"consensusAddr","type":"address"}],"name":"unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":true,"internalType":"address","name":"consensusAddr","type":"address"},{"indexed":true,"internalType":"address","name":"feeAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"commissionThousandths","type":"uint256"}],"name":"updated","type":"event"},{"inputs":[],"name":"ACTIVE_STATUS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_COUNT_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_INACTIVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_JAIL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_VALIDATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_DUES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_VALIDATOR_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_COMMISSION_CHANGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_CANDIDATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_INACTIVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_JAIL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_VALIDATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNREGISTER_STATUS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"addMargin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"canDelegate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateSet","outputs":[{"internalType":"address","name":"operateAddr","type":"address"},{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"address payable","name":"feeAddr","type":"address"},{"internalType":"uint256","name":"commissionThousandths","type":"uint256"},{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"status","type":"uint256"},{"internalType":"uint256","name":"commissionLastChangeRound","type":"uint256"},{"internalType":"uint256","name":"commissionLastRoundValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dues","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCandidates","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRoundInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getRoundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"}],"name":"isCandidateByConsensus","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operateAddr","type":"address"}],"name":"isCandidateByOperate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operateAddr","type":"address"}],"name":"isJailed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"isValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"jailMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operateAddress","type":"address"},{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"fine","type":"uint256"}],"name":"jailValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxCommissionChange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operateMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refuseDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"address payable","name":"feeAddr","type":"address"},{"internalType":"uint32","name":"commissionThousandths","type":"uint32"}],"name":"register","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requiredMargin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"turnRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unregister","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"address payable","name":"feeAddr","type":"address"},{"internalType":"uint32","name":"commissionThousandths","type":"uint32"}],"name":"update","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"validatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"address","name":"newAgent","type":"address"}],"name":"AgentUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"CommissionRateEdited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"address","name":"newConsensusAddr","type":"address"}],"name":"ConsensusAddressEdited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"string","name":"moniker","type":"string"},{"indexed":false,"internalType":"string","name":"identity","type":"string"},{"indexed":false,"internalType":"string","name":"website","type":"string"},{"indexed":false,"internalType":"string","name":"details","type":"string"}],"name":"DescriptionEdited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"address","name":"newFeeAddr","type":"address"}],"name":"FeeAddressEdited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"bytes","name":"newVoteAddr","type":"bytes"}],"name":"VoteAddressEdited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalMargin","type":"uint256"}],"name":"addedMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalMargin","type":"uint256"}],"name":"deductedMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":true,"internalType":"address","name":"consensusAddr","type":"address"},{"indexed":true,"internalType":"address","name":"feeAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"commissionThousandths","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"voteAddr","type":"bytes"}],"name":"registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldStatus","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newStatus","type":"uint256"}],"name":"statusChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"}],"name":"turnedRound","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddr","type":"address"},{"indexed":true,"internalType":"address","name":"consensusAddr","type":"address"}],"name":"unregistered","type":"event"},{"inputs":[],"name":"ACTIVE_STATUS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_COUNT_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_INACTIVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_JAIL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEL_VALIDATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_DUES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_VALIDATOR_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_COMMISSION_CHANGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_CANDIDATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_INACTIVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_JAIL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SET_VALIDATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNREGISTER_STATUS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"addMargin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"agentMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"canDelegate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateSet","outputs":[{"internalType":"address","name":"operateAddr","type":"address"},{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"address payable","name":"feeAddr","type":"address"},{"internalType":"uint256","name":"commissionThousandths","type":"uint256"},{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"status","type":"uint256"},{"internalType":"uint256","name":"commissionLastChangeRound","type":"uint256"},{"internalType":"uint256","name":"commissionLastRoundValue","type":"uint256"},{"internalType":"bytes","name":"voteAddr","type":"bytes"},{"internalType":"address","name":"agent","type":"address"},{"components":[{"internalType":"string","name":"moniker","type":"string"},{"internalType":"string","name":"identity","type":"string"},{"internalType":"string","name":"website","type":"string"},{"internalType":"string","name":"details","type":"string"}],"internalType":"struct CandidateHub.Description","name":"description","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dues","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"newRate","type":"uint32"}],"name":"editCommissionRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newConsensusAddr","type":"address"}],"name":"editConsensusAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"moniker","type":"string"},{"internalType":"string","name":"identity","type":"string"},{"internalType":"string","name":"website","type":"string"},{"internalType":"string","name":"details","type":"string"}],"name":"editDescription","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newFeeAddr","type":"address"}],"name":"editFeeAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"voteAddr","type":"bytes"}],"name":"editVoteAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCandidates","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRoundInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getRoundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"}],"name":"isCandidateByConsensus","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operateAddr","type":"address"}],"name":"isCandidateByOperate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operateAddr","type":"address"}],"name":"isJailed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"isValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"jailMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operateAddress","type":"address"},{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"fine","type":"uint256"}],"name":"jailValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxAlternateCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxCommissionChange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operateMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refuseDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"address payable","name":"feeAddr","type":"address"},{"internalType":"uint32","name":"commissionThousandths","type":"uint32"},{"internalType":"bytes","name":"voteAddr","type":"bytes"}],"name":"register","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"removeAgent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requiredMargin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"turnRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unregister","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAgent","type":"address"}],"name":"updateAgent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"validatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/abi/Channel.abi b/abi/Channel.abi new file mode 100644 index 000000000..4e41f75c8 --- /dev/null +++ b/abi/Channel.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"DuplicateRegister","type":"error"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"InsufficientAmount","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"uint32","name":"id","type":"uint32"}],"name":"NoPartner","type":"error"},{"inputs":[{"internalType":"address","name":"expected","type":"address"},{"internalType":"address","name":"actual","type":"address"}],"name":"NotPermissionalCaller","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_COMMISSION_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_MARGIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"commissionLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"uint32","name":"id","type":"uint32"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"delegators","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dues","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"id","type":"uint32"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegatorAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getIds","outputs":[{"internalType":"uint32[]","name":"","type":"uint32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"partnerIdGenerator","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"partnerIdMap","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"partners","outputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"feeAddress","type":"address"},{"internalType":"uint16","name":"coreCommissionRate","type":"uint16"},{"internalType":"uint16","name":"btcCommissionRate","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"commission","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"partnerId","type":"uint32"},{"internalType":"uint256","name":"reward","type":"uint256"}],"name":"payCommissionById","outputs":[{"internalType":"uint256","name":"remainingReward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"}],"name":"payCommissions","outputs":[{"internalType":"uint256","name":"remainingReward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeAddress","type":"address"},{"internalType":"uint16","name":"coreCommissionRate","type":"uint16"},{"internalType":"uint16","name":"btcCommissionRate","type":"uint16"}],"name":"register","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requiredMargin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"partner","type":"address"}],"name":"resetCommission","outputs":[{"internalType":"uint256","name":"commission","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"btcCommissionRate","type":"uint16"}],"name":"setBtcCommissionRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"coreCommissionRate","type":"uint16"}],"name":"setCoreCommissionRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeAddress","type":"address"}],"name":"setFeeAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint32","name":"id","type":"uint32"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unregister","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/Configuration.abi b/abi/Configuration.abi index 88b938bf5..3630ab7f8 100644 --- a/abi/Configuration.abi +++ b/abi/Configuration.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"AddressAlreadyExists","type":"error"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"AddressNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"gas","type":"uint256"}],"name":"InvalidGasValue","type":"error"},{"inputs":[{"internalType":"address","name":"issuer","type":"address"}],"name":"InvalidIssuer","type":"error"},{"inputs":[{"internalType":"uint256","name":"percentage","type":"uint256"}],"name":"InvalidRewardPercentage","type":"error"},{"inputs":[{"internalType":"address","name":"issuer","type":"address"}],"name":"IssuerNotFound","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[],"name":"TooManyEvents","type":"error"},{"inputs":[],"name":"TooManyFunctionSigs","type":"error"},{"inputs":[],"name":"TooManyRewardAddresses","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"inputs":[],"name":"ZeroEvents","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"configAddress","type":"address"}],"name":"ConfigRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"configAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"eventCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"functionCount","type":"uint256"}],"name":"ConfigUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"ConstantUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_EVENTS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_FUNCTIONS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_GAS","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_REWARDS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"configAddresses","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"}],"name":"configExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"configsMap","outputs":[{"internalType":"address","name":"configAddress","type":"address"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllConfigAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"}],"name":"getConfig","outputs":[{"components":[{"internalType":"address","name":"configAddress","type":"address"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bytes32","name":"eventSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Event[]","name":"events","type":"tuple[]"},{"components":[{"internalType":"bytes32","name":"functionSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Function[]","name":"functions","type":"tuple[]"}],"internalType":"struct Configuration.Config","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"},{"internalType":"uint256","name":"eventIndex","type":"uint256"}],"name":"getEventDetails","outputs":[{"components":[{"internalType":"bytes32","name":"eventSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Event","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"},{"internalType":"uint256","name":"functionIndex","type":"uint256"}],"name":"getFunctionDetails","outputs":[{"components":[{"internalType":"bytes32","name":"functionSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Function","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setAlreadyInit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_systemContractAddr","type":"bytes"}],"name":"updateContractAddr","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"AddressAlreadyExists","type":"error"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"AddressNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"gas","type":"uint256"}],"name":"InvalidGasValue","type":"error"},{"inputs":[{"internalType":"address","name":"issuer","type":"address"}],"name":"InvalidIssuer","type":"error"},{"inputs":[{"internalType":"uint256","name":"percentage","type":"uint256"}],"name":"InvalidRewardPercentage","type":"error"},{"inputs":[{"internalType":"address","name":"issuer","type":"address"}],"name":"IssuerNotFound","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[],"name":"TooManyEvents","type":"error"},{"inputs":[],"name":"TooManyFunctionSigs","type":"error"},{"inputs":[],"name":"TooManyRewardAddresses","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"inputs":[],"name":"ZeroEvents","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"configAddress","type":"address"}],"name":"ConfigRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"configAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"eventCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"functionCount","type":"uint256"}],"name":"ConfigUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"ConstantUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_EVENTS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_FUNCTIONS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_GAS","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_REWARDS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"configAddresses","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"}],"name":"configExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"configsMap","outputs":[{"internalType":"address","name":"configAddress","type":"address"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllConfigAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"}],"name":"getConfig","outputs":[{"components":[{"internalType":"address","name":"configAddress","type":"address"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bytes32","name":"eventSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Event[]","name":"events","type":"tuple[]"},{"components":[{"internalType":"bytes32","name":"functionSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Function[]","name":"functions","type":"tuple[]"}],"internalType":"struct Configuration.Config","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"},{"internalType":"uint256","name":"eventIndex","type":"uint256"}],"name":"getEventDetails","outputs":[{"components":[{"internalType":"bytes32","name":"eventSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Event","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddr","type":"address"},{"internalType":"uint256","name":"functionIndex","type":"uint256"}],"name":"getFunctionDetails","outputs":[{"components":[{"internalType":"bytes32","name":"functionSignature","type":"bytes32"},{"internalType":"uint32","name":"gas","type":"uint32"},{"components":[{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"uint16","name":"rewardPercentage","type":"uint16"}],"internalType":"struct Configuration.Reward[]","name":"rewards","type":"tuple[]"}],"internalType":"struct Configuration.Function","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/Context.abi b/abi/Context.abi deleted file mode 100644 index 0637a088a..000000000 --- a/abi/Context.abi +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/abi/CoreAgent.abi b/abi/CoreAgent.abi index ff95b88ea..d24184781 100644 --- a/abi/CoreAgent.abi +++ b/abi/CoreAgent.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"claimedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"collectedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"delegatedCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sourceCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"targetCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"transferredCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegatedCoin","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_COIN_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"settleRound","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getCandidateListByDelegator","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getContinuousRewardEndRoundsByCandidate","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"changeRound","type":"uint256"}],"internalType":"struct CoreAgent.CoinDelegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"moveData","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyDelegate","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyUnDelegate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requiredCoinDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"settleRound","type":"uint256"}],"name":"viewCollectRewardFromCandidate","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"uint32","name":"channelId","type":"uint32"}],"name":"InsufficientTokens","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[],"name":"NotImplemented","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"claimedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"collectedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"delegatedCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedCoinReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sourceCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"targetCandidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"name":"transferredCoin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"candidate","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegatedCoin","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_COIN_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accruedRewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateRewards","outputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256[]","name":"rewards","type":"uint256[]"},{"internalType":"uint256","name":"stakedAmount1","type":"uint256"},{"internalType":"uint256","name":"stakedAmount2","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"candidateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"stakedAmount1","type":"uint256"},{"internalType":"uint256","name":"stakedAmount2","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"channelAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getCandidateListByDelegator","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getContinuousRewardEndRoundsByCandidate","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"realtimeAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"changeRound","type":"uint256"}],"internalType":"struct CoreAgent.CoinDelegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"transferredAmount","type":"uint256"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"moveData","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint32","name":"channelId","type":"uint32"}],"name":"proxyDelegate","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"proxyTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint32","name":"channelId","type":"uint32"}],"name":"proxyUnDelegate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requiredCoinDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceCandidate","type":"address"},{"internalType":"address","name":"targetCandidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/Foundation.abi b/abi/Foundation.abi index a14a5c267..546e1880b 100644 --- a/abi/Foundation.abi +++ b/abi/Foundation.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"payee","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"}],"name":"fundFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"payee","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fundSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"payee","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fund","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"payee","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"}],"name":"fundFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"payee","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fundSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"payee","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fund","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/abi/GovHub.abi b/abi/GovHub.abi index 1d6142c14..85b66c5da 100644 --- a/abi/GovHub.abi +++ b/abi/GovHub.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"string","name":"signature","type":"string"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"ExecuteTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"address","name":"proposer","type":"address"},{"indexed":false,"internalType":"address[]","name":"targets","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"},{"indexed":false,"internalType":"string[]","name":"signatures","type":"string[]"},{"indexed":false,"internalType":"bytes[]","name":"calldatas","type":"bytes[]"},{"indexed":false,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalVotes","type":"uint256"},{"indexed":false,"internalType":"string","name":"description","type":"string"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"voter","type":"address"},{"indexed":false,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"support","type":"bool"}],"name":"VoteCast","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"receiveDeposit","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXECUTING_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_MEMBERS","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROPOSAL_MAX_OPERATIONS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"addMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"cancel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"},{"internalType":"bool","name":"support","type":"bool"}],"name":"castVote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"executingPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"getState","outputs":[{"internalType":"enum GovHub.ProposalState","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"latestProposalIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"memberSet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"members","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposalCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposalMaxOperations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"proposals","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"proposer","type":"address"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"uint256","name":"forVotes","type":"uint256"},{"internalType":"uint256","name":"againstVotes","type":"uint256"},{"internalType":"uint256","name":"totalVotes","type":"uint256"},{"internalType":"bool","name":"canceled","type":"bool"},{"internalType":"bool","name":"executed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"targets","type":"address[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"},{"internalType":"string[]","name":"signatures","type":"string[]"},{"internalType":"bytes[]","name":"calldatas","type":"bytes[]"},{"internalType":"string","name":"description","type":"string"}],"name":"propose","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"removeMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"votingPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"string","name":"signature","type":"string"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"ExecuteTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"address","name":"proposer","type":"address"},{"indexed":false,"internalType":"address[]","name":"targets","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"},{"indexed":false,"internalType":"string[]","name":"signatures","type":"string[]"},{"indexed":false,"internalType":"bytes[]","name":"calldatas","type":"bytes[]"},{"indexed":false,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalVotes","type":"uint256"},{"indexed":false,"internalType":"string","name":"description","type":"string"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"voter","type":"address"},{"indexed":false,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"support","type":"bool"}],"name":"VoteCast","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"receiveDeposit","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXECUTING_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_MEMBERS","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROPOSAL_MAX_OPERATIONS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"addMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"cancel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"},{"internalType":"bool","name":"support","type":"bool"}],"name":"castVote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"executingPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalId","type":"uint256"}],"name":"getState","outputs":[{"internalType":"enum GovHub.ProposalState","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"latestProposalIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"memberSet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"members","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposalCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposalMaxOperations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"proposals","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"proposer","type":"address"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"uint256","name":"forVotes","type":"uint256"},{"internalType":"uint256","name":"againstVotes","type":"uint256"},{"internalType":"uint256","name":"totalVotes","type":"uint256"},{"internalType":"bool","name":"canceled","type":"bool"},{"internalType":"bool","name":"executed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"targets","type":"address[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"},{"internalType":"string[]","name":"signatures","type":"string[]"},{"internalType":"bytes[]","name":"calldatas","type":"bytes[]"},{"internalType":"string","name":"description","type":"string"}],"name":"propose","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"removeMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"votingPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/abi/HashPowerAgent.abi b/abi/HashPowerAgent.abi index e890592cb..744376628 100644 --- a/abi/HashPowerAgent.abi +++ b/abi/HashPowerAgent.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"claimedHashReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"name":"storedHashReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"avgReward","type":"uint256"}],"name":"validatorAvgReward","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"InactiveCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"SameCandidate","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedHashReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"storedHashReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"avgReward","type":"uint256"}],"name":"validatorAvgReward","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"int256","name":"floatReward","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"getStakeAmounts","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"accStakedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/abi/HashStakeAgent.abi b/abi/HashStakeAgent.abi deleted file mode 100644 index 482d4224d..000000000 --- a/abi/HashStakeAgent.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"","type":"address[]"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validatorList","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"getStakeAmount","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/PledgeAgent.abi b/abi/PledgeAgent.abi index 6f38b95af..0b4e12974 100644 --- a/abi/PledgeAgent.abi +++ b/abi/PledgeAgent.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"agentsMap","outputs":[{"internalType":"uint256","name":"totalDeposit","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"uint256","name":"coin","type":"uint256"},{"internalType":"uint256","name":"btc","type":"uint256"},{"internalType":"uint256","name":"totalBtc","type":"uint256"},{"internalType":"bool","name":"moved","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"btc2ethMap","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcConfirmBlock","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"btcReceiptMap","outputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"endRound","type":"uint256"},{"internalType":"uint256","name":"rewardIndex","type":"uint256"},{"internalType":"address payable","name":"feeReceiver","type":"address"},{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"agentList","type":"address[]"},{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"agentList","type":"address[]"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"debtDepositMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"delegateBtcGasPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"uint256","name":"newDeposit","type":"uint256"},{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint256","name":"rewardIndex","type":"uint256"},{"internalType":"uint256","name":"transferOutDeposit","type":"uint256"},{"internalType":"uint256","name":"transferInDeposit","type":"uint256"}],"internalType":"struct PledgeAgent.CoinDelegator","name":"cd","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getExpireList","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"agent","type":"address"}],"name":"getExpireValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getReward","outputs":[{"components":[{"internalType":"uint256","name":"totalReward","type":"uint256"},{"internalType":"uint256","name":"remainReward","type":"uint256"},{"internalType":"uint256","name":"score","type":"uint256"},{"internalType":"uint256","name":"coin","type":"uint256"},{"internalType":"uint256","name":"round","type":"uint256"}],"internalType":"struct PledgeAgent.Reward","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minBtcLockRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minBtcValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"moveCOREData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"powerFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"requiredCoinDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stateMap","outputs":[{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"uint256","name":"coin","type":"uint256"},{"internalType":"uint256","name":"powerFactor","type":"uint256"},{"internalType":"uint256","name":"btc","type":"uint256"},{"internalType":"uint256","name":"btcFactor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sourceAgent","type":"address"},{"internalType":"address","name":"targetAgent","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceAgent","type":"address"},{"internalType":"address","name":"targetAgent","type":"address"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"agentsMap","outputs":[{"internalType":"uint256","name":"totalDeposit","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"uint256","name":"coin","type":"uint256"},{"internalType":"uint256","name":"btc","type":"uint256"},{"internalType":"uint256","name":"totalBtc","type":"uint256"},{"internalType":"bool","name":"moved","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"btc2ethMap","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcConfirmBlock","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"btcFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"btcReceiptMap","outputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"endRound","type":"uint256"},{"internalType":"uint256","name":"rewardIndex","type":"uint256"},{"internalType":"address payable","name":"feeReceiver","type":"address"},{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"agentList","type":"address[]"},{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"agentList","type":"address[]"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"debtDepositMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"delegateBtcGasPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"delegateCoin","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"uint256","name":"newDeposit","type":"uint256"},{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint256","name":"rewardIndex","type":"uint256"},{"internalType":"uint256","name":"transferOutDeposit","type":"uint256"},{"internalType":"uint256","name":"transferInDeposit","type":"uint256"}],"internalType":"struct PledgeAgent.CoinDelegator","name":"cd","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getExpireList","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"agent","type":"address"}],"name":"getExpireValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getReward","outputs":[{"components":[{"internalType":"uint256","name":"totalReward","type":"uint256"},{"internalType":"uint256","name":"remainReward","type":"uint256"},{"internalType":"uint256","name":"score","type":"uint256"},{"internalType":"uint256","name":"coin","type":"uint256"},{"internalType":"uint256","name":"round","type":"uint256"}],"internalType":"struct PledgeAgent.Reward","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minBtcLockRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minBtcValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"},{"internalType":"address","name":"delegator","type":"address"}],"name":"moveCOREData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"powerFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"requiredCoinDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"roundTag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stateMap","outputs":[{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"uint256","name":"coin","type":"uint256"},{"internalType":"uint256","name":"powerFactor","type":"uint256"},{"internalType":"uint256","name":"btc","type":"uint256"},{"internalType":"uint256","name":"btcFactor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sourceAgent","type":"address"},{"internalType":"address","name":"targetAgent","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sourceAgent","type":"address"},{"internalType":"address","name":"targetAgent","type":"address"}],"name":"transferCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegateCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/abi/README.md b/abi/README.md index 37a98df77..806b04c86 100644 --- a/abi/README.md +++ b/abi/README.md @@ -15,7 +15,7 @@ | TeamVesting |0x0000000000000000000000000000000000002000 | [teamvesting](TeamVesting.abi) | | StakeHub |0x0000000000000000000000000000000000001010 | [stakehub](StakeHub.abi) | | CoreAgent |0x0000000000000000000000000000000000001011 | [coreagent](CoreAgent.abi) | -| HashStakeAgent |0x0000000000000000000000000000000000001012 | [hashstakeagent](HashStakeAgent.abi) | +| HashPowerAgent |0x0000000000000000000000000000000000001012 | [hashpoweragent](HashPowerAgent.abi) | | BitcoinAgent |0x0000000000000000000000000000000000001013 | [bitcoinagent](BitcoinAgent.abi) | | BitcoinStake |0x0000000000000000000000000000000000001014 | [bitcoinstake](BitcoinStake.abi) | | BitcoinLSTStake |0x0000000000000000000000000000000000001015 | [bitcoinlststake](BitcoinLSTStake.abi) | diff --git a/abi/RelayerHub.abi b/abi/RelayerHub.abi index 6a9bf8f84..b3bf1ea8c 100644 --- a/abi/RelayerHub.abi +++ b/abi/RelayerHub.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"}],"name":"relayerRegister","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"}],"name":"relayerUnRegister","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_DUES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dues","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"isRelayer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"register","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requiredDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unregister","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"}],"name":"relayerRegister","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"}],"name":"relayerUnRegister","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_DUES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REQUIRED_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dues","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"isRelayer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"register","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requiredDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unregister","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abi/SatoshiPlusHelper.abi b/abi/SatoshiPlusHelper.abi index 982ae0586..a827a4f99 100644 --- a/abi/SatoshiPlusHelper.abi +++ b/abi/SatoshiPlusHelper.abi @@ -1 +1 @@ -[{"inputs":[],"name":"BECH32_HRP_SEGWIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_STAKE_VERSION","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_DECIMAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_MAGIC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_VERSION","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHAINID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_DECIMAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_STAKE_DECIMAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_BTC_CONFIRM_BLOCK","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROUND_INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"BECH32_HRP_SEGWIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_DECIMAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_INIT_VERSION","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_MAGIC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_VERSION","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHAINID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_DECIMAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_STAKE_DECIMAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_BTC_CONFIRM_BLOCK","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROUND_INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/abi/SlashIndicator.abi b/abi/SlashIndicator.abi index 9170f592c..20e458986 100644 --- a/abi/SlashIndicator.abi +++ b/abi/SlashIndicator.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[],"name":"indicatorCleaned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"validatorSlashed","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DECREASE_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FELONY_THRESHOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INFINITY_ROUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_FELONY_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_FELONY_ROUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REWARD_FOR_REPORT_DOUBLE_SIGN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MISDEMEANOR_THRESHOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"clean","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"header1","type":"bytes"},{"internalType":"bytes","name":"header2","type":"bytes"}],"name":"doubleSignSlash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"felonyDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"felonyRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"felonyThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"getSlashIndicator","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"indicators","outputs":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"uint256","name":"count","type":"uint256"},{"internalType":"bool","name":"exist","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"misdemeanorThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"previousHeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardForReportDoubleSign","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"slash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"validators","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[],"name":"indicatorCleaned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"validatorSlashed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"blockCount","type":"uint256"}],"name":"validatorSlashed","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DECREASE_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FELONY_THRESHOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INFINITY_ROUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_FELONY_DEPOSIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_FELONY_ROUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REWARD_FOR_REPORT_DOUBLE_SIGN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MISDEMEANOR_THRESHOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"clean","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"header1","type":"bytes"},{"internalType":"bytes","name":"header2","type":"bytes"}],"name":"doubleSignSlash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"blockCount","type":"uint256"}],"name":"exitMaintenanceSlash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"felonyDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"felonyRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"felonyThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"getSlashIndicator","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"indicators","outputs":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"uint256","name":"count","type":"uint256"},{"internalType":"bool","name":"exist","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"misdemeanorThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"previousHeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardForReportDoubleSign","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardForReportFinalityViolation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"slash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"uint256","name":"srcNum","type":"uint256"},{"internalType":"bytes32","name":"srcHash","type":"bytes32"},{"internalType":"uint256","name":"tarNum","type":"uint256"},{"internalType":"bytes32","name":"tarHash","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"internalType":"struct SlashIndicator.VoteData","name":"voteA","type":"tuple"},{"components":[{"internalType":"uint256","name":"srcNum","type":"uint256"},{"internalType":"bytes32","name":"srcHash","type":"bytes32"},{"internalType":"uint256","name":"tarNum","type":"uint256"},{"internalType":"bytes32","name":"tarHash","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"internalType":"struct SlashIndicator.VoteData","name":"voteB","type":"tuple"},{"internalType":"bytes","name":"voteAddr","type":"bytes"}],"internalType":"struct SlashIndicator.FinalityEvidence","name":"evidence","type":"tuple"}],"name":"submitFinalityViolationEvidence","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"validators","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/abi/StakeHub.abi b/abi/StakeHub.abi index 80f88d4b6..cadf47ef2 100644 --- a/abi/StakeHub.abi +++ b/abi/StakeHub.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedRelayerReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"validator","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amount","type":"uint256[]"}],"name":"roundReward","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"addRoundReward","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"assets","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateScoresMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimReward","outputs":[{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"changeRound","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAssets","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"internalType":"struct StakeHub.Asset[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getCandidateScores","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"internalType":"struct StakeHub.Delegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getHybridScore","outputs":[{"internalType":"uint256[]","name":"scores","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"onStakeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyClaimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"factor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"surplus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"relayer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimedRelayerReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"claimedReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"validator","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amount","type":"uint256[]"}],"name":"roundReward","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"rewardList","type":"uint256[]"},{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"addRoundReward","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"assets","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"calculateReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateScoresMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimCommission","outputs":[{"internalType":"uint256","name":"commission","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimReward","outputs":[{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegatorMap","outputs":[{"internalType":"uint256","name":"changeRound","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAssets","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"agent","type":"address"},{"internalType":"uint32","name":"hardcap","type":"uint32"}],"internalType":"struct StakeHub.Asset[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"candidate","type":"address"}],"name":"getCandidateScores","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"getDelegator","outputs":[{"components":[{"internalType":"uint256","name":"changeRound","type":"uint256"},{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"internalType":"struct StakeHub.Delegator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"candidates","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getHybridScore","outputs":[{"internalType":"uint256[]","name":"scores","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"onStakeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"proxyClaimReward","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256","name":"round","type":"uint256"}],"name":"setNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stateMap","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"factor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"surplus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/abi/SystemReward.abi b/abi/SystemReward.abi index 42ec97bfb..4bd103dd5 100644 --- a/abi/SystemReward.abi +++ b/abi/SystemReward.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"receiveDeposit","type":"event"},{"anonymous":false,"inputs":[],"name":"rewardEmpty","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rewardTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"whitelistTransferFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"whitelistTransferSuccess","type":"event"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INCENTIVE_BALANCE_CAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getWhiteListSet","outputs":[{"components":[{"internalType":"address","name":"member","type":"address"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct SystemReward.WhiteList[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incentiveBalanceCap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isBurn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numOperator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"receiveRewards","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"whiteListSet","outputs":[{"internalType":"address","name":"member","type":"address"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whiteLists","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"receiveDeposit","type":"event"},{"anonymous":false,"inputs":[],"name":"rewardEmpty","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rewardTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"whitelistTransferFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"whitelistTransferSuccess","type":"event"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INCENTIVE_BALANCE_CAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getWhiteListSet","outputs":[{"components":[{"internalType":"address","name":"member","type":"address"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"internalType":"struct SystemReward.WhiteList[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incentiveBalanceCap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isBurn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numOperator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"receiveRewards","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"whiteListSet","outputs":[{"internalType":"address","name":"member","type":"address"},{"internalType":"uint32","name":"percentage","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whiteLists","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/abi/TeamVesting.abi b/abi/TeamVesting.abi deleted file mode 100644 index ef8076967..000000000 --- a/abi/TeamVesting.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address payable","name":"_beneficiary","type":"address"},{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_cliff","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"},{"internalType":"bool","name":"_revocable","type":"bool"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Released","type":"event"},{"anonymous":false,"inputs":[],"name":"Revoked","type":"event"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"beneficiary","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"target","type":"address"}],"name":"changeBeneficiary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cliff","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"duration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nowTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"releasableAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"release","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"target","type":"address"}],"name":"releaseTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"released","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"revocable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"revoke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revoked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"start","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"valAddr","type":"address"},{"internalType":"address","name":"slashAddr","type":"address"},{"internalType":"address","name":"rewardAddr","type":"address"},{"internalType":"address","name":"lightAddr","type":"address"},{"internalType":"address","name":"relayerHubAddr","type":"address"},{"internalType":"address","name":"candidateHubAddr","type":"address"},{"internalType":"address","name":"govHubAddr","type":"address"},{"internalType":"address","name":"pledgeAgentAddr","type":"address"},{"internalType":"address","name":"burnAddr","type":"address"}],"name":"updateContractAddr","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vestedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/abi/ValidatorSet.abi b/abi/ValidatorSet.abi index 3c5556577..325bebe11 100644 --- a/abi/ValidatorSet.abi +++ b/abi/ValidatorSet.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deprecatedDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddress","type":"address"},{"indexed":true,"internalType":"address payable","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalReward","type":"uint256"}],"name":"directTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddress","type":"address"},{"indexed":true,"internalType":"address payable","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalReward","type":"uint256"}],"name":"directTransferFail","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"systemTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"validatorDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"validatorFelony","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"validatorMisdemeanor","type":"event"},{"anonymous":false,"inputs":[],"name":"validatorSetUpdated","type":"event"},{"inputs":[],"name":"BLOCK_REWARD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCK_REWARD_INCENTIVE_PERCENT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTCLST_TOKEN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_VALIDATORSET_BYTES","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REDUCE_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBSIDY_REDUCE_INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockRewardIncentivePercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"currentValidatorSet","outputs":[{"internalType":"address","name":"operateAddress","type":"address"},{"internalType":"address","name":"consensusAddress","type":"address"},{"internalType":"address payable","name":"feeAddress","type":"address"},{"internalType":"uint256","name":"commissionThousandths","type":"uint256"},{"internalType":"uint256","name":"income","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"currentValidatorSetMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"valAddr","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"distributeReward","outputs":[{"internalType":"address[]","name":"operateAddressList","type":"address[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"felonyRound","type":"uint256"},{"internalType":"uint256","name":"felonyDeposit","type":"uint256"}],"name":"felony","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"getIncoming","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidatorOps","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"misdemeanor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalInCome","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"operateAddrList","type":"address[]"},{"internalType":"address[]","name":"consensusAddrList","type":"address[]"},{"internalType":"address payable[]","name":"feeAddrList","type":"address[]"},{"internalType":"uint256[]","name":"commissionThousandthsList","type":"uint256[]"}],"name":"updateValidatorSet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"MismatchParamLength","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"given","type":"uint256"},{"internalType":"uint256","name":"lowerBound","type":"uint256"},{"internalType":"uint256","name":"upperBound","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"UnsupportedGovParam","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deprecatedDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddress","type":"address"},{"indexed":true,"internalType":"address payable","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalReward","type":"uint256"}],"name":"directTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddress","type":"address"},{"indexed":true,"internalType":"address payable","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalReward","type":"uint256"}],"name":"directTransferFail","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"key","type":"string"},{"indexed":false,"internalType":"bytes","name":"value","type":"bytes"}],"name":"paramChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"systemTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"validatorDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"validatorEnterMaintenance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"validatorExitMaintenance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"validatorFelony","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"validatorMisdemeanor","type":"event"},{"anonymous":false,"inputs":[],"name":"validatorSetUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddress","type":"address"},{"indexed":true,"internalType":"address payable","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"voteRewardTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operateAddress","type":"address"},{"indexed":true,"internalType":"address payable","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"voteRewardTransferFail","type":"event"},{"inputs":[],"name":"BLOCK_REWARD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCK_REWARD_INCENTIVE_PERCENT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BTC_STAKE_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANDIDATE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHANNEL_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIGURATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CORE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOUNDATION_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOV_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HASH_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INIT_VALIDATORSET_BYTES","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIGHT_CLIENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PLEDGE_AGENT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REDUCE_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLASH_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_HUB_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBSIDY_REDUCE_INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_REWARD_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_CONTRACT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"alreadyInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockRewardIncentivePercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"canEnterMaintenance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"currentValidatorSet","outputs":[{"internalType":"address","name":"operateAddress","type":"address"},{"internalType":"address","name":"consensusAddress","type":"address"},{"internalType":"address payable","name":"feeAddress","type":"address"},{"internalType":"uint256","name":"commissionThousandths","type":"uint256"},{"internalType":"uint256","name":"income","type":"uint256"},{"internalType":"bytes","name":"voteAddr","type":"bytes"},{"internalType":"uint256","name":"voteWeight","type":"uint256"},{"internalType":"uint256","name":"enterMaintenanceHeight","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"currentValidatorSetMap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"valAddr","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundTag","type":"uint256"}],"name":"distributeReward","outputs":[{"internalType":"address[]","name":"operateAddressList","type":"address[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enterMaintenance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"val","type":"address"}],"name":"enterMaintenance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitMaintenance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitMaintenanceTurnRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"felonyRound","type":"uint256"},{"internalType":"uint256","name":"felonyDeposit","type":"uint256"}],"name":"felony","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"getIncoming","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ops","type":"address"}],"name":"getValidatorIndexFromOps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidatorOps","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidatorsAndVoteAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWorkingCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWorkingValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maintainSlashPercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"misdemeanor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rankedValidatorList","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalInCome","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"},{"internalType":"bytes","name":"value","type":"bytes"}],"name":"updateParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"operateAddrList","type":"address[]"},{"internalType":"address[]","name":"consensusAddrList","type":"address[]"},{"internalType":"address payable[]","name":"feeAddrList","type":"address[]"},{"internalType":"uint256[]","name":"commissionThousandthsList","type":"uint256[]"},{"internalType":"bytes[]","name":"voteAddrList","type":"bytes[]"},{"internalType":"uint256","name":"_validatorCount","type":"uint256"}],"name":"updateValidatorSet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"validatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"valAddrs","type":"address[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"voteRewardPercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/contracts/BitcoinAgent.sol b/contracts/BitcoinAgent.sol index e9d6cd7f2..ddccfbfb0 100644 --- a/contracts/BitcoinAgent.sol +++ b/contracts/BitcoinAgent.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; -import "./interface/IAgent.sol"; +import "./interface/IBtcAgent.sol"; import "./interface/IBitcoinStake.sol"; import "./interface/IParamSubscriber.sol"; import "./lib/Memory.sol"; @@ -14,7 +14,7 @@ import "./lib/SafeCast.sol"; /// This contract handles BTC staking. /// It interacts with BitcoinStake.sol for /// non-custodial BTC staking correspondingly. -contract BitcoinAgent is IAgent, System, IParamSubscriber { +contract BitcoinAgent is IBtcAgent, System, IParamSubscriber { using BytesLib for *; using SafeCast for *; using RLPDecode for bytes; @@ -37,7 +37,7 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { // conversion rate between CORE and the asset uint256 public assetWeight; - // Deprecated + // Deprecated in V-1.0.20 uint256 public lstGradePercentage; struct StakeAmount { @@ -47,11 +47,6 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { uint256 stakeAmount; } - struct DualStakingGrade { - uint32 stakeRate; - uint32 percentage; - } - event claimedBtcReward(address indexed delegator, uint256 amount, uint256 unclaimedAmount, int256 floatReward, uint256 accStakedAmount, uint256 dualStakingRate); event storedBtcReward(address indexed delegator, uint256 amount, uint256 unclaimedAmount, int256 floatReward, uint256 accStakedAmount, uint256 dualStakingRate); @@ -90,43 +85,43 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { /// Claim reward for delegator /// @param delegator the delegator address - /// @param coreAmount the accumurated amount of staked CORE. + /// @param coreAmount the staked amount of staked CORE. /// @param settleRound the settlement round /// @param claim claim or store rewards /// @return reward Amount claimed /// @return floatReward floating reward amount - /// @return accStakedAmount accumulated stake amount (multiplied by rounds), used for grading calculation - function claimReward(address delegator, uint256 coreAmount, uint256 settleRound, bool claim) external override onlyStakeHub returns (uint256 reward, int256 floatReward, uint256 accStakedAmount) { - (uint256 btcReward, uint256 btcRewardUnclaimed, uint256 btcAccStakedAmount) = IBitcoinStake(BTC_STAKE_ADDR).claimReward(delegator, settleRound, claim); + function claimReward(address delegator, uint256 coreAmount, uint256 settleRound, bool claim) external override onlyStakeHub returns (uint256 reward, int256 floatReward) { + return IBitcoinStake(BTC_STAKE_ADDR).claimReward(delegator, coreAmount, settleRound, claim); + } + + /*********************** External methods ********************************/ + /// Returns grades. + function getGrades() external view override returns (DualStakingGrade[] memory) { + return grades; + } + + /// Return a grade for the given stake rate. + /// + /// @param rate the stake rate + /// @return percentage the target percentage + /// @return stakeRate the target stake rate + function getGrade(uint256 rate) external view override returns (uint256 percentage, uint256 stakeRate) { + percentage = SatoshiPlusHelper.DENOMINATOR; + stakeRate = 0; uint256 gradeLength = grades.length; - uint256 p = SatoshiPlusHelper.DENOMINATOR; - if (gradeActive && gradeLength != 0 && btcAccStakedAmount != 0) { - uint256 stakeRate = coreAmount / btcAccStakedAmount / assetWeight; - p = grades[0].percentage; + if (gradeActive && gradeLength != 0) { + percentage = grades[0].percentage; + stakeRate = grades[0].stakeRate; + rate /= assetWeight; for (uint256 j = gradeLength - 1; j != 0; j--) { - if (stakeRate >= grades[j].stakeRate) { - p = grades[j].percentage; + if (rate >= grades[j].stakeRate) { + percentage = grades[j].percentage; + stakeRate = grades[j].stakeRate; break; } } - uint256 pReward = btcReward * p / SatoshiPlusHelper.DENOMINATOR; - floatReward = pReward.toInt256() - btcReward.toInt256(); - btcReward = pReward; - } - if (btcRewardUnclaimed != 0) { - floatReward -= btcRewardUnclaimed.toInt256(); + stakeRate *= assetWeight; } - if (claim) { - emit claimedBtcReward(delegator, btcReward, btcRewardUnclaimed, floatReward, btcAccStakedAmount, p); - } else { - emit storedBtcReward(delegator, btcReward, btcRewardUnclaimed, floatReward, btcAccStakedAmount, p); - } - return (btcReward, floatReward, btcAccStakedAmount); - } - - /*********************** External methods ********************************/ - function getGrades() external view returns (DualStakingGrade[] memory) { - return grades; } /*********************** Governance ********************************/ @@ -180,12 +175,6 @@ contract BitcoinAgent is IAgent, System, IParamSubscriber { revert OutOfBounds(key, newGradeActive, 0, 1); } gradeActive = newGradeActive == 1; - } else if (Memory.compareStrings(key, "lstGradePercentage")) { - if (value.length != 32) { - revert MismatchParamLength(key); - } - uint256 newPercentage = value.toUint256(0); - lstGradePercentage = newPercentage; } else { revert UnsupportedGovParam(key); } diff --git a/contracts/BitcoinStake.sol b/contracts/BitcoinStake.sol index cda8658d2..f4ec8bc4c 100644 --- a/contracts/BitcoinStake.sol +++ b/contracts/BitcoinStake.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; +import "./interface/IChannel.sol"; import "./interface/IBitcoinStake.sol"; +import "./interface/IBtcAgent.sol"; import "./interface/ICandidateHub.sol"; import "./interface/ILightClient.sol"; import "./interface/IParamSubscriber.sol"; @@ -21,6 +23,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar using BitcoinHelper for *; using TypedMemView for *; using BytesLib for *; + using SafeCast for uint256; using RLPDecode for bytes; using RLPDecode for RLPDecode.Iterator; using RLPDecode for RLPDecode.RLPItem; @@ -52,6 +55,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar // value: Candidate information mapping(address => Candidate) public candidateMap; + // Deprecated in V-1.0.20 - and all data cleaned. // This field is used to store reward of delegators // key: delegator address // value: amount of CORE tokens claimable @@ -74,6 +78,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar struct BtcTx { uint64 amount; uint32 outputIndex; + uint32 channelId; uint64 blockTimestamp; uint32 lockTime; uint32 usedHeight; @@ -122,8 +127,8 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar uint256 amount ); event btcExpired(bytes32 indexed txid, address indexed delegator); - event claimedRewardPerTx(bytes32 indexed txid, uint256 reward, bool expired, uint256 accStakedAmount, uint256 unclaimedReward); - event storedRewardPerTx(bytes32 indexed txid, uint256 reward, bool expired, uint256 accStakedAmount, uint256 unclaimedReward); + event claimedRewardBtcTx(bytes32 indexed txid, uint256 reward, bool expired, uint256 lockLengthRate, uint256 dualStakingRate); + event storedRewardBtcTx(bytes32 indexed txid, uint256 reward, bool expired, uint256 lockLengthRate, uint256 dualStakingRate); /// The validator candidate is inactive, it is expected to be active /// @param candidate Address of the validator candidate @@ -177,13 +182,15 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar { (,,bytes29 voutView,) = btcTx.extractTx(); uint32 outputIndex; - (btcAmount, outputIndex, delegator, candidate) = _parseVout(voutView, script); + uint32 channelId; + (btcAmount, outputIndex, channelId, delegator, candidate) = _parseVout(voutView, script); require(IRelayerHub(RELAYER_HUB_ADDR).isRelayer(msg.sender) || msg.sender == delegator, "only delegator or relayer can submit the BTC transaction"); IStakeHub(STAKE_HUB_ADDR).onStakeChange(delegator); bt.lockTime = lockTime; bt.blockTimestamp = blockTimestamp; bt.amount = btcAmount; bt.outputIndex = outputIndex; + bt.channelId = channelId; emit delegated(txid, candidate, delegator, script, outputIndex, btcAmount, 0); } @@ -275,36 +282,32 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar /// Claim reward for delegator /// @param delegator the delegator address + /// @param coreAmount the amount of staked CORE. /// @param settleRound the settlement round /// @param claim claim or store claim /// @return reward Amount claimed - /// @return rewardUnclaimed Amount unclaimed - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function claimReward(address delegator, uint256 settleRound, bool claim) external override onlyBtcAgent returns (uint256 reward, uint256 rewardUnclaimed, uint256 accStakedAmount) { - reward = rewardMap[delegator].reward; - rewardUnclaimed = rewardMap[delegator].unclaimedReward; - accStakedAmount = rewardMap[delegator].accStakedAmount; - if (reward != 0 || accStakedAmount != 0) { - delete rewardMap[delegator]; - } - + /// @return floatReward floating reward amount + function claimReward(address delegator, uint256 coreAmount, uint256 settleRound, bool claim) external override onlyBtcAgent returns (uint256 reward, int256 floatReward) { bool expired; uint256 rewardPerTx; - uint256 rewardUnclaimedPerTx; - uint256 accStakedAmountPerTx; + int256 floatRewardPerTx; bytes32[] storage txids = delegatorMap[delegator].txids; + bytes32 txid; for (uint256 i = txids.length; i != 0; i--) { - (rewardPerTx, expired, rewardUnclaimedPerTx, accStakedAmountPerTx) = _collectReward(txids[i - 1], settleRound); - reward += rewardPerTx; - rewardUnclaimed += rewardUnclaimedPerTx; - accStakedAmount += accStakedAmountPerTx; - if (claim) { - emit claimedRewardPerTx(txids[i - 1], rewardPerTx, expired, accStakedAmountPerTx, rewardUnclaimedPerTx); - } else { - emit storedRewardPerTx(txids[i - 1], rewardPerTx, expired, accStakedAmountPerTx, rewardUnclaimedPerTx); + txid = txids[i - 1]; + (rewardPerTx, expired, floatRewardPerTx, coreAmount) = _collectReward(txid, coreAmount, receiptMap[txid].round, settleRound, claim); + if (rewardPerTx != 0) { + uint32 channelId = btcTxMap[txid].channelId; + if (channelId != 0) { + rewardPerTx = IChannel(CHANNEL_ADDR).payCommissionById(channelId, rewardPerTx); + } } - + reward += rewardPerTx; + floatReward += floatRewardPerTx; if (expired) { + emit btcExpired(txid, receiptMap[txid].delegator); + delete receiptMap[txid]; + if (i != txids.length) { txids[i - 1] = txids[txids.length - 1]; } @@ -491,12 +494,13 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar /// @param _script redeem script of the locked up output /// @return btcAmount Amount of BTC to stake /// @return outputIndex The output index of target output. + /// @return channelId The channel Id. /// @return delegator The one who delegate the Bitcoin /// @return candidate A candidate node address. function _parseVout( bytes29 _voutView, bytes memory _script - ) internal pure returns (uint64 btcAmount, uint32 outputIndex, address delegator, address candidate) { + ) internal pure returns (uint64 btcAmount, uint32 outputIndex, uint32 channelId, address delegator, address candidate) { _voutView.assertType(uint40(BitcoinHelper.BTCTypes.Vout)); bytes29 _outputView; bytes29 _scriptPubkeyView; @@ -532,7 +536,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar } } else { // Returns the whole bytes array - (delegator, candidate) = _parsePayloadAndCheckProtocol(_arbitraryData); + (channelId, delegator, candidate) = _parsePayloadAndCheckProtocol(_arbitraryData); opreturn = true; } } @@ -542,14 +546,20 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar /// parse the payload and do sanity check for SAT+ bytes /// @param payload the BTC transaction payload - function _parsePayloadAndCheckProtocol(bytes29 payload) internal pure returns (address delegator, address candidate) { + /// @return channelId The channel Id. + /// @return delegator The reward addresss of BTC staking. + /// @return candidate The validator address. + function _parsePayloadAndCheckProtocol(bytes29 payload) internal pure returns (uint32 channelId, address delegator, address candidate) { require(payload.len() >= 48, "payload length is too small"); require(payload.indexUint(0, 4) == SatoshiPlusHelper.BTC_STAKE_MAGIC, "wrong magic"); require(payload.indexUint(5, 2) == SatoshiPlusHelper.CHAINID, "wrong chain id"); uint32 version = uint32(payload.indexUint(4, 1)); - require(version == SatoshiPlusHelper.BTC_STAKE_VERSION, "unsupported sat+ version in btc staking"); + require(version >= SatoshiPlusHelper.BTC_STAKE_INIT_VERSION && version <= SatoshiPlusHelper.BTC_STAKE_VERSION, "unsupported sat+ version in btc staking"); candidate = payload.indexAddress(27); delegator = payload.indexAddress(7); + if (version == SatoshiPlusHelper.BTC_STAKE_VERSION) { + channelId = uint32(payload.indexCompactInt(47)); + } } /// get accrued rewards of a validator candidate on a given round @@ -597,65 +607,101 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar } /// Exposed for staking API to do readonly calls, restricted to onlyBtcAgent() for safety reasons. - /// @param txid the BTC stake transaction id - /// @param drRound the start round + /// @param delegator The reward address of BTC staking. + /// @param coreAmount the amount of staked CORE. /// @param settleRound the settlement round - /// @return reward reward of the BTC stake transaction - /// @return expired whether the stake is expired - /// @return rewardUnclaimed unclaimed reward of the BTC stake transaction - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function viewCollectReward(bytes32 txid, uint256 drRound, uint256 settleRound) external onlyBtcAgent returns (uint256 reward, bool expired, uint256 rewardUnclaimed, uint256 accStakedAmount) { - return _collectReward(txid, drRound, settleRound); + /// @return rewards reward of the BTC stake transaction + /// @return floatRewards floating reward amount + function calculateRewards(address delegator, uint256 coreAmount, uint256 settleRound) external onlyBtcAgent returns (uint256[] memory rewards, int256[] memory floatRewards) { + bytes32[] storage txids = delegatorMap[delegator].txids; + uint256 size = txids.length; + rewards = new uint256[](size); + floatRewards = new int256[](size); + bool expired; + bytes32 txid; + for (uint256 i = size; i != 0; i--) { + txid = txids[i - 1]; + (rewards[i - 1], expired, floatRewards[i - 1], coreAmount) = _collectReward(txid, coreAmount, receiptMap[txid].round, settleRound, true); + } } - function _collectReward(bytes32 txid, uint256 settleRound) internal returns (uint256 reward, bool expired, uint256 rewardUnclaimed, uint256 accStakedAmount) { - DepositReceipt storage dr = receiptMap[txid]; - return _collectReward(txid, dr.round, settleRound); + function _getCalculateRound(bytes32 txid, uint256 settleRound) internal view returns (uint calculateRound, bool expired) { + BtcTx storage bt = btcTxMap[txid]; + calculateRound = bt.lockTime / SatoshiPlusHelper.ROUND_INTERVAL - 1; + expired = calculateRound <= settleRound; + if (!expired) { + calculateRound = settleRound; + } + } + + function _applyDualStaking(uint256 coreAmount, uint256 bctAmount) internal view returns (uint256, uint256) { + uint256 dsPercentage; + uint256 stakeRate = coreAmount / bctAmount; + (dsPercentage, stakeRate) = IBtcAgent(BTC_AGENT_ADDR).getGrade(stakeRate); + uint256 dualAmount = stakeRate * bctAmount; + if (coreAmount > dualAmount) { + coreAmount -= dualAmount; + } else { + coreAmount = 0; + } + return (coreAmount, dsPercentage); } /// collect rewards for a given BTC stake transaction & time grading is applied /// @param txid the BTC stake transaction id + /// @param coreAmount the amount of staked CORE. /// @param drRound the start round /// @param settleRound the settlement round + /// @param claim claim or store claim /// @return reward reward of the BTC stake transaction /// @return expired whether the stake is expired - /// @return rewardUnclaimed unclaimed reward of the BTC stake transaction - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function _collectReward(bytes32 txid, uint256 drRound, uint256 settleRound) internal returns (uint256 reward, bool expired, uint256 rewardUnclaimed, uint256 accStakedAmount) { - BtcTx storage bt = btcTxMap[txid]; - DepositReceipt storage dr = receiptMap[txid]; + /// @return floatReward floating reward amount + /// @return remainingCoreAmount the remaining coreAmount + function _collectReward(bytes32 txid, uint256 coreAmount, uint256 drRound, uint256 settleRound, bool claim) internal returns (uint256 reward, bool expired, int256 floatReward, uint256 remainingCoreAmount) { require(drRound != 0, "invalid deposit receipt"); require(settleRound < roundTag, "invalid settle round"); - uint256 unlockRound1 = bt.lockTime / SatoshiPlusHelper.ROUND_INTERVAL - 1; - if (drRound < settleRound && drRound < unlockRound1) { - uint256 minRound = settleRound < unlockRound1 ? settleRound : unlockRound1; + uint256 bctAmount; + (settleRound, expired) = _getCalculateRound(txid, settleRound); + uint256 ldPercentage; + uint256 dsPercentage; + remainingCoreAmount = coreAmount; + if (drRound < settleRound) { + BtcTx storage bt = btcTxMap[txid]; + DepositReceipt storage dr = receiptMap[txid]; // full reward - reward = (_getRoundAccruedReward(dr.candidate, minRound) - _getRoundAccruedReward(dr.candidate, drRound)) * bt.amount / SatoshiPlusHelper.BTC_DECIMAL; - accStakedAmount = bt.amount * (minRound - drRound); - - // apply time grading to BTC rewards - if (gradeActive && grades.length != 0) { - uint64 lockDuration = bt.lockTime - bt.blockTimestamp; - uint256 p = grades[0].percentage; - for (uint256 j = grades.length - 1; j != 0; j--) { - if (lockDuration >= grades[j].lockDuration) { - p = grades[j].percentage; - break; + reward = (_getRoundAccruedReward(dr.candidate, settleRound) - _getRoundAccruedReward(dr.candidate, drRound)) * bt.amount / SatoshiPlusHelper.BTC_DECIMAL; + bctAmount = bt.amount; + dr.round = settleRound; + + if (reward != 0) { + uint256 pReward; + // apply time grading to BTC rewards + if (gradeActive && grades.length != 0) { + uint64 lockDuration = bt.lockTime - bt.blockTimestamp; + ldPercentage = grades[0].percentage; + for (uint256 j = grades.length - 1; j != 0; j--) { + if (lockDuration >= grades[j].lockDuration) { + ldPercentage = grades[j].percentage; + break; + } } + pReward = reward * ldPercentage / SatoshiPlusHelper.DENOMINATOR; + floatReward = pReward.toInt256() - reward.toInt256(); + reward = pReward; + } + + (remainingCoreAmount, dsPercentage) = _applyDualStaking(coreAmount, bctAmount); + pReward = reward * dsPercentage / SatoshiPlusHelper.DENOMINATOR; + floatReward += pReward.toInt256() - reward.toInt256(); + reward = pReward; + + if (claim) { + emit claimedRewardBtcTx(txid, reward, expired, ldPercentage, dsPercentage); + } else { + emit storedRewardBtcTx(txid, reward, expired, ldPercentage, dsPercentage); } - uint256 rewardClaimed = reward * p / SatoshiPlusHelper.DENOMINATOR; - rewardUnclaimed = reward - rewardClaimed; - reward = rewardClaimed; } - - dr.round = minRound; - } - - if (unlockRound1 <= settleRound) { - emit btcExpired(txid, dr.delegator); - delete receiptMap[txid]; - return (reward, true, rewardUnclaimed, accStakedAmount); } - return (reward, false, rewardUnclaimed, accStakedAmount); + return (reward, expired, floatReward, remainingCoreAmount); } } diff --git a/contracts/Channel.sol b/contracts/Channel.sol new file mode 100644 index 000000000..d28a72195 --- /dev/null +++ b/contracts/Channel.sol @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: Apache2.0 +pragma solidity 0.8.4; + +import "./lib/Address.sol"; +import "./interface/IChannel.sol"; +import "./interface/ICoreAgent.sol"; +import "./interface/IParamSubscriber.sol"; +import "./lib/Memory.sol"; +import "./lib/SatoshiPlusHelper.sol"; +import "./System.sol"; +import "./lib/BytesLib.sol"; +import "./lib/BytesToTypes.sol"; + +contract Channel is IChannel, System, IParamSubscriber { + using BytesLib for *; + + uint256 public constant INIT_REQUIRED_MARGIN = 1e22; + uint256 public constant DEFAULT_COMMISSION_LIMIT = 2000; + + // the refundable deposit + uint256 public requiredMargin; + // the unregister feeAddress + uint256 public dues; + uint256 public commissionLimit; + + uint32 public partnerIdGenerator; + mapping(uint256 => Partner) public partners; + mapping(address => uint32) public partnerIdMap; + + mapping(address => uint32[]) public delegators; + + struct Partner { + address operator; + address feeAddress; + uint16 coreCommissionRate; + uint16 btcCommissionRate; + uint8 status; + uint256 margin; + uint256 commission; + mapping(address => uint256) delegatorAmounts; + } + + modifier exist() { + uint32 id = partnerIdMap[msg.sender]; + if (partners[id].status == 0) { + revert NoPartner(id); + } + _; + } + + /// There is insufficient amount + /// @param required required amount. + /// @param actual actual amount. + error InsufficientAmount(uint256 required, uint256 actual); + + /// The msg sender is not allowed. + /// @param id the partner id. + error NoPartner(uint32 id); + + /// Register dublicated. + /// @param addr user who tries to register. + error DuplicateRegister(address addr); + + function init() external onlyNotInit { + requiredMargin = INIT_REQUIRED_MARGIN; + commissionLimit = DEFAULT_COMMISSION_LIMIT; + alreadyInit = true; + } + + /*********************** IChannel implementations ***************************/ + /// When claiming reward, user should pay commissions to the channel partner if bind. + /// + /// @param partnerId the id of channel partner + /// @param reward the reward for total staked tx + /// @return remainingReward the remain reward after pay commission. + function payCommissionById(uint32 partnerId, uint256 reward) external override onlyCaller(BTC_STAKE_ADDR) returns (uint256 remainingReward) { + if (partnerId == 0 && partnerId > partnerIdGenerator) { + return reward; + } + Partner storage p = partners[partnerId]; + uint256 commission = reward * p.btcCommissionRate / SatoshiPlusHelper.DENOMINATOR; + p.commission += commission; + remainingReward = reward - commission; + } + + /// When claiming reward, user should pay commissions to the channel partner if bind. + /// + /// @param delegator the delegator address + /// @param amount the staked amount + /// @param reward the amount of rewards collected + /// @return remainingReward the remain reward after pay commission. + function payCommissions(address delegator, uint256 amount, uint256 reward) external override onlyCaller(CORE_AGENT_ADDR) returns (uint256 remainingReward) { + if (reward == 0) { + return reward; + } + uint32[] storage ids = delegators[delegator]; + remainingReward = reward; + for (uint256 i = ids.length; i != 0; i--) { + Partner storage p = partners[ids[i-1]]; + uint256 commission = p.delegatorAmounts[delegator] * p.coreCommissionRate / SatoshiPlusHelper.DENOMINATOR * reward / amount; + p.commission += commission; + remainingReward -= commission; + } + } + + /// Reset commission for partner + /// + /// @param partner the channel partner + /// @return commission the Amount of commission + /// @return feeAddress the fee address + function resetCommission(address partner) external override onlyCaller(STAKE_HUB_ADDR) returns (uint256 commission, address feeAddress) { + uint32 id = partnerIdMap[partner]; + if (id != 0) { + feeAddress = partners[id].feeAddress; + commission = partners[id].commission; + if (commission != 0) { + partners[id].commission = 0; + } + } + } + + /*********************** External methods ***************************/ + /// Register as a channel partner + function register(address feeAddress, uint16 coreCommissionRate, uint16 btcCommissionRate) external payable { + uint32 id = partnerIdMap[msg.sender]; + if (partners[id].status != 0) { + revert DuplicateRegister(msg.sender); + } + if (msg.value < requiredMargin) { + revert InsufficientAmount(requiredMargin, msg.value); + } + if (coreCommissionRate > commissionLimit) { + revert OutOfBounds('coreCommissionRate', coreCommissionRate, 0, commissionLimit); + } + if (btcCommissionRate > commissionLimit) { + revert OutOfBounds('btcCommissionRate', btcCommissionRate, 0, commissionLimit); + } + if (id == 0) { + id = ++partnerIdGenerator; + partnerIdMap[msg.sender] = id; + } + Partner storage p = partners[id]; + p.operator = msg.sender; + p.feeAddress = feeAddress; + p.coreCommissionRate = coreCommissionRate; + p.btcCommissionRate = btcCommissionRate; + p.status = 1; + p.margin = msg.value; + } + + /// Unregister from a channel partner + function unregister() external exist { + Partner storage p = partners[partnerIdMap[msg.sender]]; + p.status = 0; + + uint256 margin = p.margin; + if (margin > dues) { + uint256 value = margin - dues; + Address.sendValue(payable(msg.sender), value); + if (dues > 0) { + payable(SYSTEM_REWARD_ADDR).transfer(uint256(dues)); + } + } else { + payable(SYSTEM_REWARD_ADDR).transfer(margin); + } + } + + /// Set the fee address + /// @param _feeAddress The new fee address + function setFeeAddress(address _feeAddress) external exist { + partners[partnerIdMap[msg.sender]].feeAddress = _feeAddress; + } + + /// Set the core commission rate + /// @param coreCommissionRate The new value + function setCoreCommissionRate(uint16 coreCommissionRate) external exist { + if (coreCommissionRate > commissionLimit) { + revert OutOfBounds('coreCommissionRate', coreCommissionRate, 0, commissionLimit); + } + partners[partnerIdMap[msg.sender]].coreCommissionRate = coreCommissionRate; + } + + /// Set the btc commission rate + /// @param btcCommissionRate The new value + function setBtcCommissionRate(uint16 btcCommissionRate) external exist { + if (btcCommissionRate > commissionLimit) { + revert OutOfBounds('btcCommissionRate', btcCommissionRate, 0, commissionLimit); + } + partners[partnerIdMap[msg.sender]].btcCommissionRate = btcCommissionRate; + } + + /// Get the staking amount of special id & delegator. + function getDelegatorAmount(uint32 id, address delegator) external view returns (uint256) { + return partners[id].delegatorAmounts[delegator]; + } + + /// Get the id array for the delegator. + function getIds(address delegator) external view returns (uint32[] memory) { + return delegators[delegator]; + } + + /*********************** External methods ***************************/ + /// stake CORE from a partner channel + /// @param candidate The operator address of validator + /// @param id The partner id + function delegateCoin(address candidate, uint32 id) external payable { + if (partners[id].status == 0) { + revert NoPartner(id); + } + address delegator = msg.sender; + uint256 amount = msg.value; + ICoreAgent(CORE_AGENT_ADDR).proxyDelegate{value: amount}(candidate, delegator, id); + Partner storage p = partners[id]; + uint256 existAmount = p.delegatorAmounts[delegator]; + if (existAmount == 0) { + delegators[delegator].push(id); + } + p.delegatorAmounts[delegator] = existAmount + amount; + } + + /// unstake CORE from a partner channel + /// @param candidate The operator address of validator + /// @param amount The amount of CORE to undelegate + /// @param id The partner id + function undelegateCoin(address candidate, uint256 amount, uint32 id) external { + if (id == 0 || id > partnerIdGenerator) { + return; + } + uint256 stakedAmount = partners[id].delegatorAmounts[msg.sender]; + if (stakedAmount < amount) { + revert InsufficientAmount(amount, stakedAmount); + } + ICoreAgent(CORE_AGENT_ADDR).proxyUnDelegate(candidate, msg.sender, amount, id); + if (stakedAmount > amount) { + partners[id].delegatorAmounts[msg.sender] = stakedAmount - amount; + } else { + delete partners[id].delegatorAmounts[msg.sender]; + // remove id from Delegator + uint32[] storage ids = delegators[msg.sender]; + for (uint256 i = ids.length; i != 0; i--) { + if (ids[i-1] == id) { + if (i != ids.length) { + ids[i-1] = ids[ids.length-1]; + } + ids.pop(); + } + } + } + Address.sendValue(payable(msg.sender), amount); + } + + /*********************** Governance ********************************/ + /// Update parameters through governance vote + /// @param key The name of the parameter + /// @param value the new value set to the parameter + function updateParam(string calldata key, bytes calldata value) external override onlyInit onlyCaller(GOV_HUB_ADDR) { + if (value.length != 32) { + revert MismatchParamLength(key); + } + if (Memory.compareStrings(key, "requiredMargin")) { + uint256 newRequiredMargin = BytesToTypes.bytesToUint256(32, value); + if (newRequiredMargin <= dues) { + revert OutOfBounds(key, newRequiredMargin, dues+1, type(uint256).max); + } + requiredMargin = newRequiredMargin; + } else if (Memory.compareStrings(key, "dues")) { + uint256 newDues = BytesToTypes.bytesToUint256(32, value); + if (newDues == 0 || newDues >= requiredMargin) { + revert OutOfBounds(key, newDues, 1, requiredMargin - 1); + } + dues = newDues; + } else if (Memory.compareStrings(key, "commissionLimit")) { + uint256 newRate = BytesToTypes.bytesToUint256(32, value); + if (newRate > SatoshiPlusHelper.DENOMINATOR) { + revert OutOfBounds('commissionLimit', newRate, 0, SatoshiPlusHelper.DENOMINATOR); + } + commissionLimit = newRate; + } else { + revert UnsupportedGovParam(key); + } + + emit paramChange(key, value); + } +} \ No newline at end of file diff --git a/contracts/CoreAgent.sol b/contracts/CoreAgent.sol index dd4f44854..73b9ce5a0 100644 --- a/contracts/CoreAgent.sol +++ b/contracts/CoreAgent.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; -import "./interface/IAgent.sol"; +import "./interface/IChannel.sol"; +import "./interface/ICoreAgent.sol"; import "./interface/IParamSubscriber.sol"; import "./interface/ICandidateHub.sol"; import "./interface/ISystemReward.sol"; @@ -13,7 +14,7 @@ import "./lib/SatoshiPlusHelper.sol"; import "./System.sol"; /// This contract handles CORE staking. -contract CoreAgent is IAgent, System, IParamSubscriber { +contract CoreAgent is ICoreAgent, System, IParamSubscriber { uint256 public constant INIT_REQUIRED_COIN_DEPOSIT = 1e18; @@ -61,6 +62,7 @@ contract CoreAgent is IAgent, System, IParamSubscriber { struct Delegator { address[] candidates; uint256 amount; + uint256 channelAmount; } struct Reward { @@ -68,6 +70,10 @@ contract CoreAgent is IAgent, System, IParamSubscriber { uint256 accStakedAmount; } + error NotImplemented(); + + error InsufficientTokens(uint32 channelId); + /*********************** events **************************/ event delegatedCoin(address indexed candidate, address indexed delegator, uint256 amount, uint256 realtimeAmount); event undelegatedCoin(address indexed candidate, address indexed delegator, uint256 amount); @@ -83,8 +89,8 @@ contract CoreAgent is IAgent, System, IParamSubscriber { event collectedReward(address indexed candidate, address indexed delegator, uint256 reward, uint256 accStakedAmount); event storedReward(address indexed candidate, address indexed delegator, uint256 reward, uint256 accStakedAmount); - modifier onlyPledgeAgent() { - require(msg.sender == PLEDGE_AGENT_ADDR, "the sender must be PledgeAgent contract"); + modifier onlyInternalCall() { + require(msg.sender == PLEDGE_AGENT_ADDR || msg.sender == CHANNEL_ADDR, "the sender must be PledgeAgent or Channel contracts"); _; } @@ -177,6 +183,11 @@ contract CoreAgent is IAgent, System, IParamSubscriber { /// @param candidate The operator address of validator /// @param amount The amount of CORE to undelegate function undelegateCoin(address candidate, uint256 amount) public { + Delegator storage d = delegatorMap[msg.sender]; + if (d.amount < d.channelAmount + amount) { + revert InsufficientTokens(0); + } + IStakeHub(STAKE_HUB_ADDR).onStakeChange(msg.sender); uint256 dAmount = _undelegateCoin(candidate, msg.sender, amount, false); _deductTransferredAmount(msg.sender, dAmount); @@ -202,51 +213,62 @@ contract CoreAgent is IAgent, System, IParamSubscriber { emit transferredCoin(sourceCandidate, targetCandidate, msg.sender, amount, newDeposit); } - /// Claim reward for delegator /// @param delegator the delegator address - /// @param settleRound the settlement round /// @param claim claim or store rewards /// @return reward Amount claimed - /// @return floatReward floating reward amount - /// @return accStakedAmount accumulated stake amount (multiplied by rounds), used for grading calculation - function claimReward(address delegator, uint256 /*coreAmount*/, uint256 settleRound, bool claim) external override onlyStakeHub returns (uint256 reward, int256 floatReward, uint256 accStakedAmount) { + /// @return stakedAmount1 the staked amount in the first round + /// @return stakedAmount2 the real amount in the last round + function claimReward(address delegator, bool claim) external override onlyStakeHub returns (uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2) { + uint256 initAmount = delegatorMap[delegator].amount; address[] storage candidates = delegatorMap[delegator].candidates; uint256 candidateSize = candidates.length; address candidate; uint256 rewardSum; - uint256 accStakedAmountSum; + uint256 s1; + uint256 s2; for (uint256 i = candidateSize; i != 0; --i) { candidate = candidates[i - 1]; CoinDelegator storage cd = candidateMap[candidate].cDelegatorMap[delegator]; - (reward, accStakedAmount) = _collectRewardFromCandidate(candidate, cd, settleRound); + (reward, s1, s2) = _collectRewardFromCandidate(candidate, cd); rewardSum += reward; - accStakedAmountSum += accStakedAmount; - if (claim) { - emit collectedReward(candidate, delegator, reward, accStakedAmount); - } else { - emit storedReward(candidate, delegator, reward, accStakedAmount); + stakedAmount1 += s1; + stakedAmount2 += s2; + if (reward != 0) { + if (claim) { + emit collectedReward(candidate, delegator, reward, 0); + } else { + emit storedReward(candidate, delegator, reward, 0); + } } if (cd.realtimeAmount == 0 && cd.transferredAmount == 0) { _removeDelegation(delegator, candidate); } } + if (rewardSum != 0) { + rewardSum = IChannel(CHANNEL_ADDR).payCommissions(delegator, initAmount, rewardSum); + } + reward = rewardMap[delegator].reward; - accStakedAmount = rewardMap[delegator].accStakedAmount; - if (accStakedAmount != 0 || reward != 0) { + if (reward != 0 || rewardMap[delegator].accStakedAmount != 0) { delete rewardMap[delegator]; } reward += rewardSum; - floatReward = 0; - accStakedAmount += accStakedAmountSum; - if (claim) { - emit claimedCoinReward(delegator, reward, accStakedAmount); - } else { - emit storedCoinReward(delegator, reward, accStakedAmount); + if (reward != 0) { + if (claim) { + emit claimedCoinReward(delegator, reward, 0); + } else { + emit storedCoinReward(delegator, reward, 0); + } } } + /// Claim reward for delegator + function claimReward(address, uint256, uint256, bool) external override pure returns (uint256, int256) { + revert NotImplemented(); + } + /*********************** Receive data from PledgeAgent ***************************/ /// move staking data for a candidate/delegator pair from PledgeAgent /// @param candidate the validator candidate address @@ -254,7 +276,7 @@ contract CoreAgent is IAgent, System, IParamSubscriber { /// @param stakedAmount the staked amount of last round snapshot /// @param transferredAmount the transferred out amount counted in reward calculation /// @param round data of the round - function moveData(address candidate, address delegator, uint256 stakedAmount, uint256 transferredAmount, uint256 round) external payable onlyPledgeAgent { + function moveData(address candidate, address delegator, uint256 stakedAmount, uint256 transferredAmount, uint256 round) external payable onlyInternalCall { uint256 realtimeAmount = msg.value; require(stakedAmount <= realtimeAmount, "require stakedAmount <= realtimeAmount"); Candidate storage a = candidateMap[candidate]; @@ -264,20 +286,20 @@ contract CoreAgent is IAgent, System, IParamSubscriber { cd.changeRound = roundTag; delegatorMap[delegator].candidates.push(candidate); } else if (changeRound != roundTag) { - uint256 lastRoundTag = roundTag - 1; - (uint256 reward, uint256 accStakedAmount) = _collectRewardFromCandidate(candidate, cd, lastRoundTag); - rewardMap[delegator].reward += reward; - rewardMap[delegator].accStakedAmount += accStakedAmount; - emit storedReward(candidate, delegator, reward, accStakedAmount); + (uint256 reward, ,) = _collectRewardFromCandidate(candidate, cd); + if (reward != 0) { + rewardMap[delegator].reward += reward; + emit storedReward(candidate, delegator, reward, 0); + } } if (round < roundTag) { - uint256 lastRoundTag = roundTag - 1; - (uint256 reward,,uint256 accStakedAmount) = _collectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, round, lastRoundTag); + (, uint256 reward, ,) = _collectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, round); stakedAmount = realtimeAmount; - rewardMap[delegator].reward += reward; - rewardMap[delegator].accStakedAmount += accStakedAmount; cd.changeRound = roundTag; - emit storedReward(candidate, delegator, reward, accStakedAmount); + if (reward != 0) { + rewardMap[delegator].reward += reward; + emit storedReward(candidate, delegator, reward, 0); + } } else { cd.transferredAmount += transferredAmount; } @@ -286,10 +308,12 @@ contract CoreAgent is IAgent, System, IParamSubscriber { delegatorMap[delegator].amount += realtimeAmount; } - /// for backward compatibility - allow users to stake through PledgeAgent + /// for backward compatibility - allow users to unstake through PledgeAgent + /// support channel from v1.0.20 /// @param candidate the validator candidate address /// @param delegator the delegator address - function proxyDelegate(address candidate, address delegator) external payable onlyPledgeAgent { + /// @param channelId the channel id, 0 represents from PledgeAgent + function proxyDelegate(address candidate, address delegator, uint32 channelId) external payable override onlyInternalCall { if (!ICandidateHub(CANDIDATE_HUB_ADDR).canDelegate(candidate)) { revert InactiveCandidate(candidate); } @@ -297,17 +321,35 @@ contract CoreAgent is IAgent, System, IParamSubscriber { IStakeHub(STAKE_HUB_ADDR).onStakeChange(delegator); uint256 realtimeAmount = _delegateCoin(candidate, delegator, msg.value, false); emit delegatedCoin(candidate, delegator, msg.value, realtimeAmount); + if (channelId != 0) { + delegatorMap[delegator].channelAmount += msg.value; + } } /// for backward compatibility - allow users to unstake through PledgeAgent + /// support channel from v1.0.20 /// @param candidate the validator candidate address /// @param delegator the delegator address /// @param amount the amount of CORE to unstake - function proxyUnDelegate(address candidate, address delegator, uint256 amount) external onlyPledgeAgent returns(uint256) { + /// @param channelId the channel id, 0 represents from PledgeAgent + function proxyUnDelegate(address candidate, address delegator, uint256 amount, uint32 channelId) external override onlyInternalCall returns(uint256) { IStakeHub(STAKE_HUB_ADDR).onStakeChange(delegator); if (amount == 0) { amount = candidateMap[candidate].cDelegatorMap[delegator].realtimeAmount; } + + Delegator storage d = delegatorMap[delegator]; + if (channelId == 0) { + if (d.amount < d.channelAmount + amount) { + revert InsufficientTokens(channelId); + } + } else { + if (d.channelAmount < amount) { + revert InsufficientTokens(channelId); + } + d.channelAmount -= amount; + } + uint256 dAmount = _undelegateCoin(candidate, delegator, amount, false); _deductTransferredAmount(delegator, dAmount); Address.sendValue(payable(PLEDGE_AGENT_ADDR), amount); @@ -320,7 +362,7 @@ contract CoreAgent is IAgent, System, IParamSubscriber { /// @param targetCandidate the validator candidate address to transfer to /// @param delegator the delegator address /// @param amount the amount of CORE to unstake - function proxyTransfer(address sourceCandidate, address targetCandidate, address delegator, uint256 amount) external onlyPledgeAgent { + function proxyTransfer(address sourceCandidate, address targetCandidate, address delegator, uint256 amount) external onlyInternalCall { if (!ICandidateHub(CANDIDATE_HUB_ADDR).canDelegate(targetCandidate)) { revert InactiveCandidate(targetCandidate); } @@ -414,7 +456,7 @@ contract CoreAgent is IAgent, System, IParamSubscriber { CoinDelegator storage cd = candidateMap[candidate].cDelegatorMap[delegator]; transferredAmount = cd.transferredAmount; if (transferredAmount != 0) { - if (transferredAmount < amount) { + if (transferredAmount <= amount) { amount -= transferredAmount; cd.transferredAmount = 0; if (cd.realtimeAmount == 0) { @@ -433,28 +475,39 @@ contract CoreAgent is IAgent, System, IParamSubscriber { } /// Exposed for staking API to do readonly calls, restricted to onlyStakeHub() for safety reasons. - /// @param candidate the validator candidate to collect rewards /// @param delegator the address of delegator - /// @param settleRound the settlement round - /// @return reward The amount of CORE collected - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function viewCollectRewardFromCandidate(address candidate, address delegator, uint256 settleRound) external onlyStakeHub returns (uint256 reward, uint256 accStakedAmount) { - CoinDelegator storage cd = candidateMap[candidate].cDelegatorMap[delegator]; - return _collectRewardFromCandidate(candidate, cd, settleRound); + /// @return candidates the validator list with stakes + /// @return rewards rewards on each validator + /// @return stakedAmount1 the staked amount in the first round + /// @return stakedAmount2 the staked amount in the last round + function calculateRewards(address delegator) external onlyStakeHub returns (address[] memory candidates, uint256[] memory rewards, uint256 stakedAmount1, uint256 stakedAmount2) { + address candidate; + uint256 size = delegatorMap[delegator].candidates.length; + rewards = new uint256[](size); + uint256 s1; + uint256 s2; + for (uint256 i = 0; i < size; ++i) { + candidate = delegatorMap[delegator].candidates[i]; + CoinDelegator storage cd = candidateMap[candidate].cDelegatorMap[delegator]; + (rewards[i], s1, s2) = _collectRewardFromCandidate(candidate, cd); + stakedAmount1 += s1; + stakedAmount2 += s2; + } + return (delegatorMap[delegator].candidates, rewards, stakedAmount1, stakedAmount2); } /// collect reward from a validator candidate - /// @param candidate the validator candidate to collect rewards + /// @param candidate the validator candidate to collect reward from /// @param cd the structure stores user CORE stake information - /// @param settleRound the settlement round - /// @return reward The amount of CORE collected - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function _collectRewardFromCandidate(address candidate, CoinDelegator storage cd, uint256 settleRound) internal returns (uint256 reward, uint256 accStakedAmount) { + /// @return reward the amount of rewards collected + /// @return stakedAmount1 the staked amount in the first round + /// @return stakedAmount2 the staked amount in the last round + function _collectRewardFromCandidate(address candidate, CoinDelegator storage cd) internal returns (uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2) { uint256 stakedAmount = cd.stakedAmount; uint256 realtimeAmount = cd.realtimeAmount; uint256 transferredAmount = cd.transferredAmount; bool changed; - (reward, changed, accStakedAmount) = _collectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, cd.changeRound, settleRound); + (changed, reward, stakedAmount1, stakedAmount2) = _collectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, cd.changeRound); if (changed) { if (transferredAmount != 0) { cd.transferredAmount = 0; @@ -462,7 +515,7 @@ contract CoreAgent is IAgent, System, IParamSubscriber { if (realtimeAmount != stakedAmount) { cd.stakedAmount = realtimeAmount; } - cd.changeRound = settleRound + 1; + cd.changeRound = roundTag; } } @@ -472,39 +525,28 @@ contract CoreAgent is IAgent, System, IParamSubscriber { /// @param realtimeAmount realtime staked CORE amount /// @param transferredAmount transferred in CORE amount, also eligible for rewards /// @param changeRound the last round when the delegator acted - /// @param settleRound the settlement round - /// @return reward the amount of rewards collected /// @return changed whether the changedRound value should be updated - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function _collectReward(address candidate, uint256 stakedAmount, uint256 realtimeAmount, uint256 transferredAmount, uint256 changeRound, uint256 settleRound) internal returns (uint256 reward, bool changed, uint256 accStakedAmount) { + /// @return reward the amount of rewards + /// @return stakedAmount1 the staked amount in the change round + /// @return stakedAmount2 the staked amount in the last round + function _collectReward(address candidate, uint256 stakedAmount, uint256 realtimeAmount, uint256 transferredAmount, uint256 changeRound) internal returns (bool changed, uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2) { require(changeRound != 0, "invalid delegator"); - require(settleRound < roundTag, "invalid settle round"); - if (changeRound <= settleRound) { - uint256 lastRoundReward = _getRoundAccruedReward(candidate, settleRound); + uint256 lastRound = roundTag - 1; + changed = changeRound <= lastRound; + if (changed) { + uint256 changeRoundReward = _getRoundAccruedReward(candidate, changeRound); uint256 lastChangeRoundReward = _getRoundAccruedReward(candidate, changeRound - 1); - uint256 changeRoundReward; - reward = stakedAmount * (lastRoundReward - lastChangeRoundReward); - accStakedAmount = stakedAmount * (settleRound - changeRound + 1); - - if (transferredAmount != 0) { - changeRoundReward = _getRoundAccruedReward(candidate, changeRound); - reward += transferredAmount * (changeRoundReward - lastChangeRoundReward); - accStakedAmount += transferredAmount; - } + stakedAmount1 = stakedAmount + transferredAmount; + reward = stakedAmount1 * (changeRoundReward - lastChangeRoundReward) / SatoshiPlusHelper.CORE_STAKE_DECIMAL; - if (realtimeAmount != stakedAmount) { - if (changeRound < settleRound) { - if (changeRoundReward == 0) { - changeRoundReward = _getRoundAccruedReward(candidate, changeRound); - } - reward += (realtimeAmount - stakedAmount) * (lastRoundReward - changeRoundReward); - accStakedAmount += (realtimeAmount - stakedAmount) * (settleRound - changeRound); - } + if (changeRound < lastRound) { + stakedAmount2 = realtimeAmount; + uint256 lastRoundReward = _getRoundAccruedReward(candidate, lastRound); + reward += stakedAmount2 * (lastRoundReward - changeRoundReward) / SatoshiPlusHelper.CORE_STAKE_DECIMAL; + } else { + stakedAmount2 = stakedAmount1; } - reward /= SatoshiPlusHelper.CORE_STAKE_DECIMAL; - return (reward, true, accStakedAmount); } - return (0, false, 0); } /// remove delegate record of a candidate/delegator pair diff --git a/contracts/HashPowerAgent.sol b/contracts/HashPowerAgent.sol index 0a652dcb5..2b59461a2 100644 --- a/contracts/HashPowerAgent.sol +++ b/contracts/HashPowerAgent.sol @@ -16,9 +16,9 @@ contract HashPowerAgent is IAgent, System, IParamSubscriber { mapping(address => Reward) public rewardMap; /*********************** events **************************/ - event claimedHashReward(address indexed delegator, uint256 amount, uint256 accStakedAmount); + event claimedHashReward(address indexed delegator, uint256 amount); event validatorAvgReward(address indexed validator, uint256 avgReward); - event storedHashReward(address indexed delegator, uint256 amount, uint256 accStakedAmount); + event storedHashReward(address indexed delegator, uint256 amount); struct Reward { uint256 reward; @@ -54,7 +54,6 @@ contract HashPowerAgent is IAgent, System, IParamSubscriber { avgReward = rewardList[i] / minerSize; for (uint256 j = 0; j < minerSize; ++j) { rewardMap[miners[j]].reward += avgReward; - rewardMap[miners[j]].accStakedAmount += 1; } emit validatorAvgReward(validators[i], avgReward); } @@ -84,19 +83,17 @@ contract HashPowerAgent is IAgent, System, IParamSubscriber { /// @param claim claim or store claim /// @return reward Amount claimed /// @return floatReward floating reward amount - /// @return accStakedAmount accumulated stake amount (multiplied by rounds), used for grading calculation - function claimReward(address delegator, uint256 /*coreAmount*/, uint256 /*settleRound*/, bool claim) external override onlyStakeHub returns (uint256 reward, int256 floatReward, uint256 accStakedAmount) { + function claimReward(address delegator, uint256 /*coreAmount*/, uint256 /*settleRound*/, bool claim) external override onlyStakeHub returns (uint256 reward, int256 floatReward) { reward = rewardMap[delegator].reward; if (reward != 0) { - accStakedAmount = rewardMap[delegator].accStakedAmount; delete rewardMap[delegator]; + if (claim) { + emit claimedHashReward(delegator, reward); + } else { + emit storedHashReward(delegator, reward); + } } - if (claim) { - emit claimedHashReward(delegator, reward, accStakedAmount); - } else { - emit storedHashReward(delegator, reward, accStakedAmount); - } - return (reward, 0, accStakedAmount); + return (reward, 0); } /*********************** Governance ********************************/ diff --git a/contracts/PledgeAgent.sol b/contracts/PledgeAgent.sol index a8c3cff68..3bb7c09d4 100644 --- a/contracts/PledgeAgent.sol +++ b/contracts/PledgeAgent.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; +import "./interface/ICoreAgent.sol"; import "./interface/IPledgeAgent.sol"; import "./interface/IParamSubscriber.sol"; import "./interface/ISystemReward.sol"; @@ -163,9 +164,7 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { function delegateCoin(address agent) external payable override noReentrant{ _moveCOREData(agent, msg.sender); _distributeReward(msg.sender); - - (bool success, ) = CORE_AGENT_ADDR.call {value: msg.value} (abi.encodeWithSignature("proxyDelegate(address,address)", agent, msg.sender)); - require (success, "call CORE_AGENT_ADDR.proxyDelegate() failed"); + ICoreAgent(CORE_AGENT_ADDR).proxyDelegate{value: msg.value}(agent, msg.sender, 0); } /// Undelegate coin from a validator @@ -183,9 +182,7 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { _moveCOREData(agent, msg.sender); _distributeReward(msg.sender); - (bool success, bytes memory data) = CORE_AGENT_ADDR.call(abi.encodeWithSignature("proxyUnDelegate(address,address,uint256)", agent, msg.sender, amount)); - require (success, "call CORE_AGENT_ADDR.proxyUnDelegate() failed"); - uint256 undelegateAmount = abi.decode(data, (uint256)); + uint256 undelegateAmount = ICoreAgent(CORE_AGENT_ADDR).proxyUnDelegate(agent, msg.sender, amount, 0); Address.sendValue(payable(msg.sender), undelegateAmount); } @@ -228,7 +225,7 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { if (proxyRewardSum != 0) { rewardMap[msg.sender] += proxyRewardSum; } - + _distributeReward(msg.sender); return (rewardSum + proxyRewardSum, true); diff --git a/contracts/StakeHub.sol b/contracts/StakeHub.sol index 1d7038fbb..95f5fc1bf 100644 --- a/contracts/StakeHub.sol +++ b/contracts/StakeHub.sol @@ -8,6 +8,8 @@ import "./interface/ISystemReward.sol"; import "./interface/IBitcoinStake.sol"; import "./interface/IValidatorSet.sol"; import "./interface/ICandidateHub.sol"; +import "./interface/IChannel.sol"; +import "./interface/ICoreAgent.sol"; import "./System.sol"; import "./lib/Address.sol"; import "./lib/Memory.sol"; @@ -71,7 +73,7 @@ contract StakeHub is IStakeHub, System, IParamSubscriber { /*********************** events **************************/ event roundReward(string indexed name, uint256 round, address[] validator, uint256[] amount); - event claimedReward(address indexed delegator, uint256 amount); + event claimedReward(address indexed delegator, uint256[] amounts); event claimedRelayerReward(address indexed relayer, uint256 amount); event received(address indexed from, uint256 amount); @@ -245,7 +247,7 @@ contract StakeHub is IStakeHub, System, IParamSubscriber { } if (reward != 0) { Address.sendValue(payable(delegator), reward); - emit claimedReward(delegator, reward); + emit claimedReward(delegator, rewards); } } @@ -309,25 +311,18 @@ contract StakeHub is IStakeHub, System, IParamSubscriber { uint256 assetSize = assets.length; rewards = new uint256[](assetSize); int256 totalFloatReward; - int256 floatReward; - uint256 accStakedCoreAmount; - if (d.changeRound != 0 && d.changeRound < lastRound) { - (rewards[0], floatReward, accStakedCoreAmount) = IAgent(assets[0].agent).claimReward(delegator, 0, d.changeRound, claim); - totalFloatReward += floatReward; - (rewards[2], floatReward,) = IAgent(assets[2].agent).claimReward(delegator, accStakedCoreAmount, d.changeRound, claim); - totalFloatReward += floatReward; - } + uint256 stakedCoreAmount1; + uint256 stakedCoreAmount2; + (rewards[0], stakedCoreAmount1, stakedCoreAmount2) = ICoreAgent(assets[0].agent).claimReward(delegator, claim); - uint256 tempReward; - (tempReward, floatReward, accStakedCoreAmount) = IAgent(assets[0].agent).claimReward(delegator, 0, lastRound, claim); - totalFloatReward += floatReward; - rewards[0] += tempReward; + (rewards[1],) = IAgent(assets[1].agent).claimReward(delegator, 0, lastRound, claim); - uint256 totalReward = rewards[0]; - for (uint256 i = 1; i < assetSize; ++i) { - (tempReward, floatReward,) = IAgent(assets[i].agent).claimReward(delegator, accStakedCoreAmount, lastRound, claim); - rewards[i] += tempReward; - totalReward += rewards[i]; + if (stakedCoreAmount1 != stakedCoreAmount2) { + (rewards[2], totalFloatReward) = IAgent(assets[2].agent).claimReward(delegator, stakedCoreAmount1, d.changeRound, claim); + } + if (d.changeRound < lastRound || stakedCoreAmount1 == stakedCoreAmount2) { + (uint256 reward, int256 floatReward) = IAgent(assets[2].agent).claimReward(delegator, stakedCoreAmount2, lastRound, claim); + rewards[2] += reward; totalFloatReward += floatReward; } @@ -339,6 +334,13 @@ contract StakeHub is IStakeHub, System, IParamSubscriber { surplus = (surplus.toInt256() - totalFloatReward).toUint256(); } + function claimCommission() external returns(uint256 commission, address feeAddress) { + (commission, feeAddress) = IChannel(CHANNEL_ADDR).resetCommission(msg.sender); + if (commission != 0) { + Address.sendValue(payable(feeAddress), commission); + } + } + /*********************** Governance ********************************/ /// Update parameters through governance vote /// @param key The name of the parameter diff --git a/contracts/System.sol b/contracts/System.sol index 4e5b68c37..5a55aa07e 100644 --- a/contracts/System.sol +++ b/contracts/System.sol @@ -28,6 +28,7 @@ contract System { address public constant BTC_STAKE_ADDR = 0x0000000000000000000000000000000000001014; // 0x0000000000000000000000000000000000001015 is deprecated address public constant CONFIGURATION_ADDR = 0x0000000000000000000000000000000000001016; + address public constant CHANNEL_ADDR = 0x0000000000000000000000000000000000001017; // 0x0000000000000000000000000000000000010001 is deprecated; modifier onlyCoinbase() { @@ -84,13 +85,15 @@ contract System { _; } - modifier onlyBtcStake() { - require(msg.sender == BTC_STAKE_ADDR, "the msg sender must be bitcoin stake contract"); + modifier onlyBtcAgent() { + require(msg.sender == BTC_AGENT_ADDR, "the msg sender must be bitcoin agent contract"); _; } - modifier onlyBtcAgent() { - require(msg.sender == BTC_AGENT_ADDR, "the msg sender must be bitcoin agent contract"); + modifier onlyCaller(address expected) { + if (msg.sender != expected) { + revert NotPermissionalCaller(expected, msg.sender); + } _; } @@ -109,4 +112,9 @@ contract System { /// The passed param is unsupported. /// @param key The name of the parameter error UnsupportedGovParam(string key); + + /// The msg sender is not allowed. + /// @param expected expected caller. + /// @param actual actual caller. + error NotPermissionalCaller(address expected, address actual); } diff --git a/contracts/System.template b/contracts/System.template index 8bc6fd9f7..112c279ab 100644 --- a/contracts/System.template +++ b/contracts/System.template @@ -26,6 +26,7 @@ contract System { address public CORE_AGENT_ADDR; address public HASH_AGENT_ADDR; address public CONFIGURATION_ADDR; + address public CHANNEL_ADDR; struct SystemContractAddr { address validator; @@ -44,6 +45,7 @@ contract System { address coreAgent; address hashAgent; address configurationContract; + address channel; } function updateContractAddr(bytes memory _systemContractAddr) external { @@ -64,6 +66,7 @@ contract System { CORE_AGENT_ADDR = systemContractAddr.coreAgent; HASH_AGENT_ADDR = systemContractAddr.hashAgent; CONFIGURATION_ADDR = systemContractAddr.configurationContract; + CHANNEL_ADDR = systemContractAddr.channel; } function setAlreadyInit(bool value) external { @@ -89,6 +92,7 @@ contract System { address public constant BTC_STAKE_ADDR = 0x0000000000000000000000000000000000001014; // 0x0000000000000000000000000000000000001015 is deprecated address public constant CONFIGURATION_ADDR = 0x0000000000000000000000000000000000001016; + address public constant CHANNEL_ADDR = 0x0000000000000000000000000000000000001017; // 0x0000000000000000000000000000000000010001 is deprecated; {% endif %} modifier onlyCoinbase() { @@ -145,13 +149,15 @@ contract System { _; } - modifier onlyBtcStake() { - require(msg.sender == BTC_STAKE_ADDR, "the msg sender must be bitcoin stake contract"); + modifier onlyBtcAgent() { + require(msg.sender == BTC_AGENT_ADDR, "the msg sender must be bitcoin agent contract"); _; } - modifier onlyBtcAgent() { - require(msg.sender == BTC_AGENT_ADDR, "the msg sender must be bitcoin agent contract"); + modifier onlyCaller(address expected) { + if (msg.sender != expected) { + revert NotPermissionalCaller(expected, msg.sender); + } _; } @@ -170,4 +176,9 @@ contract System { /// The passed param is unsupported. /// @param key The name of the parameter error UnsupportedGovParam(string key); + + /// The msg sender is not allowed. + /// @param expected expected caller. + /// @param actual actual caller. + error NotPermissionalCaller(address expected, address actual); } diff --git a/contracts/interface/IAgent.sol b/contracts/interface/IAgent.sol index a6b4eac25..fd527e259 100644 --- a/contracts/interface/IAgent.sol +++ b/contracts/interface/IAgent.sol @@ -30,11 +30,10 @@ interface IAgent { /// Claim reward for delegator /// @param delegator the delegator address - /// @param coreAmount the accumurated amount of staked CORE. + /// @param coreAmount the staked amount of staked CORE. /// @param settleRound the settlement round /// @param claim claim or store rewards /// @return reward Amount claimed /// @return floatReward floating reward amount - /// @return accStakedAmount accumulated stake amount (multiplied by rounds), used for grading calculation - function claimReward(address delegator, uint256 coreAmount, uint256 settleRound, bool claim) external returns (uint256 reward, int256 floatReward, uint256 accStakedAmount); + function claimReward(address delegator, uint256 coreAmount, uint256 settleRound, bool claim) external returns (uint256 reward, int256 floatReward); } \ No newline at end of file diff --git a/contracts/interface/IBitcoinStake.sol b/contracts/interface/IBitcoinStake.sol index 0992b60cf..a36e28433 100644 --- a/contracts/interface/IBitcoinStake.sol +++ b/contracts/interface/IBitcoinStake.sol @@ -41,10 +41,10 @@ interface IBitcoinStake { /// Claim reward for delegator /// @param delegator the delegator address + /// @param coreAmount the staked core amount /// @param settleRound the settlement round /// @param claim claim or store rewards /// @return reward Amount claimed - /// @return rewardUnclaimed Amount unclaimed - /// @return accStakedAmount accumulated stake amount (multiplied by days), used for grading calculation - function claimReward(address delegator, uint256 settleRound, bool claim) external returns (uint256 reward, uint256 rewardUnclaimed, uint256 accStakedAmount); + /// @return floatReward floating reward amount + function claimReward(address delegator, uint256 coreAmount, uint256 settleRound, bool claim) external returns (uint256 reward, int256 floatReward); } diff --git a/contracts/interface/IBtcAgent.sol b/contracts/interface/IBtcAgent.sol new file mode 100644 index 000000000..05e0c1ca7 --- /dev/null +++ b/contracts/interface/IBtcAgent.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache2.0 +pragma solidity 0.8.4; + +import "./IAgent.sol"; + +interface IBtcAgent is IAgent { + struct DualStakingGrade { + uint32 stakeRate; + uint32 percentage; + } + + /// Returns grades. + function getGrades() external view returns (DualStakingGrade[] memory); + + /// Return a grade for the given stake rate. + /// + /// @param rate the stake rate + /// @return percentage the target percentage + /// @return stakeRate the target stake rate + function getGrade(uint256 rate) external view returns (uint256 percentage, uint256 stakeRate); +} \ No newline at end of file diff --git a/contracts/interface/IChannel.sol b/contracts/interface/IChannel.sol new file mode 100644 index 000000000..4126a2507 --- /dev/null +++ b/contracts/interface/IChannel.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache2.0 +pragma solidity 0.8.4; + +interface IChannel { + /// When claiming reward, user should pay commissions to the channel partner if bind. + /// + /// @param partnerId the id of channel partner + /// @param reward the reward for total staked tx + /// @return remainingReward the remain reward after pay commission. + function payCommissionById(uint32 partnerId, uint256 reward) external returns (uint256 remainingReward); + + /// When claiming reward, user should pay commissions to the channel partner if bind. + /// + /// @param delegator the delegator address + /// @param amount the staked amount + /// @param reward the amount of rewards collected + /// @return remainingReward the remain reward after pay commission. + function payCommissions(address delegator, uint256 amount, uint256 reward) external returns (uint256 remainingReward); + + + /// Reset commission for partner + /// + /// @param partner the channel partner + /// @return commission Amount claimed + /// @return feeAddress the fee address + function resetCommission(address partner) external returns (uint256 commission, address feeAddress); +} diff --git a/contracts/interface/ICoreAgent.sol b/contracts/interface/ICoreAgent.sol new file mode 100644 index 000000000..22002cd7a --- /dev/null +++ b/contracts/interface/ICoreAgent.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache2.0 +pragma solidity 0.8.4; + +import "./IAgent.sol"; + +interface ICoreAgent is IAgent { + /// Claim reward for delegator + /// @param delegator the delegator address + /// @param claim claim or store rewards + /// @return reward Amount claimed + /// @return stakedAmount1 the staked amount in the first round + /// @return stakedAmount2 the real amount in the last round + function claimReward(address delegator, bool claim) external returns (uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2); + + /// for backward compatibility - allow users to unstake through PledgeAgent + /// support channel from v1.0.20 + /// @param candidate the validator candidate address + /// @param delegator the delegator address + /// @param channelId the channel id, 0 represents from PledgeAgent + function proxyDelegate(address candidate, address delegator, uint32 channelId) external payable; + + /// for backward compatibility - allow users to unstake through PledgeAgent + /// support channel from v1.0.20 + /// @param candidate the validator candidate address + /// @param delegator the delegator address + /// @param amount the amount of CORE to unstake + /// @param channelId the channel id, 0 represents from PledgeAgent + function proxyUnDelegate(address candidate, address delegator, uint256 amount, uint32 channelId) external returns(uint256); +} \ No newline at end of file diff --git a/contracts/lib/SatoshiPlusHelper.sol b/contracts/lib/SatoshiPlusHelper.sol index 85215c99a..799307e57 100644 --- a/contracts/lib/SatoshiPlusHelper.sol +++ b/contracts/lib/SatoshiPlusHelper.sol @@ -13,8 +13,8 @@ library SatoshiPlusHelper { uint256 public constant CHAINID = 1116; uint32 public constant INIT_BTC_CONFIRM_BLOCK = 6; - uint32 public constant BTC_STAKE_VERSION = 1; - uint32 public constant BTCLST_STAKE_VERSION = 2; + uint32 public constant BTC_STAKE_INIT_VERSION = 1; + uint32 public constant BTC_STAKE_VERSION = 2; // Bech32 encoded segwit addresses start with a human-readable part // (hrp) followed by '1'. For Bitcoin mainnet the hrp is "bc"(0x6263), and for diff --git a/contracts/lib/SatoshiPlusHelper.template b/contracts/lib/SatoshiPlusHelper.template index e9b6eea8d..e5fa88de9 100644 --- a/contracts/lib/SatoshiPlusHelper.template +++ b/contracts/lib/SatoshiPlusHelper.template @@ -13,8 +13,8 @@ library SatoshiPlusHelper { uint256 public constant CHAINID = {{chainid}}; uint32 public constant INIT_BTC_CONFIRM_BLOCK = 6; - uint32 public constant BTC_STAKE_VERSION = 1; - uint32 public constant BTCLST_STAKE_VERSION = 2; + uint32 public constant BTC_STAKE_INIT_VERSION = 1; + uint32 public constant BTC_STAKE_VERSION = 2; // Bech32 encoded segwit addresses start with a human-readable part // (hrp) followed by '1'. For Bitcoin mainnet the hrp is "bc"(0x6263), and for diff --git a/contracts/mock/BitcoinStakeMock.sol b/contracts/mock/BitcoinStakeMock.sol index 5155920a5..ef28f87f8 100644 --- a/contracts/mock/BitcoinStakeMock.sol +++ b/contracts/mock/BitcoinStakeMock.sol @@ -126,44 +126,6 @@ contract BitcoinStakeMock is BitcoinStake { return _collectReward(txid, settleRound); } - - function moveDataMock(bytes32[] calldata txids) external { - uint256 txLength = txids.length; - bytes32 txid; - for (uint256 i = 0; i < txLength; i++) { - txid = txids[i]; - (bool success, bytes memory data) = PLEDGE_AGENT_ADDR.call(abi.encodeWithSignature("moveBtcData(bytes32)", txid)); - require(success, "call PLEDGE_AGENT_ADDR.moveBtcData() failed."); - (address candidate, address delegator, uint256 amount, uint256 round, uint256 lockTime) = abi.decode(data, (address, address, uint256, uint256, uint256)); - { - uint256 endRound = uint256(lockTime) / SatoshiPlusHelper.ROUND_INTERVAL; - if (endRound <= roundTag) { - continue; - } - } - BtcTx storage bt = btcTxMap[txid]; - if (bt.amount != 0) { - continue; - } - - // Set receiptMap - DepositReceipt storage dr = receiptMap[txids[i]]; - dr.candidate = candidate; - dr.delegator = delegator; - dr.round = round; - bt.amount = uint64(amount); - bt.lockTime = uint32(lockTime); - - // Set delegatorMap - Delegator storage d = delegatorMap[delegator]; - d.txids.push(txid); - - _addExpire(dr, uint32(lockTime), uint64(amount)); - - emit migrated(txid); - } - } - function mockDelegateBtc(bytes32 txid, uint64 btcValue, address candidate, address delegator, uint32 lockTime, uint64 blockTimestamp, uint32 outputIndex) external nonReentrant { BtcTx storage bt = btcTxMap[txid]; require(bt.amount == 0, "btc tx is already delegated."); diff --git a/contracts/mock/PledgeAgentMock.sol b/contracts/mock/PledgeAgentMock.sol index bb2242880..85148a91f 100644 --- a/contracts/mock/PledgeAgentMock.sol +++ b/contracts/mock/PledgeAgentMock.sol @@ -305,72 +305,6 @@ contract PledgeAgentMock is PledgeAgent { return rewardSum; } - // HARDFORK V-1.0.12 - /*********************** Move data ***************************/ - /// move BTC data to BitcoinStake by transaction id - /// the reward will be calculated and saved in rewardMap and the record will be deleted - /// this method is called by BitcoinStake.moveData - /// @param txid the BTC stake transaction id - /// @return candidate the validator candidate address - /// @return delegator the delegator address - /// @return amount the staked BTC amount - /// @return round the round of stake - /// @return lockTime the CLTV locktime value - function moveBtcData(bytes32 txid) external onlyBtcStake returns (address candidate, address delegator, uint256 amount, uint256 round, uint256 lockTime) { - BtcReceipt storage br = btcReceiptMap[txid]; - if (br.value == 0) { - return (address(0), address(0), 0, 0, 0); - } - - // set return values, which will be used by BitcoinStake to restore staking record - candidate = br.agent; - delegator = br.delegator; - amount = br.value; - lockTime = br.endRound * SatoshiPlusHelper.ROUND_INTERVAL; - - Agent storage agent = agentsMap[br.agent]; - if (br.rewardIndex == agent.rewardSet.length) { - round = roundTag; - } else { - Reward storage reward = agent.rewardSet[br.rewardIndex]; - if (reward.round == 0) { - round = roundTag; - } else { - round = roundTag - 1; - } - } - - // calculate and record rewards - uint256 rewardAmount = _collectBtcReward(txid); - rewardMap[delegator] += rewardAmount; - - // Clean round2expireInfoMap - BtcExpireInfo storage expireInfo = round2expireInfoMap[br.endRound]; - uint256 length = expireInfo.agentAddrList.length; - for (uint256 j = length; j != 0; j--) { - if (expireInfo.agentAddrList[j - 1] == candidate) { - // agentsMap[candidate].totalBtc -= amount; - if (expireInfo.agent2valueMap[candidate] == amount) { - delete expireInfo.agent2valueMap[candidate]; - delete expireInfo.agentExistMap[candidate]; - if (j != length) { - expireInfo.agentAddrList[j - 1] = expireInfo.agentAddrList[length - 1]; - } - expireInfo.agentAddrList.pop(); - } else { - expireInfo.agent2valueMap[candidate] -= amount; - } - break; - } - } - if (expireInfo.agentAddrList.length == 0) { - delete round2expireInfoMap[br.endRound]; - } - - // Clean btcReceiptMap - delete btcReceiptMap[txid]; - } - /// calculate reward for a BTC stake transaction /// @param txid the BTC transaction id function _collectBtcReward(bytes32 txid) internal returns (uint256) { diff --git a/generate-abi.sh b/generate-abi.sh index 2b9f2c4f2..42f12f21a 100755 --- a/generate-abi.sh +++ b/generate-abi.sh @@ -5,8 +5,8 @@ if [ ! -z "$name" ]; then else contracts=( "BtcLightClient" "Burn" "CandidateHub" "Foundation" "GovHub" \ "PledgeAgent" "RelayerHub" "SlashIndicator" "SystemReward" "ValidatorSet" \ - "BitcoinAgent" "BitcoinLSTStake" "BitcoinStake" "HashPowerAgent" "CoreAgent" \ - "StakeHub" "BitcoinLSTToken" "Configuration") + "BitcoinAgent" "BitcoinStake" "HashPowerAgent" "CoreAgent" \ + "StakeHub" "Channel" "Configuration") for e in ${contracts[@]} do diff --git a/generate-genesis.js b/generate-genesis.js index d9f76ba08..a6b1f9684 100644 --- a/generate-genesis.js +++ b/generate-genesis.js @@ -158,6 +158,11 @@ Promise.all([ "configuration", "contracts/Configuration.sol", "Configuration" + ), + compileContract( + "channel", + "contracts/Channel.sol", + "Channel" ) ]).then(result => { From 31a3a1810d7638f9f799337e4b9453b4482eeee8 Mon Sep 17 00:00:00 2001 From: zfliex Date: Mon, 18 Aug 2025 16:12:12 +0800 Subject: [PATCH 12/27] fix audit suggestions --- contracts/CandidateHub.sol | 4 ++++ contracts/SlashIndicator.sol | 3 ++- contracts/ValidatorSet.sol | 15 +++++++++++++++ contracts/ValidatorSet.template | 15 +++++++++++++++ contracts/interface/IValidatorSet.sol | 1 + 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/contracts/CandidateHub.sol b/contracts/CandidateHub.sol index f6898f355..87e4d85f7 100644 --- a/contracts/CandidateHub.sol +++ b/contracts/CandidateHub.sol @@ -594,6 +594,10 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { if (newValidatorCount <= 5 || newValidatorCount >= 42) { revert OutOfBounds(key, newValidatorCount, 6, 41); } + // Check if the current maxAlternateCount would violate the constraint with the new validatorCount + if (maxAlternateCount > newValidatorCount / 3) { + revert OutOfBounds("maxAlternateCount", maxAlternateCount, 0, newValidatorCount / 3); + } validatorCount = newValidatorCount; } else if (Memory.compareStrings(key, "maxCommissionChange")) { uint256 newMaxCommissionChange = BytesToTypes.bytesToUint256(32, value); diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 4ab50e94f..0cdf2f1ee 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -159,12 +159,13 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ require(verifyBLSSignature(evidence.voteA, evidence.voteAddr) && verifyBLSSignature(evidence.voteB, evidence.voteAddr), "verify signature failed"); - (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getValidatorsAndVoteAddresses(); + (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getLivingValidators(); if (voteAddrs.length <= 1) { return; } for (uint256 i; i < voteAddrs.length; ++i) { if (BytesLib.equal(voteAddrs[i], evidence.voteAddr)) { + indicators[vals[i]].count = 0; IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(vals[i], felonyRound, felonyDeposit); ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportFinalityViolation); break; diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index fb7e6dfef..b83189247 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -418,6 +418,21 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return 0; } + /// Get the list of validators that are still in the validator set + /// @return (List of validator consensus addresses, List of voting addresses) + function getLivingValidators() external view override returns (address[] memory, bytes[] memory) { + uint256 len = currentValidatorSet.length; + address[] memory consensusAddrs = new address[](len); + bytes[] memory voteAddrs = new bytes[](len); + + for (uint256 i = 0; i < len; i++) { + consensusAddrs[i] = currentValidatorSet[i].consensusAddress; + voteAddrs[i] = currentValidatorSet[i].voteAddr; + } + + return (consensusAddrs, voteAddrs); + } + /*********************** For slash **************************/ /// Slash the validator for misdemeanor behaviors /// @param validator The validator to slash diff --git a/contracts/ValidatorSet.template b/contracts/ValidatorSet.template index d29506b92..48886a824 100644 --- a/contracts/ValidatorSet.template +++ b/contracts/ValidatorSet.template @@ -418,6 +418,21 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return 0; } + /// Get the list of validators that are still in the validator set + /// @return (List of validator consensus addresses, List of voting addresses) + function getLivingValidators() external view override returns (address[] memory, bytes[] memory) { + uint256 len = currentValidatorSet.length; + address[] memory consensusAddrs = new address[](len); + bytes[] memory voteAddrs = new bytes[](len); + + for (uint256 i = 0; i < len; i++) { + consensusAddrs[i] = currentValidatorSet[i].consensusAddress; + voteAddrs[i] = currentValidatorSet[i].voteAddr; + } + + return (consensusAddrs, voteAddrs); + } + /*********************** For slash **************************/ /// Slash the validator for misdemeanor behaviors /// @param validator The validator to slash diff --git a/contracts/interface/IValidatorSet.sol b/contracts/interface/IValidatorSet.sol index 5ce57cadf..385ab2fcf 100644 --- a/contracts/interface/IValidatorSet.sol +++ b/contracts/interface/IValidatorSet.sol @@ -9,6 +9,7 @@ interface IValidatorSet { function isValidator(address addr) external view returns (bool); function getValidatorOps() external view returns (address[] memory); function getValidatorsAndVoteAddresses() external view returns (address[] memory, bytes[] memory); + function getLivingValidators() external view returns (address[] memory, bytes[] memory); function enterMaintenance(address val) external; function exitMaintenanceTurnRound() external; } From b2e1fb2959b68e8e402d4028232c8f559f7362d3 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 18 Aug 2025 15:38:26 +0800 Subject: [PATCH 13/27] add tests for delegate commission --- tests/delegate.py | 62 +- tests/test_channel.py | 1915 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1961 insertions(+), 16 deletions(-) create mode 100644 tests/test_channel.py diff --git a/tests/delegate.py b/tests/delegate.py index 76f0565f8..ef15fb652 100644 --- a/tests/delegate.py +++ b/tests/delegate.py @@ -58,7 +58,10 @@ def build_btc_lst_stake_opreturn(delegator, fee=1, version=2, chain_id=1112, mag def build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_id=1112, core_fee=1, - version=1): + version=1, channel_id=0): + if channel_id > 0: + version = 2 + assert 1 <= version <= 2 if isinstance(lock_data, int): hex_result = value2hex(lock_data) lock_time_hex = reverse_by_bytes(hex_result) @@ -70,8 +73,16 @@ def build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_i chain_id_hex = format(int(chain_id), 'x').zfill(4) delegate_address_hex = str(delegate_address)[2:].lower() agent_address_hex = str(agent_address)[2:] - core_fee_hex = format(core_fee, 'x').zfill(2) - message = flag_hex + version_hex + chain_id_hex + delegate_address_hex + agent_address_hex + core_fee_hex + op_return_data + message = flag_hex + version_hex + chain_id_hex + delegate_address_hex + agent_address_hex + if version == 1: + core_fee_hex = format(core_fee, 'x').zfill(2) + message += core_fee_hex + if version == 2: + assert channel_id > 0 + channel_id_hex = int_to_varint_hex(channel_id) + message += channel_id_hex + message += op_return_data + message_length = remove_0x(hex(len(message) // 2)) op_return = remove_0x(hex(Opcode.OP_RETURN)) if len(message) // 2 > 76: @@ -86,11 +97,14 @@ def build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_i 'chain_id_hex': chain_id_hex, 'delegate_address_hex': delegate_address_hex, 'agent_address_hex': agent_address_hex, - 'core_fee_hex': core_fee_hex, 'op_return_data': op_return_data, 'hex_value': op_return, 'message': message } + if version == 1: + opreturn_info['core_fee_hex'] = core_fee_hex + if version == 2: + opreturn_info['channel_id'] = channel_id_hex return opreturn_info @@ -170,21 +184,24 @@ def build_btc_lst_tx(delegator, amount, pay_address, input_tx_id=None, vout=0, f return btc_tx -def build_btc_tx(agent, delegator, amount, lock_script, lock_data=1736956800, pay_address_type='p2sh', fee=1, - version=1, - chain_id=1112): +def build_btc_tx( + agent, delegator, amount, lock_script, lock_data=1736956800, pay_address_type='p2sh', fee=1, + version=1, chain_id=1112, channel_id=0 +): + if channel_id > 0: + version = 2 pay_address = get_btc_script().k2_btc_pay_address(lock_script, pay_address_type) btc_tx_info = {} inputs = [build_input()] outputs = [build_output(amount, pay_address), - build_btc_stake_opreturn(agent, delegator, lock_data, chain_id, fee, version)] + build_btc_stake_opreturn(agent, delegator, lock_data, chain_id, fee, version, channel_id)] generate_btc_transaction_info(btc_tx_info, inputs, outputs) btc_tx = build_btc_transaction(btc_tx_info) return btc_tx -def get_transaction_op_return_data(chain_id, agent_address, delegate_address, lock_data, core_fee=1, version=1): - opreturn = build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_id, core_fee, version) +def get_transaction_op_return_data(chain_id, agent_address, delegate_address, lock_data, core_fee=1, version=1, channel_id=0): + opreturn = build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_id, core_fee, version, channel_id) return opreturn['message'] @@ -221,7 +238,7 @@ def set_last_round_tag(stake_round, time0=None): # delegate def run_stake_operation(operation, candidate, account, operation_amount, target_agent=None, tx_id=None, - lock_script=None, btc_value=None): + lock_script=None, btc_value=None, channel_id=0): tx = None if operation == 'delegate': tx = delegate_coin_success(candidate, account, operation_amount) @@ -230,7 +247,7 @@ def run_stake_operation(operation, candidate, account, operation_amount, target_ elif operation == 'transfer': tx = transfer_coin_success(candidate, target_agent, account, operation_amount) elif operation == 'delegate_btc': - tx = delegate_btc_success(candidate, account, btc_value, lock_script, events=True) + tx = delegate_btc_success(candidate, account, btc_value, lock_script, events=True, channel_id=channel_id) elif operation == 'transfer_btc': tx = transfer_btc_success(tx_id, target_agent, account) elif operation == 'claim': @@ -271,7 +288,7 @@ def undelegate_coin_success(candidate, delegator, amount): def delegate_btc_success(agent, delegator, btc_amount, lock_script, lock_data=None, relay=None, stake_duration=None, - fee=1, script_type='p2sh', lock_time=None, events=None): + fee=1, script_type='p2sh', lock_time=None, events=None, channel_id=0): if stake_duration is None: stake_duration = Utils.MONTH if lock_time is None: @@ -279,7 +296,7 @@ def delegate_btc_success(agent, delegator, btc_amount, lock_script, lock_data=No set_block_time_stamp(stake_duration, lock_time) if lock_data is None: lock_data = lock_script - btc_tx0 = build_btc_tx(agent, delegator, int(btc_amount), lock_script, lock_data, script_type, fee) + btc_tx0 = build_btc_tx(agent, delegator, int(btc_amount), lock_script, lock_data, script_type, fee, channel_id=channel_id) if relay is None: relay = accounts[0] tx = BitcoinStakeMock[0].delegate(btc_tx0, 1, [], 0, lock_script, {"from": relay}) @@ -304,13 +321,13 @@ def delegate_power_success(candidate, delegator, value=1, stake_round=0): # mock delegate def mock_delegate_btc_success(agent, delegator, btc_amount, lock_time=None, block_timestamp=0, output_index=0, - tx_id=None): + tx_id=None, channel_id=0): if tx_id is None: tx_id = random_btc_tx_id() if lock_time is None: lock_time = LOCK_TIME tx = BitcoinStakeMock[0].mockDelegateBtc(tx_id, btc_amount, agent, delegator, lock_time, block_timestamp, - output_index) + output_index, channel_id) assert 'mockDelegatedBtc' in tx.events return tx_id @@ -639,3 +656,16 @@ def mock_btc_reward_map(delegator, reward, unclaimed_reward, delegate_amount): @staticmethod def mock_power_reward_map(delegator, reward, delegate_amount): HashPowerAgentMock[0].setPowerRewardMap(delegator, reward, delegate_amount) + + +def int_to_varint_hex(value: int) -> str: + if value < 0xfd: + return value.to_bytes(1, "little").hex() + elif value <= 0xffff: + return "fd" + value.to_bytes(2, "little").hex() + elif value <= 0xffffffff: + return "fe" + value.to_bytes(4, "little").hex() + elif value <= 0xffffffffffffffff: + return "ff" + value.to_bytes(8, "little").hex() + else: + raise ValueError("Integer too large for Bitcoin varint encoding") \ No newline at end of file diff --git a/tests/test_channel.py b/tests/test_channel.py new file mode 100644 index 000000000..a0e0fc2bb --- /dev/null +++ b/tests/test_channel.py @@ -0,0 +1,1915 @@ +import pytest +import brownie +from brownie import accounts, Wei +from brownie.test import given, strategy +from brownie.network.transaction import Status +from eth_abi import encode +from web3 import Web3 +from .constant import Utils +from .common import execute_proposal, register_candidate, turn_round +from .delegate import delegate_btc_success, StakeManager, delegate_power_success, delegate_coin_success + + +stake_manager = StakeManager() + + +BLOCK_REWARD = 0 +TOTAL_REWARD = 0 +BTC_VALUE = 2000 +LOCK_SCRIPT = '0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac' + + +@pytest.fixture(scope="module", autouse=True) +def deposit_for_reward(validator_set): + accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) + + +@pytest.fixture(scope="module", autouse=True) +def set_up(min_init_delegate_value, core_agent, candidate_hub, validator_set): + global BLOCK_REWARD + global TOTAL_REWARD + + block_reward = validator_set.blockReward() + block_reward_incentive_percent = validator_set.blockRewardIncentivePercent() + tx_fee = 100 + BLOCK_REWARD = (block_reward + tx_fee) * ((100 - block_reward_incentive_percent) / 100) + TOTAL_REWARD = BLOCK_REWARD // 2 + + +@pytest.fixture() +def set_candidate(): + operators = [] + consensuses = [] + for operator in accounts[5:8]: + operators.append(operator) + consensuses.append(register_candidate(operator=operator)) + return operators, consensuses + + +@pytest.fixture(scope="module") +def required_margin(channel): + return channel.requiredMargin() + + +@pytest.fixture(scope="module") +def dues(channel): + return channel.dues() + + +@pytest.fixture(scope="module") +def commission_limit(channel): + return channel.commissionLimit() + + +@pytest.fixture() +def set_channel_partner(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + + partner_id = channel.partnerIdMap(partner) + return partner, fee_address, partner_id, core_commission_rate, btc_commission_rate + + +def test_init(channel): + assert channel.alreadyInit() is True + assert channel.requiredMargin() == channel.INIT_REQUIRED_MARGIN() + assert channel.dues() == 0 + assert channel.commissionLimit() == channel.DEFAULT_COMMISSION_LIMIT() + + +def test_register_success(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + + partner_id = channel.partnerIdMap(partner) + assert partner_id > 0 + + partner_info = channel.partners(partner_id) + assert partner_info[0] == partner + assert partner_info[1] == fee_address + assert partner_info[2] == core_commission_rate + assert partner_info[3] == btc_commission_rate + assert partner_info[4] == 1 + assert partner_info[5] == required_margin + assert partner_info[6] == 0 + + +def test_register_insufficient_margin(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + insufficient_margin = required_margin // 2 + + with brownie.reverts(f"InsufficientAmount: {required_margin}, {insufficient_margin}"): + channel.register( + fee_address, 1000, 1500, + {'from': partner, 'value': insufficient_margin} + ) + + +@pytest.mark.parametrize("core_commission_rate", [ + pytest.param(2001, marks=pytest.mark.xfail), + pytest.param(3000, marks=pytest.mark.xfail), + pytest.param(10000, marks=pytest.mark.xfail) +]) +def test_register_core_commission_rate_too_high(channel, required_margin, core_commission_rate): + partner = accounts[1] + fee_address = accounts[2] + + with brownie.reverts(f"OutOfBounds: coreCommissionRate, {core_commission_rate}, 0, {channel.commissionLimit()}"): + channel.register( + fee_address, core_commission_rate, 1500, + {'from': partner, 'value': required_margin} + ) + + +@pytest.mark.parametrize("btc_commission_rate", [ + pytest.param(2001, marks=pytest.mark.xfail), + pytest.param(3000, marks=pytest.mark.xfail), + pytest.param(10000, marks=pytest.mark.xfail) +]) +def test_register_btc_commission_rate_too_high(channel, required_margin, btc_commission_rate): + partner = accounts[1] + fee_address = accounts[2] + + with brownie.reverts(f"OutOfBounds: btcCommissionRate, {btc_commission_rate}, 0, {channel.commissionLimit()}"): + channel.register( + fee_address, 1000, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + + +def test_register_duplicate_partner(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + + channel.register( + fee_address, 1000, 1500, + {'from': partner, 'value': required_margin} + ) + + with brownie.reverts(f"DuplicateRegister: {partner.address.lower()}"): + channel.register( + fee_address, 1200, 1700, + {'from': partner, 'value': required_margin} + ) + + +def test_unregister_success(channel, set_channel_partner, dues, required_margin): + partner, fee_address, partner_id, _, _ = set_channel_partner + + initial_balance = partner.balance() + + channel.unregister({'from': partner}) + + partner_info = channel.partners(partner_id) + assert partner_info[4] == 0 + + expected_refund = required_margin - dues + assert partner.balance() == initial_balance + expected_refund + + +def test_unregister_nonexistent_partner(channel): + non_existent_partner = accounts[99] + with brownie.reverts(f"NoPartner: 0"): + channel.unregister({'from': non_existent_partner}) + + +def test_set_fee_address_success(channel, set_channel_partner): + partner, _, partner_id, _, _ = set_channel_partner + new_fee_address = accounts[5] + + channel.setFeeAddress(new_fee_address, {'from': partner}) + + partner_info = channel.partners(partner_id) + assert partner_info[1] == new_fee_address + + +def test_set_fee_address_nonexistent_partner(channel): + non_existent_partner = accounts[99] + new_fee_address = accounts[5] + + with brownie.reverts(f"NoPartner: 0"): + channel.setFeeAddress(new_fee_address, {'from': non_existent_partner}) + + +def test_set_core_commission_rate_success(channel, set_channel_partner): + partner, _, partner_id, _, _ = set_channel_partner + new_rate = 1200 + + channel.setCoreCommissionRate(new_rate, {'from': partner}) + + partner_info = channel.partners(partner_id) + assert partner_info[2] == new_rate + + +@pytest.mark.parametrize("invalid_rate", [ + pytest.param(2001, marks=pytest.mark.xfail), + pytest.param(3000, marks=pytest.mark.xfail), + pytest.param(10000, marks=pytest.mark.xfail) +]) +def test_set_core_commission_rate_invalid(channel, set_channel_partner, invalid_rate): + partner, _, _, _, _ = set_channel_partner + + with brownie.reverts(f"OutOfBounds: coreCommissionRate, {invalid_rate}, 0, {channel.commissionLimit()}"): + channel.setCoreCommissionRate(invalid_rate, {'from': partner}) + + +def test_set_btc_commission_rate_success(channel, set_channel_partner): + partner, _, partner_id, _, _ = set_channel_partner + new_rate = 1700 + + channel.setBtcCommissionRate(new_rate, {'from': partner}) + + partner_info = channel.partners(partner_id) + assert partner_info[3] == new_rate + + +@pytest.mark.parametrize("invalid_rate", [ + pytest.param(2001, marks=pytest.mark.xfail), + pytest.param(3000, marks=pytest.mark.xfail), + pytest.param(10000, marks=pytest.mark.xfail) +]) +def test_set_btc_commission_rate_invalid(channel, set_channel_partner, invalid_rate): + partner, _, _, _, _ = set_channel_partner + + with brownie.reverts(f"OutOfBounds: btcCommissionRate, {invalid_rate}, 0, {channel.commissionLimit()}"): + channel.setBtcCommissionRate(invalid_rate, {'from': partner}) + + +def test_reset_commission_success(channel, set_channel_partner, stake_hub): + partner, _, partner_id, _, _ = set_channel_partner + + channel.resetCommission(partner, {'from': stake_hub}) + + partner_info = channel.partners(partner_id) + assert partner_info[6] == 0 + + +def test_reset_commission_zero_commission(channel, set_channel_partner, stake_hub): + partner, _, partner_id, _, _ = set_channel_partner + partner_info = channel.partners(partner_id) + assert partner_info[6] == 0 + channel.resetCommission(partner, {'from': stake_hub}) + + partner_info = channel.partners(partner_id) + assert partner_info[6] == 0 + + +def test_reset_commission_nonexistent_partner(channel, stake_hub): + non_existent_partner = accounts[99] + channel.resetCommission(non_existent_partner, {'from': stake_hub}) + + +def test_reset_commission_unauthorized_caller(channel, set_channel_partner, stake_hub): + partner, _, _, _, _ = set_channel_partner + unauthorized_caller = accounts[99] + + with brownie.reverts(f"NotPermissionalCaller: {stake_hub.address.lower()}, {unauthorized_caller.address.lower()}"): + channel.resetCommission(partner, {'from': unauthorized_caller}) + + +def test_governance_update_required_margin(channel, gov_hub): + new_margin = Wei("15 ether") + + execute_proposal( + channel.address, 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ["requiredMargin", new_margin.to_bytes(32, "big")]), + "Update required margin" + ) + + assert channel.requiredMargin() == new_margin + + +def test_governance_update_dues(channel, required_margin): + new_dues = required_margin // 2 + + execute_proposal( + channel.address, 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ["dues", new_dues.to_bytes(32)]), + "Update dues" + ) + + assert channel.dues() == new_dues + + +def test_governance_update_commission_limit(channel, gov_hub): + new_limit = 2500 + execute_proposal( + channel.address, 0, + "updateParam(string,bytes)", + encode(['string', 'bytes'], ["commissionLimit", new_limit.to_bytes(32)]), + "Update commissionLimit" + ) + assert channel.commissionLimit() == new_limit + + +def test_governance_unauthorized_caller(channel, gov_hub): + unauthorized_caller = accounts[99] + new_margin = Wei("15 ether") + + with brownie.reverts(f"NotPermissionalCaller: {gov_hub.address.lower()}, {unauthorized_caller.address.lower()}"): + channel.updateParam( + "requiredMargin", new_margin.to_bytes(32), + {'from': unauthorized_caller} + ) + + +def test_governance_invalid_param_length(channel, gov_hub): + invalid_value = b"invalid" + + with brownie.reverts("MismatchParamLength: requiredMargin"): + channel.updateParam("requiredMargin", invalid_value, {'from': gov_hub}) + + +def test_governance_unsupported_param(channel, gov_hub): + new_value = 100 + + with brownie.reverts("UnsupportedGovParam: unsupportedParam"): + channel.updateParam("unsupportedParam", new_value.to_bytes(32), {'from': gov_hub}) + + +def test_governance_required_margin_invalid_range(channel, gov_hub, dues): + MAX_UINT256 = 2 ** 256 - 1 + invalid_margin = dues + + with brownie.reverts(f"OutOfBounds: requiredMargin, {invalid_margin}, {dues+1}, {MAX_UINT256}"): + channel.updateParam( + "requiredMargin", invalid_margin.to_bytes(32), + {'from': gov_hub} + ) + + +def test_governance_dues_invalid_range(channel, gov_hub, required_margin): + invalid_dues = required_margin + + with brownie.reverts(f"OutOfBounds: dues, {invalid_dues}, 1, {required_margin - 1}"): + channel.updateParam( + "dues", invalid_dues.to_bytes(32), + {'from': gov_hub} + ) + + +def test_governance_commission_limit_invalid_range(channel, gov_hub): + invalid_limit = Utils.DENOMINATOR + 1 + invalid_limit_hex = "0x" + invalid_limit.to_bytes(32).hex() + + with brownie.reverts(f"OutOfBounds: commissionLimit, {invalid_limit}, 0, 10000"): + channel.updateParam( + "commissionLimit", invalid_limit_hex, + {'from': gov_hub} + ) + + +def test_partner_id_generator_increment(channel, required_margin): + initial_generator = channel.partnerIdGenerator() + + partner1 = accounts[1] + fee_address1 = accounts[2] + tx1 = channel.register( + fee_address1, 1000, 1500, + {'from': partner1, 'value': required_margin} + ) + assert tx1.status == Status.Confirmed + + partner2 = accounts[3] + fee_address2 = accounts[4] + tx2 = channel.register( + fee_address2, 1100, 1600, + {'from': partner2, 'value': required_margin} + ) + assert tx2.status == Status.Confirmed + + final_generator = channel.partnerIdGenerator() + assert final_generator == initial_generator + 2 + + +def test_partner_mapping_consistency(channel, set_channel_partner): + partner, _, partner_id, _, _ = set_channel_partner + + mapped_id = channel.partnerIdMap(partner) + assert mapped_id == partner_id + + partner_info = channel.partners(partner_id) + assert partner_info[0] == partner + + +def test_commission_calculation_accuracy(channel, set_channel_partner): + partner, _, partner_id, _, btc_commission_rate = set_channel_partner + + partner_info = channel.partners(partner_id) + assert partner_info[2] == 1000 + assert partner_info[3] == btc_commission_rate + + assert partner_info[6] == 0 + + +def test_partner_status_management(channel, set_channel_partner): + partner, _, partner_id, _, _ = set_channel_partner + + partner_info = channel.partners(partner_id) + assert partner_info[4] == 1 + + channel.unregister({'from': partner}) + partner_info = channel.partners(partner_id) + assert partner_info[4] == 0 + + +def test_margin_refund_calculation(channel, required_margin, dues): + partner = accounts[1] + fee_address = accounts[2] + + channel.register( + fee_address, 1000, 1500, + {'from': partner, 'value': required_margin} + ) + + initial_balance = partner.balance() + + channel.unregister({'from': partner}) + + expected_refund = required_margin - dues + assert partner.balance() == initial_balance + expected_refund + + +def test_commission_limit_boundary(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + + boundary_rate = channel.commissionLimit() + above_boundary_rate = boundary_rate + 1 + + with brownie.reverts(f"OutOfBounds: coreCommissionRate, {above_boundary_rate}, 0, {boundary_rate}"): + channel.register( + fee_address, above_boundary_rate, above_boundary_rate, + {'from': partner, 'value': required_margin} + ) + + +def test_register_reuse_id_after_unregister(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + + channel.register( + fee_address, 1000, 1500, + {'from': partner, 'value': required_margin} + ) + partner_id1 = channel.partnerIdMap(partner) + + channel.unregister({'from': partner}) + + channel.register( + fee_address, 1200, 1700, + {'from': partner, 'value': required_margin} + ) + partner_id2 = channel.partnerIdMap(partner) + + assert partner_id1 == partner_id2 + + +def test_multiple_partners_registration(channel, required_margin): + partners = [] + for i in range(3): + partner = accounts[i + 1] + fee_address = accounts[i + 4] + core_commission_rate = 1000 + i * 100 + btc_commission_rate = 1500 + i * 100 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + + partner_id = channel.partnerIdMap(partner) + partners.append({ + 'partner': partner, + 'partner_id': partner_id, + 'core_commission_rate': core_commission_rate, + 'btc_commission_rate': btc_commission_rate + }) + + for i, partner_data in enumerate(partners): + partner = partner_data['partner'] + partner_id = partner_data['partner_id'] + core_rate = partner_data['core_commission_rate'] + btc_rate = partner_data['btc_commission_rate'] + + partner_info = channel.partners(partner_id) + assert partner_info[0] == partner + assert partner_info[2] == core_rate + assert partner_info[3] == btc_rate + assert partner_info[4] == 1 + + +@given(commission_rate=strategy('uint16', max_value=2000)) +def test_commission_rate_range(channel, required_margin, commission_rate): + partner = accounts[1] + fee_address = accounts[2] + + if commission_rate <= 2000: + channel.register( + fee_address, commission_rate, commission_rate, + {'from': partner, 'value': required_margin} + ) + else: + with brownie.reverts("OutOfBounds"): + channel.register( + fee_address, commission_rate, commission_rate, + {'from': partner, 'value': required_margin} + ) + + +def test_register_zero_fee_address(channel, required_margin): + partner = accounts[1] + zero_fee_address = "0x0000000000000000000000000000000000000000" + + channel.register( + zero_fee_address, 1000, 1500, + {'from': partner, 'value': required_margin} + ) + + partner_id = channel.partnerIdMap(partner) + partner_info = channel.partners(partner_id) + assert partner_info[1] == zero_fee_address + + +def test_register_zero_commission_rates(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + + channel.register( + fee_address, 0, 0, + {'from': partner, 'value': required_margin} + ) + + partner_id = channel.partnerIdMap(partner) + partner_info = channel.partners(partner_id) + assert partner_info[2] == 0 + assert partner_info[3] == 0 + + +def test_unregister_insufficient_margin_for_dues(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + + channel.register( + fee_address, 1000, 1500, + {'from': partner, 'value': required_margin} + ) + channel.unregister({'from': partner}) + + +def test_set_core_commission_rate_nonexistent_partner(channel): + non_existent_partner = accounts[99] + + with brownie.reverts("NoPartner: 0"): + channel.setCoreCommissionRate(1200, {'from': non_existent_partner}) + + +def test_set_btc_commission_rate_nonexistent_partner(channel): + non_existent_partner = accounts[99] + + with brownie.reverts("NoPartner: 0"): + channel.setBtcCommissionRate(1700, {'from': non_existent_partner}) + + +def test_delegate_coin_success(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("10 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + partner_info = channel.partners(partner_id) + assert partner_info[4] == 1 + + delegator_amount = channel.getDelegatorAmount(partner_id, delegator) + assert delegator_amount == delegate_amount + + channel_ids = channel.getIds(delegator) + assert partner_id in channel_ids + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == delegate_amount + + +def test_delegate_coin_invalid_channel_id(channel, required_margin): + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + delegate_amount = Wei("10 ether") + invalid_channel_id = 999 + + with brownie.reverts(f"NoPartner: {invalid_channel_id}"): + channel.delegateCoin( + candidate, invalid_channel_id, + {'from': delegator, 'value': delegate_amount} + ) + + +def test_undelegate_coin_success(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("10 ether") + undelegate_amount = Wei("3 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + initial_balance = delegator.balance() + channel.undelegateCoin( + candidate, undelegate_amount, partner_id, + {'from': delegator} + ) + + remaining_amount = channel.getDelegatorAmount(partner_id, delegator) + assert remaining_amount == delegate_amount - undelegate_amount + + assert delegator.balance() > initial_balance + + +def test_undelegate_coin_insufficient_amount(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("10 ether") + undelegate_amount = Wei("15 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + with brownie.reverts(f"InsufficientAmount: {undelegate_amount}, {delegate_amount}"): + channel.undelegateCoin( + candidate, undelegate_amount, partner_id, + {'from': delegator} + ) + + +def test_undelegate_coin_complete_removal(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("10 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + channel_ids = channel.getIds(delegator) + assert partner_id in channel_ids + + channel.undelegateCoin( + candidate, delegate_amount, partner_id, + {'from': delegator} + ) + + channel_ids = channel.getIds(delegator) + assert partner_id not in channel_ids + + delegator_amount = channel.getDelegatorAmount(partner_id, delegator) + assert delegator_amount == 0 + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == 0 + + +def test_undelegate_coin_invalid_channel_id(channel, required_margin): + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + undelegate_amount = Wei("3 ether") + invalid_channel_id = 999 + + initial_balance = delegator.balance() + channel.undelegateCoin( + candidate, undelegate_amount, invalid_channel_id, + {'from': delegator} + ) + assert delegator.balance() == initial_balance + + +def test_pay_commission_by_id_success(channel, required_margin, btc_stake): + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + reward_amount = Wei("100 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + expected_commission = reward_amount * btc_commission_rate // 10000 + expected_remaining = reward_amount - expected_commission + + remaining_reward = channel.payCommissionById.call( + partner_id, reward_amount, + {'from': btc_stake.address} + ) + channel.payCommissionById( + partner_id, reward_amount, + {'from': btc_stake.address} + ) + + new_commission = channel.partners(partner_id)[6] + assert new_commission == expected_commission + + assert remaining_reward == expected_remaining + + +def test_pay_commission_by_id_zero_partner_id(channel, btc_stake): + reward_amount = Wei("100 ether") + + remaining_reward = channel.payCommissionById.call( + 0, reward_amount, + {'from': btc_stake.address} + ) + channel.payCommissionById( + 0, reward_amount, + {'from': btc_stake.address} + ) + + assert remaining_reward == reward_amount + + +def test_pay_commission_by_id_unauthorized_caller(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + reward_amount = Wei("100 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + with brownie.reverts(f"NotPermissionalCaller: {channel.BTC_STAKE_ADDR().lower()}, {accounts[5].address.lower()}"): + channel.payCommissionById( + partner_id, reward_amount, + {'from': accounts[5]} + ) + + +def test_pay_commissions_success(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("50 ether") + reward_amount = Wei("20 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + expected_commission = (delegate_amount * core_commission_rate // 10000) * reward_amount // delegate_amount + expected_remaining = reward_amount - expected_commission + + remaining_reward = channel.payCommissions.call( + delegator, delegate_amount, reward_amount, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator, delegate_amount, reward_amount, + {'from': core_agent.address} + ) + + partner_info = channel.partners(partner_id) + assert partner_info[6] == expected_commission + + assert remaining_reward == expected_remaining + + +def test_pay_commissions_zero_reward(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("50 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + remaining_reward = channel.payCommissions.call( + delegator, delegate_amount, 0, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator, delegate_amount, 0, + {'from': core_agent.address} + ) + + assert remaining_reward == 0 + + +def test_pay_commissions_unauthorized_caller(channel, required_margin): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("50 ether") + reward_amount = Wei("20 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + with brownie.reverts(f"NotPermissionalCaller: {channel.CORE_AGENT_ADDR().lower()}, {accounts[5].address.lower()}"): + channel.payCommissions( + delegator, delegate_amount, reward_amount, + {'from': accounts[5]} + ) + + +def test_pay_commissions_no_delegations(channel, core_agent): + delegator = accounts[3] + delegate_amount = Wei("50 ether") + reward_amount = Wei("20 ether") + + remaining_reward = channel.payCommissions.call( + delegator, delegate_amount, reward_amount, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator, delegate_amount, reward_amount, + {'from': core_agent.address} + ) + + assert remaining_reward == reward_amount + + +def test_delegate_coin_integration_with_core_agent(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("10 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[1] == delegate_amount + assert delegator_map[2] == delegate_amount + + channel_delegation = channel.getDelegatorAmount(partner_id, delegator) + assert channel_delegation == delegate_amount + + channel_ids = channel.getIds(delegator) + assert partner_id in channel_ids + + +def test_undelegate_coin_integration_with_core_agent(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + delegate_amount = Wei("10 ether") + undelegate_amount = Wei("3 ether") + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + initial_balance = delegator.balance() + initial_channel_delegation = channel.getDelegatorAmount(partner_id, delegator) + initial_channel_amount = core_agent.getDelegatorMap(delegator)[2] + + tx = channel.undelegateCoin( + candidate, undelegate_amount, partner_id, + {'from': delegator} + ) + + assert tx.status == 1 + + final_balance = delegator.balance() + assert final_balance > initial_balance + + final_channel_delegation = channel.getDelegatorAmount(partner_id, delegator) + assert final_channel_delegation == initial_channel_delegation - undelegate_amount + + channel_ids = channel.getIds(delegator) + assert partner_id in channel_ids + + final_channel_amount = core_agent.getDelegatorMap(delegator)[2] + assert final_channel_amount == initial_channel_amount - undelegate_amount + + +def test_delegate_coin_multiple_delegations(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + first_amount = Wei("5 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': first_amount} + ) + + second_amount = Wei("3 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': second_amount} + ) + + total_amount = channel.getDelegatorAmount(partner_id, delegator) + assert total_amount == first_amount + second_amount + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == first_amount + second_amount + + +def test_pay_commissions_multiple_channels(channel, required_margin, core_agent): + partner1 = accounts[1] + partner2 = accounts[2] + fee_address1 = accounts[3] + fee_address2 = accounts[4] + delegator = accounts[5] + operator = accounts[6] + register_candidate(operator=operator) + candidate = operator + + core_commission_rate1 = 1000 + btc_commission_rate1 = 1500 + core_commission_rate2 = 800 + btc_commission_rate2 = 1200 + + channel.register( + fee_address1, core_commission_rate1, btc_commission_rate1, + {'from': partner1, 'value': required_margin} + ) + partner_id1 = channel.partnerIdMap(partner1) + + channel.register( + fee_address2, core_commission_rate2, btc_commission_rate2, + {'from': partner2, 'value': required_margin} + ) + partner_id2 = channel.partnerIdMap(partner2) + delegate_amount1 = Wei("30 ether") + delegate_amount2 = Wei("20 ether") + total_delegate_amount = delegate_amount1 + delegate_amount2 + + channel.delegateCoin( + candidate, partner_id1, + {'from': delegator, 'value': delegate_amount1} + ) + channel.delegateCoin( + candidate, partner_id2, + {'from': delegator, 'value': delegate_amount2} + ) + + reward_amount = Wei("25 ether") + commission1 = (delegate_amount1 * core_commission_rate1 // 10000) * reward_amount // total_delegate_amount + commission2 = (delegate_amount2 * core_commission_rate2 // 10000) * reward_amount // total_delegate_amount + total_commission = commission1 + commission2 + expected_remaining = reward_amount - total_commission + + remaining_reward = channel.payCommissions.call( + delegator, total_delegate_amount, reward_amount, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator, total_delegate_amount, reward_amount, + {'from': core_agent.address} + ) + + partner1_commission = channel.partners(partner_id1)[6] + partner2_commission = channel.partners(partner_id2)[6] + assert partner1_commission == commission1 + assert partner2_commission == commission2 + + assert remaining_reward == expected_remaining + + +def test_delegate_coin_mixed_delegation_scenario(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_amount = Wei("5 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': channel_amount} + ) + + direct_amount = Wei("3 ether") + core_agent.delegateCoin(candidate, {'value': direct_amount, 'from': delegator}) + + channel_delegation = channel.getDelegatorAmount(partner_id, delegator) + assert channel_delegation == channel_amount + + channel_ids = channel.getIds(delegator) + assert partner_id in channel_ids + + delegator_map = core_agent.getDelegatorMap(delegator) + total_amount = delegator_map[1] + channel_amount_tracked = delegator_map[2] + + assert channel_amount_tracked == channel_amount + assert total_amount == channel_amount + direct_amount + + +def test_undelegate_coin_mixed_delegation_scenario(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_amount = Wei("10 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': channel_amount} + ) + + direct_amount = Wei("5 ether") + core_agent.delegateCoin(candidate, {'from': delegator, 'value': direct_amount}) + + initial_channel_amount = core_agent.getDelegatorMap(delegator)[2] + assert initial_channel_amount == channel_amount + + undelegate_channel_amount = Wei("3 ether") + initial_balance = delegator.balance() + + channel.undelegateCoin( + candidate, undelegate_channel_amount, partner_id, + {'from': delegator} + ) + + remaining_channel_amount = channel.getDelegatorAmount(partner_id, delegator) + assert remaining_channel_amount == channel_amount - undelegate_channel_amount + + assert delegator.balance() > initial_balance + + final_channel_amount = core_agent.getDelegatorMap(delegator)[2] + assert final_channel_amount == initial_channel_amount - undelegate_channel_amount + + +def test_pay_commissions_mixed_delegation_scenario(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_amount = Wei("30 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': channel_amount} + ) + + direct_amount = Wei("20 ether") + core_agent.delegateCoin(candidate, {'from': delegator, 'value': direct_amount}) + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == channel_amount + assert delegator_map[1] == channel_amount + direct_amount + + total_delegation = channel_amount + direct_amount + reward_amount = Wei("25 ether") + + expected_commission = (channel_amount * core_commission_rate // 10000) * reward_amount // total_delegation + expected_remaining = reward_amount - expected_commission + + remaining_reward = channel.payCommissions.call( + delegator, total_delegation, reward_amount, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator, total_delegation, reward_amount, + {'from': core_agent.address} + ) + + partner_info = channel.partners(partner_id) + assert partner_info[6] == expected_commission + + assert remaining_reward == expected_remaining + + +def test_delegate_coin_channel_tracking_accuracy(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + delegations = [ + (Wei("5 ether"), partner_id), + (Wei("3 ether"), 0), + (Wei("2 ether"), partner_id), + (Wei("4 ether"), 0), + (Wei("1 ether"), partner_id), + ] + + total_channel_amount = 0 + total_direct_amount = 0 + + for amount, ch_id in delegations: + if ch_id == partner_id: + channel.delegateCoin(candidate, ch_id, {'from': delegator, 'value': amount}) + total_channel_amount += amount + else: + core_agent.delegateCoin(candidate, {'from': delegator, 'value': amount}) + total_direct_amount += amount + + channel_delegation = channel.getDelegatorAmount(partner_id, delegator) + assert channel_delegation == total_channel_amount + + channel_ids = channel.getIds(delegator) + assert len(channel_ids) == 1 + assert partner_id in channel_ids + + delegator_map = core_agent.getDelegatorMap(delegator) + total_amount = delegator_map[1] + channel_amount_tracked = delegator_map[2] + + assert total_amount == total_channel_amount + total_direct_amount + assert channel_amount_tracked == total_channel_amount + + +def test_pay_commissions_mixed_delegation_edge_cases(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_amount = Wei("1 ether") + direct_amount = Wei("100 ether") + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': channel_amount} + ) + core_agent.delegateCoin(candidate, {'from': delegator, 'value': direct_amount}) + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == channel_amount + assert delegator_map[1] == channel_amount + direct_amount + + total_delegation = channel_amount + direct_amount + reward_amount = Wei("50 ether") + + expected_commission = (channel_amount * core_commission_rate // 10000) * reward_amount // total_delegation + expected_remaining = reward_amount - expected_commission + + remaining_reward = channel.payCommissions.call( + delegator, total_delegation, reward_amount, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator, total_delegation, reward_amount, + {'from': core_agent.address} + ) + + partner_info = channel.partners(partner_id) + assert partner_info[6] == expected_commission + assert remaining_reward == expected_remaining + + partner2 = accounts[5] + fee_address2 = accounts[6] + + channel.register( + fee_address2, core_commission_rate, btc_commission_rate, + {'from': partner2, 'value': required_margin} + ) + partner_id2 = channel.partnerIdMap(partner2) + + delegator2 = accounts[7] + equal_channel_amount = Wei("25 ether") + equal_direct_amount = Wei("25 ether") + + channel.delegateCoin(candidate, partner_id2, {'from': delegator2, 'value': equal_channel_amount}) + core_agent.delegateCoin(candidate, {'from': delegator2, 'value': equal_direct_amount}) + + delegator_map2 = core_agent.getDelegatorMap(delegator2) + assert delegator_map2[2] == equal_channel_amount + assert delegator_map2[1] == equal_channel_amount + equal_direct_amount + + total_equal_delegation = equal_channel_amount + equal_direct_amount + reward_amount2 = Wei("20 ether") + + expected_commission2 = (equal_channel_amount * core_commission_rate // 10000) * reward_amount2 // total_equal_delegation + expected_remaining2 = reward_amount2 - expected_commission2 + + remaining_reward2 = channel.payCommissions.call( + delegator2, total_equal_delegation, reward_amount2, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator2, total_equal_delegation, reward_amount2, + {'from': core_agent.address} + ) + + partner_info2 = channel.partners(partner_id2) + assert partner_info2[6] == expected_commission2 + assert remaining_reward2 == expected_remaining2 + + delegator3 = accounts[8] + only_channel_amount = Wei("40 ether") + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator3, 'value': only_channel_amount} + ) + + delegator_map3 = core_agent.getDelegatorMap(delegator3) + assert delegator_map3[2] == only_channel_amount + assert delegator_map3[1] == only_channel_amount + + reward_amount3 = Wei("15 ether") + + expected_commission3 = (only_channel_amount * core_commission_rate // 10000) * reward_amount3 // only_channel_amount + expected_remaining3 = reward_amount3 - expected_commission3 + + remaining_reward3 = channel.payCommissions.call( + delegator3, only_channel_amount, reward_amount3, + {'from': core_agent.address} + ) + channel.payCommissions( + delegator3, only_channel_amount, reward_amount3, + {'from': core_agent.address} + ) + + partner_info3 = channel.partners(partner_id) + assert partner_info3[6] >= expected_commission3 + assert remaining_reward3 == expected_remaining3 + + +def test_pay_commission_by_id_through_btc_stake_claim_reward( + channel, btc_stake, set_candidate, set_channel_partner, stake_hub +): + operators, consensuses = set_candidate + candidate = operators[0] + delegator = accounts[0] + + partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner + btc_tx_id = delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner_id) + tx_map = btc_stake.btcTxMap(btc_tx_id) + assert tx_map[2] == partner_id + turn_round() + turn_round(consensuses) + + expected_commission = TOTAL_REWARD * btc_commission_rate // 10000 + expected_remaining = TOTAL_REWARD - expected_commission + + init_balance = delegator.balance() + stake_hub.claimReward({'from': delegator}) + assert delegator.balance() - init_balance == expected_remaining + + partner_init_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - partner_init_balance == expected_commission + + +@pytest.mark.parametrize("channel_id", [0, 252, 253, 0xffffffff]) +def test_pay_commission_by_id_btc_stake_invalid_channel(channel, btc_stake, set_candidate, stake_hub, channel_id): + operators, consensuses = set_candidate + candidate = operators[0] + delegator = accounts[0] + + btc_tx_id = delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=1736956800, channel_id=channel_id) + tx_map = btc_stake.btcTxMap(btc_tx_id) + assert tx_map[2] == channel_id + turn_round() + turn_round(consensuses) + + expected_commission = 0 + expected_remaining = TOTAL_REWARD - expected_commission + + init_balance = delegator.balance() + stake_hub.claimReward({'from': delegator}) + assert delegator.balance() - init_balance == expected_remaining + + +def test_pay_commission_by_id_btc_stake_multiple_txs( + channel, btc_stake, set_candidate, set_channel_partner, stake_hub +): + operators, consensuses = set_candidate + candidate1 = operators[0] + candidate2 = operators[1] + delegator = accounts[0] + + partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner + delegate_btc_success(candidate1, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner_id) + delegate_btc_success(candidate2, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner_id) + turn_round() + turn_round(consensuses) + + expected_commission = TOTAL_REWARD * btc_commission_rate // 10000 * 2 + expected_remaining = TOTAL_REWARD * 2 - expected_commission + + init_balance = delegator.balance() + stake_hub.claimReward({'from': delegator}) + assert delegator.balance() - init_balance == expected_remaining + + partner_init_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - partner_init_balance == expected_commission + + +def test_pay_commission_by_id_btc_stake_mixed_channels( + channel, btc_stake, set_candidate, stake_hub, required_margin +): + operators, consensuses = set_candidate + candidate = operators[0] + delegator = accounts[0] + + partner1 = accounts[1] + fee_address1 = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address1, core_commission_rate, btc_commission_rate, + {'from': partner1, 'value': required_margin} + ) + partner1_id = channel.partnerIdMap(partner1) + + partner2 = accounts[3] + fee_address2 = accounts[4] + channel.register( + fee_address2, core_commission_rate, btc_commission_rate, + {'from': partner2, 'value': required_margin} + ) + partner2_id = channel.partnerIdMap(partner2) + + delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner1_id) + delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner2_id) + + turn_round() + turn_round(consensuses) + + expected_commission = TOTAL_REWARD // 2 * btc_commission_rate // 10000 + expected_remaining = TOTAL_REWARD - expected_commission * 2 - 1 + + init_balance = delegator.balance() + stake_hub.claimReward({'from': delegator}) + assert delegator.balance() - init_balance == expected_remaining + + partner_init_balance = fee_address1.balance() + stake_hub.claimCommission({'from': partner1}) + assert fee_address1.balance() - partner_init_balance == expected_commission + + +def test_core_agent_channel_amount_tracking(channel, required_margin, core_agent): + partner = accounts[1] + fee_address = accounts[2] + delegator = accounts[3] + operator = accounts[4] + register_candidate(operator=operator) + candidate = operator + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == 0 + + delegate_amount = Wei("10 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == delegate_amount + assert delegator_map[1] == delegate_amount + + direct_amount = Wei("5 ether") + core_agent.delegateCoin(candidate, {'from': delegator, 'value': direct_amount}) + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == delegate_amount + assert delegator_map[1] == delegate_amount + direct_amount + + additional_channel_amount = Wei("3 ether") + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': additional_channel_amount} + ) + + delegator_map = core_agent.getDelegatorMap(delegator) + expected_channel_amount = delegate_amount + additional_channel_amount + expected_total_amount = delegate_amount + direct_amount + additional_channel_amount + assert delegator_map[2] == expected_channel_amount + assert delegator_map[1] == expected_total_amount + + undelegate_amount = Wei("2 ether") + + channel.undelegateCoin( + candidate, undelegate_amount, partner_id, + {'from': delegator} + ) + + delegator_map = core_agent.getDelegatorMap(delegator) + final_channel_amount = expected_channel_amount - undelegate_amount + final_total_amount = expected_total_amount - undelegate_amount + assert delegator_map[2] == final_channel_amount + assert delegator_map[1] == final_total_amount + + remaining_channel_amount = final_channel_amount + channel.undelegateCoin( + candidate, remaining_channel_amount, partner_id, + {'from': delegator} + ) + + delegator_map = core_agent.getDelegatorMap(delegator) + assert delegator_map[2] == 0 + assert delegator_map[1] == direct_amount + assert delegator_map[1] == direct_amount + + +def test_claim_reward_after_channel_delegation( + channel, required_margin, core_agent, set_candidate, min_init_delegate_value, stake_hub +): + delegator = accounts[0] + operators, consensuses = set_candidate + turn_round() + + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_delegate_amount = 800 + channel.delegateCoin( + operators[0], partner_id, + {'value': channel_delegate_amount} + ) + + delegator_map = core_agent.getDelegatorMap(delegator) + total_amount = channel_delegate_amount + assert delegator_map[1] == total_amount + assert delegator_map[2] == channel_delegate_amount + + expected_commission = channel_delegate_amount * core_commission_rate // 10000 * TOTAL_REWARD // total_amount + expected_remaining = TOTAL_REWARD - expected_commission + + turn_round() + turn_round(consensuses) + + init_balance = delegator.balance() + stake_hub.claimReward() + assert delegator.balance() - init_balance == expected_remaining + + partner_init_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - partner_init_balance == expected_commission + + +def test_claim_reward_after_direct_and_channel_delegation( + channel, required_margin, core_agent, set_candidate, min_init_delegate_value, stake_hub +): + delegator = accounts[0] + operators, consensuses = set_candidate + direct_delegate_amount = 1200 + turn_round() + + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_delegate_amount = 800 + channel.delegateCoin( + operators[0], partner_id, + {'value': channel_delegate_amount} + ) + + core_agent.delegateCoin(operators[0], {'value': direct_delegate_amount}) + + delegator_map = core_agent.getDelegatorMap(delegator) + total_amount = direct_delegate_amount + channel_delegate_amount + assert delegator_map[1] == total_amount + assert delegator_map[2] == channel_delegate_amount + + expected_commission = channel_delegate_amount * core_commission_rate // 10000 * TOTAL_REWARD // total_amount + expected_remaining = TOTAL_REWARD - expected_commission + + turn_round() + turn_round(consensuses) + + init_balance = delegator.balance() + stake_hub.claimReward() + assert delegator.balance() - init_balance == expected_remaining + + partner_init_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - partner_init_balance == expected_commission + + +def test_claim_reward_before_channel_delegation( + channel, required_margin, core_agent, set_candidate, min_init_delegate_value, + stake_hub +): + delegator = accounts[0] + operators, consensuses = set_candidate + direct_delegate_amount = min_init_delegate_value + core_agent.delegateCoin(operators[0], {'value': direct_delegate_amount}) + turn_round() + turn_round(consensuses) + + partner = accounts[1] + fee_address = accounts[2] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_delegate_amount = 1000 + channel.delegateCoin( + operators[0], partner_id, + {'value': channel_delegate_amount} + ) + + delegator_map = core_agent.getDelegatorMap(delegator) + total_amount = direct_delegate_amount + channel_delegate_amount + assert delegator_map[1] == total_amount + assert delegator_map[2] == channel_delegate_amount + + expected_remaining = TOTAL_REWARD + expected_commission = 0 + + init_balance = delegator.balance() + stake_hub.claimReward() + assert delegator.balance() - init_balance == expected_remaining + + partner_init_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - partner_init_balance == expected_commission + + +def test_claim_reward_after_multi_channel_delegation( + channel, required_margin, core_agent, set_candidate, min_init_delegate_value, stake_hub +): + delegator = accounts[0] + operators, consensuses = set_candidate + direct_delegate_amount = 1200 + turn_round() + + partner1 = accounts[1] + fee_address1 = accounts[2] + core_commission_rate1 = 1000 + btc_commission_rate = 1500 + + partner2 = accounts[21] + fee_address2 = accounts[22] + core_commission_rate2 = 2000 + + channel.register( + fee_address1, core_commission_rate1, btc_commission_rate, + {'from': partner1, 'value': required_margin} + ) + partner_id1 = channel.partnerIdMap(partner1) + channel.register( + fee_address2, core_commission_rate2, btc_commission_rate, + {'from': partner2, 'value': required_margin} + ) + partner_id2 = channel.partnerIdMap(partner2) + + channel_delegate_amount1 = 200 + channel_delegate_amount2 = 600 + + channel.delegateCoin( + operators[0], partner_id1, + {'value': channel_delegate_amount1} + ) + channel.delegateCoin( + operators[0], partner_id2, + {'value': channel_delegate_amount2} + ) + + core_agent.delegateCoin(operators[0], {'value': direct_delegate_amount}) + + delegator_map = core_agent.getDelegatorMap(delegator) + total_amount = direct_delegate_amount + channel_delegate_amount1 + channel_delegate_amount2 + assert delegator_map[1] == total_amount + assert delegator_map[2] == channel_delegate_amount1 + channel_delegate_amount2 + + expected_commission1 = channel_delegate_amount1 * core_commission_rate1 // 10000 * TOTAL_REWARD // total_amount + expected_commission2 = channel_delegate_amount2 * core_commission_rate2 // 10000 * TOTAL_REWARD // total_amount + expected_remaining = TOTAL_REWARD - expected_commission1 - expected_commission2 + + turn_round() + turn_round(consensuses) + + init_balance = delegator.balance() + stake_hub.claimReward() + assert delegator.balance() - init_balance == expected_remaining + + partner1_init_balance = fee_address1.balance() + stake_hub.claimCommission({'from': partner1}) + assert fee_address1.balance() - partner1_init_balance == expected_commission1 + + partner2_init_balance = fee_address2.balance() + stake_hub.claimCommission({'from': partner2}) + assert fee_address2.balance() - partner2_init_balance == expected_commission2 + + +def test_claim_reward_after_multi_users_delegate_through_same_channel( + channel, required_margin, core_agent, set_candidate, min_init_delegate_value, stake_hub +): + delegator1 = accounts[0] + delegator2 = accounts[1] + operators, consensuses = set_candidate + direct_delegate_amount = 1200 + turn_round() + + partner = accounts[2] + fee_address = accounts[3] + core_commission_rate = 1000 + btc_commission_rate = 1500 + + channel.register( + fee_address, core_commission_rate, btc_commission_rate, + {'from': partner, 'value': required_margin} + ) + partner_id = channel.partnerIdMap(partner) + + channel_delegate_amount = 800 + channel.delegateCoin( + operators[0], partner_id, + {'value': channel_delegate_amount, 'from': delegator1} + ) + channel.delegateCoin( + operators[1], partner_id, + {'value': channel_delegate_amount, 'from': delegator2} + ) + + core_agent.delegateCoin(operators[0], {'value': direct_delegate_amount, 'from': delegator1}) + core_agent.delegateCoin(operators[1], {'value': direct_delegate_amount, 'from': delegator2}) + + total_amount = direct_delegate_amount + channel_delegate_amount + + expected_commission = channel_delegate_amount * core_commission_rate // 10000 * TOTAL_REWARD // total_amount + expected_remaining = TOTAL_REWARD - expected_commission + + turn_round() + turn_round(consensuses) + + init_balance1 = delegator1.balance() + stake_hub.claimReward({'from': delegator1}) + assert delegator1.balance() - init_balance1 == expected_remaining + + stake_hub.calculateReward(delegator2) + + partner_init_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - partner_init_balance == expected_commission * 2 + + +def test_delegate_coin_and_btc_mixed( + channel, core_agent, set_candidate, stake_hub, set_channel_partner, btc_stake, btc_agent +): + stake_manager.set_lp_rates([[0, 1000], [10000, 5000], [20000, 10000]]) + btc_agent.setAssetWeight(1) + delegate_amount = 1000000 + channel_delegate_amount = 3000000 + btc_value = 1000 + power_value = 1 + stake_manager.set_tlp_rates() + stake_manager.set_is_stake_hub_active(True) + + delegator = accounts[0] + operators, consensuses = set_candidate + turn_round() + + partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner + + total_coin_amount = delegate_amount + channel_delegate_amount + expected_coin_commission = channel_delegate_amount * core_commission_rate // 10000 * TOTAL_REWARD // total_coin_amount + expected_btc_commission = btc_commission_rate * TOTAL_REWARD // 2 * 1000 // 10000 // 10000 + expected_coin_remaining = TOTAL_REWARD - expected_coin_commission - 1 + expected_btc_remaining = TOTAL_REWARD // 10 // 2 - expected_btc_commission + TOTAL_REWARD // 10 // 2 + + delegate_coin_success(operators[0], delegator, delegate_amount) + channel.delegateCoin( + operators[0], partner_id, + {'value': channel_delegate_amount, 'from': delegator} + ) + delegate_btc_success(operators[2], delegator, btc_value // 2, LOCK_SCRIPT) + delegate_btc_success(operators[2], delegator, btc_value // 2, LOCK_SCRIPT, channel_id=partner_id) + delegate_power_success(operators[1], delegator, power_value) + turn_round(consensuses, round_count=2) + + coin_reward, hash_reward, btc_reward = stake_hub.claimReward.call({'from': delegator}) + assert coin_reward == expected_coin_remaining + assert hash_reward == TOTAL_REWARD + assert btc_reward == expected_btc_remaining + + stake_hub.claimReward({'from': delegator}) + + init_partner_balance = fee_address.balance() + stake_hub.claimCommission({'from': partner}) + assert fee_address.balance() - init_partner_balance == expected_coin_commission + expected_btc_commission From aff83a415e54f196bbd81b95e35563d654bd9609 Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Sat, 2 Aug 2025 23:14:22 +0800 Subject: [PATCH 14/27] Remove data migration tests; fix dual staking tests --- contracts/mock/BitcoinAgentMock.sol | 8 +- contracts/mock/BitcoinStakeMock.sol | 38 +- contracts/mock/BtcLightClientMock.sol | 1 + contracts/mock/CandidateHubMock.sol | 2 + contracts/mock/ConfigurationMock.sol | 2 +- contracts/mock/CoreAgentMock.sol | 24 +- contracts/mock/GovHubMock.sol | 1 + contracts/mock/HashPowerAgentMock.sol | 1 + contracts/mock/PledgeAgentMock.sol | 137 +- contracts/mock/RelayerHubMock.sol | 1 + contracts/mock/SlashIndicatorMock.sol | 4 +- contracts/mock/StakeHubMock.sol | 1 + contracts/mock/SystemRewardMock.sol | 1 + contracts/mock/ValidatorSetMock.sol | 1 + contracts/test/BaseProxy.sol | 1 + contracts/test/SelfDestroy.sol | 1 + contracts/test/TestLibMemory.sol | 1 + contracts/test/reentryCandidateHubProxy.sol | 1 + contracts/test/reentryPledgeAgentProxy.sol | 1 + contracts/test/stakeProxy.sol | 1 + requirements.txt | 3 +- tests/calc_reward.py | 119 +- tests/conftest.py | 10 +- tests/delegate.py | 27 +- tests/scenario/account_mgr.py | 2 +- tests/test_btc_agent.py | 393 +---- tests/test_btc_stake.py | 737 ++++---- tests/test_candidate_hub.py | 20 + tests/test_channel.py | 16 +- tests/test_claim_reward.py | 88 +- tests/test_core_agent.py | 548 +++--- tests/test_core_agent_unit.py | 2 +- tests/test_delegate_btc.py | 6 +- tests/test_delegate_coin.py | 9 +- tests/test_dual_staking.py | 1711 ++++++------------- tests/test_hash_power_agent.py | 46 +- tests/test_pledge_agent.py | 1397 +-------------- tests/test_slash.py | 65 +- tests/test_stake_hub_unit.py | 399 +++-- tests/test_validator_set.py | 97 +- tests/utils.py | 13 +- 41 files changed, 2117 insertions(+), 3819 deletions(-) diff --git a/contracts/mock/BitcoinAgentMock.sol b/contracts/mock/BitcoinAgentMock.sol index 6561897d1..f32a2f837 100644 --- a/contracts/mock/BitcoinAgentMock.sol +++ b/contracts/mock/BitcoinAgentMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../BitcoinAgent.sol"; @@ -30,9 +31,6 @@ contract BitcoinAgentMock is BitcoinAgent { grades.push(DualStakingGrade(value1, balue01)); } - function setAssetWeight(uint256 value) external { - assetWeight = value; - } function getGradesLength() external view returns (uint256) { return grades.length; @@ -45,6 +43,8 @@ contract BitcoinAgentMock is BitcoinAgent { function setIsActive(bool value) external { gradeActive = value; } - + function setAssetWeight(uint256 value) external { + assetWeight = value; + } } diff --git a/contracts/mock/BitcoinStakeMock.sol b/contracts/mock/BitcoinStakeMock.sol index ef28f87f8..42220e6ad 100644 --- a/contracts/mock/BitcoinStakeMock.sol +++ b/contracts/mock/BitcoinStakeMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../BitcoinStake.sol"; @@ -8,7 +9,7 @@ contract BitcoinStakeMock is BitcoinStake { uint64 public MONTH_TIMESTAMP = 2592000; event migrated(bytes32 indexed txid); - event mockDelegatedBtc(bytes32 indexed txid, address indexed candidate, address indexed delegator, uint32 outputIndex, uint64 amount, uint256 endRound); + event mockDelegatedBtc(bytes32 indexed txid, address indexed candidate, address indexed delegator, uint32 outputIndex, uint64 amount, uint256 endRound, uint32 channelId); event mockTransferredBtc( bytes32 indexed txid, address sourceCandidate, @@ -29,7 +30,7 @@ contract BitcoinStakeMock is BitcoinStake { unclaimedReward = rewardMap[delegator].unclaimedReward; return (reward, unclaimedReward); } - + function setRewardMap(address delegator, uint256 reward, uint256 unclaimedReward) external { rewardMap[delegator].reward = reward; rewardMap[delegator].unclaimedReward = unclaimedReward; @@ -109,24 +110,11 @@ contract BitcoinStakeMock is BitcoinStake { } } - function calculateRewardMock(bytes32[] calldata txids, uint256 settleRound) external returns (uint256 amount, uint256 rewardUnclaimed, uint256 accStakedAmount) { - uint256 reward; - uint256 stakedAmount; - uint256 unclaimed; - bool expired; - for (uint256 i = txids.length; i != 0; i--) { - (reward, expired, unclaimed, stakedAmount) = _collectReward(txids[i - 1], settleRound); - amount += reward; - accStakedAmount += stakedAmount; - rewardUnclaimed += unclaimed; - } - } - function collectRewardMock(bytes32 txid, uint256 settleRound) external returns (uint256 reward, bool expired, uint256 rewardUnclaimed, uint256 accStakedAmount) { - return _collectReward(txid, settleRound); - } - function mockDelegateBtc(bytes32 txid, uint64 btcValue, address candidate, address delegator, uint32 lockTime, uint64 blockTimestamp, uint32 outputIndex) external nonReentrant { + + + function mockDelegateBtc(bytes32 txid, uint64 btcValue, address candidate, address delegator, uint32 lockTime, uint64 blockTimestamp, uint32 outputIndex, uint32 channelId) external nonReentrant { BtcTx storage bt = btcTxMap[txid]; require(bt.amount == 0, "btc tx is already delegated."); { @@ -141,7 +129,8 @@ contract BitcoinStakeMock is BitcoinStake { (btcAmount, outputIndex, delegator, candidate) = (btcValue, outputIndex, delegator, candidate); bt.amount = btcAmount; bt.outputIndex = outputIndex; - emit mockDelegatedBtc(txid, candidate, delegator, outputIndex, btcAmount, lockTime / SatoshiPlusHelper.ROUND_INTERVAL); + bt.channelId = channelId; + emit mockDelegatedBtc(txid, candidate, delegator, outputIndex, btcAmount, lockTime / SatoshiPlusHelper.ROUND_INTERVAL, channelId); } delegatorMap[delegator].txids.push(txid); candidateMap[candidate].realtimeAmount += btcAmount; @@ -234,5 +223,16 @@ contract BitcoinStakeMock is BitcoinStake { emit mockTransferredBtc(txid, candidate, targetCandidate, msg.sender, bt.amount); } + // mock contract for test + function collectRewardMock(bytes32 txid, uint256 coreAmount, uint256 drRound, uint256 settleRound, bool claim) external returns (uint256 reward, bool expired, int256 floatReward, uint256 remainingCoreAmount) { + return _collectReward(txid, coreAmount, drRound, settleRound, claim); + } + function getCalculateRoundMock(bytes32 txid, uint256 settleRound) external view returns (uint calculateRound, bool expired) { + return _getCalculateRound(txid, settleRound); + } + function applyDualStakingMock(uint256 coreAmount, uint256 bctAmount) external view returns (uint256, uint256) { + return _applyDualStaking(coreAmount, bctAmount); + } + } diff --git a/contracts/mock/BtcLightClientMock.sol b/contracts/mock/BtcLightClientMock.sol index 17928030f..a6c110adc 100644 --- a/contracts/mock/BtcLightClientMock.sol +++ b/contracts/mock/BtcLightClientMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../BtcLightClient.sol"; diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index d3e25a250..0fc430d75 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../CandidateHub.sol"; @@ -349,4 +350,5 @@ contract CandidateHubMock is CandidateHub { function mockGetAlternateCount(uint256 maxAlternateCount, uint256 count, uint256 candidateSize) public pure returns (uint256) { return getAlternateCount(maxAlternateCount, count, candidateSize); } + receive() external payable {} } diff --git a/contracts/mock/ConfigurationMock.sol b/contracts/mock/ConfigurationMock.sol index 8d7832ffa..5d5681ecc 100644 --- a/contracts/mock/ConfigurationMock.sol +++ b/contracts/mock/ConfigurationMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache2.0 pragma solidity ^0.8.0; import {Configuration} from "../Configuration.sol"; diff --git a/contracts/mock/CoreAgentMock.sol b/contracts/mock/CoreAgentMock.sol index 700fc84bc..fd2127f88 100644 --- a/contracts/mock/CoreAgentMock.sol +++ b/contracts/mock/CoreAgentMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import {CoreAgent} from "../CoreAgent.sol"; @@ -46,10 +47,11 @@ contract CoreAgentMock is CoreAgent { } - function getDelegatorMap(address delegator) external view returns (address[] memory, uint256) { + function getDelegatorMap(address delegator) external view returns (address[] memory, uint256, uint256) { address[] memory candidates = delegatorMap[delegator].candidates; uint256 amount = delegatorMap[delegator].amount; - return (candidates, amount); + uint256 channelAmount = delegatorMap[delegator].channelAmount; + return (candidates, amount, channelAmount); } function getAccruedRewardMap(address validator, uint256 round) external view returns (uint256) { @@ -57,11 +59,16 @@ contract CoreAgentMock is CoreAgent { return accruedReward; } - function setAccruedRewardMap(address candidate, uint256 round, uint256 amount) external { + function setAccruedRewardMap(address candidate, uint256 round, uint256 amount) public { accruedRewardMap[candidate][round] = amount; } function setCoreRewardMap(address delegator, uint256 reward, uint256 accStakedAmount) external { + uint256 accrueRound = roundTag - 1; + address[] memory candidates = delegatorMap[delegator].candidates; + for (uint256 i = 0; i < candidates.length; ++i) { + setAccruedRewardMap(candidates[i], accrueRound, 0); + } rewardMap[delegator] = Reward(reward, accStakedAmount); } @@ -73,17 +80,20 @@ contract CoreAgentMock is CoreAgent { candidateMap[candidate].continuousRewardEndRounds.push(endRound); } } + function setCoinDelegatorMap(address candidate, address delegator, uint256 stakedAmount, uint256 realtimeAmount) external { + CoinDelegator storage d = candidateMap[candidate].cDelegatorMap[delegator]; + d.stakedAmount = stakedAmount; + d.realtimeAmount = realtimeAmount; + } function getRewardAmount() external view returns (uint256) { return rewardAmountM; } - function collectCoinRewardMock(address agent, address delegator, uint256 settleRound) external returns (uint256, uint256) { - uint256 avgStakedAmount; + function collectCoinRewardMock(address agent, address delegator) external returns (uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2) { Candidate storage a = candidateMap[agent]; CoinDelegator storage d = a.cDelegatorMap[delegator]; - (rewardAmountM, avgStakedAmount) = _collectRewardFromCandidate(agent, d, settleRound); - return (rewardAmountM, avgStakedAmount); + (reward, stakedAmount1, stakedAmount2) = _collectRewardFromCandidate(agent, d); } function _mockCollectReward(address candidate, uint256 stakedAmount, uint256 realtimeAmount, uint256 transferredAmount, uint256 changeRound) internal returns (uint256 reward, bool changed, uint256 accStakedAmount) { diff --git a/contracts/mock/GovHubMock.sol b/contracts/mock/GovHubMock.sol index 0520f9042..c36d09d3e 100644 --- a/contracts/mock/GovHubMock.sol +++ b/contracts/mock/GovHubMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../GovHub.sol"; diff --git a/contracts/mock/HashPowerAgentMock.sol b/contracts/mock/HashPowerAgentMock.sol index b131dbc51..d8f35dfd2 100644 --- a/contracts/mock/HashPowerAgentMock.sol +++ b/contracts/mock/HashPowerAgentMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import {HashPowerAgent} from "../HashPowerAgent.sol"; diff --git a/contracts/mock/PledgeAgentMock.sol b/contracts/mock/PledgeAgentMock.sol index 85148a91f..19ecb47a3 100644 --- a/contracts/mock/PledgeAgentMock.sol +++ b/contracts/mock/PledgeAgentMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../PledgeAgent.sol"; @@ -255,141 +256,7 @@ contract PledgeAgentMock is PledgeAgent { } expireInfo.agent2valueMap[br.agent] += br.value; } - - - function delegateBtcMock(bytes32 txId, uint256 btcValue, address agent, address delegator, bytes memory script, uint32 lockTime, uint256 fee) external { - BtcReceipt storage br = btcReceiptMap[txId]; - require(br.value == 0, "btc tx confirmed"); - br.endRound = lockTime / ROUND_INTERVAL; - br.value = btcValue; - require(br.value >= (minBtcValue == 0 ? INIT_MIN_BTC_VALUE : minBtcValue), "staked value does not meet requirement"); - br.delegator = delegator; - br.agent = agent; - if (!ICandidateHub(CANDIDATE_HUB_ADDR).isCandidateByOperate(br.agent)) { - revert InactiveAgent(br.agent); - } - emit delegatedBtcOld(txId, br.agent, br.delegator, script, btcValue); - if (fee != 0) { - br.fee = fee; - br.feeReceiver = payable(msg.sender); - } - Agent storage a = agentsMap[br.agent]; - br.rewardIndex = a.rewardSet.length; - addExpire(br); - a.totalBtc += br.value; - } - // HARDFORK V-1.0.7 - /// claim BTC staking rewards - /// @param txidList the list of BTC staking transaction id to claim rewards - /// @return rewardSum amount of reward claimed - function claimBtcReward(bytes32[] calldata txidList) external returns (uint256 rewardSum) { - uint256 len = txidList.length; - for (uint256 i = 0; i < len; i++) { - bytes32 txid = txidList[i]; - BtcReceipt storage br = btcReceiptMap[txid]; - require(br.value != 0, "btc tx not found"); - address delegator = br.delegator; - require(delegator == msg.sender, "not the delegator of this btc receipt"); - - uint256 reward = _collectBtcReward(txid); - rewardSum += reward; - if (br.value == 0) { - emit btcPledgeExpired(txid, delegator); - } - } - - if (rewardSum != 0) { - rewardMap[msg.sender] += rewardSum; - _distributeReward(msg.sender); - } - return rewardSum; - } - - /// calculate reward for a BTC stake transaction - /// @param txid the BTC transaction id - function _collectBtcReward(bytes32 txid) internal returns (uint256) { - uint256 curRound = roundTag; - BtcReceipt storage br = btcReceiptMap[txid]; - uint256 reward = 0; - Agent storage a = agentsMap[br.agent]; - uint256 rewardIndex = br.rewardIndex; - uint256 rewardLength = a.rewardSet.length; - while (rewardIndex < rewardLength) { - Reward storage r = a.rewardSet[rewardIndex]; - uint256 rRound = r.round; - if (rRound == curRound || br.endRound <= rRound) { - break; - } - uint256 deposit = br.value * stateMap[rRound].btcFactor; - reward += _collectFromRoundReward(r, deposit); - if (r.coin == 0) { - delete a.rewardSet[rewardIndex]; - } - rewardIndex += 1; - } - - uint256 fee = br.fee; - uint256 feeReward; - if (fee != 0) { - if (fee <= reward) { - feeReward = fee; - } else { - feeReward = reward; - } - - if (feeReward != 0) { - br.fee -= feeReward; - bool success = br.feeReceiver.send(feeReward); - if (success) { - reward -= feeReward; - emit transferredBtcFee(txid, br.feeReceiver, feeReward); - } else { - emit failedTransferBtcFee(txid, br.feeReceiver, feeReward); - } - } - } - - if (br.endRound <= (rewardIndex == rewardLength ? curRound : a.rewardSet[rewardIndex].round)) { - delete btcReceiptMap[txid]; - } else { - br.rewardIndex = rewardIndex; - } - return reward; - } - - /// @param targetAgent the new validator address to stake to - function transferBtcOld(bytes32 txid, address targetAgent) external { - BtcReceipt storage br = btcReceiptMap[txid]; - require(br.value != 0, "btc tx not found"); - require(br.delegator == msg.sender, "not the delegator of this btc receipt"); - address agent = br.agent; - require(agent != targetAgent, "can not transfer to the same validator"); - require(br.endRound > roundTag + 1, "insufficient locking rounds"); - if (!ICandidateHub(CANDIDATE_HUB_ADDR).canDelegate(targetAgent)) { - revert InactiveAgent(targetAgent); - } - (uint256 reward) = _collectBtcReward(txid); - Agent storage a = agentsMap[agent]; - a.totalBtc -= br.value; - round2expireInfoMap[br.endRound].agent2valueMap[agent] -= br.value; - - Reward storage r = a.rewardSet[a.rewardSet.length - 1]; - if (r.round == roundTag && br.rewardIndex < a.rewardSet.length) { - r.coin -= br.value * stateMap[roundTag].btcFactor; - } - - Agent storage ta = agentsMap[targetAgent]; - br.agent = targetAgent; - br.rewardIndex = ta.rewardSet.length; - addExpire(br); - ta.totalBtc += br.value; - - if (reward != 0) { - Address.sendValue(payable(msg.sender), reward); - } - - emit transferredBtcOld(txid, agent, targetAgent, msg.sender, br.value, ta.totalBtc); - } + function distributePowerRewardOld(address candidate, address[] calldata miners) external onlyCandidate { Agent storage a = agentsMap[candidate]; uint256 l = a.rewardSet.length; diff --git a/contracts/mock/RelayerHubMock.sol b/contracts/mock/RelayerHubMock.sol index 27a7074b0..5ddab713c 100644 --- a/contracts/mock/RelayerHubMock.sol +++ b/contracts/mock/RelayerHubMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../RelayerHub.sol"; diff --git a/contracts/mock/SlashIndicatorMock.sol b/contracts/mock/SlashIndicatorMock.sol index f047128b1..816502d77 100644 --- a/contracts/mock/SlashIndicatorMock.sol +++ b/contracts/mock/SlashIndicatorMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../SlashIndicator.sol"; @@ -73,12 +74,13 @@ contract SlashIndicatorMock is SlashIndicator { // require(verifyBLSSignature(evidence.voteA, evidence.voteAddr) && // verifyBLSSignature(evidence.voteB, evidence.voteAddr), "verify signature failed"); - (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getValidatorsAndVoteAddresses(); + (address[] memory vals, bytes[] memory voteAddrs) = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getLivingValidators(); if (voteAddrs.length <= 1) { return; } for (uint256 i; i < voteAddrs.length; ++i) { if (BytesLib.equal(voteAddrs[i], evidence.voteAddr)) { + indicators[vals[i]].count = 0; ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(msg.sender), rewardForReportFinalityViolation); IValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(vals[i], felonyRound, felonyDeposit); break; diff --git a/contracts/mock/StakeHubMock.sol b/contracts/mock/StakeHubMock.sol index e08ff4a5a..12607b08f 100644 --- a/contracts/mock/StakeHubMock.sol +++ b/contracts/mock/StakeHubMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../StakeHub.sol"; diff --git a/contracts/mock/SystemRewardMock.sol b/contracts/mock/SystemRewardMock.sol index f573af97d..5d83a571e 100644 --- a/contracts/mock/SystemRewardMock.sol +++ b/contracts/mock/SystemRewardMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../SystemReward.sol"; diff --git a/contracts/mock/ValidatorSetMock.sol b/contracts/mock/ValidatorSetMock.sol index 1c0b74bdf..baf7c1f04 100644 --- a/contracts/mock/ValidatorSetMock.sol +++ b/contracts/mock/ValidatorSetMock.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../ValidatorSet.sol"; diff --git a/contracts/test/BaseProxy.sol b/contracts/test/BaseProxy.sol index 9071a7f9b..18146e53e 100644 --- a/contracts/test/BaseProxy.sol +++ b/contracts/test/BaseProxy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; contract BaseProxy { diff --git a/contracts/test/SelfDestroy.sol b/contracts/test/SelfDestroy.sol index 09d74480b..fdae28d4c 100644 --- a/contracts/test/SelfDestroy.sol +++ b/contracts/test/SelfDestroy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; contract SelfDestroy { diff --git a/contracts/test/TestLibMemory.sol b/contracts/test/TestLibMemory.sol index f97936052..58f0669dc 100644 --- a/contracts/test/TestLibMemory.sol +++ b/contracts/test/TestLibMemory.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "../lib/Memory.sol"; diff --git a/contracts/test/reentryCandidateHubProxy.sol b/contracts/test/reentryCandidateHubProxy.sol index e1e6f7363..9130ed836 100644 --- a/contracts/test/reentryCandidateHubProxy.sol +++ b/contracts/test/reentryCandidateHubProxy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "./BaseProxy.sol"; diff --git a/contracts/test/reentryPledgeAgentProxy.sol b/contracts/test/reentryPledgeAgentProxy.sol index f8aca7e1e..41bae6cd4 100644 --- a/contracts/test/reentryPledgeAgentProxy.sol +++ b/contracts/test/reentryPledgeAgentProxy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; import "./BaseProxy.sol"; diff --git a/contracts/test/stakeProxy.sol b/contracts/test/stakeProxy.sol index 4636d9bf6..a050fa545 100644 --- a/contracts/test/stakeProxy.sol +++ b/contracts/test/stakeProxy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache2.0 pragma solidity 0.8.4; contract delegateCoinProxy { diff --git a/requirements.txt b/requirements.txt index 292cc3e1c..d236cf168 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ eth-brownie==1.20.6 ecdsa==0.19.0 -python-bitcoinlib==0.12.2 \ No newline at end of file +python-bitcoinlib==0.12.2 +blspy==2.0.3 \ No newline at end of file diff --git a/tests/calc_reward.py b/tests/calc_reward.py index 97c910d6e..c55ea706d 100644 --- a/tests/calc_reward.py +++ b/tests/calc_reward.py @@ -57,13 +57,16 @@ def get_tlp_rate(day): def get_lp_rate(coin_amount, asset_amount, asset): - discount = Utils.DENOMINATOR - level = (coin_amount * get_asset_weight(asset)) // (asset_amount * get_asset_weight('coin')) + percentage = Utils.DENOMINATOR + stake_rate = 0 + rate = (coin_amount * get_asset_weight(asset)) // (asset_amount * get_asset_weight('coin')) for l in Discount.lp_rates: - if level >= l: - discount = Discount.lp_rates[l] + if rate >= l: + percentage = Discount.lp_rates[l] + stake_rate = l break - return level, discount + dual_amount = stake_rate * asset_amount + return stake_rate, percentage, dual_amount def init_btc_lst_count(btc_lst_stake, validator_count): @@ -96,7 +99,7 @@ def init_current_round_factor(factor_map, stake_count, reward_cap): factor_map[s] = factor -def init_stake_score(agents, total_reward, btc_lst_stake): +def init_stake_score(agents, total_reward): stake_count = { 'coin': 0, 'power': 0, @@ -115,11 +118,6 @@ def init_stake_score(agents, total_reward, btc_lst_stake): stake_count['coin'] += total_coin stake_count['btc'] += total_btc validator_count += 1 - btc_lst_amount, single_agent_btc_lst, agent_btc_lst = init_btc_lst_count(btc_lst_stake, validator_count) - for agent in agents: - agent['total_btc'] += single_agent_btc_lst - agent['total_btc_lst'] = single_agent_btc_lst - stake_count['btc'] += agent_btc_lst return stake_count @@ -137,10 +135,7 @@ def calc_agent_asset_reward_distribution(agent, asset, asset_factor): if asset == 'btc': agent['sum_btc'] = asset_amount total_btc_reward = agent[key_asset_reward] - lst_amount = agent['total_btc_lst'] - agent['btc_lst_reward'] = total_btc_reward * lst_amount // asset_amount - agent[key_asset_reward] = total_btc_reward - agent['btc_lst_reward'] - agent[key_asset_amount] -= lst_amount + agent[key_asset_reward] = total_btc_reward def calc_agent_asset_reward(agent, asset, unit_amount): @@ -201,9 +196,9 @@ def calc_btc_delegator_reward(agent, stake_list, delegator_asset_reward, bonus, for item in stake_list: stake_amount = item['value'] - item['undelegate_amount'] if delegator_map['btc'].get(item['address']) is None: - delegator_map['btc'][item['address']] = item['value'] + delegator_map['btc'][item['address']] = [item['value']] else: - delegator_map['btc'][item['address']] += item['value'] + delegator_map['btc'][item['address']].append(item['value']) actual_account_btc_reward = agent['single_btc_reward'] * stake_amount // Utils.BTC_DECIMAL # staking duration discount logic if item['stake_duration'] < 360: @@ -213,9 +208,9 @@ def calc_btc_delegator_reward(agent, stake_list, delegator_asset_reward, bonus, duration_discount) bonus['total_bonus'] += unclaimed if delegator_asset_reward['btc'].get(item['address']) is None: - delegator_asset_reward['btc'][item['address']] = actual_account_btc_reward + delegator_asset_reward['btc'][item['address']] = [actual_account_btc_reward] else: - delegator_asset_reward['btc'][item['address']] += actual_account_btc_reward + delegator_asset_reward['btc'][item['address']].append(actual_account_btc_reward) print(f"btc reward: {agent['address']} on {item['address']} => {actual_account_btc_reward}") @@ -250,28 +245,29 @@ def calc_core_discounted_reward(discount_asset, delegator_asset_reward, bonus, c delegator_info = delegator_map.get(r, {}) for delegator in delegator_reward: coin_acc_stake_amount = delegator_map['coin'].get(delegator, 0) - asset_acc_stake_amount = delegator_info[delegator] - asset_reward = delegator_reward[delegator] - level, reward_discount = get_lp_rate(coin_acc_stake_amount, asset_acc_stake_amount, r) - actual_account_btc_reward = asset_reward * reward_discount // Utils.DENOMINATOR - if reward_discount >= Utils.DENOMINATOR: - actual_bonus = actual_account_btc_reward - asset_reward - system_reward = compensation_reward['system_reward'] - if actual_bonus > compensation_reward['reward_pool']: - system_reward -= actual_bonus * 10 - compensation_reward['reward_pool'] += actual_bonus * 10 - compensation_reward['reward_pool'] -= actual_bonus - if asset_reward > actual_account_btc_reward: - compensation_reward['reward_pool'] += asset_reward - actual_account_btc_reward - bonus['reward_pool'] = compensation_reward['reward_pool'] - delegator_reward[delegator] = actual_account_btc_reward - - -def calc_accrued_reward_per_asset(agents, btc_lst_stake, reward_unit_amount_map, asset_unit_reward_map): - # calculate the reward for BTC LST separately - asset = 'btc_lst' - asset_unit_reward = calc_btc_lst_asset_reward(agents, btc_lst_stake, asset, reward_unit_amount_map['btc']) - asset_unit_reward_map[asset] = asset_unit_reward + remaining_core_amount = coin_acc_stake_amount + for index,btc_amount in enumerate(delegator_info[delegator]): + asset_reward = delegator_reward[delegator][index] + stake_rate, percentage, dual_amount = get_lp_rate(remaining_core_amount, btc_amount, r) + if remaining_core_amount > dual_amount: + remaining_core_amount -= dual_amount + else: + remaining_core_amount = 0 + actual_account_btc_reward = asset_reward * percentage // Utils.DENOMINATOR + if percentage >= Utils.DENOMINATOR: + actual_bonus = actual_account_btc_reward - asset_reward + system_reward = compensation_reward['system_reward'] + if actual_bonus > compensation_reward['reward_pool']: + system_reward -= actual_bonus * 10 + compensation_reward['reward_pool'] += actual_bonus * 10 + compensation_reward['reward_pool'] -= actual_bonus + if asset_reward > actual_account_btc_reward: + compensation_reward['reward_pool'] += asset_reward - actual_account_btc_reward + bonus['reward_pool'] = compensation_reward['reward_pool'] + delegator_reward[delegator][index] = actual_account_btc_reward + + +def calc_accrued_reward_per_asset(agents, reward_unit_amount_map, asset_unit_reward_map): # calculate Core Power BTC reward for asset in reward_unit_amount_map: if asset_unit_reward_map.get(asset) is None: @@ -287,7 +283,11 @@ def update_delegator_total_reward(asset_reward_map, account_rewards_map): for delegator in asset_reward_map[asset]: if account_rewards_map.get(delegator) is None: account_rewards_map[delegator] = 0 - account_rewards_map[delegator] += asset_reward_map[asset][delegator] + if asset != 'btc': + account_rewards_map[delegator] += asset_reward_map[asset][delegator] + else: + account_rewards_map[delegator] += sum(asset_reward_map[asset][delegator]) + def get_core_lp_asset(n): @@ -305,10 +305,8 @@ def calculate_coin_rewards(score, sum_score, coin_reward): return coin_reward * score // sum_score -def parse_delegation(agents, block_reward, btc_lst_stake=None, state_map=None, compensation_reward=None, +def parse_delegation(agents, block_reward, state_map=None, compensation_reward=None, reward_cap=None): - if btc_lst_stake is None: - btc_lst_stake = {} if compensation_reward is None: compensation_reward = { 'reward_pool': 0, @@ -346,7 +344,7 @@ def parse_delegation(agents, block_reward, btc_lst_stake=None, state_map=None, c } # init asset score for 3 assets: coin, power, btc - stake_count = init_stake_score(agents, total_reward, btc_lst_stake) + stake_count = init_stake_score(agents, total_reward) # init asset factor for 3 assets: coin, power, btc init_current_round_factor(factor_map, stake_count, reward_cap) @@ -359,10 +357,10 @@ def parse_delegation(agents, block_reward, btc_lst_stake=None, state_map=None, c calc_agent_asset_reward_distribution(agent, asset, factor_map[asset]) asset_unit_reward_map = {} - # calculate the accrued reward for each asset (coin power btc btc_lst) - calc_accrued_reward_per_asset(agents, btc_lst_stake, reward_unit_amount_map, asset_unit_reward_map) + # calculate the accrued reward for each asset (coin power btc) + calc_accrued_reward_per_asset(agents, reward_unit_amount_map, asset_unit_reward_map) - # keys coin & power & btc & btc_lst + # keys coin & power & btc delegator_asset_reward = defaultdict(dict) # keys total_bonus @@ -370,9 +368,6 @@ def parse_delegation(agents, block_reward, btc_lst_stake=None, state_map=None, c # keys coin & power & btc delegator_map = defaultdict(dict) - - # calculate rewards for each asset of the delegator - calc_btc_lst_delegator_reward(btc_lst_stake, asset_unit_reward_map, delegator_asset_reward, bonus, delegator_map) for agent in agents: calc_coin_delegator_reward(agent, agent.get('coin', []), delegator_asset_reward, delegator_map) calc_power_delegator_reward(agent, agent.get('power', []), delegator_asset_reward, delegator_map) @@ -394,9 +389,19 @@ def parse_delegation(agents, block_reward, btc_lst_stake=None, state_map=None, c if __name__ == '__main__': - reward, unclaimed_reward, account_rewards, round_reward = parse_delegation([{ + DELEGATE_VALUE = 2000000 + BTC_VALUE = 200 + MONTH = 30 + _, unclaimed_reward, account_rewards, _ = parse_delegation([{ "address": 'v0', - "active": True, - "coin": [set_delegate('a1', 1000)], - "btc": [set_delegate('a0', 200)] - }], 13545) + "coin": [set_delegate('a0', DELEGATE_VALUE), set_delegate('a1', DELEGATE_VALUE)], + "btc": [set_delegate('a0', BTC_VALUE, stake_duration=MONTH)] + }, { + "address": 'v1', + "coin": [set_delegate('a0', DELEGATE_VALUE), set_delegate('a1', DELEGATE_VALUE)], + "btc": [set_delegate('a0', BTC_VALUE, stake_duration=MONTH)] + }, { + "address": 'v2', + "power": [set_delegate('a2', 100)], + "coin": [set_delegate('a0', DELEGATE_VALUE // 2), set_delegate('a1', DELEGATE_VALUE // 2)], + }], 13545, state_map={'core_lp': True}) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 4e1cbff0f..05d60409f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -166,6 +166,11 @@ def configuration(accounts): c.init() return c +@pytest.fixture(scope="module") +def channel(accounts): + c = accounts[0].deploy(Channel) + c.init() + return c # test contract @pytest.fixture(scope="module") @@ -191,11 +196,12 @@ def set_system_contract_address( btc_agent, core_agent, hash_power_agent, - configuration + configuration, + channel ): contracts = [ validator_set, slash_indicator, system_reward, btc_light_client, relay_hub, candidate_hub, gov_hub, - pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, core_agent, hash_power_agent, configuration + pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, core_agent, hash_power_agent, configuration, channel ] args = encode(['address'] * len(contracts), [c.address for c in contracts]) diff --git a/tests/delegate.py b/tests/delegate.py index ef15fb652..6e90fd86c 100644 --- a/tests/delegate.py +++ b/tests/delegate.py @@ -1,4 +1,5 @@ import time +import hashlib from tests.common import get_current_round, stake_hub_claim_reward from tests.utils import * @@ -61,7 +62,6 @@ def build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_i version=1, channel_id=0): if channel_id > 0: version = 2 - assert 1 <= version <= 2 if isinstance(lock_data, int): hex_result = value2hex(lock_data) lock_time_hex = reverse_by_bytes(hex_result) @@ -73,14 +73,13 @@ def build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_i chain_id_hex = format(int(chain_id), 'x').zfill(4) delegate_address_hex = str(delegate_address)[2:].lower() agent_address_hex = str(agent_address)[2:] + core_fee_hex = format(core_fee, 'x').zfill(2) message = flag_hex + version_hex + chain_id_hex + delegate_address_hex + agent_address_hex - if version == 1: - core_fee_hex = format(core_fee, 'x').zfill(2) - message += core_fee_hex if version == 2: assert channel_id > 0 channel_id_hex = int_to_varint_hex(channel_id) message += channel_id_hex + message += core_fee_hex message += op_return_data message_length = remove_0x(hex(len(message) // 2)) @@ -99,10 +98,9 @@ def build_btc_stake_opreturn(agent_address, delegate_address, lock_data, chain_i 'agent_address_hex': agent_address_hex, 'op_return_data': op_return_data, 'hex_value': op_return, - 'message': message + 'message': message, + 'core_fee_hex': core_fee_hex } - if version == 1: - opreturn_info['core_fee_hex'] = core_fee_hex if version == 2: opreturn_info['channel_id'] = channel_id_hex return opreturn_info @@ -450,7 +448,7 @@ def k2_btc_pay_address(lock_script, lock_script_type='p2sh'): script_hash = f"{hex(Opcode.OP_HASH160)}14{public_key_hash}{hex(Opcode.OP_EQUAL)}" pay_address = '0x' + script_hash.replace('0x', '') elif lock_script_type == 'p2wsh': - redeem_script = sha256(bytes.fromhex(lock_script)).hexdigest() + redeem_script = hashlib.sha256(bytes.fromhex(lock_script)).hexdigest() script_hash = f"{op2hex(Opcode.OP_0)}{hex(Opcode.OP_DATA_32)}{redeem_script}" pay_address = '0x' + script_hash.replace('0x', '') return pay_address @@ -484,12 +482,12 @@ def k2_btc_lst_script(lock_public_key, lock_script_type='p2sh'): lock_script = '0x' + script_hash.replace('0x', '') elif lock_script_type == AddressType.P2WSH: # 34 bytes public_key_hash = public_key_2pkhash(lock_public_key) - redeem_script = sha256(bytes.fromhex(public_key_hash.replace('0x', ''))).hexdigest() + redeem_script = hashlib.sha256(bytes.fromhex(public_key_hash.replace('0x', ''))).hexdigest() script_hash = f"{op2hex(Opcode.OP_0)}{hex(Opcode.OP_DATA_32)}{redeem_script}" lock_script = '0x' + script_hash.replace('0x', '') elif lock_script_type == AddressType.P2TAPROOT: # 34 bytes public_key_hash = public_key_2pkhash(lock_public_key) - redeem_script = sha256(bytes.fromhex(public_key_hash.replace('0x', ''))).hexdigest() + redeem_script = hashlib.sha256(bytes.fromhex(public_key_hash.replace('0x', ''))).hexdigest() script_hash = f"{op2hex(Opcode.OP_1)}{hex(Opcode.OP_DATA_32)}{redeem_script}" lock_script = '0x' + script_hash.replace('0x', '') return lock_script @@ -650,8 +648,13 @@ def mock_core_reward_map(delegator, reward, acc_stake_amount): BitcoinAgentMock[0].setCoreRewardMap(delegator, reward, acc_stake_amount) @staticmethod - def mock_btc_reward_map(delegator, reward, unclaimed_reward, delegate_amount): - BitcoinStakeMock[0].setBtcRewardMap(delegator, reward, unclaimed_reward, delegate_amount) + def mock_btc_reward_map(candidate, roundTag, reward, unclaimed_reward, btc_amount=1): + BitcoinStakeMock[0].setAccruedRewardPerBTCMap(candidate, roundTag - 1, + (reward + unclaimed_reward) * 1e8 // btc_amount) + if unclaimed_reward > 0: + BitcoinStakeMock[0].setIsActive(True) + BitcoinStakeMock[0].popTtlpRates() + BitcoinStakeMock[0].setTlpRates(0, reward / (reward + unclaimed_reward) * 10000) @staticmethod def mock_power_reward_map(delegator, reward, delegate_amount): diff --git a/tests/scenario/account_mgr.py b/tests/scenario/account_mgr.py index 1f71da043..300ae9342 100644 --- a/tests/scenario/account_mgr.py +++ b/tests/scenario/account_mgr.py @@ -75,7 +75,7 @@ def init_account_mgr(cls): cls.__inited = True - ## Divide the 100 addresses in accounts as follows,because these accounts require funding initially + ## Divide the 100 addresses in accounts as follows, because these accounts require funding initially # accounts[0]~accounts[29] are allocated to the candidate operator # accounts[30]~accounts[89] are allocated to the delegator # accounts[90]~accounts[94] as backup random addresses diff --git a/tests/test_btc_agent.py b/tests/test_btc_agent.py index 0c99d9004..bcd69eb30 100644 --- a/tests/test_btc_agent.py +++ b/tests/test_btc_agent.py @@ -225,274 +225,6 @@ def test_only_stake_hub_can_call_claim_reward(btc_agent): btc_agent.claimReward(constants.ADDRESS_ZERO, 1000, 1, False) -@pytest.mark.parametrize("grade", [True, False]) -def test_reward_claiming_after_grade_update(btc_agent, grade): - rates = [[0, 1000], [10000, 5000], [12000, 10000]] - stake_manager.set_lp_rates(rates) - btc_reward = 20000 - btc_amount = 1 - core_reward = 10000 - rate = 5000 - stake_manager.set_is_stake_hub_active(grade) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward( - accounts[0], core_reward, get_current_round() - 1, False).return_value - if grade: - float_reward = -btc_reward // 2 - actual_reward = btc_reward * rate // Utils.DENOMINATOR - else: - actual_reward = btc_reward - float_reward = 0 - assert reward == [actual_reward, float_reward, btc_amount] - - -def test_grades_not_set(btc_agent): - stake_manager.set_lp_rates() - btc_reward = 20000 - btc_amount = 1 - core_reward = 10000 - stake_manager.set_is_stake_hub_active(True) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward( - accounts[0], core_reward, get_current_round() - 1, False).return_value - assert reward[0] == btc_reward - - -@pytest.mark.parametrize("grades", [ - [[0, 5000]], - [[0, 1000], [5000, 5000]], - [[0, 1000], [5000, 5000], [10000, 10000]], - [[0, 1000], [5000, 5000], [10000, 10000], [11000, 12000]] -]) -def test_grades_length_mismatch(btc_agent, grades): - stake_manager.set_lp_rates(grades) - btc_reward = 20000 - btc_amount = 10 - core_reward = 50000 - stake_manager.set_is_stake_hub_active(True) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward( - accounts[0], core_reward, get_current_round() - 1, False).return_value - assert reward[0] == btc_reward // 2 - - -@pytest.mark.parametrize("tests", [ - [[[0, 5000]], 10000, 10, 20000, 5000], - [[[0, 2000], [1000, 3000]], 10000, 20, 20000, 3000], - [[[0, 2000], [1000, 3000], [10000, 15000]], 10000, 10, 100000, 15000], -]) -def test_update_core_amount_and_claim_reward(btc_agent, tests): - grades = tests[0] - stake_manager.set_lp_rates(grades) - btc_reward = tests[1] - btc_amount = tests[2] - core_amount = tests[3] - actual_btc_reward = tests[4] - unclaimed_reward = actual_btc_reward - btc_reward - stake_manager.set_is_stake_hub_active(True) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward.call( - accounts[0], core_amount, get_current_round() - 2, False) - assert reward == [actual_btc_reward, unclaimed_reward, btc_amount] - tx = btc_agent.claimReward( - accounts[0], core_amount, get_current_round() - 2, False) - dual_staking_rate = actual_btc_reward - expect_event(tx, 'storedBtcReward', { - 'delegator': accounts[0], - 'amount': actual_btc_reward, - 'unclaimedAmount': 0, - 'floatReward': unclaimed_reward, - 'accStakedAmount': btc_amount, - 'dualStakingRate': dual_staking_rate - }) - - -@pytest.mark.parametrize("claim", [True, False]) -@pytest.mark.parametrize("tests", [ - [[[0, 5000]], 10000, 1, 2000, 5000], - [[[0, 2000], [1000, 3000]], 10000, 20, 20000, 3000], - [[[0, 2000], [1000, 3000], [10000, 15000]], 10000, 10, 100000, 15000], - [[[0, 2000], [1000, 3000], [10000, 5000]], 100000, 10, 100000, 50000], - [[[0, 2000], [1000, 3000], [10000, 15000]], 100000, 10, 100000, 150000] -]) -def test_unclaimed_reward_record_correct(btc_agent, set_candidate, tests, claim): - stake_manager.set_lp_rates(tests[0]) - stake_manager.set_is_stake_hub_active(True) - operators, consensuses = set_candidate - turn_round() - turn_round(consensuses, round_count=2) - btc_reward = tests[1] - btc_amount = tests[2] - core_amount = tests[3] - actual_btc_reward = tests[4] - unclaimed_reward = actual_btc_reward - btc_reward - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, 0, btc_amount) - reward = btc_agent.claimReward.call( - accounts[0], core_amount, get_current_round() - 1, False) - assert reward == [actual_btc_reward, unclaimed_reward, btc_amount] - tx = btc_agent.claimReward( - accounts[0], core_amount, get_current_round() - 1, claim) - dual_staking_rate = tests[0][-1][-1] - event_name = ['storedBtcReward'] - if claim: - event_name = ['claimedBtcReward'] - expect_event(tx, event_name[0], { - 'amount': actual_btc_reward, - 'unclaimedAmount': 0, - 'floatReward': actual_btc_reward - btc_reward, - 'accStakedAmount': btc_amount, - 'dualStakingRate': dual_staking_rate - }) - - -@pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'duration_unclaimed': 4000}, - {'btc_reward': 3000, 'duration_unclaimed': 0}, - {'btc_reward': 3000, 'duration_unclaimed': 1000}, - {'btc_reward': 3000, 'duration_unclaimed': 4000}, - {'btc_reward': 3000, 'duration_unclaimed': 1000}, - {'btc_reward': 20000, 'duration_unclaimed': 8000}, - {'btc_reward': 5000, 'duration_unclaimed': 6000}, - {'btc_reward': 5000, 'duration_unclaimed': 0}, - {'btc_reward': 5000, 'duration_unclaimed': 0} -]) -def test_claim_reward_with_additional_bonus(btc_agent, tests): - stake_manager.set_lp_rates([[0, 20000]]) - btc_reward = tests['btc_reward'] - additional_bonus = btc_reward * 2 - btc_reward - duration_unclaimed = tests['duration_unclaimed'] - stake_manager.set_is_stake_hub_active(True) - turn_round() - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, duration_unclaimed, 100) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - reward = btc_agent.claimReward.call( - accounts[0], 0, get_current_round() - 1, False) - actual_reward = btc_reward + additional_bonus - if duration_unclaimed > 0: - additional_bonus -= duration_unclaimed - assert reward == [actual_reward, additional_bonus, 100] - - -@pytest.mark.parametrize("claim", [True, False]) -@pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'duration_unclaimed': 4000, 'btc_percentage': 12000, 'expect_reward': 12000, - 'expect_surplus': -2000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 12000, 'expect_reward': 12000, - 'expect_surplus': 1000}, - {'btc_reward': 10000, 'duration_unclaimed': 10000, 'btc_percentage': 18000, 'expect_reward': 18000, - 'expect_surplus': -2000}, - {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 14000, 'expect_reward': 14000, - 'expect_surplus': 4000}, - {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 9000, 'expect_reward': 9000, - 'expect_surplus': -3000}, - {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 15000, 'expect_reward': 15000, - 'expect_surplus': 5000} -]) -def test_btc_extra_pool_reward(btc_agent, tests, claim): - btc_reward = tests['btc_reward'] - duration_unclaimed = tests['duration_unclaimed'] - btc_percentage = tests['btc_percentage'] - stake_manager.set_lp_rates([[0, btc_percentage]]) - stake_manager.set_is_stake_hub_active(True) - turn_round() - actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - additional_bonus = actual_btc_reward - btc_reward - btc_acc_amount = 1100 - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, duration_unclaimed, btc_acc_amount) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - reward = btc_agent.claimReward.call( - accounts[0], 0, get_current_round() - 1, False) - if duration_unclaimed > 0: - additional_bonus -= duration_unclaimed - actual_reward = actual_btc_reward - assert actual_reward == tests['expect_reward'] - assert additional_bonus == tests['expect_surplus'] - assert reward == [actual_reward, additional_bonus, btc_acc_amount] - tx = btc_agent.claimReward(accounts[0], 0, get_current_round() - 1, claim) - event_name = ['storedBtcReward', 'storedBtcLstReward'] - if claim: - event_name = ['claimedBtcReward', 'claimedBtcLstReward'] - expect_event(tx, event_name[0], { - 'delegator': accounts[0].address, - 'amount': actual_btc_reward, - 'unclaimedAmount': duration_unclaimed, - 'floatReward': actual_btc_reward - btc_reward - duration_unclaimed, - 'accStakedAmount': btc_acc_amount, - 'dualStakingRate': btc_percentage - }) - - -@pytest.mark.parametrize("tests", [ - {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 8000, 'expect_reward': 8000, - 'expect_surplus': -2000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 8000, 'expect_reward': 8000, - 'expect_surplus': -3000}, - {'btc_reward': 3000, 'duration_unclaimed': 1000, 'btc_percentage': 0, 'expect_reward': 0, 'expect_surplus': -4000}, - {'btc_reward': 20000, 'duration_unclaimed': 3000, 'btc_percentage': 10000, 'expect_reward': 20000, - 'expect_surplus': -3000}, - {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 7000, 'expect_reward': 7000, - 'expect_surplus': -3000}, - {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 9000, 'expect_reward': 9000, - 'expect_surplus': -3000}, - {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 12000, 'expect_reward': 12000, - 'expect_surplus': 0}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'expect_reward': 12000, - 'expect_surplus': -1000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 12000, 'expect_reward': 12000, - 'expect_surplus': 1000}, - {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 12000, 'expect_reward': 12000, - 'expect_surplus': 2000}, - {'btc_reward': 20000, 'duration_unclaimed': 0, 'btc_percentage': 0, 'expect_reward': 0, 'expect_surplus': -20000}, - {'btc_reward': 20000, 'duration_unclaimed': 2000, 'btc_percentage': 0, 'expect_reward': 0, - 'expect_surplus': -22000}, - {'btc_reward': 20000, 'duration_unclaimed': 2000, 'btc_percentage': 5000, 'expect_reward': 10000, - 'expect_surplus': -12000} -]) -def test_btc_add_reward_to_pool(btc_agent, tests): - btc_reward = tests['btc_reward'] - duration_unclaimed = tests['duration_unclaimed'] - btc_percentage = tests['btc_percentage'] - stake_manager.set_lp_rates([[0, btc_percentage]]) - stake_manager.set_is_stake_hub_active(True) - turn_round() - actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - additional_bonus = actual_btc_reward - btc_reward - round_reward_manager.mock_btc_reward_map( - accounts[0], btc_reward, duration_unclaimed, 100) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - reward = btc_agent.claimReward.call( - accounts[0], 0, get_current_round() - 1, False) - if duration_unclaimed > 0: - additional_bonus -= duration_unclaimed - actual_reward = actual_btc_reward - assert actual_reward == tests['expect_reward'] - assert additional_bonus == tests['expect_surplus'] - assert reward == [actual_reward, additional_bonus, 100] - - -def test_get_acc_stake_amount_success(btc_agent, set_candidate): - btc_value = 200 - operators, consensuses = set_candidate - turn_round() - delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_agent, stake_hub=accounts[0]) - _, _, acc_staked_amount = btc_agent.claimReward(accounts[0], 100, get_current_round() - 1, False).return_value - assert acc_staked_amount == btc_value - - def test_get_grades(btc_agent, btc_stake): old_grades = [[0, 1000], [2000, 10000]] update_system_contract_address(btc_agent, gov_hub=accounts[0]) @@ -501,6 +233,94 @@ def test_get_grades(btc_agent, btc_stake): assert old_grades == btc_agent.getGrades() +# getGrade +def test_get_grade_inactive_no_grades(btc_agent): + btc_agent.setIsActive(False) + btc_agent.popLpRates() + percentage, stake_rate = btc_agent.getGrade(0) + assert percentage == 10000 + assert stake_rate == 0 + + +def test_get_grade_inactive_with_grades(btc_agent): + btc_agent.setIsActive(False) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 2000) + asset_weight = btc_agent.assetWeight() + percentage, stake_rate = btc_agent.getGrade(asset_weight * 10) + assert percentage == 10000 + assert stake_rate == 0 + + +def test_get_grade_active_empty_grades(btc_agent): + btc_agent.setIsActive(True) + btc_agent.popLpRates() + percentage, stake_rate = btc_agent.getGrade(0) + assert percentage == 10000 + assert stake_rate == 0 + + +def test_get_grade_active_selects_first_grade_for_low_rate(btc_agent): + btc_agent.setIsActive(True) + btc_agent.popLpRates() + # grades: (stakeRate, percentage) + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 2000) + btc_agent.setLpRates(7000, 4000) + btc_agent.setLpRates(10000, 10000) + percentage, stake_rate = btc_agent.getGrade(0) + assert percentage == 1000 + assert stake_rate == 0 + + +def test_get_grade_active_selects_middle_grade(btc_agent): + btc_agent.setAssetWeight(1e10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 2000) + btc_agent.setLpRates(7000, 4000) + btc_agent.setLpRates(10000, 10000) + asset_weight = btc_agent.assetWeight() + percentage, stake_rate = btc_agent.getGrade(asset_weight * 7000) + assert percentage == 4000 + assert stake_rate == asset_weight * 7000 + + +def test_get_grade_active_selects_correct_lower_bound(btc_agent): + btc_agent.setAssetWeight(1e10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 2000) + btc_agent.setLpRates(7000, 4000) + btc_agent.setLpRates(10000, 10000) + asset_weight = btc_agent.assetWeight() + # rate between 3000 and 7000 should select grade stakeRate=3000 + btc_amount = 3e8 + coin_amount = 15000e18 + percentage, stake_rate = btc_agent.getGrade(coin_amount / btc_amount) + assert percentage == 2000 + assert stake_rate == asset_weight * 3000 + + +def test_get_grade_active_selects_highest_grade(btc_agent): + btc_agent.setAssetWeight(1e10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 2000) + btc_agent.setLpRates(7000, 4000) + btc_agent.setLpRates(10000, 10000) + btc_agent.setLpRates(12000, 11000) + asset_weight = btc_agent.assetWeight() + btc_amount = 1e8 + coin_amount = 12000e18 + percentage, stake_rate = btc_agent.getGrade(coin_amount / btc_amount) + assert percentage == 11000 + assert stake_rate == asset_weight * 12000 + def test_update_param_failed(btc_agent): update_system_contract_address(btc_agent, gov_hub=accounts[0]) with brownie.reverts("UnsupportedGovParam: error key"): @@ -624,43 +444,6 @@ def test_percentage_cannot_be_zero(btc_agent): btc_agent.updateParam('grades', grades_encode) -def test_update_param_percentage_success(btc_agent): - percentage = 4000 - update_system_contract_address(btc_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(percentage)), 64) - tx = btc_agent.updateParam('lstGradePercentage', hex_value) - assert btc_agent.lstGradePercentage() == percentage - expect_event(tx, 'paramChange', { - 'key': 'lstGradePercentage', - 'value': hex_value - }) - - -def test_update_param_percentage_length_error(btc_agent): - percentage = 4000 - update_system_contract_address(btc_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(percentage)), 65) - with brownie.reverts(f"MismatchParamLength: lstGradePercentage"): - btc_agent.updateParam('lstGradePercentage', hex_value) - - -@pytest.mark.skip(reason="deprecated") -def test_revert_on_percentage_zero(btc_agent): - percentage = 0 - update_system_contract_address(btc_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(percentage), 64) - with brownie.reverts(f"OutOfBounds: lstGradePercentage, {percentage}, 1, 100000"): - btc_agent.updateParam('lstGradePercentage', hex_value) - -@pytest.mark.skip(reason="deprecated") -def test_revert_on_percentage_exceeding_limit(btc_agent): - percentage = 100001 - update_system_contract_address(btc_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(percentage), 64) - with brownie.reverts(f"OutOfBounds: lstGradePercentage, {percentage}, 1, 100000"): - btc_agent.updateParam('lstGradePercentage', hex_value) - - @pytest.mark.parametrize("grade_active", [0, 1]) def test_update_param_grade_active_success(btc_agent, grade_active): update_system_contract_address(btc_agent, gov_hub=accounts[0]) diff --git a/tests/test_btc_stake.py b/tests/test_btc_stake.py index 3b6bd5c7b..a14fcaa7b 100644 --- a/tests/test_btc_stake.py +++ b/tests/test_btc_stake.py @@ -1,10 +1,9 @@ import brownie import pytest import rlp -from brownie import * from web3 import constants from .calc_reward import set_delegate, parse_delegation -from .common import register_candidate, turn_round, get_current_round, set_round_tag, stake_hub_claim_reward +from .common import register_candidate, turn_round, set_round_tag from .delegate import * from .utils import * @@ -468,7 +467,7 @@ def test_revert_on_incorrect_version(btc_stake, set_candidate): operators, consensuses = set_candidate set_last_round_tag(STAKE_ROUND) lock_script = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME) - btc_tx = build_btc_tx(operators[0], accounts[0], BTC_VALUE, lock_script, version=2) + btc_tx = build_btc_tx(operators[0], accounts[0], BTC_VALUE, lock_script, version=3) with brownie.reverts("unsupported sat+ version in btc staking"): btc_stake.delegate(btc_tx, 0, [], 0, lock_script) @@ -939,18 +938,17 @@ def test_query_validator_zero_address(btc_stake, set_candidate): assert amounts == [0] -def test_btc_claim_reward_success(btc_stake, set_candidate): +# claimReward +def test_btc_claim_reward_success(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate for index, o in enumerate(operators): delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() turn_round(consensuses) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 1, + False, {'from': btc_agent}).return_value assert reward == TOTAL_REWARD * 3 assert reward_unclaimed == 0 - assert acc_staked_amount == BTC_VALUE * 3 @pytest.mark.parametrize("claim", [True, False]) @@ -967,25 +965,24 @@ def test_claim_rewards_for_multiple_btc(btc_stake, set_candidate, claim): turn_round(consensuses, round_count=3) if claim: tx = stake_hub_claim_reward(accounts[0]) - event_name = 'claimedRewardPerTx' + event_name = 'claimedRewardBtcTx' else: tx = delegate_coin_success(operators[0], accounts[0], BTC_VALUE) - event_name = 'storedRewardPerTx' + event_name = 'storedRewardBtcTx' actual_reward = [ - [tx_id1, 0, False, 0, 0], - [tx_id0, TOTAL_REWARD // 2, False, BTC_VALUE, TOTAL_REWARD - TOTAL_REWARD // 2], - [tx_id1, TOTAL_REWARD * 2 // 2, False, BTC_VALUE * 2 * 2, TOTAL_REWARD * 2 - TOTAL_REWARD * 2 // 2], - [tx_id0, TOTAL_REWARD * 2 // 2, True, BTC_VALUE * 2, TOTAL_REWARD * 2 - TOTAL_REWARD * 2 // 2], + [tx_id1, TOTAL_REWARD * 2 // 2, False, Utils.DENOMINATOR // 2, Utils.DENOMINATOR], + [tx_id0, TOTAL_REWARD * 3 // 2, True, Utils.DENOMINATOR // 2, Utils.DENOMINATOR], ] for index, i in enumerate(tx.events[event_name]): assert i['txid'] == actual_reward[index][0] assert i['reward'] == actual_reward[index][1] assert i['expired'] == actual_reward[index][2] - assert i['accStakedAmount'] == actual_reward[index][3] - assert i['unclaimedReward'] == actual_reward[index][4] + assert i['lockLengthRate'] == actual_reward[index][3] + assert i['dualStakingRate'] == actual_reward[index][4] + assert tx.events['btcExpired']['txid'] == tx_id0 -def test_reward_increase_with_longer_stake_duration(btc_stake, set_candidate, btc_light_client): +def test_reward_increase_with_longer_stake_duration(btc_stake, set_candidate, btc_light_client, btc_agent): operators, consensuses = set_candidate btc_light_client.setCheckResult(True, LOCK_TIME - 1000) grades = [(100, 2000), (500, 5000)] @@ -995,12 +992,11 @@ def test_reward_increase_with_longer_stake_duration(btc_stake, set_candidate, bt delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() turn_round(consensuses) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, True).return_value + tx = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, + {'from': btc_agent}) + reward, reward_unclaimed = tx.return_value assert reward == TOTAL_REWARD // 2 * 3 - assert reward_unclaimed == TOTAL_REWARD * 3 - TOTAL_REWARD // 2 * 3 - assert acc_staked_amount == BTC_VALUE * 3 + assert reward_unclaimed == -(TOTAL_REWARD * 3 - TOTAL_REWARD // 2 * 3) assert btc_stake.rewardMap(accounts[0]) == (0, 0, 0) assert len(btc_stake.getTxIdsByDelegator(accounts[0])) == 3 @@ -1015,19 +1011,14 @@ def test_claim_expired_stake_btc_reward(btc_stake, set_candidate, btc_agent): tx_id1 = delegate_btc_success(operators[2], accounts[0], BTC_VALUE, lock_script1) __get_receipt_map_info(tx_id0) turn_round() - update_system_contract_address(btc_stake, btc_agent=accounts[0]) tx_ids = btc_stake.getTxIdsByDelegator(accounts[0]) - reward, _, acc_staked_amount = btc_stake.claimReward(accounts[0], get_current_round() - 1, True).return_value + reward, _ = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, {'from': btc_agent}).return_value assert reward == 0 - assert acc_staked_amount == 0 assert len(tx_ids) == 3 - update_system_contract_address(btc_stake, btc_agent=btc_agent) turn_round(consensuses) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, True).return_value + tx = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, {'from': btc_agent}) + reward, reward_unclaimed = tx.return_value assert reward == TOTAL_REWARD * 3 - assert acc_staked_amount == BTC_VALUE * 3 tx_ids = btc_stake.getTxIdsByDelegator(accounts[0]) assert tx_ids == [tx_id1, tx_id0] @@ -1038,18 +1029,14 @@ def test_claim_multiple_rounds_of_btc_rewards(btc_stake, set_candidate, btc_agen delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() turn_round(consensuses, round_count=3) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, True).return_value + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, + {'from': btc_agent}).return_value assert reward == TOTAL_REWARD * 9 - assert acc_staked_amount == BTC_VALUE * 9 - update_system_contract_address(btc_stake, btc_agent=btc_agent) + assert reward_unclaimed == 0 turn_round(consensuses, round_count=2) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, True).return_value + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, + {'from': btc_agent}).return_value assert reward == TOTAL_REWARD * 6 - assert acc_staked_amount == BTC_VALUE * 6 def test_claim_rewards_after_multiple_expired_stake_rounds(btc_stake, set_candidate, btc_agent): @@ -1059,11 +1046,9 @@ def test_claim_rewards_after_multiple_expired_stake_rounds(btc_stake, set_candid delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() turn_round(consensuses, round_count=3) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, True).return_value + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, + {'from': btc_agent}).return_value assert reward == TOTAL_REWARD * 3 - assert acc_staked_amount == BTC_VALUE * 3 def test_claim_reward_reverts_on_nonexistent_tx_id(btc_stake, set_candidate, btc_agent): @@ -1075,124 +1060,12 @@ def test_claim_reward_reverts_on_nonexistent_tx_id(btc_stake, set_candidate, btc btc_stake.setDelegatorMap(accounts[0], error_tx_id) update_system_contract_address(btc_stake, btc_agent=accounts[0]) with brownie.reverts("invalid deposit receipt"): - btc_stake.claimReward(accounts[0], get_current_round() - 1, True) + btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True) def test_only_btc_agent_can_call_claim_reward(btc_stake, btc_agent): with brownie.reverts("the msg sender must be bitcoin agent contract"): - btc_stake.claimReward(accounts[0], get_current_round() - 1, True) - - -@pytest.mark.parametrize('round_count', [0, 1]) -@pytest.mark.parametrize("tests", [ - [2000, 'delegate', 'transfer', 'claim'], - [6000, 'delegate', 'delegate', 'claim'], - [2000, 'delegate', 'delegate', 'transfer', 'claim'], - [2000, 'delegate', 'transfer', 'claim'], - [2000, 'transfer', 'claim'], - [2000, 'transfer', 'delegate'] -]) -def test_get_acc_stake_amount_success(btc_stake, btc_agent, set_candidate, round_count, tests): - operators, consensuses = set_candidate - tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - for i in tests: - if i == 'delegate': - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - elif i == 'transfer': - transfer_btc_success(tx_id, operators[2], accounts[0]) - else: - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses, round_count=round_count) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - expect_stake_amount = tests[0] - if round_count == 0: - expect_stake_amount = 0 - assert acc_staked_amount == expect_stake_amount - - -@pytest.mark.parametrize("tests", [ - [10000, 'delegate', 'transfer', 'claim'], - [16000, 'delegate', 'delegate', 'claim'], - [12000, 'delegate', 'delegate', 'transfer', 'claim'], - [10000, 'delegate', 'transfer', 'claim'], - [8000, 'transfer', 'claim'], - [10000, 'transfer', 'delegate'] -]) -def test_multi_round_acc_amount(btc_stake, btc_agent, set_candidate, tests): - operators, consensuses = set_candidate - tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE * 2, LOCK_SCRIPT) - delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - for i in tests: - if i == 'delegate': - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - elif i == 'transfer': - transfer_btc_success(tx_id, operators[2], accounts[0]) - else: - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - expect_stake_amount = tests[0] - assert acc_staked_amount == expect_stake_amount - - -def test_check_acc_stake_amount_after_btc_expiration(btc_stake, btc_agent, set_candidate): - operators, consensuses = set_candidate - set_last_round_tag(1) - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - turn_round(consensuses, round_count=5) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - assert acc_staked_amount == BTC_VALUE - - -def test_clear_acc_stake_amount_after_claiming_rewards(btc_stake, slash_indicator, btc_agent, set_candidate): - operators, consensuses = set_candidate - set_last_round_tag(20) - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - felony_threshold = slash_indicator.felonyThreshold() - for _ in range(felony_threshold): - slash_indicator.slash(consensuses[0]) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - assert reward == 0 - assert acc_staked_amount == BTC_VALUE * 2 - update_system_contract_address(btc_stake, btc_agent=btc_agent) - turn_round(consensuses, round_count=2) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], - get_current_round() - 1, False).return_value - assert acc_staked_amount == BTC_VALUE * 2 - - -def test_historical_rewards_exist(btc_stake, set_candidate, btc_agent): - operators, consensuses = set_candidate - set_last_round_tag(1) - for index, o in enumerate(operators): - delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - turn_round(consensuses, round_count=3) - add_reward = 1000 - unclaimed_reward = 2000 - acc_amount = 3000 - btc_stake.setBtcRewardMap(accounts[0], add_reward, unclaimed_reward, acc_amount) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], get_current_round() - 1, - True).return_value - assert reward == TOTAL_REWARD * 3 + add_reward - assert reward_unclaimed == unclaimed_reward - assert acc_staked_amount == BTC_VALUE * 3 + acc_amount + btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True) @pytest.mark.parametrize("specified_round", [1, 2, 3, 4, 10]) @@ -1203,12 +1076,10 @@ def test_calculate_for_specified_round(btc_stake, set_candidate, btc_agent, spec delegate_round = get_current_round() turn_round() turn_round(consensuses, round_count=10) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = btc_stake.claimReward(accounts[0], delegate_round + specified_round, - True).return_value + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 1e18, delegate_round + specified_round, True, + {'from': btc_agent}).return_value assert reward == TOTAL_REWARD * 3 * specified_round assert reward_unclaimed == 0 - assert acc_staked_amount == BTC_VALUE * 3 * specified_round def test_calculate_round_greater_than_current_revert(btc_stake, set_candidate, btc_agent): @@ -1217,11 +1088,10 @@ def test_calculate_round_greater_than_current_revert(btc_stake, set_candidate, b delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() turn_round(consensuses) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) with brownie.reverts("invalid settle round"): - btc_stake.claimReward(accounts[0], get_current_round() + 1, True) + btc_stake.claimReward(accounts[0], 1e18, get_current_round() + 1, True, {'from': btc_agent}) with brownie.reverts("invalid settle round"): - btc_stake.claimReward(accounts[0], get_current_round(), True) + btc_stake.claimReward(accounts[0], 1e18, get_current_round(), True, {'from': btc_agent}) def test_calculate_round_less_than_stake_round(btc_stake, set_candidate, btc_agent): @@ -1231,9 +1101,9 @@ def test_calculate_round_less_than_stake_round(btc_stake, set_candidate, btc_age delegate_round = get_current_round() turn_round() turn_round(consensuses) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_amount = btc_stake.claimReward(accounts[0], delegate_round - 1, True).return_value - assert reward == reward_unclaimed == acc_amount == 0 + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 1e18, delegate_round - 1, True, + {'from': btc_agent}).return_value + assert reward == reward_unclaimed == 0 def test_calculate_round_greater_than_unlock_round(btc_stake, set_candidate, btc_agent): @@ -1244,11 +1114,9 @@ def test_calculate_round_greater_than_unlock_round(btc_stake, set_candidate, btc delegate_btc_success(o, accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() turn_round(consensuses, round_count=8) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, reward_unclaimed, acc_amount = btc_stake.claimReward(accounts[0], get_current_round() - 2, - True).return_value + reward, reward_unclaimed = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 2, True, + {'from': btc_agent}).return_value assert reward == TOTAL_REWARD * 3 * 3 - assert acc_amount == BTC_VALUE * 3 * 3 tx_ids = btc_stake.getTxIdsByDelegator(accounts[0]) assert len(tx_ids) == 0 @@ -1261,156 +1129,436 @@ def test_stake_round_greater_than_unlock_round(btc_stake, set_candidate, btc_age current_round = 20092 assert btc_stake.receiptMap(tx_id)['round'] == current_round turn_round(consensuses, round_count=3) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) settle_round = 10 - reward, _, acc_amount = btc_stake.claimReward(accounts[0], settle_round, True).return_value + reward, _ = btc_stake.claimReward(accounts[0], 1e18, settle_round, + True, {'from': btc_agent}).return_value assert reward == 0 - assert acc_amount == 0 -@pytest.mark.parametrize("settleRound", [1, 2, 3, 4, 10]) -def test_collectReward_success(btc_stake, set_candidate, btc_agent, settleRound): +def test_claim_reward_empty_delegator(btc_stake, btc_agent): + reward, float_reward = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 1, True, + {'from': btc_agent}).return_value + assert reward == 0 + assert float_reward == 0 + + +def test_claim_reward_dr_round_equals_settle_round(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + stake_round = get_current_round() turn_round() - set_last_round_tag(3) - tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - current_round = 20099 - turn_round() - assert btc_stake.receiptMap(tx_id)['round'] == current_round - turn_round(consensuses, round_count=12) - reward, expired, _, acc_amount = btc_stake.collectRewardMock(tx_id, current_round + settleRound).return_value - actual_reward = TOTAL_REWARD * settleRound - actual_expired = False - actual_acc_amount = BTC_VALUE * settleRound - if settleRound >= 3: - actual_reward = TOTAL_REWARD * 3 - actual_expired = True - actual_acc_amount = BTC_VALUE * 3 - assert reward == actual_reward - assert expired == actual_expired - assert acc_amount == actual_acc_amount + turn_round(consensuses) + reward, float_reward = btc_stake.claimReward(accounts[0], 1e18, stake_round, True, {'from': btc_agent}).return_value + assert reward == 0 + assert float_reward == 0 -@pytest.mark.parametrize("tests", [ - [20099, 20101, 25000, 20101], - [20101, 20103, 10000, 0], - [20101, 20101, 0, 20097] -]) -def test_viewCollectReward_success(btc_stake, set_candidate, btc_agent, tests): +def test_claim_reward_zero_candidate_reward(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() - set_last_round_tag(5) - tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - delegate_round = 20099 - turn_round() - turn_round(consensuses, round_count=12) - btc_stake.setAccruedRewardPerBTCMap(operators[0], delegate_round, 0) - btc_stake.setAccruedRewardPerBTCMap(operators[0], delegate_round + 1, 20000 * Utils.BTC_DECIMAL // BTC_VALUE) - btc_stake.setAccruedRewardPerBTCMap(operators[0], delegate_round + 2, 25000 * Utils.BTC_DECIMAL // BTC_VALUE) - btc_stake.setAccruedRewardPerBTCMap(operators[0], delegate_round + 3, 35000 * Utils.BTC_DECIMAL // BTC_VALUE) - btc_stake.setAccruedRewardPerBTCMap(operators[0], delegate_round + 4, 40000 * Utils.BTC_DECIMAL // BTC_VALUE) - btc_stake.setAccruedRewardPerBTCMap(operators[0], delegate_round + 5, 40000 * Utils.BTC_DECIMAL // BTC_VALUE) - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, tests[0], tests[1]).return_value - assert btc_stake.receiptMap(tx_id)['round'] == tests[3] - assert reward == tests[2] + turn_round(consensuses) + btc_stake.setAccruedRewardPerBTCMap(operators[0], get_current_round() - 1, 0) + reward, float_reward = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 1, True, + {'from': btc_agent}).return_value + assert reward == 0 + assert float_reward == 0 -def test_viewCollectReward_segmented_calculation_success(btc_stake, set_candidate, btc_agent): - stake_manager.set_lp_rates([[4999, 1000], [5000, 20000], [5001, 1000]]) +def test_claim_reward_grade_inactive(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate + btc_stake.setIsActive(False) + btc_stake.setTlpRates(0, 5000) + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round() - delegate_amount = 500000 - btc_value = 100 - delegate_coin_success(operators[1], accounts[0], delegate_amount) - turn_round() - tx_id = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - delegate_round = get_current_round() - turn_round(consensuses, round_count=2) - last_round = get_current_round() - 1 - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, delegate_round, delegate_round).return_value - assert reward == 0 - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, delegate_round, last_round).return_value + turn_round(consensuses) + reward, float_reward = btc_stake.claimReward.call(accounts[0], 1e18, get_current_round() - 1, True, + {'from': btc_agent}) assert reward == TOTAL_REWARD + assert float_reward == 0 + btc_stake.setIsActive(True) + reward, float_reward = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 1, True, + {'from': btc_agent}).return_value + assert reward == TOTAL_REWARD // 2 + assert float_reward == -(TOTAL_REWARD - TOTAL_REWARD // 2) -def test_segmented_reward_btc_first(btc_stake, set_candidate, btc_agent): +def test_claim_reward_discount_by_lock_length(btc_stake, set_candidate, btc_agent, btc_light_client): operators, consensuses = set_candidate + btc_light_client.setCheckResult(True, LOCK_TIME - 1000) + btc_stake.setIsActive(True) + stake_manager.set_tlp_rates([[0, 5000], [1 * Utils.MONTH_TIMESTAMP, 5000], [5 * Utils.MONTH_TIMESTAMP, 15000]]) + btc_agent.setIsActive(False) + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT, stake_duration=30) turn_round() - delegate_amount = 500000 - btc_value = 100 - tx_id = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT) - turn_round() - delegate_coin_success(operators[1], accounts[0], delegate_amount) - assert btc_stake.receiptMap(tx_id)['round'] == get_current_round() - 1 - delegate_round = get_current_round() - turn_round(consensuses, round_count=2) - last_round = get_current_round() - 1 - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, delegate_round - 1, delegate_round).return_value - assert reward == TOTAL_REWARD // 2 - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, delegate_round, last_round).return_value + turn_round(consensuses) + tx = btc_stake.claimReward(accounts[0], 0, get_current_round() - 1, True, {'from': btc_agent}) + reward, float_reward = tx.return_value + assert tx.events['claimedRewardBtcTx'][0]['lockLengthRate'] == 5000 assert reward == TOTAL_REWARD // 2 + assert float_reward == (TOTAL_REWARD // 2) - TOTAL_REWARD -@pytest.mark.parametrize("start_round", [0, 1]) -def test_start_time_greater_than_calculation_time(btc_stake, set_candidate, btc_agent, start_round): +def test_claim_reward_dual_staking_core_insufficient(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 9000) + btc_agent.setLpRates(6000, 10000) + btc_agent.setLpRates(12000, 15000) + delegate_btc_success(operators[0], accounts[0], 1e8, LOCK_SCRIPT) turn_round() - delegate_amount = 500000 - btc_value = 100 - tx_id = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) + turn_round(consensuses) + small_core_amount = 6000e18 + reward, float_reward = btc_stake.claimReward.call(accounts[0], small_core_amount, get_current_round() - 1, True, + {'from': btc_agent}) + assert reward == TOTAL_REWARD + assert float_reward == 0 + btc_agent.popLpRates() + btc_agent.setLpRates(2000, 10000) + btc_agent.setLpRates(6000, 12000) + btc_agent.setLpRates(12000, 13000) + small_core_amount = 1900e18 + reward, float_reward = btc_stake.claimReward(accounts[0], small_core_amount, get_current_round() - 1, True, + {'from': btc_agent}).return_value + assert reward == TOTAL_REWARD + assert float_reward == 0 + + +def test_claim_reward_dual_staking_core_sufficient(btc_stake, set_candidate, btc_agent): + operators, consensuses = set_candidate + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 2000) + btc_agent.setLpRates(2000, 4000) + btc_agent.setLpRates(7000, 10000) + btc_agent.setLpRates(10000, 12000) + delegate_btc_success(operators[0], accounts[0], 1e8, LOCK_SCRIPT) turn_round() - delegate_coin_success(operators[1], accounts[0], delegate_amount) - assert btc_stake.receiptMap(tx_id)['round'] == get_current_round() - 1 - turn_round(consensuses, round_count=3) - last_round = get_current_round() - 1 - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round + start_round, - last_round).return_value - assert reward == 0 + turn_round(consensuses) + large_core_amount = 8000e18 + tx = btc_stake.claimReward(accounts[0], large_core_amount, get_current_round() - 1, True, + {'from': btc_agent}) + reward, float_reward = tx.return_value + assert reward == TOTAL_REWARD + assert float_reward == 0 -@pytest.mark.parametrize("start_round", [0, 1]) -def test_start_time_greater_than_unlock_time(btc_stake, set_candidate, btc_agent, start_round): +def test_claim_reward_mixed_expiry_transactions(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate + set_last_round_tag(1) + short_lock_script = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 2) + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, short_lock_script) + long_lock_script = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 10) + delegate_btc_success(operators[1], accounts[0], BTC_VALUE, long_lock_script) turn_round() - set_last_round_tag(3) - delegate_amount = 500000 - btc_value = 100 - tx_id = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) + turn_round(consensuses, round_count=5) + + initial_tx_count = len(btc_stake.getTxIdsByDelegator(accounts[0])) + reward, float_reward = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 1, True, + {'from': btc_agent}).return_value + final_tx_count = len(btc_stake.getTxIdsByDelegator(accounts[0])) + + assert reward == TOTAL_REWARD * 8 + assert float_reward == 0 + assert final_tx_count == initial_tx_count - 1 + + +def test_claim_reward_all_transactions_expired(btc_stake, set_candidate, btc_agent): + operators, consensuses = set_candidate + set_last_round_tag(1) + short_lock_script = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 2) + for i, op in enumerate(operators[:2]): + delegate_btc_success(op, accounts[0], BTC_VALUE, short_lock_script) + turn_round() - delegate_coin_success(operators[1], accounts[0], delegate_amount) - assert btc_stake.receiptMap(tx_id)['round'] == get_current_round() - 1 - turn_round(consensuses, round_count=8) - end_round = 20102 - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, end_round + start_round, - end_round - 1).return_value - assert reward == 0 + tx = turn_round(consensuses, round_count=5) + reward, float_reward = btc_stake.claimReward(accounts[0], 1e18, get_current_round() - 1, True, + {'from': btc_agent}).return_value + final_tx_count = len(btc_stake.getTxIdsByDelegator(accounts[0])) + + assert reward == TOTAL_REWARD * 6 + assert float_reward == 0 + assert final_tx_count == 0 -def test_viewCollectReward_only_BtcAgent(btc_stake, set_candidate, btc_agent): +def test_claim_reward_dual_staking_tiers_by_amount(btc_stake, set_candidate, btc_agent): operators, consensuses = set_candidate + btc_agent.setAssetWeight(1e10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 5000) + btc_agent.setLpRates(5999, 10000) + btc_agent.setLpRates(10000, 12000) + + delegate_btc_success(operators[0], accounts[0], 1e8, LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], 2e8, LOCK_SCRIPT) + turn_round() - btc_value = 100 - tx_id = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) + turn_round(consensuses) + + core_amount = 5999e18 + tx = btc_stake.claimReward(accounts[0], core_amount, get_current_round() - 1, True, {'from': btc_agent}) + events = tx.events['claimedRewardBtcTx'] + assert events[0]['dualStakingRate'] == 1000 + assert events[1]['dualStakingRate'] == 10000 + + +@pytest.mark.parametrize("dual_staking_rate", [6000, 10000, 12000]) +def test_claim_reward_complex_floatreward(btc_stake, set_candidate, btc_agent, btc_light_client, dual_staking_rate): + operators, consensuses = set_candidate + btc_light_client.setCheckResult(True, LOCK_TIME - 1000) + + btc_stake.setIsActive(True) + btc_stake.popTtlpRates() + btc_stake.setTlpRates(0, 8000) + btc_stake.setTlpRates(300 * Utils.ROUND_INTERVAL, 10000) + + btc_agent.setAssetWeight(1) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, dual_staking_rate) + + short_lock = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 100) + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, short_lock) + long_lock = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 600) + delegate_btc_success(operators[1], accounts[0], BTC_VALUE, long_lock) + turn_round() - last_round = get_current_round() - 1 - with brownie.reverts("the msg sender must be bitcoin agent contract"): - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round, last_round) + turn_round(consensuses) + + core_amount = 1 + reward, float_reward = btc_stake.claimReward(accounts[0], core_amount, get_current_round() - 1, True, + {'from': btc_agent}).return_value + + base = TOTAL_REWARD + long_time_reward = base + long_time_float = long_time_reward - base + long_final = long_time_reward * dual_staking_rate // 10000 + long_dual_float = long_final - long_time_reward + + short_time_reward = base * 8000 // 10000 + short_time_float = short_time_reward - base + short_final = short_time_reward * dual_staking_rate // 10000 + short_dual_float = short_final - short_time_reward + + expected_reward = long_final + short_final + expected_float = (long_time_float + long_dual_float) + (short_time_float + short_dual_float) + assert reward == expected_reward + assert float_reward == expected_float + +@pytest.mark.parametrize("core_amount", [10000, 40000, 75000, 400000, 600000, 700000, 1000000]) +def test_dual_staking_multiple_btc_stake(btc_stake, set_candidate, btc_agent, btc_light_client, core_amount): + operators, consensuses = set_candidate + btc_light_client.setCheckResult(True, LOCK_TIME - 1000) -def test_view_collect_reward_invalid_txid(btc_stake, set_candidate, btc_agent): + btc_stake.setIsActive(True) + btc_stake.popTtlpRates() + btc_stake.setTlpRates(0, 10000) + + btc_agent.setAssetWeight(1) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 6000) + btc_agent.setLpRates(6000, 10000) + btc_agent.setLpRates(10000, 12000) + btc_agent.setLpRates(12000, 15000) + + lock1 = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 100) + txid1 = delegate_btc_success(operators[0], accounts[0], 10, lock1) + lock2 = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 200) + txid2 = delegate_btc_success(operators[1], accounts[0], 20, lock2) + lock3 = __get_stake_lock_script(PUBLIC_KEY, LOCK_TIME + Utils.ROUND_INTERVAL * 400) + txid3 = delegate_btc_success(operators[2], accounts[0], 30, lock3) turn_round() - tx_id = random_btc_tx_id() + turn_round(consensuses) + btc_amounts = [30, 20, 10] + tx = btc_stake.claimReward(accounts[0], core_amount, get_current_round() - 1, True, + {'from': btc_agent}) + reward, float_reward = tx.return_value + + base = TOTAL_REWARD + reward1 = base + reward2 = base + reward3 = base + stake_rate_list = [0, 3000, 6000, 10000, 12000] + rate_list = [1000, 6000, 10000, 12000, 15000] + rewards_rate = [] + remaining_core_amount = core_amount + asset_weight = 1 + + for btc_amount in btc_amounts: + stakeRateRaw = remaining_core_amount // btc_amount + adj_rate = stakeRateRaw // max(asset_weight, 1) + + selected_idx = 0 + for k in range(len(stake_rate_list) - 1, -1, -1): + if adj_rate >= stake_rate_list[k]: + selected_idx = k + break + stakeRateThreshold = stake_rate_list[selected_idx] * asset_weight + dualAmount = stakeRateThreshold * btc_amount + if remaining_core_amount > dualAmount: + remaining_core_amount -= dualAmount + else: + remaining_core_amount = 0 + + rewards_rate.append(rate_list[selected_idx]) + final1 = reward1 * rewards_rate[0] // 10000 + dual_float1 = final1 - reward1 + final2 = reward2 * rewards_rate[1] // 10000 + dual_float2 = final2 - reward2 + final3 = reward3 * rewards_rate[2] // 10000 + dual_float3 = final3 - reward3 + + expected_reward = final1 + final2 + final3 + expected_float = dual_float1 + dual_float2 + dual_float3 + assert reward == expected_reward + assert float_reward == expected_float + + +# calculateRewards +def test_calculateRewards_success(btc_stake, set_candidate, btc_agent, btc_light_client): + operators, consensuses = set_candidate + btc_agent.setAssetWeight(1e10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(7000, 10000) + btc_agent.setLpRates(10000, 15000) + delegate_btc_success(operators[0], accounts[0], 1e8, LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], 1e8, LOCK_SCRIPT) + delegate_btc_success(operators[2], accounts[0], 3e8, LOCK_SCRIPT) turn_round() - last_round = get_current_round() - 1 - update_system_contract_address(btc_stake, btc_agent=accounts[0]) - with brownie.reverts(): - reward, expired, _, acc_amount = btc_stake.viewCollectReward(tx_id, last_round, last_round) + turn_round(consensuses) + core_amount = 10000e18 + tx = btc_stake.calculateRewards(accounts[0], core_amount, get_current_round() - 1, {'from': btc_agent}) + reward, float_reward = tx.return_value + base = TOTAL_REWARD + reward1 = base + reward2 = base + reward3 = base + final1 = reward1 * 1000 // 10000 + dual_float1 = final1 - reward1 + final2 = reward2 * 15000 // 10000 + dual_float2 = final2 - reward2 + final3 = reward3 * 1000 // 10000 + dual_float3 = final3 - reward3 + + expected_reward = [final1, final2, final3] + expected_float = [dual_float1, dual_float2, dual_float3] + assert reward == expected_reward + assert float_reward == expected_float + + +def test_calculateRewards_some_zero_rewards(btc_stake, set_candidate, btc_agent, btc_light_client): + operators, consensuses = set_candidate + btc_agent.setAssetWeight(1e10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 0) + btc_agent.setLpRates(5000, 10000) + btc_agent.setLpRates(10000, 15000) + delegate_btc_success(operators[0], accounts[0], 1e8, LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], 1e8, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) + core_amount = 10000e18 + tx = btc_stake.calculateRewards(accounts[0], core_amount, get_current_round() - 1, {'from': btc_agent}) + reward, float_reward = tx.return_value + base = TOTAL_REWARD + reward1 = base + reward2 = base + final1 = reward1 * 15000 // 10000 + dual_float1 = final1 - reward1 + final2 = 0 + dual_float2 = final2 - reward2 + + expected_reward = [final2, final1] + expected_float = [dual_float2, dual_float1] + assert reward == expected_reward + assert float_reward == expected_float + + +# getCalculateRound +def test_get_calculate_round_mock_basic(btc_stake, set_candidate): + operators, _ = set_candidate + txid = Web3.to_bytes(hexstr='0x' + '11' * 32) + lock_time = LOCK_TIME + Utils.ROUND_INTERVAL * 3 + block_ts = LOCK_TIME + btc_amount = 1 * Utils.BTC_DECIMAL + btc_stake.mockDelegateBtc(txid, btc_amount, operators[0], accounts[0], lock_time, block_ts, 0, 0) + + unlock_round_minus_1 = lock_time // Utils.ROUND_INTERVAL - 1 + + settle_round_low = unlock_round_minus_1 - 1 + calc_round, expired = btc_stake.getCalculateRoundMock(txid, settle_round_low) + assert expired is False + assert calc_round == settle_round_low + + settle_round_high = unlock_round_minus_1 + 2 + calc_round, expired = btc_stake.getCalculateRoundMock(txid, settle_round_high) + assert expired is True + assert calc_round == unlock_round_minus_1 + settle_round_high = unlock_round_minus_1 + calc_round, expired = btc_stake.getCalculateRoundMock(txid, settle_round_high) + assert expired is True + assert calc_round == unlock_round_minus_1 + + +# applyDualStaking + +def test_apply_dual_staking_mock_basic(btc_stake, btc_agent): + btc_agent.setAssetWeight(10 ** 10) + btc_agent.setIsActive(True) + btc_agent.popLpRates() + btc_agent.setLpRates(0, 1000) + btc_agent.setLpRates(3000, 5000) + btc_agent.setLpRates(8000, 10000) + btc_agent.setLpRates(15000, 15000) + + bct_amount = 1 * Utils.BTC_DECIMAL + + core_amount_low = 2999e18 + remaining, ds_rate = btc_stake.applyDualStakingMock(core_amount_low, bct_amount) + assert ds_rate == 1000 + assert remaining == core_amount_low + + core_amount_eq = 3000e18 + remaining, ds_rate = btc_stake.applyDualStakingMock(core_amount_eq, bct_amount) + assert ds_rate == 5000 + assert remaining == 0 + + core_amount_eq = 18000e18 + remaining, ds_rate = btc_stake.applyDualStakingMock(core_amount_eq, bct_amount * 2) + assert ds_rate == 10000 + assert remaining == 2000e18 + + +# collectReward +@pytest.mark.parametrize("settleRound", [1, 2, 3, 4, 10]) +def test_collectReward_success(btc_stake, set_candidate, btc_agent, settleRound): + operators, consensuses = set_candidate + turn_round() + set_last_round_tag(3) + tx_id = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + current_round = 20099 + turn_round() + assert btc_stake.receiptMap(tx_id)['round'] == current_round + turn_round(consensuses, round_count=12) + reward, expired, _, _ = btc_stake.collectRewardMock(tx_id, 1e18, current_round, + current_round + settleRound, + True).return_value + actual_reward = TOTAL_REWARD * settleRound + actual_expired = False + if settleRound >= 3: + actual_reward = TOTAL_REWARD * 3 + actual_expired = True + assert reward == actual_reward + assert expired == actual_expired def test_only_btc_agent_can_call_set_new_round(btc_stake, btc_agent): @@ -1730,31 +1878,6 @@ def test_transfer_to_zero_address(btc_stake, set_candidate): transfer_btc_success(tx_id, ZERO_ADDRESS, accounts[0]) -def test_calculate_btc_reward_success(btc_stake, set_candidate): - operators, consensuses = set_candidate - tx_id0 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - tx_id1 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE + 1, LOCK_SCRIPT) - turn_round() - turn_round(consensuses) - reward = btc_stake.calculateRewardMock([tx_id0, tx_id1], get_current_round() - 1).return_value - assert reward == [TOTAL_REWARD // 2 * 2, 0, BTC_VALUE * 2 + 1] - # the reward is not mapped - reward = btc_stake.calculateRewardMock([tx_id0, tx_id1], get_current_round() - 1).return_value - assert reward == [0, 0, 0] - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - - -def test_calculate_btc_reward_with_invalid_txid(btc_stake, set_candidate): - operators, consensuses = set_candidate - tx_id0 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round() - turn_round(consensuses) - with brownie.reverts("invalid deposit receipt"): - btc_stake.calculateRewardMock([tx_id0, '0x00'], get_current_round() - 1) - - @pytest.mark.skip(reason="the data migration part has been removed, skip it.") def test_move_data_success(btc_stake, pledge_agent, set_candidate): operators, consensuses = set_candidate diff --git a/tests/test_candidate_hub.py b/tests/test_candidate_hub.py index 69f885b08..7fce456a1 100644 --- a/tests/test_candidate_hub.py +++ b/tests/test_candidate_hub.py @@ -554,6 +554,7 @@ def test_dues_cannot_greater_than_required_margin(candidate_hub, required_margin candidate_hub.updateParam("dues", value) +# updateParam-validatorCount @pytest.mark.parametrize("validator_count", [6, 25, 41]) def test_govern_validator_count_success(candidate_hub, required_margin, validator_count): value = padding_left(Web3.to_hex(validator_count), 64) @@ -570,6 +571,25 @@ def test_validator_count_out_of_range(candidate_hub, required_margin, validator_ candidate_hub.updateParam("validatorCount", value) +@pytest.mark.parametrize("validator_count", [19, 20, 21, 22]) +def test_update_validator_count_after_max_alternate_count(candidate_hub, validator_count): + old_validator_count = candidate_hub.validatorCount() + max_alternate = old_validator_count // 3 + value = padding_left(Web3.to_hex(max_alternate), 64) + update_system_contract_address(candidate_hub, gov_hub=accounts[0]) + candidate_hub.updateParam("maxAlternateCount", value) + assert candidate_hub.maxAlternateCount() == max_alternate + assert old_validator_count == 21 + assert max_alternate == 7 + value2 = padding_left(Web3.to_hex(validator_count), 64) + if validator_count < 21: + with brownie.reverts(f"OutOfBounds: maxAlternateCount, {max_alternate}, 0, {validator_count // 3}"): + candidate_hub.updateParam("validatorCount", value2) + else: + candidate_hub.updateParam("validatorCount", value2) + assert candidate_hub.validatorCount() == validator_count + + @pytest.mark.parametrize("maxCommissionChange", [1, 500, 1000]) def test_govern_max_commission_change_success(candidate_hub, required_margin, maxCommissionChange): value = padding_left(Web3.to_hex(maxCommissionChange), 64) diff --git a/tests/test_channel.py b/tests/test_channel.py index a0e0fc2bb..b5be73fd9 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -17,6 +17,7 @@ TOTAL_REWARD = 0 BTC_VALUE = 2000 LOCK_SCRIPT = '0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac' +LOCK_TIMESTAMP = 1736956800 @pytest.fixture(scope="module", autouse=True) @@ -1433,7 +1434,8 @@ def test_pay_commission_by_id_through_btc_stake_claim_reward( delegator = accounts[0] partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner - btc_tx_id = delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner_id) + btc_tx_id = delegate_btc_success( + candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner_id) tx_map = btc_stake.btcTxMap(btc_tx_id) assert tx_map[2] == partner_id turn_round() @@ -1457,7 +1459,7 @@ def test_pay_commission_by_id_btc_stake_invalid_channel(channel, btc_stake, set_ candidate = operators[0] delegator = accounts[0] - btc_tx_id = delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=1736956800, channel_id=channel_id) + btc_tx_id = delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=channel_id) tx_map = btc_stake.btcTxMap(btc_tx_id) assert tx_map[2] == channel_id turn_round() @@ -1480,8 +1482,8 @@ def test_pay_commission_by_id_btc_stake_multiple_txs( delegator = accounts[0] partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner - delegate_btc_success(candidate1, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner_id) - delegate_btc_success(candidate2, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner_id) + delegate_btc_success(candidate1, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner_id) + delegate_btc_success(candidate2, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner_id) turn_round() turn_round(consensuses) @@ -1523,8 +1525,8 @@ def test_pay_commission_by_id_btc_stake_mixed_channels( ) partner2_id = channel.partnerIdMap(partner2) - delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner1_id) - delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, channel_id=partner2_id) + delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner1_id) + delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner2_id) turn_round() turn_round(consensuses) @@ -1899,7 +1901,7 @@ def test_delegate_coin_and_btc_mixed( {'value': channel_delegate_amount, 'from': delegator} ) delegate_btc_success(operators[2], delegator, btc_value // 2, LOCK_SCRIPT) - delegate_btc_success(operators[2], delegator, btc_value // 2, LOCK_SCRIPT, channel_id=partner_id) + delegate_btc_success(operators[2], delegator, btc_value // 2, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner_id) delegate_power_success(operators[1], delegator, power_value) turn_round(consensuses, round_count=2) diff --git a/tests/test_claim_reward.py b/tests/test_claim_reward.py index 0ee0a7982..62326c3f0 100644 --- a/tests/test_claim_reward.py +++ b/tests/test_claim_reward.py @@ -556,7 +556,7 @@ def test_bep127_major_offense_alternate_replace_and_repeat(slash_indicator): assert trackers[4].delta() == 0 -def test_bep127_major_offense_alternate_replace_until_no_alternate_then_claim_reward(): +def test_bep127_major_offense_alternate_replace_until_no_alternate_then_claim_reward(validator_set): set_validator_count() tx_fee = 10000 delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 @@ -567,12 +567,18 @@ def test_bep127_major_offense_alternate_replace_until_no_alternate_then_claim_re delegate_coin_success(operators[7], accounts[4], 1000) delegate_coin_success(operators[7], accounts[5], 1000) turn_round() + expect_validator_consensus(consensuses[:6]) assert chain_get_validator_consensus() == consensuses[:6] slash_validator(consensuses[0]) chain_deposit(consensuses[6], deposit_count=1, tx_fee=tx_fee) slash_validator(consensuses[6]) chain_deposit(consensuses[7], deposit_count=2, tx_fee=tx_fee) + total_reward = 30000 + tx_fee + assert validator_set.getIncoming(consensuses[1]) == total_reward // 6 slash_validator(consensuses[7]) + assert validator_set.getIncoming(consensuses[1]) == total_reward // 6 + (total_reward * 2 + total_reward // 6) // 5 + assert validator_set.getIncoming(consensuses[6]) == 0 + assert validator_set.getIncoming(consensuses[7]) == 0 assert len(chain_get_validator_consensus()) == 5 block_validators = chain_get_validator_consensus() expect_validator_consensus([consensuses[1], consensuses[2], consensuses[3], consensuses[4], consensuses[5]]) @@ -586,6 +592,34 @@ def test_bep127_major_offense_alternate_replace_until_no_alternate_then_claim_re turn_round(chain_get_validator_consensus()) +def test_bep127_major_offense_no_alternate_block_after_exit_maintenance(validator_set, slash_indicator): + set_validator_count() + validator_set.setMaintainSlashPercent(30) + tx_fee = 10000 + operators, consensuses = add_candidates(8) + delegate_validator(operators) + delegate_coin_success(operators[6], accounts[2], 1000) + delegate_coin_success(operators[6], accounts[3], 1000) + delegate_coin_success(operators[7], accounts[4], 1000) + delegate_coin_success(operators[7], accounts[5], 1000) + turn_round() + assert chain_get_validator_consensus() == consensuses[:6] + chain_deposit(consensuses[0], deposit_count=1, tx_fee=tx_fee) + enter_maintenance(operators[0]) + slash_validator(consensuses[5]) + slash_validator(consensuses[6]) + slash_validator(consensuses[7]) + chain.mine(300) + exit_maintenance(operators[0]) + assert validator_set.getIncoming(consensuses[5]) == 0 + assert validator_set.getIncoming(consensuses[6]) == 0 + assert validator_set.getIncoming(consensuses[7]) == 0 + assert validator_set.getIncoming(consensuses[1]) == 40000 // 4 + assert len(chain_get_validator_consensus()) == 4 + expect_validator_consensus([consensuses[1], consensuses[2], consensuses[3], consensuses[4]]) + turn_round(chain_get_validator_consensus(), round_count=3) + + @pytest.mark.parametrize("slash_type", ["minor", "felony"]) def test_bep127_minor_offense_enter_maintenance_alternate_replace(slash_type, validator_set): stake_manager.set_lp_rates() @@ -1493,22 +1527,22 @@ def test_edit_description_and_claim_reward(candidate_hub): delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) turn_round() expect_validator_consensus(consensuses[:5]) - tx = turn_round(chain_get_validator_consensus()) + turn_round(chain_get_validator_consensus()) stake_hub_claim_reward(accounts[50]) moniker = "Test Validator" identity = "test-identity" website = "https://test.com" details = "Test validator details" - tx = candidate_hub.editDescription(moniker, identity, website, details, {'from': operators[0]}) + candidate_hub.editDescription(moniker, identity, website, details, {'from': operators[0]}) turn_round(chain_get_validator_consensus()) tracker = get_tracker(accounts[50]) stake_hub_claim_reward(accounts[50]) assert tracker.delta() == TOTAL_REWARD // 2 - 1 turn_round(chain_get_validator_consensus()) - tx = candidate_hub.editDescription('test1', identity, website, details, {'from': operators[0]}) + candidate_hub.editDescription('test1', identity, website, details, {'from': operators[0]}) turn_round(chain_get_validator_consensus()) stake_hub_claim_reward(accounts[50]) - assert tracker.delta() == (TOTAL_REWARD // 2 - 1) * 2 + assert tracker.delta() == (TOTAL_REWARD // 2) * 2 - 1 turn_round(chain_get_validator_consensus()) @@ -1673,47 +1707,3 @@ def test_update_consensus_address_block_produce_on_old_and_new(validator_set, ca turn_round(chain_get_validator_consensus()) stake_hub_claim_reward(accounts[50]) assert tracker.delta() == TOTAL_REWARD - 1 - - -def test_validator_and_alternate_count_exceed_candidates(validator_set, candidate_hub, slash_indicator): - validator_set.setMaintainSlashPercent(0) - set_validator_count(validator_count=31, alternate_count=0) - validator_set = ValidatorSetMock[0] - stake_manager.set_lp_rates() - stake_manager.set_tlp_rates() - btc_amount = 1000 - delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 - operators, consensuses = add_candidates(15) - for i in range(0, 15): - delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) - delegate_validator(operators, i, accounts[1 + i], delegate_amount - i, btc_amount) - tx_id0 = delegate_btc_success(operators[1], accounts[91], btc_amount, LOCK_SCRIPT, relay=accounts[91]) - tx_id1 = delegate_btc_success(operators[3], accounts[92], btc_amount, LOCK_SCRIPT, relay=accounts[92]) - candidate_hub.unregister({'from': operators[-1]}) - candidate_hub.unregister({'from': operators[-2]}) - candidate_hub.unregister({'from': operators[-3]}) - turn_round() - set_validator_count(validator_count=31, alternate_count=3) - validator_set.setValidatorCount(0) - validator_set.setMaintainSlashPercent(10) - validators = validator_set.getValidators() - assert validator_set.validatorCount() == 0 - transfer_coin_success(operators[0], operators[2], accounts[50], delegate_amount) - transfer_coin_success(operators[2], operators[0], accounts[52], delegate_amount - 2) - transfer_btc_success(tx_id0, operators[2], accounts[91]) - delegate_btc_success(operators[4], accounts[93], btc_amount, LOCK_SCRIPT, relay=accounts[93]) - delegate_coin_success(operators[0], accounts[50], delegate_amount) - turn_round(chain_get_validator_consensus()) - value = padding_left(Web3.to_hex(10), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam("maxAlternateCount", value) - validatorCount = padding_left(Web3.to_hex(7), 64) - candidate_hub.updateParam("validatorCount", validatorCount) - alternate_count = candidate_hub.mockGetAlternateCount(10, 7, 12) - assert alternate_count == 5 - expect_validator_consensus(consensuses[:12]) - turn_round(chain_get_validator_consensus()) - trackers = get_trackers(accounts[50:65]) - stake_hub_claim_reward(accounts[50:65]) - assert trackers[0].delta() > 0 - turn_round(chain_get_validator_consensus()) diff --git a/tests/test_core_agent.py b/tests/test_core_agent.py index 845734cc2..bbac455a1 100644 --- a/tests/test_core_agent.py +++ b/tests/test_core_agent.py @@ -366,7 +366,7 @@ def test_redelegate_calculates_reward(core_agent, set_candidate, stake_hub): turn_round() turn_round(consensuses) tx = core_agent.delegateCoin(operators[0], {'value': MIN_INIT_DELEGATE_VALUE}) - assert core_agent.delegatorMap(accounts[0]) == MIN_INIT_DELEGATE_VALUE * 2 + assert core_agent.delegatorMap(accounts[0]) == [MIN_INIT_DELEGATE_VALUE * 2, 0] expect_event(tx, "delegatedCoin", { 'candidate': operators[0], 'delegator': accounts[0], @@ -395,6 +395,29 @@ def test_delegate_transfer_reward_calculation(core_agent, stake_hub, set_candida assert stake_hub_reward_map[1] == [TOTAL_REWARD, 0, 0] +# undelegateCoin +def test_cannot_undelegate_directly_if_staked_via_channel(core_agent, set_candidate, channel): + operators, consensuses = set_candidate + turn_round() + update_system_contract_address(core_agent, channel=accounts[0]) + core_agent.proxyDelegate(operators[0], accounts[0], 1, {"value": MIN_INIT_DELEGATE_VALUE}) + turn_round() + turn_round(consensuses) + update_system_contract_address(core_agent, channel=channel) + with brownie.reverts("InsufficientTokens: 0"): + core_agent.undelegateCoin(operators[0], MIN_INIT_DELEGATE_VALUE * 2) + with brownie.reverts("InsufficientTokens: 0"): + core_agent.undelegateCoin(operators[0], MIN_INIT_DELEGATE_VALUE) + with brownie.reverts("Undelegate zero coin"): + core_agent.undelegateCoin(operators[0], 0) + update_system_contract_address(core_agent, channel=channel) + delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + assert core_agent.candidateMap(operators[0])['realtimeAmount'] == MIN_INIT_DELEGATE_VALUE + core_agent.undelegateCoin(operators[0], MIN_INIT_DELEGATE_VALUE) + assert core_agent.candidateMap(operators[0])['realtimeAmount'] == 0 + assert core_agent.delegatorMap(accounts[0]) == [MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] + + def test_undelegate_amount_small(core_agent, validator_set): undelegate_value = MIN_INIT_DELEGATE_VALUE - 1 operator = accounts[1] @@ -440,8 +463,7 @@ def test_undelegate_coin_failed_with_no_delegate(core_agent): agent = accounts[1] delegator = accounts[2] __candidate_register(agent) - - with brownie.reverts("no delegator information found"): + with brownie.reverts("InsufficientTokens: 0"): core_agent.undelegateCoin(agent, required_coin_deposit, {'from': delegator}) @@ -710,9 +732,9 @@ def test_cancel_all_after_transfer_existing_stake(core_agent, set_candidate): transfer_coin_success(operators[0], operators[1], accounts[0], delegate_value) core_agent.undelegateCoin(operators[1], undelegate_value * 2) candidate_list = core_agent.getCandidateListByDelegator(accounts[0]) - assert len(candidate_list) == 2 + assert len(candidate_list) == 1 __check_coin_delegator_map(operators[0], accounts[0], { - 'changeRound': get_current_round() + 'changeRound': 0 }) __check_coin_delegator_map(operators[1], accounts[0], { 'changeRound': 0 @@ -768,10 +790,9 @@ def test_remove_validator_after_full_transfer_deduction(core_agent, deduct_equal }) candidate_list = core_agent.getCandidateListByDelegator(accounts[0]) assert [operators[4], operators[3], operators[2]] == candidate_list - actual_candidate_list = [operators[4], operators[3], operators[2]] + actual_candidate_list = [operators[4], operators[2]] if deduct_equal is False: undelegate_value += 1 - actual_candidate_list = [operators[4], operators[2]] core_agent.deductTransferredAmountMock(accounts[0], undelegate_value) candidate_list = core_agent.getCandidateListByDelegator(accounts[0]) assert actual_candidate_list == candidate_list @@ -1074,9 +1095,10 @@ def test_transfer_after_staking_new_validator(core_agent): assert tracker.delta() == TOTAL_REWARD // 2 +# claimReward def test_only_stake_hub_can_call_claim_reward(core_agent): with brownie.reverts("the msg sender must be stake hub contract"): - core_agent.claimReward(accounts[0], 0, get_current_round() - 1, False) + core_agent.claimReward(accounts[0], True) def test_core_claim_reward_success(core_agent, set_candidate): @@ -1085,16 +1107,16 @@ def test_core_claim_reward_success(core_agent, set_candidate): turn_round() turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - tx = core_agent.claimReward(accounts[0], 0, get_current_round() - 1, True) + tx = core_agent.claimReward(accounts[0], True) expect_event(tx, 'claimedCoinReward', { 'delegator': accounts[0], 'amount': TOTAL_REWARD, - 'accStakedAmount': MIN_INIT_DELEGATE_VALUE, + 'accStakedAmount': 0, }) - reward, reward_unclaimed, acc_staked_amount = tx.return_value + reward, stake_amount1, stake_amount2 = tx.return_value assert reward == TOTAL_REWARD - assert reward_unclaimed == 0 - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE + assert stake_amount1 == 0 + assert stake_amount2 == MIN_INIT_DELEGATE_VALUE assert core_agent.rewardMap(accounts[0]) == [0, 0] @@ -1106,26 +1128,21 @@ def test_claim_reward_success_with_existing_historical_rewards(core_agent, set_c tx = core_agent.delegateCoin(operators[0], {'value': MIN_INIT_DELEGATE_VALUE}) expect_event(tx, 'storedCoinReward', { 'delegator': accounts[0], - 'amount': 0, + 'amount': TOTAL_REWARD, 'accStakedAmount': 0, }, idx=0) - expect_event(tx, 'storedCoinReward', { - 'delegator': accounts[0], - 'amount': TOTAL_REWARD, - 'accStakedAmount': MIN_INIT_DELEGATE_VALUE, - }, idx=1) assert stake_hub.getDelegatorMap(accounts[0])[1][0] == TOTAL_REWARD turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - tx = core_agent.claimReward(accounts[0], 0, get_current_round() - 1, True) + tx = core_agent.claimReward(accounts[0], True) expect_event(tx, 'claimedCoinReward', { 'delegator': accounts[0], 'amount': TOTAL_REWARD, - 'accStakedAmount': MIN_INIT_DELEGATE_VALUE, + 'accStakedAmount': 0, }) - reward, reward_unclaimed, acc_staked_amount = tx.return_value + reward, stake_amount1, stake_amount2 = tx.return_value assert reward == TOTAL_REWARD - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE + assert stake_amount2 == MIN_INIT_DELEGATE_VALUE assert core_agent.rewardMap(accounts[0]) == [0, 0] assert stake_hub.getDelegatorMap(accounts[0]) == [get_current_round() - 1, [TOTAL_REWARD, 0, 0]] update_system_contract_address(core_agent, stake_hub=stake_hub) @@ -1140,42 +1157,10 @@ def test_multi_validator_stake(core_agent, set_candidate): turn_round() turn_round(consensuses, round_count=2) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, True).return_value + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value assert reward == TOTAL_REWARD * 4 - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE * 6 - - -@pytest.mark.parametrize("operate", ['delegate', 'undelegate', 'transfer', 'claim']) -def test_validate_acc_stake_amount(core_agent, set_candidate, operate, stake_hub): - operators, consensuses = set_candidate - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - acc_stake_amount0 = MIN_INIT_DELEGATE_VALUE * 3 - turn_round() - if operate == 'delegate': - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - elif operate == 'undelegate': - undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - elif operate == 'transfer': - transfer_coin_success(operators[0], operators[2], accounts[0], MIN_INIT_DELEGATE_VALUE) - else: - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value - if operate == 'undelegate': - acc_stake_amount0 -= MIN_INIT_DELEGATE_VALUE - assert acc_staked_amount == acc_stake_amount0 - if operate == 'delegate': - acc_stake_amount0 += MIN_INIT_DELEGATE_VALUE - update_system_contract_address(core_agent, stake_hub=stake_hub) - turn_round(consensuses) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value - assert acc_staked_amount == acc_stake_amount0 + assert stake_amount1 == 0 + assert stake_amount2 == MIN_INIT_DELEGATE_VALUE * 3 @pytest.mark.parametrize('round_count', [0, 1]) @@ -1203,25 +1188,19 @@ def test_calc_acc_stake_after_coin_stake(core_agent, set_candidate, stake_hub, r else: stake_hub_claim_reward(accounts[0]) turn_round(consensuses, round_count=round_count) - acc_stake_amount0 *= round_count update_system_contract_address(core_agent, stake_hub=accounts[0]) - tx = core_agent.claimReward(accounts[0], 0, get_current_round() - 1, True) - expect_event(tx, 'claimedCoinReward', { - 'accStakedAmount': acc_stake_amount0 - }) - reward, reward_unclaimed, acc_staked_amount = tx.return_value + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value expect_stake_amount = 0 if round_count > 0: expect_stake_amount = tests[0] - assert acc_staked_amount == acc_stake_amount0 - assert acc_staked_amount == expect_stake_amount + assert stake_amount2 == expect_stake_amount @pytest.mark.parametrize("tests", [ - [800, 'delegate', 'delegate', 'transfer', 'claim'], - [500, 'delegate', 'undelegate', 'transfer', 'claim'], - [400, 'undelegate', 'undelegate', 'delegate', 'delegate'], - [700, 'transfer', 'transfer', 'delegate'] + [500, 'delegate', 'delegate', 'transfer', 'claim'], + [300, 'delegate', 'undelegate', 'transfer', 'claim'], + [400, 'undelegate', 'undelegate', 'delegate', 'delegate', 'delegate'], + [400, 'transfer', 'transfer', 'delegate'] ]) def test_acc_stake_amount_cross_round_success(core_agent, set_candidate, stake_hub, tests): operators, consensuses = set_candidate @@ -1241,45 +1220,24 @@ def test_acc_stake_amount_cross_round_success(core_agent, set_candidate, stake_h stake_hub_claim_reward(accounts[0]) turn_round(consensuses, round_count=2) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value + reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], False).return_value expect_stake_amount = tests[0] assert acc_staked_amount == expect_stake_amount -def test_clear_acc_stake_amount_after_claiming_rewards(core_agent, slash_indicator, stake_hub, set_candidate): - operators, consensuses = set_candidate - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - turn_round() - felony_threshold = slash_indicator.felonyThreshold() - for _ in range(felony_threshold): - slash_indicator.slash(consensuses[0]) - turn_round(consensuses, round_count=2) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value - assert reward == 0 - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE * 2 - update_system_contract_address(core_agent, stake_hub=stake_hub) - turn_round(consensuses, round_count=2) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE * 2 - - -@pytest.mark.parametrize("round_count", [0, 1, 2]) +@pytest.mark.parametrize("round_count", [1, 2]) @pytest.mark.parametrize("tests", [ - {'transfer': 500, 'undelagate': 0, 'amount': 500, 'expect_acc_stake_amount': 2500}, - {'transfer': 500, 'undelagate': 1, 'amount': 1000, 'expect_acc_stake_amount': 2000}, - {'transfer': 500, 'undelagate': 2, 'amount': 1500, 'expect_acc_stake_amount': 1500}, + {'transfer': 500, 'undelagate': 0, 'amount': 500, 'stake_amount0': 2500, 'stake_amount1': 2500}, + {'transfer': 500, 'undelagate': 1, 'amount': 1000, 'stake_amount0': 2000, 'stake_amount1': 2000}, + {'transfer': 500, 'undelagate': 2, 'amount': 1500, 'stake_amount0': 1500, 'stake_amount1': 1500}, ]) -def test_check_acc_amount_after_cancel_stake_current_round(core_agent, validator_set, set_candidate, round_count, - tests): +def test_check_acc_amount_after_cancel_stake_current_round(core_agent, validator_set, set_candidate, tests, + round_count): delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 undelegate_amount = tests['amount'] transfer_amount = tests['transfer'] - expect_acc_stake_amount = tests['expect_acc_stake_amount'] + expect_stake_amount0 = tests['stake_amount0'] + expect_stake_amount1 = tests['stake_amount1'] agent_index = tests['undelagate'] operators, consensuses = set_candidate for op in operators: @@ -1291,9 +1249,9 @@ def test_check_acc_amount_after_cancel_stake_current_round(core_agent, validator undelegate_coin_success(operators[agent_index], accounts[0], undelegate_amount) turn_round(consensuses, round_count=round_count) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value - assert acc_staked_amount == expect_acc_stake_amount * round_count + reward, stake_amount0, stake_amount1 = core_agent.claimReward(accounts[0], False).return_value + assert stake_amount0 == expect_stake_amount0 + assert stake_amount1 == expect_stake_amount1 @pytest.mark.parametrize("tests", [ @@ -1324,49 +1282,10 @@ def test_check_rewards_after_cancel_transfer_current_round(core_agent, validator undelegate_coin_success(operators[agent_index], accounts[0], undelegate_amount) turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, reward_unclaimed, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value + reward, stake_amount0, stake_amount1 = core_agent.claimReward(accounts[0], False).return_value assert reward == expect_reward - assert acc_staked_amount == tests['expect_stake_amount'] - - -@pytest.mark.parametrize("claim", [True, False]) -def test_view_rewards_on_validator(core_agent, validator_set, set_candidate, claim): - operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 - delegate_coin_success(operators[0], accounts[0], delegate_amount) - delegate_coin_success(operators[1], accounts[0], delegate_amount) - turn_round(consensuses, round_count=2) - if claim: - tx = stake_hub_claim_reward(accounts[0]) - event_name = 'collectedReward' - else: - tx = transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2) - event_name = 'storedReward' - expect_event(tx, event_name, { - 'candidate': operators[1], - 'delegator': accounts[0], - 'reward': 0, - 'accStakedAmount': 0, - }, idx=0) - expect_event(tx, event_name, { - 'candidate': operators[0], - 'delegator': accounts[0], - 'reward': 0, - 'accStakedAmount': 0, - }, idx=1) - expect_event(tx, event_name, { - 'candidate': operators[1], - 'delegator': accounts[0], - 'reward': TOTAL_REWARD, - 'accStakedAmount': delegate_amount, - }, idx=2) - expect_event(tx, event_name, { - 'candidate': operators[0], - 'delegator': accounts[0], - 'reward': TOTAL_REWARD, - 'accStakedAmount': delegate_amount, - }, idx=3) + assert stake_amount0 == tests['expect_stake_amount'] + assert stake_amount1 == tests['expect_stake_amount'] @pytest.mark.parametrize("claim", [True, False]) @@ -1377,7 +1296,6 @@ def test_view_validator_rewards_after_unstaking(core_agent, validator_set, set_c turn_round(consensuses) undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2) turn_round(consensuses, round_count=3) - if claim: tx = stake_hub_claim_reward(accounts[0]) event_name = 'collectedReward' @@ -1387,15 +1305,9 @@ def test_view_validator_rewards_after_unstaking(core_agent, validator_set, set_c expect_event(tx, event_name, { 'candidate': operators[0], 'delegator': accounts[0], - 'reward': TOTAL_REWARD // 2, - 'accStakedAmount': delegate_amount // 2, - }, idx=0) - expect_event(tx, event_name, { - 'candidate': operators[0], - 'delegator': accounts[0], - 'reward': TOTAL_REWARD * 2, - 'accStakedAmount': delegate_amount, - }, idx=1) + 'reward': TOTAL_REWARD // 2 + TOTAL_REWARD * 2, + 'accStakedAmount': 0, + }) def test_historical_rewards_exist(core_agent, set_candidate, btc_agent): @@ -1403,130 +1315,256 @@ def test_historical_rewards_exist(core_agent, set_candidate, btc_agent): for index, o in enumerate(operators): delegate_coin_success(o, accounts[0], MIN_INIT_DELEGATE_VALUE) turn_round() - turn_round(consensuses) add_reward = 1000 acc_amount = 3000 core_agent.setCoreRewardMap(accounts[0], add_reward, acc_amount) + turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, _, acc_staked_amount = core_agent.claimReward(accounts[0], 0, get_current_round() - 1, - True).return_value + reward, _, acc_staked_amount = core_agent.claimReward(accounts[0], True).return_value assert reward == TOTAL_REWARD * 3 + add_reward - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE * 3 + acc_amount + assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE * 3 -@pytest.mark.parametrize("specified_round", [1, 2, 3, 4, 10]) -def test_calculate_for_specified_round(core_agent, set_candidate, btc_agent, specified_round): +def test_calculate_for_specified_round(core_agent, set_candidate, btc_agent): operators, consensuses = set_candidate for index, o in enumerate(operators): delegate_coin_success(o, accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_round = get_current_round() turn_round() turn_round(consensuses, round_count=10) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, _, acc_staked_amount = core_agent.claimReward(accounts[0], 0, - delegate_round + specified_round, - True).return_value - assert reward == TOTAL_REWARD * 3 * specified_round - assert acc_staked_amount == MIN_INIT_DELEGATE_VALUE * 3 * specified_round + reward, stake_amount0, stake_amount1 = core_agent.claimReward(accounts[0], True).return_value + assert reward == TOTAL_REWARD * 3 * 10 + assert stake_amount1 == MIN_INIT_DELEGATE_VALUE * 3 + + +def test_claim_reward_no_candidates(core_agent): + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + assert reward == 0 + assert stake_amount1 == 0 + assert stake_amount2 == 0 -def test_calculate_round_greater_than_current_revert(core_agent, set_candidate, btc_agent): +def test_claim_reward_zero_reward_from_candidate(core_agent, set_candidate): operators, consensuses = set_candidate - for index, o in enumerate(operators): - delegate_coin_success(o, accounts[0], MIN_INIT_DELEGATE_VALUE) + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) turn_round() - turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - with brownie.reverts("invalid settle round"): - core_agent.claimReward(accounts[0], 0, get_current_round() + 1, True) - with brownie.reverts("invalid settle round"): - core_agent.claimReward(accounts[0], 0, get_current_round(), True) + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + assert reward == 0 + assert stake_amount1 == 0 + assert stake_amount2 == 0 -def test_calculate_round_less_than_stake_round(core_agent, set_candidate, btc_agent): +def test_claim_reward_remove_delegation(core_agent, set_candidate): operators, consensuses = set_candidate - for index, o in enumerate(operators): - delegate_coin_success(o, accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_round = get_current_round() + delegate_amount = MIN_INIT_DELEGATE_VALUE + + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - turn_round(consensuses, round_count=3) + turn_round(consensuses) + core_agent.setCoinDelegatorMap(operators[0], accounts[0], 0, 0) + candidates_before = core_agent.getCandidateListByDelegator(accounts[0]) + assert len(candidates_before) == 1 + update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, _, acc_amount = core_agent.claimReward(accounts[0], 0, delegate_round - 1, True).return_value - assert reward == acc_amount == 0 + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + + assert reward == 0 + + candidates_after = core_agent.getCandidateListByDelegator(accounts[0]) + assert len(candidates_after) == 0 -@pytest.mark.parametrize("settle_round", [1, 2, 3]) -def test_view_collect_reward_success(core_agent, set_candidate, settle_round): +@pytest.mark.parametrize("round_count", [1, 2]) +@pytest.mark.parametrize("operator", ['un', 'de', 'tr']) +def test_claim_reward_s1_s2_difference_change_round_less_than_last(core_agent, set_candidate, stake_hub, operator, + round_count): operators, consensuses = set_candidate + initial_amount = MIN_INIT_DELEGATE_VALUE * 2 + additional_amount = MIN_INIT_DELEGATE_VALUE + + delegate_coin_success(operators[0], accounts[0], initial_amount) turn_round() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - change_round = get_current_round() - turn_round() - turn_round(consensuses, round_count=4) + turn_round(consensuses) + if operator == 'de': + delegate_coin_success(operators[0], accounts[0], additional_amount) + elif operator == 'tr': + transfer_coin_success(operators[0], operators[1], accounts[0], + additional_amount) + else: + undelegate_coin_success(operators[0], accounts[0], additional_amount) + turn_round(consensuses, round_count=round_count) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, acc_amount = core_agent.viewCollectRewardFromCandidate(operators[0], accounts[0], - change_round + settle_round).return_value - assert reward == TOTAL_REWARD * settle_round // 2 - assert acc_amount == MIN_INIT_DELEGATE_VALUE * settle_round - assert core_agent.getDelegator(operators[0], accounts[0])['changeRound'] == change_round + settle_round + 1 + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + if operator == 'de': + assert stake_amount1 == initial_amount + if round_count == 2: + assert stake_amount2 == initial_amount + additional_amount + else: + assert stake_amount2 == initial_amount + elif operator == 'tr': + assert stake_amount1 == initial_amount + assert stake_amount2 == initial_amount + else: + assert stake_amount1 == initial_amount - additional_amount + assert stake_amount2 == initial_amount - additional_amount + if operator == 'de': + if round_count == 2: + assert stake_amount1 < stake_amount2 + else: + assert stake_amount1 == stake_amount2 + else: + assert stake_amount1 == stake_amount2 + update_system_contract_address(core_agent, stake_hub=stake_hub) + turn_round(consensuses) -def test_start_round_greater_equal_calculate_round(core_agent, set_candidate): +def test_claim_reward_s1_s2_success(core_agent, set_candidate, stake_hub): operators, consensuses = set_candidate + initial_amount = MIN_INIT_DELEGATE_VALUE * 3 + additional_amount = MIN_INIT_DELEGATE_VALUE + + delegate_coin_success(operators[0], accounts[0], initial_amount) + delegate_coin_success(operators[0], accounts[1], initial_amount) turn_round() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - change_round = get_current_round() - assert __get_delegator_info(operators[0], accounts[0])['changeRound'] == change_round + delegate_coin_success(operators[0], accounts[0], additional_amount) + turn_round(consensuses, round_count=2) + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + + assert stake_amount1 == initial_amount + assert stake_amount2 == initial_amount + additional_amount + assert reward == TOTAL_REWARD // 2 + TOTAL_REWARD * 400 // 700 + update_system_contract_address(core_agent, stake_hub=stake_hub) + turn_round(consensuses) + + +def test_claim_reward_s1_s2_equal_change_round_equals_last(core_agent, set_candidate): + operators, consensuses = set_candidate + delegate_amount = MIN_INIT_DELEGATE_VALUE * 2 + + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - turn_round(consensuses, round_count=4) + turn_round(consensuses) + update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, acc_amount = core_agent.viewCollectRewardFromCandidate(operators[0], accounts[0], - change_round - 1).return_value + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + assert stake_amount1 == 0 + assert stake_amount2 == delegate_amount + assert reward == TOTAL_REWARD + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value assert reward == 0 - assert acc_amount == 0 + assert stake_amount1 == 0 + assert stake_amount2 == 0 +def test_claim_reward_only_historical_rewards(core_agent, set_candidate): + operators, consensuses = set_candidate + historical_reward = 5000 + acc_staked_amount = 2000 -@pytest.mark.parametrize("settle_round", [0, 1]) -def test_calculate_round_greater_equal_current_round(core_agent, set_candidate, settle_round): + core_agent.setCoreRewardMap(accounts[0], historical_reward, acc_staked_amount) + + reward_info = core_agent.rewardMap(accounts[0]) + assert reward_info[0] == historical_reward + assert reward_info[1] == acc_staked_amount + + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + + assert reward == historical_reward + assert stake_amount1 == 0 + assert stake_amount2 == 0 + + reward_info_after = core_agent.rewardMap(accounts[0]) + assert reward_info_after == [0, 0] + + +def test_claim_reward_total_zero_reward(core_agent, set_candidate): operators, consensuses = set_candidate - turn_round() + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) turn_round() - turn_round(consensuses, round_count=4) + update_system_contract_address(core_agent, stake_hub=accounts[0]) - with brownie.reverts("invalid settle round"): - core_agent.viewCollectRewardFromCandidate(operators[0], accounts[0], get_current_round() + settle_round) + tx = core_agent.claimReward(accounts[0], True) + reward, stake_amount1, stake_amount2 = tx.return_value + + assert reward == 0 + assert stake_amount1 == 0 + assert stake_amount2 == 0 + assert 'claimedCoinReward' not in [event.name for event in tx.events] -def test_start_round_equal_calculate_round(core_agent, set_candidate): + +def test_claim_reward_complex_scenario(core_agent, set_candidate, stake_hub): operators, consensuses = set_candidate + + + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE * 2) + delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + delegate_coin_success(operators[1], accounts[1], MIN_INIT_DELEGATE_VALUE * 2) turn_round() delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - turn_round() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - change_round = get_current_round() - assert __get_delegator_info(operators[0], accounts[0])['changeRound'] == change_round turn_round(consensuses, round_count=2) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward, acc_amount = core_agent.viewCollectRewardFromCandidate(operators[0], accounts[0], change_round).return_value - assert reward == TOTAL_REWARD // 2 - assert acc_amount == MIN_INIT_DELEGATE_VALUE - reward, acc_amount = core_agent.viewCollectRewardFromCandidate(operators[0], accounts[0], - get_current_round() - 1).return_value - assert reward == TOTAL_REWARD * (MIN_INIT_DELEGATE_VALUE * 2) // (MIN_INIT_DELEGATE_VALUE * 3) - assert acc_amount == MIN_INIT_DELEGATE_VALUE * 2 + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + actual_reward = TOTAL_REWARD // 2 * 2 + TOTAL_REWARD // 2 + TOTAL_REWARD * 3 // 5 + assert reward == actual_reward + assert stake_amount1 == MIN_INIT_DELEGATE_VALUE * 4 + assert stake_amount2 == MIN_INIT_DELEGATE_VALUE * 5 + update_system_contract_address(core_agent, stake_hub=stake_hub) + transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + turn_round(consensuses) + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + assert reward == TOTAL_REWARD * 3 // 5 + TOTAL_REWARD // 2 + assert stake_amount1 == MIN_INIT_DELEGATE_VALUE * 5 + assert stake_amount2 == MIN_INIT_DELEGATE_VALUE * 5 + update_system_contract_address(core_agent, stake_hub=stake_hub) + undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + turn_round(consensuses) + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward, stake_amount1, stake_amount2 = core_agent.claimReward(accounts[0], True).return_value + assert reward == TOTAL_REWARD * 2 // 5 + TOTAL_REWARD // 2 + assert stake_amount1 == MIN_INIT_DELEGATE_VALUE * 4 + assert stake_amount2 == MIN_INIT_DELEGATE_VALUE * 4 + + +# calculateRewards +def test_calculate_rewards_only_stakehub(core_agent, set_candidate, stake_hub): + with brownie.reverts("the msg sender must be stake hub contract"): + core_agent.calculateRewards(accounts[0]) -def test_only_stakehub(core_agent, set_candidate): +def test_calculate_rewards_success(core_agent, set_candidate, stake_hub): operators, consensuses = set_candidate + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE * 2) + delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + delegate_coin_success(operators[1], accounts[1], MIN_INIT_DELEGATE_VALUE) turn_round() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - with brownie.reverts("the msg sender must be stake hub contract"): - core_agent.viewCollectRewardFromCandidate(operators[0], accounts[0], get_current_round() - 1) + turn_round(consensuses) + tx = delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) + assert 'storedCoinReward' in tx.events + turn_round(consensuses, round_count=2) + update_system_contract_address(core_agent, stake_hub=accounts[0]) + + candidates, rewards, s1, s2 = core_agent.calculateRewards(accounts[0]).return_value + + assert candidates == [operators[0], operators[1]] + assert len(rewards) == 2 + candidates0_reward = TOTAL_REWARD // 2 + TOTAL_REWARD * 3 // 5 + candidates1_reward = TOTAL_REWARD // 2 + TOTAL_REWARD // 2 + assert rewards == [candidates0_reward, candidates1_reward] + assert s1 == MIN_INIT_DELEGATE_VALUE * 2 + MIN_INIT_DELEGATE_VALUE + assert s2 == MIN_INIT_DELEGATE_VALUE * 3 + MIN_INIT_DELEGATE_VALUE + + candidates2, rewards2, s1_2, s2_2 = core_agent.calculateRewards(accounts[0]).return_value + assert all(r == 0 for r in rewards2) + assert s1_2 == 0 + assert s2_2 == 0 def test_calculate_reward_success(core_agent, set_candidate, stake_hub): @@ -1538,8 +1576,10 @@ def test_calculate_reward_success(core_agent, set_candidate, stake_hub): assert stake_hub.getDelegatorMap(accounts[0])[1][0] == TOTAL_REWARD turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward = core_agent.claimReward(accounts[0], 0, get_current_round() - 1, False).return_value - assert reward == [TOTAL_REWARD, 0, MIN_INIT_DELEGATE_VALUE] + reward, stake_amount0, stake_amount1 = core_agent.claimReward(accounts[0], False).return_value + assert reward == TOTAL_REWARD + assert stake_amount0 == MIN_INIT_DELEGATE_VALUE + assert stake_amount1 == MIN_INIT_DELEGATE_VALUE assert stake_hub.getDelegatorMap(accounts[0])[1][0] == TOTAL_REWARD tracker0 = get_tracker(accounts[0]) update_system_contract_address(core_agent, stake_hub=stake_hub) @@ -1553,8 +1593,17 @@ def test_multi_round_coin_stake_success(core_agent, set_candidate, round): core_agent.delegateCoin(operators[0], {'value': MIN_INIT_DELEGATE_VALUE}) turn_round() turn_round(consensuses, round_count=round) - reward = core_agent.collectCoinRewardMock(operators[0], accounts[0], get_current_round() - 1).return_value - assert reward == (TOTAL_REWARD * round, MIN_INIT_DELEGATE_VALUE * round) + reward = core_agent.collectCoinRewardMock(operators[0], accounts[0]).return_value + if round == 0: + stake_amount1 = 0 + else: + stake_amount1 = MIN_INIT_DELEGATE_VALUE + assert reward == (TOTAL_REWARD * round, 0, stake_amount1) + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) + turn_round(consensuses) + turn_round(consensuses) + reward = core_agent.collectCoinRewardMock(operators[0], accounts[0]).return_value + assert reward == (TOTAL_REWARD * 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE * 2) def test_move_data_success(core_agent, pledge_agent): @@ -1574,7 +1623,7 @@ def test_move_data_success(core_agent, pledge_agent): # the transfer part of the reward has been settled, so it is cleared to 0 __check_coin_delegator(candidate, delegator0, real_amount, real_amount, get_current_round(), 0) assert __get_candidate_list_by_delegator(delegator0)[0] == candidate - assert core_agent.delegatorMap(delegator0) == real_amount + assert core_agent.delegatorMap(delegator0) == [real_amount, 0] # scenario 2: No reward settlement staked_amount = MIN_INIT_DELEGATE_VALUE * 10 + 1 transferred_amount = MIN_INIT_DELEGATE_VALUE * 5 + 1 @@ -1585,13 +1634,13 @@ def test_move_data_success(core_agent, pledge_agent): __check_coin_delegator(candidate, delegator1, staked_amount, real_amount, get_current_round(), transferred_amount) assert __get_candidate_list_by_delegator(delegator1)[0] == candidate - assert core_agent.delegatorMap(delegator1) == real_amount + assert core_agent.delegatorMap(delegator1) == [real_amount, 0] def test_only_pledge_agent_can_call_move_data(core_agent, set_candidate): operators, consensuses = set_candidate turn_round() - with brownie.reverts("the sender must be PledgeAgent contract"): + with brownie.reverts("the sender must be PledgeAgent or Channel contracts"): core_agent.moveData(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE, 0, 0) @@ -1605,13 +1654,13 @@ def test_move_data_with_excessive_stake_amount_reverts(core_agent): def test_only_pledge_agent_can_call_proxy(core_agent, validator_set, operate): delegate_amount = required_coin_deposit * 10 operators, consensuses = __register_candidates(accounts[2:5]) - error = 'the sender must be PledgeAgent contract' + error = 'the sender must be PledgeAgent or Channel contracts' if operate == 'delegateCoin': with brownie.reverts(error): - core_agent.proxyDelegate(operators[0], accounts[0], {'from': accounts[2], 'value': delegate_amount}) + core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[2], 'value': delegate_amount}) elif operate == 'undelegateCoin': with brownie.reverts(error): - core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, {'from': accounts[2]}) + core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, 0, {'from': accounts[2]}) else: with brownie.reverts(error): core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': accounts[2]}) @@ -1622,14 +1671,14 @@ def test_successful_proxy_method_call(core_agent, validator_set, operate, pledge delegate_amount = required_coin_deposit * 10 operators, consensuses = __register_candidates(accounts[2:5]) pledge_agent.delegateCoinOld(operators[1], {"value": Web3.to_wei(100000, 'ether')}) - tx = core_agent.proxyDelegate(operators[0], accounts[0], {'from': pledge_agent, 'value': delegate_amount}) + tx = core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': pledge_agent, 'value': delegate_amount}) assert tx.events['delegatedCoin']['amount'] == delegate_amount coin_reward = TOTAL_REWARD turn_round() if operate == 'delegateCoin': - core_agent.proxyDelegate(operators[0], accounts[0], {'from': pledge_agent, 'value': delegate_amount}) + core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': pledge_agent, 'value': delegate_amount}) elif operate == 'undelegateCoin': - core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, {'from': pledge_agent}) + core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, 0, {'from': pledge_agent}) coin_reward = 0 else: core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': pledge_agent}) @@ -1638,20 +1687,19 @@ def test_successful_proxy_method_call(core_agent, validator_set, operate, pledge stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == coin_reward - @pytest.mark.parametrize("operate", ['delegateCoin', 'undelegateCoin', 'transferCoin']) def test_proxy_operation_with_insufficient_amount_reverts(core_agent, validator_set, operate, pledge_agent): delegate_amount = required_coin_deposit - 1 operators, consensuses = __register_candidates(accounts[2:5]) turn_round() update_system_contract_address(core_agent, pledge_agent=accounts[1]) - core_agent.proxyDelegate(operators[0], accounts[0], {'from': accounts[1], 'value': delegate_amount * 2}) + core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[1], 'value': delegate_amount * 2}) if operate == 'delegateCoin': with brownie.reverts("delegate amount is too small"): - core_agent.proxyDelegate(operators[0], accounts[0], {'from': accounts[1], 'value': delegate_amount}) + core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[1], 'value': delegate_amount}) elif operate == 'undelegateCoin': with brownie.reverts("undelegate amount is too small"): - core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, {'from': accounts[1]}) + core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, 0, {'from': accounts[1]}) else: with brownie.reverts("undelegate amount is too small"): core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': accounts[1]}) @@ -1661,7 +1709,7 @@ def test_proxy_delegate2_unregistered_agent(core_agent): update_system_contract_address(core_agent, pledge_agent=accounts[1]) error_msg = encode_args_with_signature("InactiveCandidate(address)", [accounts[5].address]) with brownie.reverts(f"{error_msg}"): - core_agent.proxyDelegate(accounts[5], accounts[0], {'from': accounts[1], 'value': MIN_INIT_DELEGATE_VALUE}) + core_agent.proxyDelegate(accounts[5], accounts[0], 0, {'from': accounts[1], 'value': MIN_INIT_DELEGATE_VALUE}) def test_proxy_delegate2refused(core_agent, candidate_hub, set_candidate): @@ -1671,7 +1719,7 @@ def test_proxy_delegate2refused(core_agent, candidate_hub, set_candidate): update_system_contract_address(core_agent, pledge_agent=accounts[1]) error_msg = encode_args_with_signature("InactiveCandidate(address)", [operators[0].address]) with brownie.reverts(f"{error_msg}"): - core_agent.proxyDelegate(operators[0], accounts[3], {'from': accounts[1], 'value': MIN_INIT_DELEGATE_VALUE}) + core_agent.proxyDelegate(operators[0], accounts[3], 0, {'from': accounts[1], 'value': MIN_INIT_DELEGATE_VALUE}) def test_proxy_transfer2unregistered_agent(core_agent): @@ -1722,12 +1770,12 @@ def test_collect_coin_reward_success(validator_set, core_agent, stake_hub): turn_round() turn_round([consensuses[0]], round_count=4, tx_fee=TX_FEE) delegator_tracker = get_tracker(accounts[0]) - reward_amount_m, acc_staked_amount = core_agent.collectCoinRewardMock(operators[0], accounts[0], - get_current_round() - 1, - {'from': accounts[0]}).return_value + reward_amount_m, stake_amount0, stake_amount1 = core_agent.collectCoinRewardMock(operators[0], accounts[0], + {'from': accounts[0]}).return_value result = core_agent.getDelegator(operators[0], accounts[0]).dict() assert reward_amount_m == actual_block_reward * 90 // 100 * 4 - assert acc_staked_amount == required_coin_deposit * 4 + assert stake_amount0 == 0 + assert stake_amount1 == required_coin_deposit assert delegator_tracker.delta() == 0 assert result['stakedAmount'] == MIN_INIT_DELEGATE_VALUE assert required_coin_deposit == result['realtimeAmount'] diff --git a/tests/test_core_agent_unit.py b/tests/test_core_agent_unit.py index aee3e7b4c..23da04049 100644 --- a/tests/test_core_agent_unit.py +++ b/tests/test_core_agent_unit.py @@ -136,7 +136,7 @@ def test_core_agent_undelegate_coin(core_agent, candidate_hub): 'value': required_coin_deposit}, {'status': 'success', 'candidate': accounts[1], 'value': required_coin_deposit, 'expect_cd': (0, required_coin_deposit * 2, 0, round_tag)}, - {'status': 'failed', 'err': 'Not enough staked tokens', 'candidate': accounts[1], + {'status': 'failed', 'err': 'InsufficientTokens: 0', 'candidate': accounts[1], 'value': required_coin_deposit * 2 + 1, 'turn_round': 1}, {'status': 'failed', 'err': 'remain amount is too small', 'candidate': accounts[1], 'value': required_coin_deposit * 2 - 1}, diff --git a/tests/test_delegate_btc.py b/tests/test_delegate_btc.py index 986636f98..e666f61ca 100644 --- a/tests/test_delegate_btc.py +++ b/tests/test_delegate_btc.py @@ -127,7 +127,7 @@ def test_claim_btc_staking_rewards_success(btc_stake, set_candidate): tx = stake_hub_claim_reward(accounts[0]) expect_event(tx, "claimedReward", { "delegator": accounts[0], - "amount": TOTAL_REWARD - FEE + "amounts": [0, 0, TOTAL_REWARD - FEE] }) assert tracker.delta() == TOTAL_REWARD - FEE @@ -433,7 +433,9 @@ def test_multiple_btc_receipts_to_single_address(btc_stake, set_candidate): turn_round(consensuses) tx = stake_hub_claim_reward(accounts[0]) assert 'claimedReward' not in tx.events - turn_round(consensuses, round_count=2) + turn_round(consensuses) + delegate_btc_success(operators[2], accounts[0], BTC_VALUE, LOCK_SCRIPT, LOCK_TIME, accounts[2]) + turn_round(consensuses) tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) _, _, account_rewards, round_reward = parse_delegation([{ diff --git a/tests/test_delegate_coin.py b/tests/test_delegate_coin.py index 32f292be9..c05d1e5bd 100644 --- a/tests/test_delegate_coin.py +++ b/tests/test_delegate_coin.py @@ -188,7 +188,7 @@ def test_proxy_claim_reward_success(core_agent, stake_hub): }) expect_event(tx, "claimedReward", { "delegator": pledge_agent_proxy.address, - "amount": COIN_REWARD + "amounts": [COIN_REWARD, 0, 0] }) assert core_agent.rewardMap(pledge_agent_proxy.address) == (0, 0) @@ -1142,7 +1142,8 @@ def test_transfer_and_check_transfer_info(core_agent, validator_set, candidate_h assert tracker0.delta() == COIN_REWARD + COIN_REWARD // 2 * 2 -def test_multiple_transfers_and_check_transfer_info(core_agent,pledge_agent, validator_set, candidate_hub, set_candidate): +def test_multiple_transfers_and_check_transfer_info(core_agent, pledge_agent, validator_set, candidate_hub, + set_candidate): delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 transfer_amount0 = delegate_amount // 2 transfer_amount1 = delegate_amount // 4 @@ -1646,7 +1647,7 @@ def test_operation_on_validator_with_no_transfer(core_agent, validator_set, oper elif operator_type == 'claim': tx = stake_hub_claim_reward(accounts[0]) assert 'claimedReward' in tx.events - assert tx.events['claimedReward']['amount'] == COIN_REWARD * 3 + assert tx.events['claimedReward']['amounts'] == [COIN_REWARD * 3, 0, 0] remain_reward = COIN_REWARD * 3 else: delegate_coin_success(operators[3], accounts[0], operation_amount) @@ -2358,7 +2359,7 @@ def test_candidate_data_cleared_without_pop(core_agent): assert delegator_info['transferredAmount'] == 0 turn_round(consensuses, round_count=2) candidate_list = core_agent.getCandidateListByDelegator(accounts[0]) - assert len(candidate_list) == 1 + assert len(candidate_list) == 0 tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert tracker.delta() == 0 diff --git a/tests/test_dual_staking.py b/tests/test_dual_staking.py index 60a192121..9a4e6e5b1 100644 --- a/tests/test_dual_staking.py +++ b/tests/test_dual_staking.py @@ -2,7 +2,7 @@ import rlp from .calc_reward import set_delegate, parse_delegation, Discount, set_btc_lst_delegate -from .common import register_candidate, turn_round, stake_hub_claim_reward, set_round_tag, execute_proposal +from .common import * from .delegate import * from .utils import * @@ -182,7 +182,7 @@ def test_one_level_stake_duration_reward(btc_stake, set_candidate, tlp): assert STAKE_HUB.surplus() == unclaimed_reward -@pytest.mark.parametrize("core_rate", [0, 1989, 2001, 5000, 6000, 7000, 9000, 11000, 12001, 13000, 15000, 15001, 16000]) +@pytest.mark.parametrize("core_rate", [1989, 2001, 5000, 6000, 7000, 9000, 11000, 12001, 13000, 15000, 15001, 16000]) def test_each_bracket_discounted_rewards_accuracy(btc_stake, candidate_hub, btc_light_client, set_candidate, core_agent, core_rate, system_reward, stake_hub): stake_manager.set_tlp_rates() @@ -190,16 +190,18 @@ def test_each_bracket_discounted_rewards_accuracy(btc_stake, candidate_hub, btc_ operators, consensuses = set_candidate turn_round() delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + core_acc_stake_amount = BTC_VALUE * core_rate + delegate_coin_success(operators[1], accounts[0], core_acc_stake_amount) round_time_tag = candidate_hub.roundTag() - 6 btc_light_client.setMiners(round_time_tag, operators[2], [accounts[2]] * 2) turn_round(consensuses, round_count=2) stake_manager.set_stake_hub_delegator_map(accounts[0], 0) - core_acc_stake_amount = __update_core_stake_amount(accounts[0], core_rate, BTC_VALUE) tracker = get_tracker(accounts[0]) reward, unclaimed_reward = __calc_stake_amount_discount(BLOCK_REWARD // 2, BTC_VALUE, core_acc_stake_amount) tx = stake_hub_claim_reward(accounts[0]) + assert tx.events['claimedReward']['amounts'][2] == reward assert "claimedReward" in tx.events - assert tracker.delta() == reward + assert tracker.delta() == sum(tx.events['claimedReward']['amounts']) assert STAKE_HUB.surplus() == unclaimed_reward @@ -238,20 +240,23 @@ def test_core_hash_btc_rewards_discounted_by_core_ratio(btc_stake, candidate_hub operators, consensuses = set_candidate turn_round() turn_round(consensuses) + stake_amount = 0 + if core_rate > 0: + stake_amount = BTC_VALUE * core_rate + delegate_coin_success(operators[0], accounts[0], stake_amount) delegate_power_success(operators[1], accounts[0], value=power_value) delegate_btc_success(operators[2], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) turn_round(consensuses, round_count=2) stake_manager.set_stake_hub_delegator_map(accounts[0], 0) - core_acc_stake_amount = __update_core_stake_amount(accounts[0], core_rate, btc_value) - btc_reward, btc_unclaimed_reward = __calc_stake_amount_discount(TOTAL_REWARD, btc_value, core_acc_stake_amount) + btc_reward, btc_unclaimed_reward = __calc_stake_amount_discount(TOTAL_REWARD, btc_value, stake_amount) tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == btc_reward + TOTAL_REWARD + tx = stake_hub_claim_reward(accounts[0]) + assert tx.events['claimedReward']['amounts'][2] == btc_reward + assert tracker.delta() == sum(tx.events['claimedReward']['amounts']) assert STAKE_HUB.surplus() == btc_unclaimed_reward -def test_power_btc_discount_conversion_success(btc_stake, btc_agent, - set_candidate, core_agent, stake_hub): +def test_power_btc_discount_conversion_success(btc_agent, set_candidate, core_agent, stake_hub): stake_manager.set_lp_rates([[0, 1000], [10000, 10000], [10001, 1000]]) btc_agent.setAssetWeight(1e10) delegate_amount = 1000000e18 @@ -537,7 +542,7 @@ def test_btc_staking_reward_depleted(btc_stake, set_candidate, stake_hub): def test_btc_expired_excluded_from_stake(btc_stake, set_candidate): delegate_amount = 10000 btc_amount = 1 - stake_manager.set_lp_rates([[10000, 5000], [30000, 2000], [40000, 10000], [40001, 3000]]) + stake_manager.set_lp_rates([[9999, 2000], [10000, 10000], [10001, 40000], [20000, 3000]]) stake_manager.set_is_stake_hub_active(True) operators, consensuses = set_candidate set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) @@ -549,9 +554,9 @@ def test_btc_expired_excluded_from_stake(btc_stake, set_candidate): delegate_btc_success(operators[1], accounts[0], btc_amount, LOCK_SCRIPT, stake_duration=YEAR) turn_round(consensuses, round_count=5) # current_round = 20106 - # the effective staking duration of BTC is only 1 round, and Coin has 4 rounds, so the acc_stake_amount ratio is 1:40000 + # The effective staking time of BTC is only 1 round, while Coin has 4 rounds, because the stake amount is not affected by the staking round, so the ratio is 1:10000 tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) assert tracker.delta() == TOTAL_REWARD * 5 @@ -673,7 +678,7 @@ def test_dual_staking_reward_in_current_round(btc_stake, btc_agent, stake_hub, v {'delegator': ['delegateBtc', 'transferBtc'], 'expect_claim_reward': 1.35e18, 'expect_btc_reward': 6.75e18}, {'delegator': ['transferBtc', 'delegateBtc'], 'expect_claim_reward': 4.5e17, 'expect_btc_reward': 4.05e18}, ]) -def test_dual_staking_reward_after_rounds(btc_stake, btc_agent, stake_hub, validator_set, +def test_dual_staking_reward_after_rounds(btc_agent, stake_hub, validator_set, set_candidate, tests): btc_agent.setAssetWeight(1e10) tx_fee = ONE_ETHER @@ -719,8 +724,8 @@ def test_dual_staking_claim_success_after_btc_expiration(btc_stake, btc_agent, s turn_round() turn_round(consensuses, round_count=5) tx = delegate_coin_success(operators[1], accounts[0], 3000000) - assert tx.events['storedBtcReward'][1]['accStakedAmount'] == btc_value * 3 - assert tx.events['storedBtcReward'][1]['dualStakingRate'] == Utils.DENOMINATOR // 2 + assert tx.events['storedRewardBtcTx']['lockLengthRate'] == 0 + assert tx.events['storedRewardBtcTx']['dualStakingRate'] == Utils.DENOMINATOR // 2 turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) @@ -728,8 +733,7 @@ def test_dual_staking_claim_success_after_btc_expiration(btc_stake, btc_agent, s @pytest.mark.parametrize("asset_weight", [1, 100, 1000, 1e7, 1e10, 1e18]) -def test_claim_reward_after_modify_asset_weight(btc_stake, btc_agent, - set_candidate, core_agent, stake_hub, asset_weight): +def test_claim_reward_after_modify_asset_weight(btc_agent, set_candidate, stake_hub, asset_weight): btc_agent.setAssetWeight(asset_weight) stake_manager.set_lp_rates([[0, 1000], [10000, 10000], [10001, 1000]]) delegate_amount = 100 * 10000 * asset_weight @@ -775,14 +779,15 @@ def test_cancel_immediately_after_transfer(btc_stake, stake_hub, set_candidate, @pytest.mark.parametrize("tests", [ ['delegate', 13545 + 13545 // 2, 13545 * 8], ['undelegate', 13545 // 2 + 13545 // 4, 13545 * 2 + 13545 * 2 // 4], - ['transfer', 13545 + 13545 // 2, 13545 * 4 + 13545 // 2 * 2], - ['delegate_btc', 13545 + 13545 // 2, 13545 * 2 + 13545 // 4 * 2 * 2], - ['transfer_btc', 13545, 13545 * 2 + 13545 // 2 * 2], - ['claim', 13545 + 13545 // 2, 13545 * 2 + 13545 // 2 * 2] + ['transfer', 13545 + 13545 // 2, 13545 * 4 + 13545], + # There are a total of 4 rounds of BTC rewards, of which the amount of CORE used up when the first TXID was collected, the ratio is 1:5000, so the reward is halved, and the other one has no remaining CORE, the ratio is 1:0 + ['delegate_btc', 13545 + 13545 // 2, 13545 * 2 + 13545 * 2 // 2 + 13545 * 2 / 10], + ['transfer_btc', 13545, 13545 * 2 + 13545], + ['claim', 13545 + 13545 // 2, 13545 * 2 + 13545] ]) -def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, core_agent, set_candidate, +def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, set_candidate, tests): - stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) + stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [5001, 10000], [10000, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate @@ -805,11 +810,11 @@ def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, co elif tests[0] == 'claim': tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD * 4 + assert tracker0.delta() == TOTAL_REWARD * 2 + TOTAL_REWARD // 2 assert stake_hub.getDelegatorMap(accounts[0])[0] == current_round tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 4 + actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 if tests[0] == 'claim': actual_reward = 0 assert tracker0.delta() == actual_reward @@ -817,12 +822,12 @@ def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, co stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == tests[1] turn_round(consensuses, round_count=2) - stake_hub_claim_reward(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == tests[2] def test_staking_with_duration_discount_claim_reward_success(btc_stake, stake_hub, core_agent, set_candidate): - stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) + stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 20000], [10000, 40000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates([[0, 5000]]) operators, consensuses = set_candidate @@ -837,26 +842,27 @@ def test_staking_with_duration_discount_claim_reward_success(btc_stake, stake_hu mock_delegate_btc_success(operators[1], accounts[0], btc_value, block_timestamp=time_stamp) turn_round(consensuses, round_count=2) tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) - float_reward = TOTAL_REWARD // 2 - (TOTAL_REWARD - TOTAL_REWARD // 2) - assert tx.events['storedBtcReward']['unclaimedAmount'] == TOTAL_REWARD - TOTAL_REWARD // 2 - assert tx.events['storedBtcReward']['floatReward'] == float_reward - assert tx.events['storedBtcReward']['dualStakingRate'] == Utils.DENOMINATOR * 2 + assert tx.events['storedRewardBtcTx']['lockLengthRate'] == Utils.DENOMINATOR // 2 + assert tx.events['storedRewardBtcTx']['dualStakingRate'] == Utils.DENOMINATOR * 2 + assert stake_hub.surplus() == 1 tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 * 2 assert tracker0.delta() == actual_reward turn_round(consensuses) stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 // 2 + assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 * 2 + turn_round(consensuses) @pytest.mark.parametrize("tests", [ ['de', 13545 + 13545 // 2, 13545 * 8], ['un', 13545 // 2 + 13545 // 4, 13545 * 2 + 13545 * 2 // 4], - ['tr', 13545 + 13545 // 2, 13545 * 4 + 13545 // 2 * 2], - ['deb', 13545 + 13545 // 2, 13545 * 2 + 13545 // 4 * 4], - ['trb', 13545, 13545 * 2 + 13545 // 2 * 2], - ['cl', 13545 + 13545 // 2, 13545 * 2 + 13545 // 2 * 2] + ['tr', 13545 + 13545 // 2, 13545 * 4 + 13545], + # The first BTC ratio is 1:5000, and the second is 1:0 + ['deb', 13545 + 13545 // 2, 13545 * 2 + 13545 * 2 // 2 + 13545 * 2 / 10], + ['trb', 13545, 13545 * 2 + 13545], + ['cl', 13545 + 13545 // 2, 13545 * 2 + 13545] ]) def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agent, set_candidate, tests): stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [10000, 20000]]) @@ -872,7 +878,7 @@ def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agen turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 4 + assert tracker.delta() == TOTAL_REWARD * 2 + TOTAL_REWARD // 2 turn_round(consensuses, round_count=2) if tests[0] == 'de': delegate_coin_success(operators[2], accounts[0], delegate_amount) @@ -886,7 +892,7 @@ def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agen transfer_btc_success(tx_id, operators[2], accounts[0]) tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 * 2 + actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD * 2 // 2 assert tracker0.delta() == actual_reward turn_round(consensuses) stake_hub_claim_reward(accounts[0]) @@ -914,23 +920,25 @@ def test_dual_staking_multiple_rounds_operations_success(btc_stake, stake_hub, c turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 4 + assert tracker.delta() == TOTAL_REWARD * 2 + TOTAL_REWARD // 2 transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2) turn_round(consensuses, round_count=2) delegate_coin_success(operators[0], accounts[0], delegate_amount) - assert stake_hub.getDelegatorMap(accounts[0]) == [get_current_round(), [TOTAL_REWARD * 3, 0, TOTAL_REWARD // 2 * 2]] + assert stake_hub.getDelegatorMap(accounts[0]) == [get_current_round(), [TOTAL_REWARD * 3, 0, TOTAL_REWARD]] turn_round(consensuses) transfer_coin_success(operators[0], operators[3], accounts[0], delegate_amount) assert stake_hub.getDelegatorMap(accounts[0]) == [get_current_round(), - [TOTAL_REWARD * 5, 0, TOTAL_REWARD // 2 * 3]] + [TOTAL_REWARD * 5, 0, TOTAL_REWARD + TOTAL_REWARD // 2]] tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 5 + TOTAL_REWARD // 2 * 3 + assert tracker.delta() == TOTAL_REWARD * 5 + TOTAL_REWARD + TOTAL_REWARD // 2 -@pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'transfer', 'delegate_btc', 'transfer_btc', 'claim']) +@pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'transfer', 'delegate_btc', 'transfer_btc', 'claim', + 'calculateReward']) def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_hub, core_agent, tests): - stake_manager.set_lp_rates([[0, 1000], [3750, 2500], [3751, 5000], [10000, 20000]]) + # is 1:2500 + stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [3751, 5000], [5000, 25000], [10000, 30000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators = [] @@ -948,7 +956,7 @@ def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_h mock_undelegate_coin_success(operators[2], accounts[0], delegate_amount // 4 * 2) turn_round(consensuses, round_count=3) actual_reward = TOTAL_REWARD // 2 + TOTAL_REWARD * 4 + TOTAL_REWARD // 4 * 2 - event_name = ['storedBtcReward', 'storedCoinReward'] + event_name = ['storedRewardBtcTx', 'storedCoinReward'] if tests == 'delegate': tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) elif tests == 'undelegate': @@ -959,45 +967,15 @@ def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_h tx = delegate_btc_success(operators[2], accounts[0], btc_value, LOCK_SCRIPT, events=True) elif tests == 'transfer_btc': tx = transfer_btc_success(tx_id, operators[2], accounts[0]) + elif tests == 'calculateReward': + tx = stake_hub.calculateReward(accounts[0]) else: tracker = get_tracker(accounts[0]) tx = stake_hub_claim_reward(accounts[0]) - event_name = ['claimedBtcReward', 'claimedCoinReward'] + event_name = ['claimedRewardBtcTx', 'claimedCoinReward'] assert tracker.delta() == actual_reward actual_reward = 0 assert tx.events[event_name[0]]['dualStakingRate'] == Utils.DENOMINATOR // 4 - assert tx.events[event_name[0]]['accStakedAmount'] == btc_value * 2 - assert tx.events[event_name[1]]['accStakedAmount'] == delegate_amount // 2 * 3 - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == actual_reward - turn_round(consensuses) - - -def test_calculate_reward_success(btc_stake, stake_hub, core_agent): - stake_manager.set_lp_rates([[0, 1000], [3750, 2500], [3751, 5000], [10000, 20000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators = [] - consensuses = [] - for operator in accounts[5:9]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - turn_round() - delegate_amount = 500000 - btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 4 * 3) - mock_undelegate_coin_success(operators[2], accounts[0], delegate_amount // 4 * 2) - turn_round(consensuses, round_count=3) - actual_reward = TOTAL_REWARD // 2 + TOTAL_REWARD * 4 + TOTAL_REWARD // 4 * 2 - event_name = ['storedBtcReward', 'storedCoinReward'] - tx = stake_hub.calculateReward(accounts[0]) - assert tx.events[event_name[0]]['dualStakingRate'] == Utils.DENOMINATOR // 4 - assert tx.events[event_name[0]]['accStakedAmount'] == btc_value * 2 - assert tx.events[event_name[1]]['accStakedAmount'] == delegate_amount // 2 * 3 tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert tracker.delta() == actual_reward @@ -1017,9 +995,7 @@ def test_claim_reward_clears_change_round(btc_stake, stake_hub, core_agent, set_ mock_delegate_btc_success(operators[1], accounts[0], btc_value) mock_undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) turn_round(consensuses) - tx = undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) - assert tx.events['storedCoinReward']['accStakedAmount'] == delegate_amount * 2 + ( - delegate_amount - delegate_amount // 4) + undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert tracker.delta() == TOTAL_REWARD * 2 + TOTAL_REWARD // 4 * 3 @@ -1042,11 +1018,8 @@ def test_btc_transfer_triggers_dual_staking(btc_stake, stake_hub, core_agent, se turn_round(consensuses) tx = run_stake_operation(tests, operators[0], accounts[0], delegate_amount, operators[2], tx_id, LOCK_SCRIPT, btc_value=btc_value) - event_name = 'storedCoinReward' if tests == 'claim': - event_name = 'claimedCoinReward' - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD * 2 - assert tx.events[event_name]['accStakedAmount'] == delegate_amount * 2 + assert tx.events['claimedReward']['amounts'][0] == TOTAL_REWARD * 2 tracker = get_tracker(accounts[0]) if tests != 'claim': stake_hub_claim_reward(accounts[0]) @@ -1054,11 +1027,8 @@ def test_btc_transfer_triggers_dual_staking(btc_stake, stake_hub, core_agent, se assert stake_hub.getDelegatorMap(accounts[0])[0] == get_current_round() -@pytest.mark.parametrize("round_count", [[0, 0], [1, 1000], [2, 10000], [3, 7500], [4, 10000]]) -def test_claim_dual_staking_reward_after_btc_expiration(btc_stake, stake_hub, core_agent, system_reward, set_candidate, - round_count): - rate = round_count[1] - stake_manager.set_lp_rates([[1, 1000], [rate, 10000], [rate + 1, 1000]]) +def test_claim_dual_staking_reward_after_btc_expiration(btc_stake, stake_hub, core_agent, system_reward, set_candidate): + stake_manager.set_lp_rates([[1, 1000], [5000, 10000], [5001, 1000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate @@ -1070,882 +1040,250 @@ def test_claim_dual_staking_reward_after_btc_expiration(btc_stake, stake_hub, co mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() mock_delegate_btc_success(operators[1], accounts[0], btc_value) - turn_round(consensuses, round_count=round_count[0]) + turn_round(consensuses, round_count=3) get_current_round() tx = delegate_coin_success(operators[0], accounts[0], delegate_amount) - assert tx.events['storedBtcReward']['dualStakingRate'] == Utils.DENOMINATOR + assert tx.events['storedRewardBtcTx']['dualStakingRate'] == Utils.DENOMINATOR stake_hub_claim_reward(accounts[0]) turn_round(consensuses) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) actual_reward = TOTAL_REWARD - if round_count[0] in [1, 2]: - actual_reward += TOTAL_REWARD // 10 assert tracker.delta() == actual_reward -@pytest.mark.parametrize("round_count", [ - [0, 0], - [1, 0], - [2, 200], - [3, 400], - [4, 600], - [5, 700], - [6, 800], - [7, 900], - [8, 900], - [10, 900] -]) -def test_multiple_btc_stakes_including_expired(btc_stake, stake_hub, core_agent, system_reward, set_candidate, - round_count): - stake_manager.set_lp_rates([[0, 10000]]) +def test_coin_staking_reward_settlement(set_candidate, pledge_agent, stake_hub): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate turn_round() delegate_amount = 500000 - btc_value = 100 - set_last_round_tag(4) mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round() - get_current_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_delegate_btc_success(operators[2], accounts[0], btc_value, lock_time=LOCK_TIME + Utils.ROUND_INTERVAL * 3) - turn_round(consensuses, round_count=round_count[0]) - get_current_round() - tx = delegate_coin_success(operators[0], accounts[0], delegate_amount) - assert tx.events['storedCoinReward']['accStakedAmount'] == delegate_amount * round_count[0] - assert tx.events['storedBtcReward']['accStakedAmount'] == round_count[1] - stake_hub_claim_reward(accounts[0]) + mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round(consensuses) - get_current_round() + mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) + turn_round(consensuses) + mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) + turn_round(consensuses, round_count=3) + delegate_coin_success(operators[2], accounts[0], delegate_amount) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 3 - if round_count[0] == 0: - actual_reward = TOTAL_REWARD - if round_count[0] > 3: - actual_reward = TOTAL_REWARD * 2 - if round_count[0] > 6: - actual_reward = TOTAL_REWARD - assert tracker.delta() == actual_reward + assert tracker.delta() * TOTAL_REWARD // 2 * 4 + turn_round(consensuses, round_count=2) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() * TOTAL_REWARD // 2 * 2 -def test_btc_expiration_previous_round_operation(btc_stake, stake_hub, core_agent, system_reward, set_candidate): - stake_manager.set_lp_rates([[2499, 1000], [2500, 10000], [2501, 1000]]) +def test_dual_staking_after_expired_btc(stake_hub, set_candidate): + stake_manager.set_lp_rates([[1, 1000], [5000, 10000], [5001, 1000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate - turn_round() + set_last_round_tag(3) delegate_amount = 500000 btc_value = 100 - set_last_round_tag(4) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - turn_round(consensuses, round_count=3) - mock_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2) - # hardfork + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses, round_count=3) - tracker = get_tracker(accounts[0]) + turn_round(consensuses, round_count=6) tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedCoinReward']['accStakedAmount'] == delegate_amount - assert tx.events['claimedBtcReward']['accStakedAmount'] == btc_value * 2 - assert tx.events['claimedBtcReward']['dualStakingRate'] == Utils.DENOMINATOR - assert tracker.delta() == TOTAL_REWARD // 2 * 2 + TOTAL_REWARD * 2 // 10 + TOTAL_REWARD * 8 + assert tx.events['claimedRewardBtcTx']['dualStakingRate'] == 10000 -@pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'transfer', 'delegate_btc', 'transfer_btc', 'claim']) -def test_concurrent_stakes_data_settlement_reward(btc_stake, stake_hub, core_agent, system_reward, set_candidate, - tests): +def test_stake_btc_then_move_data(stake_hub, btc_stake, core_agent, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() + stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) operators, consensuses = set_candidate - turn_round() + old_turn_round() delegate_amount = 500000 - btc_value = 100 - set_last_round_tag(10) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses, round_count=3) - mock_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2) - # hardfork - tx = run_stake_operation(tests, operators[0], accounts[0], delegate_amount // 2, operators[2], tx_id, LOCK_SCRIPT, - btc_value=btc_value) - events_name = ['storedCoinReward', 'storedBtcReward'] - if tests == 'claim': - events_name = ['claimedCoinReward', 'claimedBtcReward'] - assert tx.events[events_name[0]]['accStakedAmount'] == delegate_amount * 2 - assert tx.events[events_name[1]]['accStakedAmount'] == btc_value * 2 + btc_value = 100000 + old_delegate_coin_success(operators[0], accounts[0], delegate_amount) + old_delegate_coin_success(operators[0], accounts[1], delegate_amount) + old_turn_round(consensuses) + old_turn_round(consensuses, round_count=6) + __init_hybrid_score_mock() + turn_round(consensuses) + mock_delegate_btc_success(operators[0], accounts[0], btc_value) + turn_round(consensuses, round_count=2) + tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) + assert tx.events['storedRewardBtcTx']['dualStakingRate'] == 1000 + turn_round(consensuses, round_count=2) + old_claim_reward_success(operators, accounts[0]) + turn_round(consensuses, round_count=2) + tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) + assert tx.events['storedRewardBtcTx'][0]['dualStakingRate'] == 10000 -@pytest.mark.parametrize("tests", ['delegate', 'undelegate', 'transfer', 'delegate_btc', 'transfer_btc', 'claim']) -def test_upgrade_after_intermediate_reward_claim(btc_stake, stake_hub, core_agent, system_reward, set_candidate, - tests): +@pytest.mark.parametrize("tests", [ + ['delegate', 'transfer', 4063 + 5416 * 2 // 3 // 2], + ['transfer', 'undelegate', 4063 // 2 + 5416 * 2 // 3 // 10], + ['undelegate', 'delegate', 4063 // 2 + 5416 * 2 // 3 // 10], + ['delegate_btc', 'transfer_btc', 4063 + 0], + ['transfer', 'undelegate', 'transfer_btc', 4063 // 2 + 0], +]) +def test_reconcile_diff_operations_in_current_round(stake_hub, btc_stake, core_agent, set_candidate, tests): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() + stake_manager.set_lp_rates([[0, 1000], [2500, 5000], [2501, 10000]]) operators, consensuses = set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) + for account in accounts[:2]: + mock_delegate_coin_success(operators[0], account, delegate_amount) turn_round(consensuses) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - stake_hub_claim_reward(accounts[0]) tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value * 2) + mock_delegate_btc_success(operators[0], accounts[1], btc_value) turn_round(consensuses, round_count=3) - mock_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2) - # hardfork - tx = run_stake_operation(tests, operators[0], accounts[0], delegate_amount // 2, operators[2], tx_id, LOCK_SCRIPT, - btc_value=btc_value) - events_name = ['storedCoinReward', 'storedBtcReward'] - if tests == 'claim': - events_name = ['claimedCoinReward', 'claimedBtcReward'] - assert tx.events[events_name[0]][0]['accStakedAmount'] == delegate_amount * 5 - assert tx.events[events_name[1]][0]['accStakedAmount'] == btc_value * 2 - assert tx.events[events_name[1]][1]['accStakedAmount'] == btc_value * 8 + for test in tests: + run_stake_operation(test, operators[0], accounts[0], delegate_amount // 2, operators[2], tx_id, + LOCK_SCRIPT, + btc_value=btc_value) + stake_hub_claim_reward(accounts[0]) + turn_round(consensuses) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == tests[-1] + turn_round(consensuses) -def test_concurrent_stakes_with_btc_transfer(btc_stake, stake_hub, core_agent, system_reward, set_candidate): +@pytest.mark.parametrize("round_reward", + [[0, 13544], [1, 27090], + [2, (13545 + 6772 * 2 + 8127) + 5417 * 2 // 3 * 2], + [3, (13545 + 6772 * 2 + 8127) + ( + 5417 * 2 // 3 * 2) + 13545 * 6 // 10 // 2 * 2 + 5418 * 2 // 3 * 2]]) +def test_claim_reward_across_different_changeRound(stake_hub, set_candidate, round_reward): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() + stake_manager.set_lp_rates([[0, 10000]]) operators, consensuses = set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value) + for account in accounts[:2]: + for op in operators[:2]: + mock_delegate_coin_success(op, account, delegate_amount) + turn_round(consensuses, round_count=2) + for index, account in enumerate(accounts[:2]): + for op in operators[:2]: + delegate_btc_value = btc_value + if index == 0: + delegate_btc_value = btc_value * 2 + mock_delegate_btc_success(op, account, delegate_btc_value) + turn_round(consensuses, round_count=round_reward[0]) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == round_reward[1] turn_round(consensuses) - mock_transfer_btc_success(tx_id, operators[2]) - turn_round(consensuses, round_count=3) - tx = delegate_coin_success(operators[1], accounts[0], delegate_amount) - events_name = ['storedCoinReward', 'storedBtcReward'] - assert tx.events[events_name[0]]['accStakedAmount'] == delegate_amount * 3 - assert tx.events[events_name[1]]['accStakedAmount'] == btc_value * 2 + stake_hub_claim_reward(accounts[0]) + account_reward = COIN_REWARD_NO_POWER // 2 * 2 + (BTC_REWARD_NO_POWER - 1) * 4 // 6 * 2 + if round_reward[0] == 0: + account_reward = TOTAL_REWARD // 2 * 2 + assert tracker.delta() == account_reward -def test_concurrent_stakes_with_expired_btc(btc_stake, stake_hub, core_agent, system_reward, set_candidate): +def test_change_round_diff_with_power_stake(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() + stake_manager.set_lp_rates([[0, 10000]]) operators, consensuses = set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - set_last_round_tag(3) - get_current_round() - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value) - mock_delegate_coin_success(operators[2], accounts[0], delegate_amount) - mock_delegate_btc_success(operators[2], accounts[0], btc_value, lock_time=LOCK_TIME + Utils.ROUND_INTERVAL * 7) + for account in accounts[:2]: + for op in operators[:2]: + mock_delegate_coin_success(op, account, delegate_amount) + turn_round(consensuses, round_count=2) + for account in accounts[:2]: + for op in operators[:2]: + mock_delegate_btc_success(op, account, btc_value) + delegate_power_success(operators[0], accounts[2]) + delegate_power_success(operators[1], accounts[2]) turn_round(consensuses) - turn_round(consensuses, round_count=5) - tx = delegate_coin_success(operators[1], accounts[0], delegate_amount) - assert btc_stake.receiptMap(tx_id)['round'] == 0 - events_name = ['storedCoinReward', 'storedBtcReward'] - assert tx.events[events_name[0]]['accStakedAmount'] == delegate_amount * 2 * 5 - assert tx.events[events_name[1]]['accStakedAmount'] == btc_value * 8 + delegate_power_success(operators[0], accounts[2]) + delegate_power_success(operators[1], accounts[2]) + turn_round(consensuses) + tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) + undelegate_coin_success(operators[2], accounts[0], delegate_amount) + trackers = get_trackers(accounts[:3]) + tx = stake_hub_claim_reward(accounts[:3]) + # coin reward + btc reward + account_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) + assert trackers[0].delta() == account_reward + assert trackers[1].delta() == account_reward + assert trackers[2].delta() == TOTAL_REWARD // 6 * 2 + stake_manager.set_lp_rates([[4000, 5000], [6000, 10000], [11000, 1000]]) + turn_round(consensuses) + tx = stake_hub_claim_reward(accounts[0]) + assert tx.events['claimedRewardBtcTx'][0]['dualStakingRate'] == Utils.DENOMINATOR + assert tx.events['claimedRewardBtcTx'][1]['dualStakingRate'] == Utils.DENOMINATOR // 2 + account_reward = TOTAL_REWARD // 2 + TOTAL_REWARD // 6 + TOTAL_REWARD // 6 // 2 + assert trackers[0].delta() == account_reward -@pytest.mark.parametrize("tests", ['delegate', 'delegate_btc']) -def test_settlement_reward_for_staking_other_validators(set_candidate, tests): +def test_upgrade_same_change_round_claim_reward(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) + for account in accounts[:2]: + delegate_coin_success(operators[0], account, delegate_amount) + turn_round(consensuses, round_count=2) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) + assert stake_hub.getDelegatorMap(accounts[0])[1][0] == TOTAL_REWARD // 2 + delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT) + delegate_power_success(operators[0], accounts[2]) turn_round(consensuses) - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value) turn_round(consensuses) - mock_delegate_coin_success(operators[1], accounts[0], delegate_amount) - turn_round(consensuses, round_count=3) - # hardfork - tx = run_stake_operation(tests, operators[2], accounts[0], delegate_amount // 2, operators[2], tx_id, - LOCK_SCRIPT, - btc_value=btc_value) - events_name = ['storedCoinReward', 'storedBtcReward'] - assert tx.events[events_name[0]]['accStakedAmount'] == delegate_amount * 7 - assert tx.events[events_name[1]]['accStakedAmount'] == btc_value * 7 + stake_manager.set_lp_rates([[4999, 1000], [5000, 10000], [5001, 1000]]) + _, _, account_rewards, _ = parse_delegation([{ + "address": operators[0], + "coin": [set_delegate(accounts[1], delegate_amount), set_delegate(accounts[0], delegate_amount)], + "power": [set_delegate(accounts[2], 1)], + "btc": [set_delegate(accounts[1], btc_value), set_delegate(accounts[0], btc_value)] + }, { + "address": operators[1], + }, { + "address": operators[2] + } + ], TOTAL_REWARD) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == account_rewards[accounts[0]] + TOTAL_REWARD // 2 * 2 -@pytest.mark.parametrize("tests", ['proxy_delegate', 'proxy_undelegate', 'proxy_transfer', 'proxy_claim']) -def test_proxy_staking_settlement_reward(set_candidate, pledge_agent, stake_hub, tests): +def test_all_stakes_operation_and_reward_claim(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses) - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_delegate_coin_success(operators[1], accounts[0], delegate_amount) - turn_round(consensuses) + for account in accounts[:2]: + for op in operators[:2]: + delegate_coin_success(op, account, delegate_amount) turn_round(consensuses, round_count=3) - # hardfork - tx = run_proxy_stake_operation(tests, operators[1], accounts[0], delegate_amount // 2, operators[2], operators) - change_round = get_current_round() - events_name = ['storedCoinReward', 'storedBtcReward'] - if tests == 'proxy_claim': - events_name = ['claimedCoinReward', 'claimedBtcReward'] - assert tx.events[events_name[0]]['accStakedAmount'] == delegate_amount * 8 - assert tx.events[events_name[1]]['accStakedAmount'] == btc_value * 7 - assert stake_hub.getDelegatorMap(accounts[0])[0] == change_round + for account in accounts[:2]: + for op in operators[:2]: + delegate_btc_success(op, account, btc_value, LOCK_SCRIPT) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == TOTAL_REWARD * 2 + delegate_power_success(operators[0], accounts[2]) + delegate_power_success(operators[1], accounts[2]) + stake_manager.set_lp_rates([[4999, 1000], [5000, 10000], [15000, 1000]]) turn_round(consensuses, round_count=2) + get_tracker(accounts[0]) tx = stake_hub_claim_reward(accounts[0]) - acc_stake_amount0 = delegate_amount * 2 - acc_stake_amount1 = delegate_amount * 2 - if tests == 'proxy_undelegate': - acc_stake_amount0 = delegate_amount * 2 - delegate_amount // 2 - acc_stake_amount1 = acc_stake_amount0 - if tests == 'proxy_delegate': - acc_stake_amount1 = acc_stake_amount0 + delegate_amount // 2 - assert tx.events['claimedCoinReward'][0]['accStakedAmount'] == acc_stake_amount0 - assert tx.events['claimedBtcReward'][0]['accStakedAmount'] == btc_value * 2 - assert tx.events['claimedCoinReward'][1]['accStakedAmount'] == acc_stake_amount1 - assert tx.events['claimedBtcReward'][1]['accStakedAmount'] == btc_value * 2 - - -@pytest.mark.parametrize("tests", ['delegate', 'delegate_btc', 'transfer_btc', 'claim']) -def test_btc_staking_reward_settlement(set_candidate, pledge_agent, stake_hub, tests): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses) - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - turn_round(consensuses) - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value) - turn_round(consensuses, round_count=3) - # hardfork - tx = run_stake_operation(tests, operators[0], accounts[0], delegate_amount // 2, operators[2], tx_id, LOCK_SCRIPT, - btc_value=btc_value) - change_round = get_current_round() - events_name = ['storedCoinReward', 'storedBtcReward'] - if tests == 'claim': - events_name = ['claimedCoinReward', 'claimedBtcReward'] - assert tx.events[events_name[1]]['accStakedAmount'] == btc_value * 9 - assert stake_hub.getDelegatorMap(accounts[0])[0] == change_round - turn_round(consensuses, round_count=2) - tx = stake_hub_claim_reward(accounts[0]) - acc_stake_amount0 = btc_value * 3 - acc_stake_amount1 = btc_value * 3 - if tests == 'delegate_btc': - acc_stake_amount1 += btc_value - elif tests == 'transfer_btc': - acc_stake_amount0 -= btc_value - assert tx.events['claimedBtcReward'][0]['accStakedAmount'] == acc_stake_amount0 - assert tx.events['claimedBtcReward'][1]['accStakedAmount'] == acc_stake_amount1 - - -def test_coin_staking_reward_settlement(set_candidate, pledge_agent, stake_hub): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) - turn_round(consensuses) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) - turn_round(consensuses) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) - turn_round(consensuses, round_count=3) - delegate_coin_success(operators[2], accounts[0], delegate_amount) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() * TOTAL_REWARD // 2 * 4 - turn_round(consensuses, round_count=2) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() * TOTAL_REWARD // 2 * 2 - - -def test_unified_reward_settlement_for_different_validators(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[1], accounts[1], delegate_amount) - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - mock_delegate_btc_success(operators[1], accounts[1], btc_value) - turn_round(consensuses) - turn_round(consensuses, round_count=1) - tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) - assert stake_hub.getDelegatorMap(accounts[0])[0] == get_current_round() - assert tx.events['storedCoinReward']['accStakedAmount'] == delegate_amount * 2 - assert tx.events['storedBtcReward']['accStakedAmount'] == btc_value - - -@pytest.mark.parametrize("tests", ['proxy_delegate', 'proxy_undelegate', 'proxy_transfer']) -def test_reward_settlement_after_data_migration(stake_hub, set_candidate, tests): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100000 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - tx_ids = [] - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - tx_ids.append(tx_id0) - tx_ids.append(tx_id1) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - turn_round(consensuses, round_count=3) - tx = run_proxy_stake_operation(tests, operators[0], accounts[0], delegate_amount // 2, operators[2], operators) - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD // 2 * 2 - 1 - tracker = get_tracker(accounts[0]) - assert sum(stake_hub.getDelegatorMap(accounts[0])[1]) == TOTAL_REWARD // 2 * 3 - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 * 3 - turn_round(consensuses) - - -@pytest.mark.parametrize("tests", [ - 'proxy_delegate', 'proxy_undelegate', 'proxy_transfer'] - ) -def test_check_acc_stake_amount_after_data_migration(stake_hub, btc_stake, core_agent, set_candidate, tests): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100000 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - tx_ids = [] - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - tx_ids.append(tx_id0) - tx_ids.append(tx_id1) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - turn_round(consensuses, round_count=3) - tx = run_proxy_stake_operation(tests, operators[0], accounts[0], delegate_amount // 2, operators[2], operators) - assert btc_stake.receiptMap(tx_id0)['round'] == get_current_round() - 1 - assert core_agent.getDelegator(operators[0], accounts[0])['changeRound'] == get_current_round() - assert tx.events['storedCoinReward']['accStakedAmount'] == delegate_amount * 3 - assert tx.events['storedBtcReward']['accStakedAmount'] == btc_value * 3 - turn_round(consensuses, round_count=2) - - -@pytest.mark.parametrize("tests", [ - ['proxy_delegate', 500000, 750000, 5000, 7500], - ['proxy_undelegate', 250000, 250000, 2500, 2500], - ['proxy_transfer', 500000, 500000, 5000, 5000] -]) -def test_delegate_current_round_calculation(stake_hub, set_candidate, tests): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - tx_ids = [] - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - tx_ids.append(tx_id0) - tx_ids.append(tx_id1) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - lp_list = [[tests[3] - 1, 1000], [tests[3], 20000], [tests[3] + 1, 1000], [tests[4] - 1, 1000], [tests[4], 40000], - [tests[4] + 1, 1000]] - stake_manager.set_lp_rates(lp_list) - turn_round(consensuses) - run_proxy_stake_operation(tests[0], operators[0], accounts[0], delegate_amount // 2, operators[2], operators) - turn_round(consensuses, round_count=2) - tx = old_claim_reward_success(operators, accounts[0]) - expect_event(tx, 'claimedCoinReward', { - 'accStakedAmount': tests[1] - }) - expect_event(tx, 'claimedCoinReward', { - 'accStakedAmount': tests[2] - }, idx=1) - dualStakingRate = Utils.DENOMINATOR * 2 - if tests[3] == tests[4]: - dualStakingRate = Utils.DENOMINATOR * 4 - expect_event(tx, 'claimedBtcReward', { - 'accStakedAmount': btc_value, - 'dualStakingRate': dualStakingRate - }) - expect_event(tx, 'claimedBtcReward', { - 'accStakedAmount': btc_value, - 'dualStakingRate': Utils.DENOMINATOR * 4 - }, idx=1) - - -def test_btc_transfer_sets_acc_to_zero(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - tx_ids = [] - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_ids.append(tx_id0) - tx_ids.append(tx_id1) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - turn_round(consensuses, round_count=2) - transfer_btc_success(tx_id0, operators[1], accounts[0]) - turn_round(consensuses, round_count=2) - tx = stake_hub_claim_reward(accounts[0]) - expect_event(tx, 'claimedBtcReward', { - 'accStakedAmount': btc_value - }) - expect_event(tx, 'claimedBtcReward', { - 'accStakedAmount': btc_value * 2 - }, idx=1) - expect_event(tx, 'claimedRewardPerTx', { - 'txid': tx_id0, - 'accStakedAmount': 0 - }, idx=1) - - -def test_dual_staking_after_expired_btc(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - set_last_round_tag(3) - delegate_amount = 500000 - btc_value = 100 - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses, round_count=6) - tx = stake_hub_claim_reward(accounts[0]) - expect_event(tx, 'claimedCoinReward', { - 'accStakedAmount': delegate_amount * 5 - }, idx=1) - expect_event(tx, 'claimedBtcReward', { - 'accStakedAmount': btc_value * 3 - }, idx=1) - - -def test_check_acc_stake_amount_after_expired_btc_stake(stake_hub, btc_stake, core_agent, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100000 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - tx_ids = [] - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 4) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0], LOCK_TIME) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1], LOCK_TIME) - tx_ids.append(tx_id0) - tx_ids.append(tx_id1) - old_turn_round(consensuses, round_count=6) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - turn_round(consensuses, round_count=3) - tx = old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - assert tx.events['storedCoinReward']['accStakedAmount'] == delegate_amount * 3 - assert tx.events['storedBtcReward']['accStakedAmount'] == 0 - turn_round(consensuses, round_count=2) - - -def test_stake_btc_then_move_data(stake_hub, btc_stake, core_agent, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100000 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - old_turn_round(consensuses, round_count=6) - __init_hybrid_score_mock() - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) - assert tx.events['storedCoinReward']['accStakedAmount'] == 0 - assert tx.events['storedBtcReward']['accStakedAmount'] == btc_value - turn_round(consensuses, round_count=2) - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) - assert tx.events['storedCoinReward'][0]['accStakedAmount'] == delegate_amount - assert tx.events['storedBtcReward'][0]['accStakedAmount'] == btc_value * 2 - assert tx.events['storedCoinReward'][1]['accStakedAmount'] == delegate_amount - assert tx.events['storedBtcReward'][1]['accStakedAmount'] == btc_value * 2 - - -def test_reward_with_duration_discount_on_current_round(stake_hub, btc_stake, core_agent, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_lp_rates([[0, 5000]]) - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) - turn_round(consensuses) - btc_stake_duration = 30 - mock_delegate_btc_success(operators[0], accounts[0], btc_value, - block_timestamp=LOCK_TIME - btc_stake_duration * Utils.ROUND_INTERVAL) - turn_round(consensuses, round_count=2) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - stake_manager.set_tlp_rates([[0, 5000]]) - turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) - btc_reward = TOTAL_REWARD * 2 // 5 - assert tx.events['storedBtcReward']['unclaimedAmount'] == btc_reward // 2 - assert tx.events['storedBtcReward']['floatReward'] == -(btc_reward // 2 // 2) - btc_reward // 2 - - -def test_unmigrated_stake_coin(stake_hub, btc_stake, core_agent, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_turn_round(consensuses) - __init_hybrid_score_mock() - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) - assert tx.events['storedCoinReward']['accStakedAmount'] == 0 - assert tx.events['storedBtcReward']['accStakedAmount'] == btc_value - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - btc_reward = TOTAL_REWARD * 2 // 5 - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == btc_reward // 10 + TOTAL_REWARD * 2 + TOTAL_REWARD - btc_reward - turn_round(consensuses, round_count=2) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedCoinReward'][0]['accStakedAmount'] == delegate_amount - assert tx.events['claimedBtcReward'][0]['accStakedAmount'] == btc_value - assert tx.events['claimedCoinReward'][1]['accStakedAmount'] == delegate_amount * 2 - assert tx.events['claimedBtcReward'][1]['accStakedAmount'] == btc_value * 2 - - -@pytest.mark.parametrize("tests", [ - ['delegate', 'transfer', 4063 + 5416 * 2 // 3 // 2], - ['transfer', 'undelegate', 4063 // 2 + 5416 * 2 // 3 // 10], - ['undelegate', 'delegate', 4063 // 2 + 5416 * 2 // 3 // 10], - ['delegate_btc', 'transfer_btc', 4063 + 0], - ['transfer', 'undelegate', 'transfer_btc', 4063 // 2 + 0], -]) -def test_reconcile_diff_operations_in_current_round(stake_hub, btc_stake, core_agent, set_candidate, tests): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [2500, 5000], [2501, 10000]]) - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - for account in accounts[:2]: - mock_delegate_coin_success(operators[0], account, delegate_amount) - turn_round(consensuses) - tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value * 2) - mock_delegate_btc_success(operators[0], accounts[1], btc_value) - turn_round(consensuses, round_count=3) - for test in tests: - run_stake_operation(test, operators[0], accounts[0], delegate_amount // 2, operators[2], tx_id, - LOCK_SCRIPT, - btc_value=btc_value) - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == tests[-1] - turn_round(consensuses) - - -@pytest.mark.parametrize("tests", [ - {'validator_state': 'minor', 'coin_acc_amount': 500000 * 3, 'btc_acc_amount': 400, - 'account_reward': [6772 + 13545 * 6 // 10, (5418 - 1) * 2 // 3 * 2], - 'coin_acc_amount1': 500000 * 2, - 'btc_acc_amount1': 200 * 2, - 'account_reward1': [13545 * 6 // 10 // 2 * 2, 5417 * 2 // 3 * 2] - }, - {'validator_state': 'major', 'coin_acc_amount': 500000 * 3, 'btc_acc_amount': 400, - 'coin_acc_amount1': 500000 * 2, - 'btc_acc_amount1': 200 * 2, - 'account_reward': [6772, 0], - 'account_reward1': [13545 * 6 // 10 // 2, (5418 - 1) * 2 // 3] - } -]) -def test_operations_on_different_validator_states(stake_hub, btc_stake, candidate_hub, slash_indicator, core_agent, - set_candidate, - tests): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 10000]]) - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value * 2) - mock_delegate_btc_success(operators[0], accounts[1], btc_value) - turn_round(consensuses) - tx0 = None - if tests['validator_state'] == 'minor': - slash_threshold = slash_indicator.misdemeanorThreshold() - event_name = 'validatorMisdemeanor' - else: - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - for count in range(slash_threshold): - tx0 = slash_indicator.slash(consensuses[0]) - assert event_name in tx0.events - turn_round(consensuses, round_count=2) - tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) - undelegate_coin_success(operators[2], accounts[0], delegate_amount) - assert tx.events['storedCoinReward']['accStakedAmount'] == tests['coin_acc_amount'] - assert tx.events['storedBtcReward']['accStakedAmount'] == tests['btc_acc_amount'] - assert tx.events['storedCoinReward']['amount'] == tests['account_reward'][0] - assert tx.events['storedBtcReward']['amount'] == tests['account_reward'][1] - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == sum(tests['account_reward']) - required_margin = candidate_hub.requiredMargin() - candidate_hub.addMargin({'value': required_margin, 'from': operators[0]}) - turn_round(consensuses, round_count=2) - tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) - assert tx.events['storedCoinReward'][0]['accStakedAmount'] == tests['coin_acc_amount1'] // 2 - assert tx.events['storedBtcReward'][0]['accStakedAmount'] == tests['btc_acc_amount1'] // 2 - assert tx.events['storedCoinReward'][1]['accStakedAmount'] == tests['coin_acc_amount1'] // 2 - assert tx.events['storedBtcReward'][1]['accStakedAmount'] == tests['btc_acc_amount1'] // 2 - tracker.update_height() - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == sum(tests['account_reward1']) - - -@pytest.mark.parametrize("round_reward", - [[0, 13544], [1, 27090], - [2, (13545 + 6772 * 2 + 8127) + 5417 * 2 // 3 * 2], - [3, (13545 + 6772 * 2 + 8127) + ( - 5417 * 2 // 3 * 2) + 13545 * 6 // 10 // 2 * 2 + 5418 * 2 // 3 * 2]]) -def test_claim_reward_across_different_changeRound(stake_hub, set_candidate, round_reward): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 10000]]) - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - for account in accounts[:2]: - for op in operators[:2]: - mock_delegate_coin_success(op, account, delegate_amount) - turn_round(consensuses, round_count=2) - for index, account in enumerate(accounts[:2]): - for op in operators[:2]: - delegate_btc_value = btc_value - if index == 0: - delegate_btc_value = btc_value * 2 - mock_delegate_btc_success(op, account, delegate_btc_value) - turn_round(consensuses, round_count=round_reward[0]) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == round_reward[1] - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - account_reward = COIN_REWARD_NO_POWER // 2 * 2 + (BTC_REWARD_NO_POWER - 1) * 4 // 6 * 2 - if round_reward[0] == 0: - account_reward = TOTAL_REWARD // 2 * 2 - assert tracker.delta() == account_reward - - -def test_change_round_diff_with_power_stake(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 10000]]) - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - for account in accounts[:2]: - for op in operators[:2]: - mock_delegate_coin_success(op, account, delegate_amount) - turn_round(consensuses, round_count=2) - for account in accounts[:2]: - for op in operators[:2]: - mock_delegate_btc_success(op, account, btc_value) - delegate_power_success(operators[0], accounts[2]) - delegate_power_success(operators[1], accounts[2]) - turn_round(consensuses) - delegate_power_success(operators[0], accounts[2]) - delegate_power_success(operators[1], accounts[2]) - turn_round(consensuses) - delegate_coin_success(operators[2], accounts[0], delegate_amount) - undelegate_coin_success(operators[2], accounts[0], delegate_amount) - trackers = get_trackers(accounts[:3]) - stake_hub_claim_reward(accounts[:3]) - account_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) - assert trackers[0].delta() == account_reward - assert trackers[1].delta() == account_reward - assert trackers[2].delta() == TOTAL_REWARD // 6 * 2 - stake_manager.set_lp_rates([[1, 1000], [5000, 10000], [5001, 1000]]) - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - account_reward = TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) - assert trackers[0].delta() == account_reward - - -def test_upgrade_claim_reward_with_diff_change_round_btcLst(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 10000]]) - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - for account in accounts[:2]: - for op in operators[:2]: - mock_delegate_coin_success(op, account, delegate_amount) - turn_round(consensuses, round_count=2) - for account in accounts[:2]: - for op in operators[:2]: - mock_delegate_btc_success(op, account, btc_value) - delegate_power_success(operators[0], accounts[2]) - delegate_power_success(operators[1], accounts[2]) - turn_round(consensuses) - delegate_power_success(operators[0], accounts[2]) - delegate_power_success(operators[1], accounts[2]) - turn_round(consensuses) - trackers = get_trackers(accounts[:3]) - account_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) - turn_round(consensuses) - account_reward += TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) - turn_round(consensuses) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedBtcReward']['accStakedAmount'] == btc_value * 2 * 3 - assert tx.events['claimedCoinReward']['accStakedAmount'] == delegate_amount * 2 * 5 - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], delegate_amount), set_delegate(accounts[0], delegate_amount)], - "btc": [set_delegate(accounts[1], btc_value), set_delegate(accounts[0], btc_value)] - }, { - "address": operators[1], - "coin": [set_delegate(accounts[1], delegate_amount), set_delegate(accounts[0], delegate_amount)], - "btc": [set_delegate(accounts[1], btc_value), set_delegate(accounts[0], btc_value)] - }, { - "address": operators[2] - } - ], TOTAL_REWARD) - assert trackers[0].delta() == account_rewards[accounts[0]] + account_reward - turn_round(consensuses) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedBtcReward']['accStakedAmount'] == btc_value * 2 - assert tx.events['claimedCoinReward']['accStakedAmount'] == delegate_amount * 2 - - -def test_upgrade_same_change_round_claim_reward(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - for account in accounts[:2]: - delegate_coin_success(operators[0], account, delegate_amount) - turn_round(consensuses, round_count=2) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - assert stake_hub.getDelegatorMap(accounts[0])[1][0] == TOTAL_REWARD // 2 - delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT) - delegate_power_success(operators[0], accounts[2]) - turn_round(consensuses) - turn_round(consensuses) - stake_manager.set_lp_rates([[4999, 1000], [5000, 10000], [5001, 1000]]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], delegate_amount), set_delegate(accounts[0], delegate_amount)], - "power": [set_delegate(accounts[2], 1)], - "btc": [set_delegate(accounts[1], btc_value), set_delegate(accounts[0], btc_value)] - }, { - "address": operators[1], - }, { - "address": operators[2] - } - ], TOTAL_REWARD) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] + TOTAL_REWARD // 2 * 2 - - -def test_all_stakes_operation_and_reward_claim(stake_hub, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - for account in accounts[:2]: - for op in operators[:2]: - delegate_coin_success(op, account, delegate_amount) - turn_round(consensuses, round_count=3) - for account in accounts[:2]: - for op in operators[:2]: - delegate_btc_success(op, account, btc_value, LOCK_SCRIPT) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 2 - delegate_power_success(operators[0], accounts[2]) - delegate_power_success(operators[1], accounts[2]) - stake_manager.set_lp_rates([[4999, 1000], [5000, 10000], [5001, 1000]]) - turn_round(consensuses, round_count=2) - get_tracker(accounts[0]) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedCoinReward'][0]['accStakedAmount'] == delegate_amount * 2 - assert tx.events['claimedCoinReward'][1]['accStakedAmount'] == delegate_amount * 2 - assert tx.events['claimedBtcReward'][0]['accStakedAmount'] == 0 - assert tx.events['claimedBtcReward'][1]['accStakedAmount'] == btc_value * 2 - - -def test_validator_power_reward_on_upgrade(stake_hub, set_candidate): + assert tx.events['claimedRewardBtcTx'][0]['dualStakingRate'] == Utils.DENOMINATOR + assert tx.events['claimedRewardBtcTx'][1]['dualStakingRate'] == Utils.DENOMINATOR + + +def test_validator_power_reward_on_upgrade(stake_hub, set_candidate): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate @@ -1981,21 +1319,16 @@ def test_claim_reward_after_current_round_operation(stake_hub, set_candidate): delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=3) - stake_manager.set_lp_rates([[2500, 20000], [4999, 1000], [5000, 10000], [7500, 20000]]) + stake_manager.set_lp_rates([[0, 1000], [2499, 1000], [2500, 10000], [10001, 20000]]) delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) stake_hub_claim_reward(accounts[0]) turn_round(consensuses, round_count=2) - tx = undelegate_coin_success(operators[0], accounts[0], delegate_amount) - assert tx.events['storedBtcReward'][0]['accStakedAmount'] == btc_value - assert tx.events['storedCoinReward'][0]['accStakedAmount'] == delegate_amount - assert tx.events['storedBtcReward'][1]['accStakedAmount'] == btc_value * 2 - assert tx.events['storedBtcReward'][1]['dualStakingRate'] == Utils.DENOMINATOR * 2 - assert tx.events['storedCoinReward'][1]['accStakedAmount'] == delegate_amount + undelegate_coin_success(operators[0], accounts[0], delegate_amount) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) btc_reward = BTC_REWARD_NO_POWER - 1 coin_reward = COIN_REWARD_NO_POWER - 1 - assert tracker.delta() == (COIN_REWARD_NO_POWER + coin_reward) + btc_reward + btc_reward * 2 + assert tracker.delta() == btc_reward + BTC_REWARD_NO_POWER + coin_reward + COIN_REWARD_NO_POWER @pytest.mark.parametrize("claim", [True, False]) @@ -2021,24 +1354,14 @@ def test_proxy_claim_reward_after_current_round_operation(stake_hub, set_candida tx = old_claim_reward_success(operators, accounts[0]) else: tx = stake_hub_claim_reward(accounts[0]) - event_name = ['claimedCoinReward', 'claimedBtcReward'] + event_name = ['claimedCoinReward', 'claimedRewardBtcTx'] expect_event(tx, event_name[0], { - 'amount': TOTAL_REWARD, - 'accStakedAmount': delegate_amount - }) - expect_event(tx, event_name[1], { - 'amount': 0, - 'accStakedAmount': 0 + 'amount': COIN_REWARD_NO_POWER + TOTAL_REWARD, }) - expect_event(tx, event_name[0], { - 'amount': COIN_REWARD_NO_POWER, - 'accStakedAmount': delegate_amount - }, idx=1) expect_event(tx, event_name[1], { - 'amount': (BTC_REWARD_NO_POWER - 1) * 2, - 'accStakedAmount': btc_value, + 'reward': (BTC_REWARD_NO_POWER - 1) * 2, 'dualStakingRate': Utils.DENOMINATOR * 2, - }, idx=1) + }) assert tracker.delta() == TOTAL_REWARD + COIN_REWARD_NO_POWER + (BTC_REWARD_NO_POWER - 1) * 2 @@ -2089,7 +1412,7 @@ def test_calculateReward_success(btc_stake, stake_hub, core_agent, set_candidate assert stake_hub.getDelegatorMap(accounts[0])[0] == current_round tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 4 + actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 assert tracker0.delta() == actual_reward turn_round(consensuses) stake_hub.calculateReward(accounts[0]) @@ -2117,7 +1440,7 @@ def test_calculateReward_repeated(btc_stake, stake_hub, core_agent, set_candidat assert stake_hub.getDelegatorMap(accounts[0])[0] == current_round tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 4 + actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 stake_hub.calculateReward(accounts[0]) assert tracker0.delta() == actual_reward turn_round(consensuses) @@ -2141,13 +1464,7 @@ def test_calculateReward_with_latest_stake(btc_stake, stake_hub, core_agent, set delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) tx = stake_hub.calculateReward(accounts[0], {'from': accounts[1]}) - assert tx.events['storedCoinReward'][0]['accStakedAmount'] == delegate_amount - assert tx.events['storedCoinReward'][1]['accStakedAmount'] == delegate_amount - assert tx.events['storedBtcReward'][0]['accStakedAmount'] == 0 - assert tx.events['storedBtcReward'][1]['accStakedAmount'] == btc_value - assert tx.events['storedBtcReward'][1]['dualStakingRate'] == Utils.DENOMINATOR // 2 - assert tx.events['storedBtcReward'][0]['delegator'] == accounts[0] - assert tx.events['storedBtcReward'][1]['delegator'] == accounts[0] + assert tx.events['storedRewardBtcTx']['dualStakingRate'] == Utils.DENOMINATOR // 2 tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == TOTAL_REWARD * 2 + TOTAL_REWARD // 2 @@ -2186,9 +1503,9 @@ def test_calculateReward_after_current_round_claim(btc_stake, stake_hub, core_ag @pytest.mark.parametrize("round_count", - [[2, 13545 // 2 * 2, 1693], [3, 13545 + 6772, 6772 // 2], [4, 13545 * 2 - 1, 6772]]) + [[2, 13545 // 2 * 2, 6772 // 2], [3, 13545 + 6772, 6772], [4, 13545 * 2 - 1, 6772]]) def test_calculateReward_with_expired_btc(btc_stake, stake_hub, core_agent, set_candidate, round_count): - stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [7500, 5000], [7501, 20000]]) + stake_manager.set_lp_rates([[0, 1000], [2500, 2500], [5000, 5000], [7501, 20000]]) stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() operators, consensuses = set_candidate @@ -2203,9 +1520,8 @@ def test_calculateReward_with_expired_btc(btc_stake, stake_hub, core_agent, set_ delegate_btc_success(operators[1], accounts[1], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=round_count[0]) tracker0 = get_tracker(accounts[0]) - stake_hub_calculate_reward(accounts[0]) - rewards = [round_count[1], 0, - round_count[2]] + tx = stake_hub_calculate_reward(accounts[0]) + rewards = [round_count[1], 0, round_count[2]] __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), 'rewards': rewards}) stake_hub_claim_reward(accounts[0]) @@ -2363,102 +1679,8 @@ def test_calculateReward_after_operation(btc_stake, btc_agent, stake_hub, core_a stake_hub_calculate_reward(accounts[1]) stake_hub_claim_reward(accounts[:2]) assert trackers[0].delta() == COIN_REWARD_NO_POWER // 2 + COIN_REWARD_NO_POWER // 4 + ( - BTC_REWARD_NO_POWER - 1) // 2 * 2 - assert trackers[1].delta() == COIN_REWARD_NO_POWER // 2 - - -def test_calculateReward_without_data_migration(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): - stake_manager.set_lp_rates([[0, 10000], [1, 1000], [2500, 1000], [4999, 1000], [5000, 1000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses, round_count=2) - tx = __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1]) - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, btc_value * 2 * 5000) - turn_round(consensuses, round_count=2) - # delegate_coin_success(operators[0], accounts[0], delegate_amount) - stake_hub_calculate_reward(accounts[0]) - stake_hub_calculate_reward(accounts[1]) - btc_reward = TOTAL_REWARD // 4 + (BTC_REWARD_NO_POWER - 1) // 2 - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), - 'rewards': [0, 0, btc_reward]}) - __check_stake_hub_delegator_map(accounts[1], {'changeRound': get_current_round(), - 'rewards': [0, 0, btc_reward]}) - trackers = get_trackers(accounts[:2]) - assert trackers[0].delta() == 0 - assert trackers[1].delta() == 0 - turn_round(consensuses) - - -def test_calculateReward_after_data_migration(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): - stake_manager.set_lp_rates([[0, 1000], [2500, 1000], [4999, 1000], [5000, 10000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1]) - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, btc_value * 2 * 5000) - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses, round_count=2) - stake_hub_calculate_reward(accounts[0]) - coin_reward = COIN_REWARD_NO_POWER // 2 + TOTAL_REWARD // 2 // 2 - btc_reward = TOTAL_REWARD // 4 + (BTC_REWARD_NO_POWER - 1) // 2 - rewards = [coin_reward, 0, btc_reward] - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), - 'rewards': rewards}) - trackers = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == sum(rewards) - turn_round(consensuses) - - -def test_calculateReward_after_operation_before_upgrade(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): - stake_manager.set_lp_rates([[0, 1000], [2500, 10000], [4999, 1000], [5000, 10000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1]) - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, btc_value * 2 * 5000) - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses) - transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2) - undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2) - turn_round(consensuses) - stake_hub_calculate_reward(accounts[0]) - coin_reward = COIN_REWARD_NO_POWER // 4 - btc_reward = (BTC_REWARD_NO_POWER - 1) // 2 - rewards = [coin_reward + TOTAL_REWARD // 4, 0, btc_reward + TOTAL_REWARD // 4] - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), - 'rewards': rewards}) - trackers = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == sum(rewards) - turn_round(consensuses) + BTC_REWARD_NO_POWER - 1) // 2 * 2 + assert trackers[1].delta() == COIN_REWARD_NO_POWER // 2 def test_calculateReward_after_claim(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): @@ -2486,36 +1708,6 @@ def test_calculateReward_after_claim(btc_stake, btc_agent, stake_hub, core_agent assert tracker.delta() == COIN_REWARD_NO_POWER // 4 + (BTC_REWARD_NO_POWER - 1) // 2 -@pytest.mark.parametrize("calculate", [True, False]) -def test_calculateReward_with_no_operation_on_expired_btc(btc_stake, btc_agent, stake_hub, core_agent, set_candidate, - calculate): - stake_manager.set_lp_rates([[0, 1000], [10000, 5000], [10001, 8000], [12500, 10000], [12511, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - set_last_round_tag(3) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses) - mock_delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_TIME) - turn_round(consensuses, round_count=4) - actual_reward = TOTAL_REWARD * 5 + TOTAL_REWARD * 2 - dual_staking_rate = Utils.DENOMINATOR - if calculate: - tx = stake_hub_calculate_reward(accounts[0], accounts[3]) - actual_reward = TOTAL_REWARD * 5 + TOTAL_REWARD * 2 // 2 - rate = Utils.DENOMINATOR // 2 - assert tx.events['storedBtcReward']['dualStakingRate'] == rate - - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedBtcReward']['dualStakingRate'] == dual_staking_rate - assert tracker.delta() == actual_reward - - def test_delete_data_for_expired_btc_in_operation_round(btc_stake, btc_agent, stake_hub, core_agent, set_candidate, ): stake_manager.set_is_stake_hub_active(True) stake_manager.set_tlp_rates() @@ -2554,49 +1746,6 @@ def test_calculateReward_generates_rewards(btc_stake, btc_agent, stake_hub, core assert delegator_map == [get_current_round(), [TOTAL_REWARD, TOTAL_REWARD, TOTAL_REWARD]] -def test_help_call_calculateReward(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): - stake_manager.set_lp_rates([[0, 1000], [4999, 1000], [5000, 10000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1]) - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, btc_value * 2 * 5000) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - turn_round(consensuses) - transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2) - transfer_coin_success(operators[0], operators[1], accounts[1], delegate_amount // 2) - undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2) - undelegate_coin_success(operators[0], accounts[1], delegate_amount // 2) - stake_manager.set_lp_rates([[0, 1000], [2500, 10000], [2501, 1000]]) - - turn_round(consensuses) - stake_hub_calculate_reward(accounts[0], accounts[1]) - stake_hub_calculate_reward(accounts[1], accounts[2]) - coin_reward = COIN_REWARD_NO_POWER // 4 - btc_reward = (BTC_REWARD_NO_POWER - 1) // 2 - rewards = [coin_reward + TOTAL_REWARD // 4, 0, btc_reward + TOTAL_REWARD // 4] - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), - 'rewards': rewards}) - trackers = get_trackers(accounts[:3]) - stake_hub_claim_reward(accounts[0]) - stake_hub_claim_reward(accounts[1]) - stake_hub_claim_reward(accounts[2]) - assert trackers[0].delta() == sum(rewards) - assert trackers[1].delta() == sum(rewards) - assert trackers[2].delta() == 0 - turn_round(consensuses) - - def test_calculateReward_after_current_round_operation(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): stake_manager.set_lp_rates([[0, 1000], [5000, 10000], [5001, 1000]]) stake_manager.set_is_stake_hub_active(True) @@ -2639,70 +1788,24 @@ def test_manual_moveCoreData(pledge_agent, stake_hub, set_candidate): operators, consensuses = set_candidate old_turn_round() delegate_amount = 500000 - btc_value = 100 old_delegate_coin_success(operators[0], accounts[0], delegate_amount) old_delegate_coin_success(operators[0], accounts[1], delegate_amount) old_turn_round(consensuses) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) old_turn_round(consensuses) __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1]) - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, btc_value * 2 * 5000) + stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, 0) pledge_agent.moveCOREData(operators[0], accounts[0], {'from': accounts[1]}) pledge_agent.moveCOREData(operators[0], accounts[1], {'from': accounts[2]}) turn_round(consensuses, round_count=2) delegate_coin_success(operators[0], accounts[0], delegate_amount) - coin_reward = COIN_REWARD_NO_POWER // 2 + TOTAL_REWARD // 2 // 2 - btc_reward = TOTAL_REWARD // 4 + (BTC_REWARD_NO_POWER - 1) // 2 - rewards = [coin_reward, 0, btc_reward] - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), - 'rewards': rewards}) - trackers = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == sum(rewards) - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == COIN_REWARD_NO_POWER // 2 + (BTC_REWARD_NO_POWER - 1) // 2 - turn_round(consensuses) - - -def test_manual_moveCoreData_after_adding_stake(pledge_agent, stake_hub, set_candidate): - stake_manager.set_lp_rates([[0, 1000], [2500, 10000], [4999, 1000], [5000, 1000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_delegate_coin_success(operators[1], accounts[0], delegate_amount) - old_delegate_coin_success(operators[1], accounts[1], delegate_amount) - old_turn_round(consensuses) - tx_id0 = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_id1 = old_delegate_btc_success(btc_value, operators[0], accounts[1]) - tx_id2 = old_delegate_btc_success(btc_value, operators[1], accounts[0]) - tx_id3 = old_delegate_btc_success(btc_value, operators[1], accounts[1]) - old_turn_round(consensuses) - __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1, tx_id2, tx_id3]) - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, btc_value * 2 * 5000) - stake_hub.setCandidateScoresMap(operators[1], delegate_amount * 2, 0, btc_value * 2 * 5000) - pledge_agent.moveCOREData(operators[0], accounts[0], {'from': accounts[1]}) - turn_round(consensuses, round_count=2) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - coin_reward = COIN_REWARD_NO_POWER // 2 + TOTAL_REWARD // 2 // 2 - btc_reward = TOTAL_REWARD // 4 * 2 + (BTC_REWARD_NO_POWER - 1) // 2 * 2 - rewards = [coin_reward, 0, btc_reward] - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), - 'rewards': rewards}) + rewards = [TOTAL_REWARD - 1, 0, 0] + __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), 'rewards': rewards}) trackers = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) assert trackers.delta() == sum(rewards) turn_round(consensuses) stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == COIN_REWARD_NO_POWER // 2 + (BTC_REWARD_NO_POWER - 1) // 2 * 2 + assert trackers.delta() == TOTAL_REWARD // 2 turn_round(consensuses) @@ -2723,12 +1826,12 @@ def test_rewardMap_with_historical_rewards(btc_stake, core_agent, hash_power_age mock_transfer_btc_success(tx_id, operators[2]) assert core_agent.rewardMap(accounts[0]) == [COIN_REWARD_NO_POWER, delegate_amount] assert btc_stake.rewardMap(accounts[0]) == [BTC_REWARD_NO_POWER - 1, 0, btc_value] - assert hash_power_agent.rewardMap(accounts[0]) == [TOTAL_REWARD, 1] + assert hash_power_agent.rewardMap(accounts[0]) == [TOTAL_REWARD, 0] delegate_coin_success(operators[1], accounts[0], delegate_amount) - assert stake_hub.getDelegatorMap(accounts[0])[1] == [COIN_REWARD_NO_POWER, TOTAL_REWARD, BTC_REWARD_NO_POWER - 1] + assert stake_hub.getDelegatorMap(accounts[0])[1] == [COIN_REWARD_NO_POWER, TOTAL_REWARD, 0] tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 * 2 + TOTAL_REWARD + tx = stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == COIN_REWARD_NO_POWER + TOTAL_REWARD def __move_btc_data(tx_ids): @@ -2821,3 +1924,327 @@ def stake_hub_calculate_reward(account, sender=None): sender = accounts[0] tx = STAKE_HUB.calculateReward(account, {'from': sender}) return tx + + + +def test_dual_staking_various_operations_flow(btc_stake, stake_hub, core_agent, set_candidate): + + stake_manager.set_lp_rates([[0, 1000], [2500, 5000], [5000, 10000], [10000, 15000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + delegate_amount = 500000 + btc_value = 100 + + + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[2], delegate_amount) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round() + + delegate_coin_success(operators[1], accounts[0], delegate_amount) + delegate_coin_success(operators[1], accounts[2], delegate_amount) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[1], accounts[2], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses) + + transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 4) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == TOTAL_REWARD // 2 - 1 + turn_round(consensuses) + + tx = transfer_coin_success(operators[2], operators[0], accounts[0], delegate_amount // 4) + undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) + stake_hub_claim_reward(accounts[0]) + expect_event(tx, 'storedRewardBtcTx', + {'dualStakingRate': 15000}) + expect_event(tx, 'storedRewardBtcTx', + {'dualStakingRate': 1000}, idx=1) + turn_round(consensuses, round_count=2) + + tx = transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 4) + expect_event(tx, 'storedRewardBtcTx', + {'dualStakingRate': 10000}) + expect_event(tx, 'storedRewardBtcTx', + {'dualStakingRate': 5000}, idx=1) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() > 0 + turn_round(consensuses) + + +def test_dual_staking_with_time_locking_flow(btc_stake, stake_hub, core_agent, set_candidate): + + stake_manager.set_lp_rates([[0, 500], [500, 4000], [3000, 8000], [6000, 15000], [12000, 20000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates([[0, 3000], [1 * Utils.MONTH_TIMESTAMP, 5000], [5 * Utils.MONTH_TIMESTAMP, 10000]]) + stake_manager.set_is_btc_stake_active(True) + operators, consensuses = set_candidate + + turn_round() + delegate_amount = 800000 + btc_value = 200 + + + stake_duration_short = 60 + stake_duration_long = 240 + + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=stake_duration_short) + turn_round() + + + delegate_coin_success(operators[1], accounts[0], delegate_amount) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=stake_duration_long) + turn_round() + + + transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2) + undelegate_coin_success(operators[1], accounts[0], delegate_amount // 4) + turn_round(consensuses) + + tracker = get_tracker(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 15000, 'lockLengthRate': 10000}) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 4000, 'lockLengthRate': 5000}, idx=1) + + btc_reward = BTC_REWARD_NO_POWER - 1 + coin_reward = COIN_REWARD_NO_POWER - 1 + actual_coin_reward = (coin_reward * (delegate_amount - delegate_amount // 4) // delegate_amount) + coin_reward + tx_reward0 = btc_reward // 2 * 4000 // 10000 + tx_reward1 = btc_reward * 15000 // 10000 + assert tracker.delta() == tx_reward0 + tx_reward1 + actual_coin_reward + + + +def test_dual_staking_multiple_core_aggregation(btc_stake, stake_hub, core_agent, set_candidate): + + stake_manager.set_lp_rates([[0, 1000], [2000, 4000], [6000, 8000], [12000, 15000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + base_amount = 200000 + btc_value = 100 + + + delegate_coin_success(operators[0], accounts[0], base_amount) + delegate_coin_success(operators[1], accounts[0], base_amount) + delegate_coin_success(operators[2], accounts[0], base_amount) + turn_round() + + + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses, round_count=2) + + + tracker = get_tracker(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 8000}) + + + +@pytest.mark.parametrize("scenario", [ + "multiple_btc_single_core", + "multiple_core_single_btc", + "multiple_btc_multiple_core" +]) +def test_dual_staking_multiple_stakes_combinations(btc_stake, stake_hub, core_agent, set_candidate, scenario): + + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + base_core_amount = 3000 + base_btc_value = 1 + stake_manager.set_lp_rates([[0, 1000], [500, 3000], [3000, 6000], [5000, 12000], [12000, 18000]]) + tracker = get_tracker(accounts[0]) + if scenario == "multiple_btc_single_core": + delegate_coin_success(operators[0], accounts[0], base_core_amount * 6) + turn_round() + delegate_btc_success(operators[0], accounts[0], base_btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[1], accounts[0], base_btc_value * 2, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[2], accounts[0], base_btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses, round_count=2) + tx = stake_hub_claim_reward(accounts[0]) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 18000}) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 6000}, idx=1) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 1000}, idx=2) + assert tracker.delta() > 0 + elif scenario == "multiple_core_single_btc": + stake_manager.set_lp_rates([[0, 1000], [500, 3000], [3000, 6000], [4000, 12000], [12000, 18000]]) + delegate_coin_success(operators[0], accounts[0], base_core_amount) + delegate_coin_success(operators[1], accounts[0], base_core_amount * 2) + delegate_coin_success(operators[2], accounts[0], base_core_amount * 3) + turn_round() + delegate_btc_success(operators[0], accounts[0], base_btc_value * 4, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses, round_count=2) + tx = stake_hub_claim_reward(accounts[0]) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 12000}) + assert tracker.delta() > 0 + + else: # multiple_btc_multiple_core + stake_manager.set_lp_rates([[0, 1000], [1500, 3000], [3000, 6000], [5000, 12000], [12000, 18000]]) + delegate_coin_success(operators[0], accounts[0], base_core_amount) + delegate_coin_success(operators[1], accounts[0], base_core_amount * 2) + turn_round() + delegate_btc_success(operators[0], accounts[0], base_btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[1], accounts[0], base_btc_value * 2, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses, round_count=2) + tx = stake_hub_claim_reward(accounts[0]) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 6000}) + expect_event(tx, 'claimedRewardBtcTx', {'dualStakingRate': 6000}, idx=1) + assert tracker.delta() > 0 + turn_round(consensuses, round_count=2) + + + +@pytest.mark.parametrize("slash_type", ["minor", "felony"]) +def test_dual_staking_with_slashing_current_round(btc_stake, stake_hub, core_agent, slash_indicator, set_candidate, + slash_type): + + stake_manager.set_lp_rates([[0, 1000], [10000, 10000], [30000, 30000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + delegate_amount = 500000 + btc_value = 100 + + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[1], accounts[0], delegate_amount) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses) + slash_validator(consensuses[0], slash_type) + slash_validator(consensuses[1], slash_type) + turn_round(consensuses) + tracker = get_tracker(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) + btc_reward = BTC_REWARD_NO_POWER - 1 + actual_reward1 = btc_reward + btc_reward * 1000 // 10000 + if slash_type == "minor": + assert tx.events['claimedReward']['amounts'][2] == actual_reward1 + else: + assert 'claimedReward' not in tx.events + turn_round(consensuses) + stake_hub_claim_reward(accounts[0]) + + +def test_dual_staking_legacy_contract_move_flow(pledge_agent, stake_hub, core_agent, set_candidate): + + stake_manager.set_lp_rates([[0, 1000], [1, 2000], [4000, 10000], [10000, 25000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + old_turn_round() + delegate_amount = 400000 + btc_value = 100 + old_delegate_coin_success(operators[0], accounts[0], delegate_amount) + old_delegate_coin_success(operators[0], accounts[2], delegate_amount) + old_turn_round(consensuses, round_count=2) + __init_hybrid_score_mock() + turn_round(consensuses) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses, round_count=2) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == BTC_REWARD_NO_POWER // 2 * 1000 // 10000 + turn_round(consensuses) + pledge_agent.moveCOREData(operators[0], accounts[0]) + stake_hub_claim_reward(accounts[0]) + turn_round(consensuses) + tx = stake_hub_claim_reward(accounts[0]) + assert tx.events['claimedReward']['amounts'][2] == (BTC_REWARD_NO_POWER - 1) // 2 + + + +def test_dual_staking_adjustment_and_flow(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): + + stake_manager.set_lp_rates([[0, 1000], [4000, 10000], [4001, 1000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + delegate_amount = 400000 + btc_value = 100 + + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round(consensuses, round_count=2) + stake_hub.calculateReward(accounts[0]) + stake_manager.set_lp_rates([[0, 1000], [4000, 5000], [4001, 1000]]) + delegate_coin_success(operators[0], accounts[0], delegate_amount // 2) + delegate_coin_success(operators[1], accounts[0], delegate_amount) + transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 3) + turn_round(consensuses) + tracker = get_tracker(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == (BTC_REWARD_NO_POWER - 1) + (BTC_REWARD_NO_POWER - 1) // 2 + ( + COIN_REWARD_NO_POWER - 1) * 2 + + +@pytest.mark.parametrize("round_count", [1, 2]) +def test_dual_staking_with_current_round_btc_addition_and_transfer(btc_stake, btc_agent, stake_hub, core_agent, + set_candidate, round_count): + + stake_manager.set_lp_rates([[0, 1000], [1, 5000], [10000, 10000], [10001, 1000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + delegate_amount = 5000 + initial_btc_value = 1 + additional_btc_value = 1 + + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) + tx_id1 = delegate_btc_success(operators[0], accounts[0], initial_btc_value, LOCK_SCRIPT, stake_duration=YEAR) + turn_round() + tx_id2 = delegate_btc_success(operators[1], accounts[0], additional_btc_value, LOCK_SCRIPT, stake_duration=YEAR) + transfer_btc_success(tx_id1, operators[2], accounts[0]) + turn_round(consensuses, round_count=round_count) + + tracker = get_tracker(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) + if round_count == 1: + assert tx.events['claimedReward']['amounts'][2] == 0 + else: + assert tx.events['claimedReward']['amounts'][2] == TOTAL_REWARD + TOTAL_REWARD * 1000 // 10000 + + +def test_dual_staking_with_current_round_power_reward(btc_stake, btc_agent, stake_hub, core_agent, hash_power_agent, + set_candidate): + + stake_manager.set_lp_rates([[0, 1000], [1, 5000], [10000, 10000], [10001, 1000]]) + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_tlp_rates() + operators, consensuses = set_candidate + + turn_round() + delegate_amount = 5000 + initial_btc_value = 1 + additional_btc_value = 1 + + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) + tx_id1 = delegate_btc_success(operators[0], accounts[0], initial_btc_value, LOCK_SCRIPT, stake_duration=YEAR) + delegate_power_success(operators[0], accounts[0], 1) + turn_round() + turn_round(consensuses) + + get_tracker(accounts[0]) + tx = stake_hub_claim_reward(accounts[0]) + assert tx.events['claimedReward']['amounts'] == [TOTAL_REWARD // 2, TOTAL_REWARD // 2 // 3, + TOTAL_REWARD // 2 // 3 * 2] diff --git a/tests/test_hash_power_agent.py b/tests/test_hash_power_agent.py index f9232c48e..13a34ad0e 100644 --- a/tests/test_hash_power_agent.py +++ b/tests/test_hash_power_agent.py @@ -75,11 +75,7 @@ def test_distribute_reward_success(hash_power_agent): reward = reward_list[index] // sum_stake_amounts[index] * staked_amounts[index] __check_reward_power(accounts[index], { 'reward': reward, - 'accStakedAmount': staked_amounts[index] }) - __check_reward_power(accounts[5], { - 'accStakedAmount': 4 - }) def test_distribute_reward_with_new_validator(hash_power_agent): @@ -95,13 +91,10 @@ def test_distribute_reward_with_new_validator(hash_power_agent): hash_power_agent.distributeReward(validators, reward_list, round_tag + 1) for index, v in enumerate(validators): reward = reward_list[index] // staked_amounts[index] * staked_amounts[index] - acc_stake_amount = staked_amounts[index] if index == 0: reward = 0 - acc_stake_amount = 0 __check_reward_power(accounts[index], { 'reward': reward, - 'accStakedAmount': acc_stake_amount }) @@ -148,12 +141,12 @@ def test_power_valid_for_one_round(btc_stake, set_candidate, hash_power_agent, r operators, consensuses = set_candidate delegate_power_success(operators[0], accounts[0], power_value) turn_round() - turn_round(consensuses) + tx = turn_round(consensuses) turn_round(consensuses, round_count=round_count) update_system_contract_address(hash_power_agent, stake_hub=accounts[0]) - reward_sum, unclaimed, acc_staked_amount = hash_power_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value - assert acc_staked_amount == power_value + reward_sum, unclaimed = hash_power_agent.claimReward(accounts[0], 0, get_current_round() - 1, False).return_value + total_reward = 13545 + assert reward_sum == total_reward @pytest.mark.parametrize("claim", [True, False]) @@ -185,9 +178,8 @@ def test_power_claim_reward_success(hash_power_agent, claim): }, idx=2) for index, v in enumerate(validators): tx = hash_power_agent.claimReward(accounts[index], 0, get_current_round() - 1, claim) - reward_sum, unclaimed, acc_staked_amount = tx.return_value + reward_sum, unclaimed = tx.return_value reward = reward_list[index] // sum_stake_amounts[index] * staked_amounts[index] - actual_acc_staked_amount = staked_amounts[index] if claim: event_name = 'claimedHashReward' else: @@ -195,41 +187,15 @@ def test_power_claim_reward_success(hash_power_agent, claim): expect_event(tx, event_name, { 'delegator': accounts[index], 'amount': reward, - 'accStakedAmount': actual_acc_staked_amount }) assert reward_sum == reward - assert actual_acc_staked_amount == actual_acc_staked_amount assert unclaimed == 0 - __check_reward_power(accounts[5], { - 'accStakedAmount': 4 - }) def test_claim_power_no_reward_success(hash_power_agent): update_system_contract_address(hash_power_agent, stake_hub=accounts[0]) - reward_sum, unclaimed, acc_staked_amount = hash_power_agent.claimReward(accounts[0], 0, - get_current_round() - 1, False).return_value + reward_sum, unclaimed = hash_power_agent.claimReward(accounts[0], 0, get_current_round() - 1, False).return_value assert reward_sum == 0 - assert acc_staked_amount == 0 - - -def test_acc_stake_amount_success(hash_power_agent, set_candidate, stake_hub): - turn_round() - operators, consensuses = set_candidate - staked_amounts = [6, 12, 15] - for index, v in enumerate(operators): - delegate_power_success(v, accounts[index], staked_amounts[index]) - turn_round() - update_system_contract_address(hash_power_agent, stake_hub=accounts[0]) - reward_sum, unclaimed, acc_staked_amount = hash_power_agent.claimReward(accounts[0], 0, - get_current_round() - 1, True).return_value - assert acc_staked_amount == 0 - update_system_contract_address(hash_power_agent, stake_hub=stake_hub) - turn_round(consensuses) - update_system_contract_address(hash_power_agent, stake_hub=accounts[0]) - reward_sum, unclaimed, acc_staked_amount = hash_power_agent.claimReward(accounts[0], 0, - get_current_round() - 1, True).return_value - assert acc_staked_amount == staked_amounts[0] def test_only_stake_hub_can_call_claim_reward(hash_power_agent): diff --git a/tests/test_pledge_agent.py b/tests/test_pledge_agent.py index 715334341..e5b701d57 100644 --- a/tests/test_pledge_agent.py +++ b/tests/test_pledge_agent.py @@ -376,455 +376,15 @@ def test_claim_reward_after_calculate_reward(pledge_agent, candidate_hub): }) -def test_claim_btc_reward(pledge_agent, btc_stake): - operator = accounts[1] - consensus = register_candidate(operator=operator) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - agent = operator - delegator = accounts[0] - script = "0x1234" - lock_time = int(time.time()) + 3600 - fee = 0 - pledge_agent.delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) - __old_turn_round() - __old_turn_round([consensus]) - __init_hybrid_score_mock() - tx = pledge_agent.claimBtcReward([tx_id]) - expect_event(tx, "claimedReward") - - -@pytest.mark.parametrize("success", [True, False]) -def test_move_btc_data_then_claim_btc_reward(pledge_agent, btc_stake, success): - operator = accounts[1] - consensus = register_candidate(operator=operator) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - agent = operator - delegator = accounts[0] - script = "0x1234" - lock_time = int(time.time()) + 3600 - fee = 0 - pledge_agent.delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) - __old_turn_round() - __old_turn_round([consensus]) - __init_hybrid_score_mock() - tx_ids = [] - if success: - tx_ids.append(tx_id) - __move_btc_data([tx_id]) - with brownie.reverts("btc tx not found"): - pledge_agent.claimBtcReward(tx_ids) - else: - tx_ids.append(tx_id) - tx = pledge_agent.claimBtcReward(tx_ids) - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD // 2 * 2 - __move_btc_data([tx_id]) - with brownie.reverts("btc tx not found"): - pledge_agent.claimBtcReward(tx_ids) - tx = stake_hub_claim_reward(accounts[0]) - assert len(tx.events) == 4 - turn_round([consensus]) - with brownie.reverts("btc tx not found"): - pledge_agent.claimBtcReward(tx_ids) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 * 2 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_only_btc_stake_can_call(pledge_agent, btc_stake): - with brownie.reverts("the msg sender must be bitcoin stake contract"): - pledge_agent.moveBtcData(random_btc_tx_id(), {'from': accounts[0]}) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_move_btc_data(pledge_agent, btc_stake): - operator = accounts[1] - consensus = register_candidate(operator=operator) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - agent = operator - delegator = accounts[0] - script = "0x1234" - lock_time = int(time.time()) + 3600 - fee = 0 - pledge_agent.delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) - __old_turn_round() - __old_turn_round([consensus]) - update_system_contract_address(pledge_agent, btc_stake=accounts[0]) - candidate, delegator, amount, round, lockTime = pledge_agent.moveBtcData(tx_id, {'from': accounts[0]}).return_value - assert candidate == agent - assert delegator == accounts[0] - assert amount == btc_value - assert lock_time // Utils.ROUND_INTERVAL * Utils.ROUND_INTERVAL == lockTime - assert round == get_current_round() - 1 - assert pledge_agent.rewardMap(delegator) == TOTAL_REWARD // 2 * 2 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_tx_id_not_found(pledge_agent, btc_stake): - tx_id = random_btc_tx_id() - update_system_contract_address(pledge_agent, btc_stake=accounts[0]) - candidate, delegator, amount, round, lock_time = pledge_agent.moveBtcData(tx_id, {'from': accounts[0]}).return_value - assert candidate == delegator == ZERO_ADDRESS - assert amount == round == lock_time == 0 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_multiple_txids_end_round(pledge_agent, btc_stake, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - lock_time = int(time.time()) + 3600 - set_round_tag(lock_time // Utils.ROUND_INTERVAL - 3) - __old_turn_round() - round_tag = get_current_round() - btc_value = 1000000 - script = "0x1234" - fee = 0 - tx_ids0 = [] - tx_ids1 = [] - for index, op in enumerate(operators): - tx_id0 = random_btc_tx_id() - tx_id1 = random_btc_tx_id() - pledge_agent.delegateBtcMock(tx_id0, btc_value + index, op, accounts[0], script, lock_time, fee) - pledge_agent.delegateBtcMock(tx_id1, btc_value + index, op, accounts[1], script, lock_time, fee) - tx_ids0.append(tx_id0) - tx_ids1.append(tx_id1) - __old_turn_round() - assert pledge_agent.getAgent2valueMap(round_tag + 2, operators[0]) == btc_value * 2 - assert len(pledge_agent.getAgentAddrList(round_tag + 2)) == 3 - update_system_contract_address(pledge_agent, btc_stake=accounts[0]) - candidate, delegator, amount, _, _ = pledge_agent.moveBtcData(tx_ids0[0], {'from': accounts[0]}).return_value - assert pledge_agent.getAgent2valueMap(round_tag + 2, operators[0]) == btc_value - assert len(pledge_agent.getAgentAddrList(round_tag + 2)) == 3 - candidate, delegator, amount, round, _ = pledge_agent.moveBtcData(tx_ids1[0], {'from': accounts[0]}).return_value - assert pledge_agent.getAgent2valueMap(round_tag + 2, operators[0]) == 0 - assert len(pledge_agent.getAgentAddrList(round_tag + 2)) == 2 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_move_transferred_btc(pledge_agent, btc_stake, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - btc_value = 1000000 - script = "0x1234" - fee = 0 - tx_id = random_btc_tx_id() - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], accounts[0], script, LOCK_TIME, fee) - __old_turn_round() - old_transfer_btc_success(tx_id, operators[1]) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - update_system_contract_address(pledge_agent, btc_stake=accounts[0]) - candidate, delegator, amount, _, _ = pledge_agent.moveBtcData(tx_id, {'from': accounts[0]}).return_value - assert pledge_agent.rewardMap(delegator) == TOTAL_REWARD - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_upgrade_and_migrate_btc_data_successfully(pledge_agent, btc_stake, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - btc_value = 1000000 - script = "0x1234" - fee = 0 - tx_id0 = random_btc_tx_id() - pledge_agent.delegateBtcMock(tx_id0, btc_value, operators[0], accounts[0], script, LOCK_TIME, fee) - tx_id1 = random_btc_tx_id() - pledge_agent.delegateBtcMock(tx_id1, btc_value, operators[0], accounts[1], script, LOCK_TIME, fee) - __old_turn_round() - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id0, tx_id1]) - __check_btc_tx_map_info(tx_id0, { - 'amount': btc_value, - 'outputIndex': 0, - 'blockTimestamp': 0, - 'lockTime': LOCK_TIME // Utils.ROUND_INTERVAL * Utils.ROUND_INTERVAL, - 'usedHeight': 0, - }) - __check_btc_tx_map_info(tx_id1, { - 'amount': btc_value, - 'outputIndex': 0, - 'blockTimestamp': 0, - 'lockTime': LOCK_TIME // Utils.ROUND_INTERVAL * Utils.ROUND_INTERVAL, - 'usedHeight': 0, - }) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("move", [True, False]) -@pytest.mark.parametrize("candidate", - [[0, 1, 0, 2], [0, 0, 1, 2], [2, 1, 0, 0], [0, 1, 2, 0], [0, 0, 0, 0], - [1, 0, 0, 1], [1, 0, 0, 0], [0, 2, 0, 0]]) -def test_btc_stake_with_duplicate_txid(pledge_agent, btc_stake, set_candidate, move, candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - btc_value = 1000000 - script = "0x1234" - fee = 0 - tx_ids = [] - for r in range(5): - tx_id0 = random_btc_tx_id() - pledge_agent.delegateBtcMock(tx_id0, btc_value, operators[0], accounts[r], script, LOCK_TIME, fee) - tx_ids.append(tx_id0) - __old_turn_round() - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - new_tx_ids = [] - if move: - __move_btc_data([tx_ids[0]]) - for i in candidate: - new_tx_ids.append(tx_ids[i]) - __move_btc_data(new_tx_ids) - for tx_id in new_tx_ids: - __check_btc_tx_map_info(tx_id, { - 'amount': btc_value, - 'outputIndex': 0, - 'blockTimestamp': 0, - 'lockTime': LOCK_TIME // Utils.ROUND_INTERVAL * Utils.ROUND_INTERVAL, - 'usedHeight': 0, - }) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("round_count", [0, 1, 2, 3]) -def test_move_expired_btc_data(pledge_agent, core_agent, btc_agent, btc_stake, candidate_hub, set_candidate, - round_count): - operators, consensuses = set_candidate - tx_id = random_btc_tx_id() - btc_value = 1000 - delegator = accounts[0] - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], delegator, script, LOCK_TIME, fee) - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], 10000, True) - __old_turn_round(consensuses, round_count=5) - __init_hybrid_score_mock() - btc_stake.moveData([tx_id], {'from': accounts[0]}) - old_claim_reward_success(operators, accounts[1]) - turn_round(consensuses, round_count=round_count) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[1]) - assert tracker1.delta() == TOTAL_REWARD * round_count - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - old_claim_reward_success(operators, accounts[0]) - assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("round_count", [1, 2, 4]) -def test_move_btc_data_after_multiple_expired_rounds(pledge_agent, core_agent, btc_agent, btc_stake, candidate_hub, - set_candidate, round_count): - operators, consensuses = set_candidate - pledge_agent.setBtcFactor(2e4 * 1e10 // 5) - tx_id = random_btc_tx_id() - btc_value = int(1e8) - delegate_amount = int(20000e18) - delegator = accounts[0] - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], delegator, script, LOCK_TIME, fee) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, True) - __old_turn_round() - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - btc_stake.moveData([tx_id], {'from': accounts[0]}) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - assert tracker0.delta() == TOTAL_REWARD // 2 * 2 - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_move_data_with_expired_btc(pledge_agent, core_agent, btc_agent, btc_stake, candidate_hub, - set_candidate): - operators, consensuses = set_candidate - tx_id0 = random_btc_tx_id() - tx_id1 = random_btc_tx_id() - btc_value = 1000 - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id0, btc_value, operators[0], accounts[0], script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(tx_id1, btc_value, operators[0], accounts[1], script, LOCK_TIME, fee) - old_delegate_coin_success(operators[0], accounts[2], 10000, True) - old_delegate_coin_success(operators[0], accounts[3], 10000, True) - __old_turn_round() - __old_turn_round(consensuses, round_count=5) - __init_hybrid_score_mock() - tx = btc_stake.moveData([tx_id0], {'from': accounts[0]}) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - turn_round(consensuses) - pledge_agent.claimBtcReward([tx_id1], {'from': accounts[1]}) - assert tracker1.delta() == TOTAL_REWARD // 2 - old_claim_reward_success(operators, accounts[0]) - assert tracker0.delta() == TOTAL_REWARD // 2 - tracker2 = get_tracker(accounts[2]) - old_claim_reward_success(operators, accounts[2]) - old_claim_reward_success(operators, accounts[3]) - assert tracker2.delta() == TOTAL_REWARD // 2 + TOTAL_REWARD // 2 * 3 + TOTAL_REWARD // 2 - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == 0 - tracker3 = get_tracker(accounts[3]) - stake_hub_claim_reward(accounts[2:4]) - assert tracker2.delta() == TOTAL_REWARD // 2 - assert tracker3.delta() == TOTAL_REWARD // 2 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("tests", [ - {'round': 0, 'btc_receipt_round': 20100, 'btc_amount': 1000, 'stakedAmount': 2000}, - {'round': 1, 'btc_receipt_round': 20101, 'btc_amount': 1000, 'stakedAmount': 2000}, - {'round': 2, 'btc_receipt_round': 0, 'btc_amount': 0, 'stakedAmount': 0}, - {'round': 3, 'btc_receipt_round': 0, 'btc_amount': 0, 'stakedAmount': 0} -]) -def test_move_expired_btc_in_same_round(pledge_agent, core_agent, btc_agent, btc_stake, candidate_hub, - set_candidate, tests): - operators, consensuses = set_candidate - tx_id0 = random_btc_tx_id() - tx_id1 = random_btc_tx_id() - tx_ids = [tx_id0, tx_id1] - btc_value = 1000 - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id0, btc_value, operators[0], accounts[0], script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(tx_id1, btc_value, operators[0], accounts[1], script, LOCK_TIME, fee) - __old_turn_round() - round_count = tests['round'] - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - btc_stake.moveData(tx_ids, {'from': accounts[0]}) - for tx_id in tx_ids: - __check_btc_receipt_map(tx_id, { - 'round': tests['btc_receipt_round'] - }) - __check_btc_tx_map_info(tx_id, { - 'amount': tests['btc_amount'] - }) - __check_btc_candidate_map_info(operators[0], { - 'stakedAmount': tests['stakedAmount'], - 'realtimeAmount': tests['stakedAmount'] - }) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("tests", [ - {'round': 0, 'old_reward': 4515, 'actual_reward': 4515 * 2}, - {'round': 1, 'old_reward': 4515 * 2, 'actual_reward': 4515 * 2 + 13545}, - {'round': 2, 'old_reward': 4515 * 2, 'actual_reward': 4515 * 2 + 13545 * 2}, - {'round': 3, 'old_reward': 4515 * 2, 'actual_reward': 4515 * 2 + 13545 * 3}, -]) -def test_claim_reward_after_moving_expired_btc(pledge_agent, core_agent, btc_agent, btc_stake, candidate_hub, - set_candidate, tests): - operators, consensuses = set_candidate - tx_id0 = random_btc_tx_id() - tx_id1 = random_btc_tx_id() - tx_id2 = random_btc_tx_id() - tx_ids = [tx_id0, tx_id1, tx_id2] - btc_value = 1000 - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id0, btc_value, operators[0], accounts[0], script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(tx_id1, btc_value, operators[0], accounts[1], script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(tx_id2, btc_value, operators[0], accounts[2], script, - LOCK_TIME + Utils.ROUND_INTERVAL * 20, fee) - __old_turn_round() - round_count = tests['round'] - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - btc_stake.moveData(tx_ids, {'from': accounts[0]}) - turn_round(consensuses) - trackers = get_trackers(accounts[:3]) - old_claim_reward_success(operators, accounts[:2]) - for tracker in trackers[:2]: - assert tracker.delta() == tests['old_reward'] - turn_round(consensuses) - old_claim_reward_success(operators, accounts[:3]) - stake_hub_claim_reward(accounts[:3]) - if round_count > 1: - assert trackers[0].delta() == 0 - assert trackers[1].delta() == 0 - assert trackers[2].delta() == tests['actual_reward'] - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("round_count", [0, 1, 2, 3]) -def test_move_candidate_after_moving_btc_data(pledge_agent, validator_set, candidate_hub, set_candidate, round_count): - operators, consensuses = set_candidate - tx_id = random_btc_tx_id() - tx_id1 = random_btc_tx_id() - btc_value = 1000 - delegator = accounts[0] - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], delegator, script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(tx_id1, btc_value, operators[0], accounts[1], script, - LOCK_TIME + Utils.ROUND_INTERVAL * 100, fee) - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - __move_btc_data([tx_id, tx_id1]) - pledge_agent.moveCandidateData([operators[0]]) - __check_btc_candidate_map_info(operators[0], {}) - turn_round(consensuses) - consensuses.append(register_candidate(operator=operators[0])) - tracker = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - turn_round(consensuses) - turn_round(consensuses, round_count=round_count) - stake_hub_claim_reward(accounts[0]) - stake_hub_claim_reward(accounts[1]) - assert tracker1.delta() == TOTAL_REWARD * round_count - assert tracker.delta() == 0 - turn_round(consensuses, round_count=2) - - def test_stake_new_then_move_core_data(pledge_agent, validator_set, candidate_hub, set_candidate): operators, consensuses = set_candidate - tx_id = random_btc_tx_id() - tx_id1 = random_btc_tx_id() - btc_value = 1000 core_value = 10000 - script = "0x1234" - fee = 0 set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 10) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], accounts[1], script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(tx_id1, btc_value, operators[0], accounts[0], script, LOCK_TIME, fee) old_delegate_coin_success(operators[0], accounts[0], core_value) old_delegate_coin_success(operators[0], accounts[1], core_value) __old_turn_round() __old_turn_round(consensuses) __init_hybrid_score_mock() - __move_btc_data([tx_id, tx_id1]) delegate_coin_success(operators[0], accounts[0], core_value) tracker = get_tracker(accounts[0]) tx = old_delegate_coin_success(operators[0], accounts[0], core_value, False) @@ -839,8 +399,7 @@ def test_stake_new_then_move_core_data(pledge_agent, validator_set, candidate_hu turn_round(consensuses) _, _, account_rewards, _ = parse_delegation([{ "address": operators[0], - "coin": [set_delegate(accounts[0], core_value * 3), set_delegate(accounts[1], core_value)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[1], btc_value)] + "coin": [set_delegate(accounts[0], core_value * 3), set_delegate(accounts[1], core_value)] }], TOTAL_REWARD) stake_hub_claim_reward(accounts[0]) assert tracker.delta() == account_rewards[accounts[0]] @@ -927,20 +486,9 @@ def test_move_candidate_data(pledge_agent, core_agent, btc_stake, btc_agent, can operator = accounts[1] consensus = register_candidate(operator=operator) pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - agent = operator - delegator = accounts[0] - script = "0x1234" - lock_time = int(time.time()) + 3600 - fee = 0 - pledge_agent.delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) - __old_turn_round() assert operator in candidate_hub.getCandidates() __old_turn_round([consensus]) - pledge_agent.moveCandidateData([operator]) agent = pledge_agent.agentsMap(operator) assert agent[-1] is True @@ -949,14 +497,6 @@ def test_move_candidate_data(pledge_agent, core_agent, btc_stake, btc_agent, can assert candidate_in_core_agent[0] == MIN_INIT_DELEGATE_VALUE assert candidate_in_core_agent[1] == MIN_INIT_DELEGATE_VALUE - candidate_in_btc_stake = btc_stake.candidateMap(operator) - assert candidate_in_btc_stake[0] == btc_value - assert candidate_in_btc_stake[1] == btc_value - - candidate_in_btc_agent = btc_agent.candidateMap(operator) - assert candidate_in_btc_agent[1] == btc_value - - def test_validator_cancel_registration_in_second_round(pledge_agent, set_candidate, validator_set, slash_indicator, candidate_hub): candidate_hub.setValidatorCount(1) @@ -1029,162 +569,25 @@ def test_deregister_multiple_rounds_then_move_candidate(pledge_agent, validator_ turn_round(consensuses) -@pytest.mark.parametrize("round_count", [0, 1, 2, 3]) -def test_revert_after_handling_btc_data(pledge_agent, validator_set, candidate_hub, - set_candidate, round_count): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - for op in operators[:3]: - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - tx_id = random_btc_tx_id() - btc_value = 1000 - delegator = accounts[0] - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], delegator, script, LOCK_TIME, fee) - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - __old_turn_round(consensuses, round_count=round_count) - assert len(validator_set.getValidators()) == 2 - __init_hybrid_score_mock() - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': 0 - }) - __move_btc_data([tx_id]) - pledge_agent.moveCandidateData([operators[0]]) - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - if round_count > 1: - btc_value = 0 - __check_btc_candidate_map_info(operators[0], { - 'stakedAmount': 0, - 'realtimeAmount': btc_value - }) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - - -def test_repeated_move_for_cancelled_validator_registration(pledge_agent, validator_set, candidate_hub, - set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - for op in operators[:3]: - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - assert len(validator_set.getValidators()) == 2 - __init_hybrid_score_mock() - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': 0 - }) - pledge_agent.moveCandidateData([operators[0]]) - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - pledge_agent.moveCandidateData([operators[0]]) - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - turn_round(consensuses, round_count=4) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - - -def test_move_expired_btc_stake(pledge_agent, core_agent, btc_agent, candidate_hub, set_candidate): - operators, consensuses = set_candidate - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - delegator = accounts[0] - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[1], delegator, script, LOCK_TIME, fee) - __old_turn_round() - __old_turn_round(consensuses, round_count=2) - pledge_agent.moveCandidateData(operators) - for op in operators: - agent_map = pledge_agent.agentsMap(op) - assert agent_map['moved'] is False - - -def test_move_candidate_no_stake(pledge_agent, core_agent, btc_agent, candidate_hub, set_candidate): +def test_single_stake_data_move_success(pledge_agent, set_candidate): operators, consensuses = set_candidate + for index, op in enumerate(operators): + old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE + index) __old_turn_round() - __old_turn_round(consensuses, round_count=2) pledge_agent.moveCandidateData(operators) - for op in operators: - agent_map = pledge_agent.agentsMap(op) - assert agent_map['moved'] is False - - -def test_move_candidate_all_moved(pledge_agent, set_candidate): - operators, consensuses = set_candidate for index, op in enumerate(operators): - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE) - __old_turn_round() - __old_turn_round(consensuses, round_count=2) - pledge_agent.moveCandidateData(operators[:2]) - for op in operators[:2]: agent_map = pledge_agent.agentsMap(op) assert agent_map['moved'] is True __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE - }) - __check_candidate_map_info(operators[2], { - 'amount': 0, - 'realtimeAmount': 0 - }) - pledge_agent.moveCandidateData([operators[2]]) - for op in operators: - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE + 'amount': MIN_INIT_DELEGATE_VALUE + index, + 'realtimeAmount': MIN_INIT_DELEGATE_VALUE + index }) -@pytest.mark.parametrize("coin", [True, False]) -def test_single_stake_data_move_success(pledge_agent, set_candidate, coin): - operators, consensuses = set_candidate - btc_value = 1000000 - for index, op in enumerate(operators): - if coin: - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE + index) - else: - old_delegate_btc_success(btc_value + index, op, accounts[1]) - __old_turn_round() - pledge_agent.moveCandidateData(operators) - for index, op in enumerate(operators): - agent_map = pledge_agent.agentsMap(op) - assert agent_map['moved'] is True - if coin: - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE + index, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE + index - }) - else: - __check_btc_candidate_map_info(op, { - 'stakedAmount': btc_value + index, - 'realtimeAmount': btc_value + index - }) - - def test_move_candidate_success(pledge_agent, set_candidate): operators, consensuses = set_candidate - btc_value = 1000000 for index, op in enumerate(operators): old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE + index) - old_delegate_btc_success(btc_value + index, op, accounts[1]) __old_turn_round() __old_turn_round(consensuses) pledge_agent.moveCandidateData(operators) @@ -1195,32 +598,6 @@ def test_move_candidate_success(pledge_agent, set_candidate): 'amount': MIN_INIT_DELEGATE_VALUE + index, 'realtimeAmount': MIN_INIT_DELEGATE_VALUE + index }) - __check_btc_candidate_map_info(op, { - 'stakedAmount': btc_value + index, - 'realtimeAmount': btc_value + index - }) - - -def test_get_expire_list(pledge_agent, core_agent, candidate_hub, set_candidate): - operators, consensuses = set_candidate - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - delegator = accounts[0] - script = "0x1234" - fee = 0 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 3) - pledge_agent.delegateBtcMock(tx_id, btc_value, operators[0], delegator, script, LOCK_TIME, fee) - pledge_agent.delegateBtcMock(random_btc_tx_id(), btc_value, operators[1], delegator, script, LOCK_TIME, fee) - expire_list = pledge_agent.getExpireList(get_current_round() + 3) - assert expire_list == [operators[0], operators[1]] - __old_turn_round() - __old_turn_round(consensuses, round_count=2) - pledge_agent.moveCandidateData(operators) - for op in operators: - agent_map = pledge_agent.agentsMap(op) - assert agent_map['moved'] is False - expire_list = pledge_agent.getExpireList(get_current_round() + 3) - assert len(expire_list) == 0 def test_move_core_data(pledge_agent, core_agent): @@ -1271,7 +648,6 @@ def test_move_core_success(pledge_agent, core_agent, set_candidate, round): 'candidate': operators[0], 'delegator': accounts[0], 'reward': TOTAL_REWARD * round, - 'accStakedAmount': MIN_INIT_DELEGATE_VALUE * 2 * round, }) else: assert 'storedReward' not in tx.events @@ -1282,7 +658,7 @@ def test_move_core_success(pledge_agent, core_agent, set_candidate, round): 'transferredAmount': 0, }) reward_map = __get_reward_map_info(accounts[0]) - assert reward_map == [TOTAL_REWARD * round, MIN_INIT_DELEGATE_VALUE * 2 * round] + assert reward_map == [TOTAL_REWARD * round, 0] __check_old_delegate_info(operators[0], accounts[0], { 'changeRound': 0 }) @@ -1301,14 +677,12 @@ def test_move_core_success_after_staking(pledge_agent, core_agent, set_candidate expect_event(tx, 'storedReward', { 'candidate': operators[0], 'delegator': accounts[0], - 'reward': TOTAL_REWARD + TOTAL_REWARD // 2, - 'accStakedAmount': MIN_INIT_DELEGATE_VALUE * 3, + 'reward': TOTAL_REWARD + TOTAL_REWARD // 2 }) expect_event(tx, 'storedReward', { 'candidate': operators[0], 'delegator': accounts[0], - 'reward': TOTAL_REWARD * 2 + TOTAL_REWARD // 2, - 'accStakedAmount': MIN_INIT_DELEGATE_VALUE * 4, + 'reward': TOTAL_REWARD * 2 + TOTAL_REWARD // 2 }, idx=1) @@ -1412,6 +786,7 @@ def test_cancel_move_data_after_transfer(pledge_agent, core_agent, set_candidate def test_move_core_data_check_acc_stake_amount(pledge_agent, stake_hub, core_agent, set_candidate): operators, consensuses = set_candidate old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + old_delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) __old_turn_round() old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) old_undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) @@ -1424,19 +799,29 @@ def test_move_core_data_check_acc_stake_amount(pledge_agent, stake_hub, core_age }) turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, True) - assert reward_map == [TOTAL_REWARD, 0, MIN_INIT_DELEGATE_VALUE] + reward_map = core_agent.claimReward.call(accounts[0], True) + assert reward_map == [TOTAL_REWARD // 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] update_system_contract_address(core_agent, stake_hub=stake_hub) turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, True) - assert reward_map == [TOTAL_REWARD * 2, 0, MIN_INIT_DELEGATE_VALUE * 2] + reward_map = core_agent.claimReward.call(accounts[0], True) + assert reward_map == [TOTAL_REWARD // 2 * 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] update_system_contract_address(core_agent, stake_hub=stake_hub) stake_hub_claim_reward(accounts[0]) turn_round(consensuses) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, True) - assert reward_map == [TOTAL_REWARD, 0, MIN_INIT_DELEGATE_VALUE] + reward_map = core_agent.claimReward.call(accounts[0], True) + assert reward_map == [TOTAL_REWARD // 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] + update_system_contract_address(core_agent, stake_hub=stake_hub) + old_claim_reward_success(operators, accounts[0]) + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward_map = core_agent.claimReward.call(accounts[0], True) + assert reward_map == [0, 0, 0] + update_system_contract_address(core_agent, stake_hub=stake_hub) + turn_round(consensuses) + update_system_contract_address(core_agent, stake_hub=accounts[0]) + reward_map = core_agent.claimReward.call(accounts[0], True) + assert reward_map == [TOTAL_REWARD // 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] def test_stake_current_round_move_core_no_reward(pledge_agent, stake_hub, core_agent, set_candidate): @@ -1451,9 +836,9 @@ def test_stake_current_round_move_core_no_reward(pledge_agent, stake_hub, core_a pledge_agent.moveCOREData(operators[0], accounts[1]) turn_round(consensuses, round_count=1) update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[1], 0, get_current_round() - 1, True) - assert reward_map == [TOTAL_REWARD, 0, MIN_INIT_DELEGATE_VALUE * 2] - reward_map = core_agent.claimReward.call(accounts[0], 0, get_current_round() - 1, True) + reward_map = core_agent.claimReward.call(accounts[1], True) + assert reward_map == [TOTAL_REWARD, MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE * 2] + reward_map = core_agent.claimReward.call(accounts[0], True) assert reward_map == [0, 0, 0] update_system_contract_address(core_agent, stake_hub=stake_hub) tracker0 = get_tracker(accounts[0]) @@ -1550,10 +935,12 @@ def test_batch_move_core(pledge_agent, stake_hub, core_agent, set_candidate): for op in operators: pledge_agent.moveCOREData(op, account, {'from': accounts[2]}) turn_round(consensuses) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedCoinReward']['accStakedAmount'] == delegate_amount * 5 + delegate_amount * 2 - tx = stake_hub_claim_reward(accounts[1]) - assert tx.events['claimedCoinReward']['accStakedAmount'] == delegate_amount * 5 * 3 + tracker0 = get_tracker(accounts[0]) + tracker1 = get_tracker(accounts[1]) + stake_hub_claim_reward(accounts[0]) + stake_hub_claim_reward(accounts[1]) + assert tracker0.delta() == (TOTAL_REWARD + TOTAL_REWARD // 2) * 5 - 2 + assert tracker1.delta() == (TOTAL_REWARD + TOTAL_REWARD // 2) * 5 - 2 turn_round(consensuses) @@ -1562,26 +949,16 @@ def test_get_stake_info(pledge_agent): consensus = register_candidate(operator=operator) pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) delegate_power_success(operator, accounts[0], 10) - tx_id = "88c233d8d6980d2c486a055c804544faa8de93eadc4a00d5bd075d19f3190b4d" - btc_value = 1000000 - agent = operator - delegator = accounts[0] - script = "0x1234" - lock_time = int(time.time()) + 3600 - fee = 0 - pledge_agent.delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) __old_turn_round() __check_old_agent_map_info(operator, { 'power': int(10e18), - 'coin': MIN_INIT_DELEGATE_VALUE, - 'btc': btc_value, + 'coin': MIN_INIT_DELEGATE_VALUE }) stake_info = pledge_agent.getStakeInfo([operator]) assert stake_info[1][0] == 10 __old_turn_round([consensus]) stake_info = pledge_agent.getStakeInfo([operator]) assert stake_info[0][0] == MIN_INIT_DELEGATE_VALUE - assert stake_info[2][0] == btc_value def test_get_power_stake_info(pledge_agent): @@ -1592,19 +969,10 @@ def test_get_power_stake_info(pledge_agent): delegate_power_success(operator, accounts[0], power_value) delegate_power_success(operator, accounts[0], power_value, 1) delegate_power_success(operator, accounts[0], power_value, 2) - tx_id = random_btc_tx_id() - btc_value = 1000000 - agent = operator - delegator = accounts[0] - script = "0x1234" - lock_time = int(time.time()) + 3600 - fee = 0 - pledge_agent.delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) __old_turn_round() __check_old_agent_map_info(operator, { 'power': int(10e18), - 'coin': MIN_INIT_DELEGATE_VALUE, - 'btc': btc_value, + 'coin': MIN_INIT_DELEGATE_VALUE }) stake_info = pledge_agent.getStakeInfo([operator]) assert stake_info[1][0] == 10 @@ -1612,7 +980,6 @@ def test_get_power_stake_info(pledge_agent): stake_info = pledge_agent.getStakeInfo([operator]) assert stake_info[0][0] == MIN_INIT_DELEGATE_VALUE assert stake_info[1][0] == power_value - assert stake_info[2][0] == btc_value def test_get_delegator_success(pledge_agent, set_candidate): @@ -1988,46 +1355,6 @@ def test_init_hybrid_score_success(): __check_candidate_amount_map_info(operators[i], [core_amount, core_amount, 0, 0]) -def test_init_hybrid_score_success_with_btc_stake(): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - btc_value = 100 - candidate_size = 3 - for i in range(candidate_size - 1): - old_delegate_coin_success(operators[i], accounts[i], MIN_INIT_DELEGATE_VALUE * 5 + i) - old_delegate_btc_success(btc_value + i, operators[i], accounts[i]) - __old_turn_round() - operators1, consensuses1 = __register_candidates(accounts[4:5]) - operators.append(operators1[0]) - consensuses.append(consensuses1[0]) - old_delegate_coin_success(operators[2], accounts[2], MIN_INIT_DELEGATE_VALUE * 5 + 2) - for i in range(candidate_size): - coin = delegate_amount + i - btc = btc_value + i - if operators[i] == operators[-1]: - coin = 0 - btc = 0 - __check_old_agent_map_info(operators[i], { - 'totalDeposit': delegate_amount + i, - 'power': 0, - 'coin': coin, - 'btc': btc, - 'totalBtc': btc, - 'moved': False, - }) - __init_hybrid_score_mock() - for i in range(candidate_size): - core_amount = delegate_amount + i - btc_amount = int((btc_value + i) * 2e14) - if operators[i] == operators[-1]: - core_amount = 0 - btc_amount = 0 - __check_candidate_amount_map_info(operators[i], - [core_amount + btc_amount, core_amount, 0, - btc_amount]) - - def test_move_agent_success(pledge_agent, validator_set, stake_hub, core_agent): delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 operators, consensuses = __register_candidates(accounts[2:4]) @@ -2266,99 +1593,10 @@ def test_upgrade_claim_reward_after_skip_round(pledge_agent, validator_set, stak assert tracker.delta() == actual_reward -@pytest.mark.parametrize("old_claim", [True, False]) -@pytest.mark.parametrize("round_count", [0, 1, 2]) @pytest.mark.parametrize("tests", [ - {'delegator': ['tr', 'de'], 'actual_reward': 13545, 'inter_round_reward': 54180}, - {'delegator': ['de', 'de'], 'actual_reward': 27090, 'inter_round_reward': 54180}, - {'delegator': ['tr'], 'actual_reward': 13545, 'inter_round_reward': 40635}, - {'delegator': ['de', 'tr'], 'actual_reward': 27090, 'inter_round_reward': 67725}, -]) -def test_claim_btc_reward_correct(pledge_agent, validator_set, stake_hub, tests, set_candidate, round_count, old_claim): - btc_value = 2000 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for op in operators[:2]: - tx_id = old_delegate_btc_success(btc_value, op, accounts[0]) - tx_ids.append(tx_id) - __old_turn_round(consensuses) - for d in tests['delegator']: - if d == 'de': - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_ids.insert(0, tx_id) - elif d == 'tr': - old_transfer_btc_success(tx_ids[0], operators[2]) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - turn_round(consensuses, round_count=round_count) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - if old_claim: - old_claim_reward_success(operators, accounts[0]) - else: - stake_hub_claim_reward(accounts[0]) - actual_reward = tests['actual_reward'] - if round_count == 0: - actual_reward = 0 - if round_count > 1: - actual_reward = tests['inter_round_reward'] - assert tracker.delta() == actual_reward - - -@pytest.mark.parametrize("tests", [ - {'delegator': ['un', 'de'], 'btc': ['tr', 'de']}, - {'delegator': ['de', 'un'], 'btc': ['de', 'tr']}, - {'delegator': ['tr', 'un', 'de', 'un'], 'btc': ['tr']}, - {'delegator': ['de', 'tr', 'de', 'un'], 'btc': []}, - {'delegator': ['un', 'un', 'de', 'un'], 'btc': []}, - {'delegator': ['un', 'un', 'de', 'tr'], 'btc': []}, -]) -def test_claim_reward_after_multiple_stake_migrations(pledge_agent, validator_set, stake_hub, set_candidate, tests): - btc_value = 1e8 - delegate_amount = 2e18 - undelegate_amount = delegate_amount // 2 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - delegate_power_success(operators[0], accounts[2], 1) - delegate_power_success(operators[1], accounts[2], 1) - delegate_power_success(operators[2], accounts[2], 1) - for op in operators[:2]: - tx_id = old_delegate_btc_success(btc_value, op, accounts[0]) - tx_ids.append(tx_id) - old_delegate_coin_success(op, accounts[1], delegate_amount) - __old_turn_round(consensuses) - for d in tests['btc']: - if d == 'de': - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_ids.insert(0, tx_id) - elif d == 'tr': - old_transfer_btc_success(tx_ids[0], operators[2]) - for d in tests['delegator']: - if d == 'de': - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - elif d == 'un': - old_undelegate_coin_success(operators[0], accounts[1], undelegate_amount) - elif d == 'tr': - old_transfer_coin_success(operators[0], operators[2], accounts[1], undelegate_amount) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - trackers = [] - for i in range(3): - new_reward = get_tracker(accounts[i]) - trackers.append(new_reward) - old_reward = __get_old_reward(operators, accounts[i]) - old_claim_reward_success(operators, accounts[i]) - assert new_reward.delta() == old_reward - turn_round(consensuses, round_count=2) - - -@pytest.mark.parametrize("tests", [ - {'delegator': ['tr', 'un'], 'btc': ['tr', 'de']}, - {'delegator': ['tr', 'de', 'un', 'un', 'un'], 'btc': ['tr', 'de']}, - {'delegator': ['tr', 'un', 'tr', 'un'], 'btc': []}, + {'delegator': ['tr', 'un']}, + {'delegator': ['tr', 'de', 'un', 'un', 'un']}, + {'delegator': ['tr', 'un', 'tr', 'un']}, ]) def test_cancel_claim_reward_after_transfer(pledge_agent, validator_set, stake_hub, set_candidate, tests): btc_value = 1e8 @@ -2366,21 +1604,12 @@ def test_cancel_claim_reward_after_transfer(pledge_agent, validator_set, stake_h undelegate_amount = delegate_amount // 2 operators, consensuses = set_candidate __old_turn_round() - tx_ids = [] delegate_power_success(operators[0], accounts[2], 1) delegate_power_success(operators[1], accounts[2], 1) delegate_power_success(operators[2], accounts[2], 1) for op in operators[:2]: - tx_id = old_delegate_btc_success(btc_value, op, accounts[0]) - tx_ids.append(tx_id) old_delegate_coin_success(op, accounts[1], delegate_amount) __old_turn_round(consensuses) - for d in tests['btc']: - if d == 'de': - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - tx_ids.insert(0, tx_id) - elif d == 'tr': - old_transfer_btc_success(tx_ids[0], operators[2]) for d in tests['delegator']: if d == 'de': old_delegate_coin_success(operators[2], accounts[1], delegate_amount) @@ -2390,7 +1619,6 @@ def test_cancel_claim_reward_after_transfer(pledge_agent, validator_set, stake_h old_transfer_coin_success(operators[0], operators[2], accounts[1], undelegate_amount) __old_turn_round(consensuses) __init_hybrid_score_mock() - __move_btc_data(tx_ids) trackers = [] for i in range(3): new_reward = get_tracker(accounts[i]) @@ -2401,38 +1629,6 @@ def test_cancel_claim_reward_after_transfer(pledge_agent, validator_set, stake_h turn_round(consensuses, round_count=2) -def test_repeat_claim_btc_reward_after_move_btc_data(pledge_agent, set_candidate): - btc_value = 2000 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id]) - __check_old_reward(operators, accounts[0]) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD - - -def test_claim_reward_after_repeat_move_btc_data(pledge_agent, set_candidate): - btc_value = 2000 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id]) - __move_btc_data([tx_id]) - __check_old_reward(operators, accounts[0]) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - __move_btc_data([tx_id]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD - - def test_power_claim_reward_success(pledge_agent, set_candidate): operators, consensuses = set_candidate __old_turn_round() @@ -2779,38 +1975,17 @@ def test_stake_immediately_after_upgrade(stake_hub, core_agent, set_candidate): __old_turn_round() old_delegate_coin_success(operators[0], accounts[0], delegate_amount) old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - tx_id = old_delegate_btc_success(btc_value, operators[1], accounts[2]) old_delegate_coin_success(operators[1], accounts[3], delegate_amount) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __move_btc_data([tx_id]) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD // 3 - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - assert tracker0.delta() == TOTAL_REWARD // 2 * 2 + TOTAL_REWARD // 3 - - -def test_btc_claim_reward_current_round_after_move_data(stake_hub, pledge_agent, core_agent, set_candidate): - btc_value = 1e8 - delegate_amount = 10000e18 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses, round_count=1) + __old_turn_round(consensuses, round_count=2) __init_hybrid_score_mock() - __move_btc_data([tx_id]) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - tx = turn_round(consensuses, round_count=1) - assert tx.events['roundReward'][0]['amount'][0] == TOTAL_REWARD // 3 + delegate_coin_success(operators[0], accounts[0], delegate_amount) + turn_round(consensuses, round_count=2) tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) + assert tracker0.delta() == TOTAL_REWARD // 3 + old_claim_reward_success(operators, accounts[0]) old_claim_reward_success(operators, accounts[1]) - assert tracker0.delta() == TOTAL_REWARD // 3 * 2 + assert tracker0.delta() == TOTAL_REWARD // 2 * 2 + TOTAL_REWARD // 3 def test_power_claim_reward_current_round_after_move_data(stake_hub, pledge_agent, core_agent, set_candidate): @@ -2830,129 +2005,18 @@ def test_power_claim_reward_current_round_after_move_data(stake_hub, pledge_agen assert tracker0.delta() == TOTAL_REWARD // 2 -def test_stake_btc_immediately_after_upgrade(stake_hub, pledge_agent, core_agent, set_candidate): - btc_value = 100 - delegate_amount = 1000 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, 0, btc_value * 10) - __move_btc_data([tx_id]) - __check_old_reward(operators, accounts[0]) - __check_old_reward(operators, accounts[1], TOTAL_REWARD - TOTAL_REWARD // 2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - stake_hub_claim_reward(accounts[:2]) - old_claim_reward_success(operators, accounts[1]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[0], btc_value)] - }], TOTAL_REWARD) - assert tracker0.delta() == account_rewards[accounts[0]] + TOTAL_REWARD // 2 - assert tracker1.delta() == account_rewards[accounts[1]] + TOTAL_REWARD // 2 - - -def test_multiple_stakes_after_upgrade(stake_hub, pledge_agent, core_agent, set_candidate): - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - delegate_power_success(operators[0], accounts[2], power_value, stake_round=1) - delegate_power_success(operators[0], accounts[2], power_value, stake_round=2) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data([tx_id]) - __check_old_reward(operators, accounts[0]) - __check_old_reward(operators, accounts[1], TOTAL_REWARD - TOTAL_REWARD // 2) - tracker = get_trackers(accounts[:3]) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - turn_round(consensuses, round_count=1) - stake_hub_claim_reward(accounts[0]) - old_claim_reward_success(operators, accounts[1]) - stake_hub_claim_reward(accounts[2]) - for t in tracker: - assert t.delta() == TOTAL_REWARD // 3 - turn_round(consensuses, round_count=1) - stake_hub_claim_reward(accounts[:3]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[0], btc_value)] - }], TOTAL_REWARD) - for index, t in enumerate(tracker): - assert t.delta() == account_rewards[accounts[index]] - - -def test_claim_reward_after_multiple_rounds_upgrade(stake_hub, pledge_agent, core_agent, set_candidate): - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - delegate_power_success(operators[0], accounts[2], power_value, stake_round=0) - delegate_power_success(operators[0], accounts[2], power_value, stake_round=1) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data([tx_id]) - turn_round(consensuses, round_count=1) - tracker = get_trackers(accounts[:3]) - old_claim_reward_success(operators, accounts[1]) - turn_round(consensuses, round_count=2) - _, _, account_rewards0, _ = parse_delegation([{ - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value)] - }], TOTAL_REWARD) - _, _, account_rewards1, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value)] - }], TOTAL_REWARD) - stake_hub_claim_reward(accounts[:3]) - for index, t in enumerate(tracker): - round_reward0 = account_rewards0[accounts[index]] - round_reward1 = account_rewards1.get(accounts[index], 0) - assert t.delta() == round_reward0 + round_reward1 + TOTAL_REWARD // 3 - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value)] - }], TOTAL_REWARD) - stake_hub_claim_reward(accounts[:3]) - for index, t in enumerate(tracker[:3]): - assert t.delta() == account_rewards.get(t.address, 0) - - def test_claim_old_contract_reward_after_move_data(stake_hub, pledge_agent, core_agent, set_candidate): - btc_value = 100 delegate_amount = 1000 power_value = 1 operators, consensuses = set_candidate __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) + old_delegate_coin_success(operators[0], accounts[0], delegate_amount) old_delegate_coin_success(operators[0], accounts[1], delegate_amount) delegate_power_success(operators[0], accounts[2], power_value, stake_round=0) delegate_power_success(operators[0], accounts[2], power_value, stake_round=1) __old_turn_round(consensuses) __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data([tx_id]) + stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, power_value * 1000, 0) turn_round(consensuses, round_count=1) trackers = get_trackers(accounts[:2]) old_claim_reward_success(operators, accounts[0]) @@ -2973,39 +2037,10 @@ def test_proxy_staking_success(stake_hub, pledge_agent, core_agent, set_candidat power_value = 1 operators, consensuses = set_candidate __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data([tx_id]) - turn_round(consensuses, round_count=1) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - if tests == 'delegate': - tx = old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) - assert 'delegatedCoin' in tx.events - elif tests == 'undelgate': - tx = old_undelegate_coin_success(operators[0], accounts[1], 0, old=False) - assert 'undelegatedCoin' in tx.events - elif tests == 'transfer': - tx = old_transfer_coin_success(operators[0], operators[1], accounts[1], delegate_amount, old=False) - assert 'transferredCoin' in tx.events - - -@pytest.mark.parametrize("tests", ['delegate', 'undelgate', 'transfer']) -def test_proxy_staking_success(stake_hub, pledge_agent, core_agent, set_candidate, tests): - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_id = old_delegate_btc_success(btc_value, operators[0], accounts[0]) old_delegate_coin_success(operators[0], accounts[1], delegate_amount) __old_turn_round(consensuses) __init_hybrid_score_mock() stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data([tx_id]) turn_round(consensuses, round_count=1) old_claim_reward_success(operators, accounts[0]) old_claim_reward_success(operators, accounts[1]) @@ -3264,7 +2299,7 @@ def test_cancel_after_all_already_cancelled(stake_hub, core_agent, set_candidate turn_round() old_undelegate_coin_success(operators[1], accounts[0], 0, old=False) turn_round(consensuses, round_count=2) - with brownie.reverts("call CORE_AGENT_ADDR.proxyUnDelegate() failed"): + with brownie.reverts("Undelegate zero coin"): old_undelegate_coin_success(operators[1], accounts[0], 0, old=False) __check_delegate_info(operators[1], accounts[0], { 'stakedAmount': 0, @@ -3332,334 +2367,6 @@ def test_claim_reward_with_both_proxy_and_regular_staking(stake_hub, btc_stake, assert tracker.delta() == account_rewards.get(tracker.address, 0) -@pytest.mark.parametrize("delegator", [True, False]) -def test_claim_reward_after_multiple_delegator_operations(stake_hub, btc_stake, core_agent, set_candidate, delegator): - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, operators[0], account) - tx_ids.append(tx_id) - old_delegate_coin_success(operators[0], account, delegate_amount) - for i in range(2): - delegate_power_success(operators[0], accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data(tx_ids) - turn_round(consensuses) - if delegator: - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - old_undelegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) - old_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 4, old=False) - undelegate_amount = delegate_amount - trackers = get_trackers(accounts[:3]) - else: - undelegate_amount = 0 - trackers = get_trackers(accounts[:3]) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount, undelegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - for index, tracker in enumerate(trackers): - old_reward = TOTAL_REWARD // 6 * 2 - if index == 2: - old_reward = TOTAL_REWARD // 3 - assert tracker.delta() == account_rewards[tracker.address] + old_reward - - -def test_claim_reward_after_btc_stake_in_upgrade_round(stake_hub, btc_stake, core_agent, set_candidate): - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, operators[0], account) - tx_ids.append(tx_id) - old_delegate_coin_success(operators[0], account, delegate_amount) - for i in range(2): - delegate_power_success(operators[0], accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data(tx_ids) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - trackers = get_trackers(accounts[:3]) - old_claim_reward_success(operators, accounts[:3]) - turn_round(consensuses) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - for index, tracker in enumerate(trackers): - old_reward = TOTAL_REWARD // 6 * 2 - if index == 2: - old_reward = TOTAL_REWARD // 3 - assert tracker.delta() == account_rewards[tracker.address] + old_reward - - -def test_claim_reward_after_upgrade(stake_hub, btc_stake, core_agent, set_candidate): - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(2): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) - stake_hub.setCandidateScoresMap(operators[1], delegate_amount, power_value * 1000, btc_value * 10) - __move_btc_data(tx_ids) - trackers = get_trackers(accounts[:3]) - old_claim_reward_success(operators, accounts[:3]) - turn_round(consensuses) - old_claim_reward_success(operators, accounts[:3]) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }, { - "address": operators[1], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - for index, tracker in enumerate(trackers): - old_reward = TOTAL_REWARD // 6 * 2 * 2 - if index == 2: - old_reward = TOTAL_REWARD // 3 * 2 - assert tracker.delta() == account_rewards[tracker.address] + old_reward - - -def test_candidate_data_migration(stake_hub, btc_stake, core_agent, candidate_hub, validator_set, set_candidate): - candidate_hub.setValidatorCount(1) - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(2): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[1], delegate_amount, power_value * 1000, btc_value * 10) - assert validator_set.getValidators()[0] == consensuses[1] - __move_btc_data(tx_ids) - candidate_hub.setValidatorCount(2) - trackers = get_trackers(accounts[:3]) - turn_round(consensuses) - old_claim_reward_success(operators, accounts[:3]) - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }, - { - "address": operators[1], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - for index, tracker in enumerate(trackers): - old_reward = TOTAL_REWARD // 6 * 2 - if index == 2: - old_reward = TOTAL_REWARD // 3 - assert tracker.delta() == account_rewards[tracker.address] + old_reward - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }, { - "address": operators[1], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - for index, tracker in enumerate(trackers): - assert tracker.delta() == account_rewards.get(tracker.address, 0) - - -@pytest.mark.parametrize("round_count", [0, 1]) -def test_claim_reward_after_candidate_migration_skip_round(stake_hub, btc_stake, core_agent, candidate_hub, - validator_set, set_candidate, round_count): - candidate_hub.setValidatorCount(1) - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[1], delegate_amount, power_value * 1000, btc_value * 10) - assert validator_set.getValidators()[0] == consensuses[1] - __move_btc_data(tx_ids) - candidate_hub.setValidatorCount(2) - trackers = get_trackers(accounts[:3]) - turn_round(consensuses) - old_claim_reward_success(operators, accounts[:3]) - turn_round(consensuses, round_count=round_count) - _, _, account_rewards, _ = parse_delegation([ - { - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }, - { - "address": operators[1], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - old_reward0 = TOTAL_REWARD // 6 * 2 - old_reward1 = TOTAL_REWARD // 3 - account_reward0 = account_rewards[accounts[0]] * round_count + old_reward0 - account_reward1 = account_rewards[accounts[1]] * round_count + old_reward0 - account_reward2 = account_rewards[accounts[2]] * round_count + old_reward1 - assert_trackers(trackers, [account_reward0, account_reward1, account_reward2]) - - -def test_claim_reward_after_multiple_rounds_candidate(stake_hub, btc_stake, core_agent, candidate_hub, set_candidate, - validator_set): - candidate_hub.setValidatorCount(1) - btc_value = 100 - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - tx_ids = [] - for op in operators[:2]: - for account in accounts[:2]: - tx_id = old_delegate_btc_success(btc_value, op, account) - tx_ids.append(tx_id) - old_delegate_coin_success(op, account, delegate_amount) - for i in range(3): - delegate_power_success(op, accounts[2], power_value, stake_round=i) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[1], delegate_amount, power_value * 1000, btc_value * 10) - assert validator_set.getValidators()[0] == consensuses[1] - __move_btc_data(tx_ids) - candidate_hub.setValidatorCount(2) - trackers = get_trackers(accounts[:3]) - turn_round(consensuses) - assert len(validator_set.getValidators()) == 2 - old_claim_reward_success(operators, accounts[:3]) - turn_round(consensuses, round_count=1) - stake_hub_claim_reward(accounts[:3]) - turn_round(consensuses, round_count=1) - _, _, account_rewards, asset_unit_reward_map = parse_delegation([ - { - "address": operators[0], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }, - { - "address": operators[1], - "power": [set_delegate(accounts[2], power_value)], - "coin": [set_delegate(accounts[0], delegate_amount), - set_delegate(accounts[1], delegate_amount)], - "btc": [set_delegate(accounts[0], btc_value), - set_delegate(accounts[1], btc_value)] - }], TOTAL_REWARD, state_map={ - 'btc_lst_gradeActive': 0, - 'btc_gradeActive': 0 - }) - stake_hub_claim_reward(accounts[:3]) - old_reward0 = TOTAL_REWARD // 6 * 2 - old_reward1 = TOTAL_REWARD // 3 - account_reward0 = account_rewards[accounts[0]] * 2 + old_reward0 - account_reward1 = account_rewards[accounts[1]] * 2 + old_reward0 - account_reward2 = account_rewards[accounts[2]] * 2 + old_reward1 - assert_trackers(trackers, [account_reward0, account_reward1, account_reward2]) - - @pytest.mark.parametrize("inter_round_cancel", [True, False]) @pytest.mark.parametrize("tests", [ {'transfer': 500, 'undelagate': 0, 'amount': 500, 'expect_reward': 13544 + 3386}, @@ -4094,12 +2801,6 @@ def __get_btc_candidate_map_info(candidate): return candidate_map -def __check_btc_candidate_map_info(candidate, result: dict): - data = __get_btc_candidate_map_info(candidate) - for i in result: - assert data[i] == result[i] - - def __get_old_reward(operators, delegator): old_reward, _ = PLEDGE_AGENT.claimRewardMock.call(operators, {'from': delegator}) return old_reward diff --git a/tests/test_slash.py b/tests/test_slash.py index 89f137c26..3f5a0b205 100644 --- a/tests/test_slash.py +++ b/tests/test_slash.py @@ -5,7 +5,7 @@ from .delegate import delegate_coin_success from .utils import * -from .common import register_candidate, turn_round, execute_proposal, stake_hub_claim_reward +from .common import register_candidate, turn_round, execute_proposal, stake_hub_claim_reward, enter_maintenance misdemeanorThreshold = 0 felonyThreshold = 0 @@ -139,6 +139,7 @@ def test_clean(slash_indicator, validator_set): for account, count in zip(slash_accounts, counts): assert slash_indicator.getSlashIndicator(account.address)[1] == max([count - decrease_value, 0]) + # submitFinalityViolationEvidence def test_old_block_involved_failed(slash_indicator, validator_set, set_candidate): signature = os.urandom(96).hex() @@ -154,6 +155,7 @@ def test_old_block_involved_failed(slash_indicator, validator_set, set_candidate with brownie.reverts(f"too old block involved"): slash_indicator.submitFinalityViolationEvidence((vote_a, vote_b, vote_addr), {'from': accounts[0]}) + def test_vote_addrs_length_le_1(slash_indicator): register_candidate(operator=accounts[0]) turn_round() @@ -347,6 +349,7 @@ def test_validator_restoration_after_felony_success(slash_indicator, validator_s tx = slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_addr_list[2]), {'from': accounts[2]}) candidate_hub.addMargin({'from': operators[2], 'value': slash_indicator.felonyDeposit()}) assert 'validatorFelony' in tx.events + assert slash_indicator.indicators(consensuses[2])['count'] == 0 turn_round(consensuses, round_count=2) assert len(validator_set.getValidators()) == 2 stake_hub_claim_reward(accounts[0]) @@ -381,6 +384,66 @@ def test_penalty_non_validator_voter(slash_indicator, validator_set, system_rewa assert len(validator_set.getValidators()) == 3 +def test_double_sign_felony_on_maintain_status_submit_evidence(slash_indicator, validator_set, candidate_hub): + candidate_hub.setMaxAlternateCount(1) + candidate_hub.setValidatorCount(2) + vote_list = [random_vote_address() for _ in range(3)] + consensuses = [] + for index, operator in enumerate(accounts[1:4]): + consensuses.append(register_candidate(operator=operator, vote_address=vote_list[index])) + delegate_coin_success(accounts[1], accounts[0], 10000) + turn_round() + tx = enter_maintenance(accounts[1]) + assert 'validatorEnterMaintenance' in tx.events + signature = os.urandom(96).hex() + src_num = 100 + src_hash_list = [random_bytes_data() for _ in range(2)] + tar_hash_list = [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tx = slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_list[0]), {'from': accounts[0]}) + assert 'validatorFelony' in tx.events + turn_round(consensuses) + assert slash_indicator.indicators(consensuses[0])['count'] == 0 + assert slash_indicator.indicators(consensuses[1])['count'] == 0 + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == 0 + + +def test_submit_finality_violation_evidence_then_slash(slash_indicator, validator_set, candidate_hub): + candidate_hub.setMaxAlternateCount(1) + candidate_hub.setValidatorCount(3) + vote_list = [random_vote_address() for _ in range(3)] + consensuses = [] + for index, operator in enumerate(accounts[1:4]): + consensuses.append(register_candidate(operator=operator, vote_address=vote_list[index])) + delegate_coin_success(accounts[1], accounts[0], 10000) + turn_round() + felony = 30 + misdemeanor = 5 + slash_indicator.setMisdemeanorThreshold(misdemeanor) + slash_indicator.setFelonyThreshold(felony) + for _ in range(misdemeanor * 5): + slash_indicator.slash(consensuses[0]) + assert slash_indicator.indicators(consensuses[0])['count'] == 25 + signature = os.urandom(96).hex() + src_num = 100 + src_hash_list = [random_bytes_data() for _ in range(2)] + tar_hash_list = [random_bytes_data() for _ in range(2)] + vote_a = build_vote_data(src_num, src_hash_list[0], 200, tar_hash_list[0], signature) + vote_b = build_vote_data(src_num + 1, src_hash_list[1], 120, tar_hash_list[1], signature) + tx = slash_indicator.mockSubmitFinalityViolationEvidence((vote_a, vote_b, vote_list[0]), {'from': accounts[0]}) + assert 'validatorFelony' in tx.events + assert slash_indicator.indicators(consensuses[0])['count'] == 0 + candidate_hub.addMargin({'from': accounts[1], 'value': slash_indicator.felonyDeposit()}) + turn_round(consensuses, round_count=3) + assert slash_indicator.indicators(consensuses[0])['count'] == 0 + for _ in range(misdemeanor * 5): + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorMisdemeanor' in tx.events + + def test_only_gov_can_call(slash_indicator): value = padding_left(Web3.to_hex(1000), 64) with brownie.reverts(f"the msg sender must be governance contract"): diff --git a/tests/test_stake_hub_unit.py b/tests/test_stake_hub_unit.py index db3c1139e..d8c0f799d 100644 --- a/tests/test_stake_hub_unit.py +++ b/tests/test_stake_hub_unit.py @@ -11,6 +11,7 @@ core_agent_instance = None btc_light_client_instance = None required_coin_deposit = 0 +LOCK_SCRIPT = '0480db8767b17576a914574fdd26858c28ede5225a809f747c01fcc1f92a88ac' TX_FEE = Web3.to_wei(1, 'ether') # the tx fee is 1 ether actual_block_reward = 0 @@ -294,24 +295,28 @@ def __mock_stake_hub_reward(): accounts[3].transfer(STAKE_HUB, Web3.to_wei(1, 'ether')) +# claimReward @pytest.mark.parametrize("tests", [ {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 0, 'actual_bonus': 0, 'claim_rewards': 10000}, {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 2000, 'actual_bonus': 0, 'claim_rewards': 8000}, {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 10000, 'actual_bonus': 0, 'claim_rewards': 0}, {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 12000, 'actual_bonus': 2000, 'claim_rewards': 0}, - {'btc_reward': 10000, 'unclaimed_reward': 2000, 'reward_pool': 0, 'actual_bonus': 0, 'claim_rewards': 8000}, - {'btc_reward': 10000, 'unclaimed_reward': 5000, 'reward_pool': 0, 'actual_bonus': 0, 'claim_rewards': 5000}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 0, 'actual_bonus': 0, 'claim_rewards': 7500}, {'btc_reward': 10000, 'unclaimed_reward': 10000, 'reward_pool': 0, 'actual_bonus': 0, 'claim_rewards': 0}, - {'btc_reward': 10000, 'unclaimed_reward': 12000, 'reward_pool': 0, 'actual_bonus': 2000, 'claim_rewards': 0}, - {'btc_reward': 10000, 'unclaimed_reward': 9000, 'reward_pool': 12000, 'actual_bonus': 11000, 'claim_rewards': 0}, - {'btc_reward': 10000, 'unclaimed_reward': 12000, 'reward_pool': 9000, 'actual_bonus': 11000, 'claim_rewards': 0}, + {'btc_reward': 10000, 'unclaimed_reward': 15000, 'reward_pool': 0, 'actual_bonus': 5000, 'claim_rewards': 0}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 12000, 'actual_bonus': 3500, 'claim_rewards': 0}, + {'btc_reward': 10000, 'unclaimed_reward': 15000, 'reward_pool': 9000, 'actual_bonus': 14000, 'claim_rewards': 0}, {'btc_reward': 10000, 'unclaimed_reward': 10000, 'reward_pool': 1000, 'actual_bonus': 1000, 'claim_rewards': 0}, - {'btc_reward': 10000, 'unclaimed_reward': 1000, 'reward_pool': 10000, 'actual_bonus': 1000, 'claim_rewards': 0}, - {'btc_reward': 10000, 'unclaimed_reward': 4000, 'reward_pool': 4000, 'actual_bonus': 0, 'claim_rewards': 2000}, - {'btc_reward': 10000, 'unclaimed_reward': 5000, 'reward_pool': 5000, 'actual_bonus': 0, 'claim_rewards': 0}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 10000, 'actual_bonus': 2500, 'claim_rewards': 0}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 3000, 'actual_bonus': 0, 'claim_rewards': 4500}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 7500, 'actual_bonus': 0, 'claim_rewards': 0}, {'btc_reward': 10000, 'unclaimed_reward': 15000, 'reward_pool': 15000, 'actual_bonus': 20000, 'claim_rewards': 0} ]) -def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests): +def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) stake_manager.set_lp_rates([[0, 20000]]) stake_manager.set_is_stake_hub_active(True) btc_reward = tests['btc_reward'] @@ -321,7 +326,7 @@ def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests): assert stake_hub.surplus() == reward_pool float_reward = btc_reward - unclaimed_reward __mock_stake_hub_reward() - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), btc_reward, unclaimed_reward) tracker = get_tracker(accounts[0]) tx = stake_hub.claimReward() if tests['claim_rewards'] > 0: @@ -336,13 +341,16 @@ def test_btc_claim_bonus_reward(stake_hub, btc_agent, tests): @pytest.mark.parametrize("tests", [ {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 0, 'actual_bonus': 5000}, - {'btc_reward': 10000, 'unclaimed_reward': 3000, 'reward_pool': 0, 'actual_bonus': 8000}, - {'btc_reward': 10000, 'unclaimed_reward': 6000, 'reward_pool': 0, 'actual_bonus': 11000}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 0, 'actual_bonus': 7500}, {'btc_reward': 10000, 'unclaimed_reward': 0, 'reward_pool': 2000, 'actual_bonus': 7000}, - {'btc_reward': 10000, 'unclaimed_reward': 1000, 'reward_pool': 2000, 'actual_bonus': 8000}, + {'btc_reward': 10000, 'unclaimed_reward': 2500, 'reward_pool': 2000, 'actual_bonus': 9500}, {'btc_reward': 0, 'unclaimed_reward': 1000, 'reward_pool': 2000, 'actual_bonus': 3000} ]) -def test_btc_no_bonus(stake_hub, btc_agent, tests): +def test_btc_no_bonus(stake_hub, btc_agent, tests, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) stake_manager.set_lp_rates([[0, 5000]]) stake_manager.set_is_stake_hub_active(True) btc_reward = tests['btc_reward'] @@ -353,7 +361,7 @@ def test_btc_no_bonus(stake_hub, btc_agent, tests): reward_pool += actual_reward reward_pool += unclaimed_reward __mock_stake_hub_reward() - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, unclaimed_reward, MIN_INIT_DELEGATE_VALUE) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), btc_reward, unclaimed_reward) tracker = get_tracker(accounts[0]) stake_hub.claimReward() assert tracker.delta() == actual_reward @@ -362,7 +370,7 @@ def test_btc_no_bonus(stake_hub, btc_agent, tests): @pytest.mark.parametrize("tests", [ - {'surplus': 2000, 'duration_unclaimed': 3000, 'rewardTo': 5000, 'actual_surplus': 0}, + {'surplus': 2000, 'duration_unclaimed': 2500, 'rewardTo': 5500, 'actual_surplus': 0}, {'surplus': 5000, 'duration_unclaimed': 15000, 'rewardTo': 0, 'actual_surplus': 10000}, {'surplus': 5000, 'duration_unclaimed': 10000, 'rewardTo': 0, 'actual_surplus': 5000}, {'surplus': 0, 'duration_unclaimed': 10000, 'rewardTo': 0, 'actual_surplus': 0}, @@ -372,7 +380,11 @@ def test_btc_no_bonus(stake_hub, btc_agent, tests): {'surplus': 0, 'duration_unclaimed': 0, 'rewardTo': 10000, 'actual_surplus': 0}, {'surplus': 7000, 'duration_unclaimed': 6000, 'rewardTo': 0, 'actual_surplus': 3000} ]) -def test_get_rewards_from_systemReward(stake_hub, btc_agent, tests): +def test_get_rewards_from_systemReward(stake_hub, btc_agent, tests, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) __mock_stake_hub_reward() stake_hub.setSurplus(tests['surplus']) btc_reward = 10000 @@ -382,10 +394,9 @@ def test_get_rewards_from_systemReward(stake_hub, btc_agent, tests): stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), btc_reward, duration_unclaimed) tracker = get_tracker(accounts[0]) tx = stake_hub_claim_reward(accounts[0]) - print('dsaf', tx.events) if tests['rewardTo'] > 0: assert tx.events['rewardTo']['amount'] == tests['rewardTo'] else: @@ -397,16 +408,16 @@ def test_get_rewards_from_systemReward(stake_hub, btc_agent, tests): @pytest.mark.parametrize("tests", [ {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 8000, 'surplus': 3000, 'expect_reward': 8000, 'expect_surplus': 5000}, - {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 6000, 'surplus': 3000, 'expect_reward': 6000, - 'expect_surplus': 9000}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 6000, 'surplus': 3000, 'expect_reward': 6000, + 'expect_surplus': 9500}, {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 10000, 'surplus': 3000, 'expect_reward': 10000, 'expect_surplus': 3000}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 15000, 'surplus': 2000, 'expect_reward': 15000, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 15000, 'surplus': 2500, 'expect_reward': 15000, 'expect_surplus': 0}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, - 'expect_surplus': 1000}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 0, 'surplus': 2000, 'expect_reward': 0, - 'expect_surplus': 15000}, + {'btc_reward': 10000, 'duration_unclaimed': 6000, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, + 'expect_surplus': 4000}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 0, 'surplus': 2000, 'expect_reward': 0, + 'expect_surplus': 14500}, {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 10000, 'surplus': 2000, 'expect_reward': 10000, 'expect_surplus': 2000}, {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 20000, 'surplus': 2000, 'expect_reward': 20000, @@ -414,7 +425,11 @@ def test_get_rewards_from_systemReward(stake_hub, btc_agent, tests): {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 5000, 'surplus': 0, 'expect_reward': 5000, 'expect_surplus': 5000} ]) -def test_claim_reward_update_surplus(stake_hub, btc_agent, tests): +def test_claim_reward_update_surplus(stake_hub, btc_agent, tests, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) __mock_stake_hub_reward() stake_hub.setSurplus(tests['surplus']) btc_reward = tests['btc_reward'] @@ -424,7 +439,7 @@ def test_claim_reward_update_surplus(stake_hub, btc_agent, tests): stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), btc_reward, duration_unclaimed) actual_reward = actual_btc_reward tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) @@ -433,17 +448,179 @@ def test_claim_reward_update_surplus(stake_hub, btc_agent, tests): assert tracker.delta() == actual_reward -def test_system_reward_insufficient_balance(stake_hub): +def test_system_reward_insufficient_balance(stake_hub, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) stake_manager.set_lp_rates([[0, 30000]]) stake_manager.set_is_stake_hub_active(True) btc_reward = 100000e18 accounts[3].transfer(STAKE_HUB, Web3.to_wei(100000, 'ether')) - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, 0, 100) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), btc_reward, 0) # revert: Address: insufficient balance with brownie.reverts("SafeCast: value must be positive"): stake_hub.claimReward() +def test_claim_reward_staked_core_amount_different(stake_hub, btc_agent, core_agent, set_candidate): + stake_manager.set_lp_rates([[0, 5000], [5000, 8000], [8000, 10000], [10000, 15000]]) + operators, consensuses = set_candidate + delegate_coin_success(operators[1], accounts[0], 4999) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) + stake_manager.set_is_stake_hub_active(True) + tracker = get_tracker(accounts[0]) + tx = stake_hub.claimReward() + expect_event(tx, 'claimedRewardBtcTx', { + 'dualStakingRate': Utils.DENOMINATOR // 2 + }) + assert tracker.delta() == (TOTAL_REWARD - 1) + TOTAL_REWARD // 2 + + +def test_claim_reward_staked_core_amount_different_with_reward(stake_hub, btc_agent, core_agent, set_candidate): + stake_manager.set_lp_rates([[0, 3000], [4000, 5000], [7000, 10000], [10000, 15000]]) + operators, consensuses = set_candidate + + delegate_coin_success(operators[1], accounts[0], 2000) + delegate_coin_success(operators[2], accounts[0], 2000) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + delegate_coin_success(operators[1], accounts[0], 3000) + turn_round(consensuses, round_count=2) + stake_manager.set_is_stake_hub_active(True) + tracker = get_tracker(accounts[0]) + tx = stake_hub.claimReward() + rewards = [TOTAL_REWARD * 4, 0, TOTAL_REWARD // 2 + TOTAL_REWARD] + expect_event(tx, 'claimedReward', { + 'delegator': accounts[0], + 'amounts': rewards + }) + assert tracker.delta() == sum(rewards) + + +@pytest.mark.parametrize("operator", ['tr', 'de', 'un']) +def test_claim_reward_staked_core_amount_equal(stake_hub, btc_agent, core_agent, set_candidate, operator): + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_lp_rates([[0, 3000], [4000, 5000], [7000, 10000], [10000, 15000]]) + operators, consensuses = set_candidate + delegate_coin_success(operators[2], accounts[0], 14000) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], 1, LOCK_SCRIPT) + turn_round() + dualStakingRate0 = 15000 + dualStakingRate1 = 5000 + coin_reward = TOTAL_REWARD + btc_reward = TOTAL_REWARD * 15000 // 10000 + TOTAL_REWARD // 2 + if operator == 'tr': + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + elif operator == 'de': + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + elif operator == 'un': + undelegate_coin_success(operators[2], accounts[0], 7000) + dualStakingRate0 = 10000 + dualStakingRate1 = 3000 + btc_reward = TOTAL_REWARD + TOTAL_REWARD * 3000 // 10000 + coin_reward = TOTAL_REWARD // 2 + turn_round(consensuses) + tx = stake_hub.claimReward() + expect_event(tx, 'claimedRewardBtcTx', { + 'dualStakingRate': dualStakingRate0 + }, idx=0) + expect_event(tx, 'claimedRewardBtcTx', { + 'dualStakingRate': dualStakingRate1 + }, idx=1) + expect_event(tx, 'claimedReward', { + 'delegator': accounts[0], + 'amounts': [coin_reward, 0, btc_reward] + }) + + +def test_claim_reward_change_round_equal_and_staked_core_equal(stake_hub, core_agent, btc_agent, set_candidate): + operators, consensuses = set_candidate + stake_manager.set_is_stake_hub_active(True) + stake_manager.set_lp_rates([[0, 10000]]) + delegate_coin_success(operators[0], accounts[0], 10000) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) + stake_hub.calculateReward(accounts[0]) + get_tracker(accounts[0]) + tx = stake_hub.claimReward() + coin_reward = 8127 + btc_reward = 5417 + expect_event(tx, 'claimedReward', { + 'delegator': accounts[0], + 'amounts': [coin_reward, 0, btc_reward] + }) + + +def test_claim_reward_change_round_less_than_last(stake_hub, btc_agent, core_agent, set_candidate): + operators, consensuses = set_candidate + delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses, round_count=3) + tracker = get_tracker(accounts[0]) + tx = stake_hub.claimReward() + expect_event(tx, 'claimedReward', { + 'delegator': accounts[0], + 'amounts': [TOTAL_REWARD * 3, 0, TOTAL_REWARD * 3] + }) + + +def test_claim_reward_with_stored_historical_rewards(stake_hub, btc_agent, set_candidate): + operators, consensuses = set_candidate + + delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) + stake_hub.calculateReward(accounts[0]) + assert stake_hub.getDelegator(accounts[0])[1] == [TOTAL_REWARD, 0, TOTAL_REWARD] + turn_round(consensuses) + tx = stake_hub.claimReward() + expect_event(tx, 'claimedReward', { + 'delegator': accounts[0], + 'amounts': [TOTAL_REWARD * 2, 0, TOTAL_REWARD * 2] + }) + assert sum(stake_hub.getDelegator(accounts[0])[1]) == 0 + + +def test_claim_reward_change_round_update(stake_hub, btc_agent, set_candidate): + operators, consensuses = set_candidate + + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) + + initial_delegator = stake_hub.getDelegator(accounts[0]) + initial_change_round = initial_delegator[0] + + turn_round(consensuses) + turn_round(consensuses) + stake_hub.claimReward() + final_delegator = stake_hub.getDelegator(accounts[0]) + final_change_round = final_delegator[0] + + assert final_change_round > initial_change_round + + +def test_claim_reward_all_asset_types_combined(stake_hub, btc_agent, core_agent, hash_power_agent, set_candidate): + operators, consensuses = set_candidate + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + delegate_power_success(operators[0], accounts[0], 1) + turn_round() + turn_round(consensuses) + tracker = get_tracker(accounts[0]) + rewards = stake_hub.claimReward().return_value + assert len(rewards) == 3 + assert tracker.delta() == TOTAL_REWARD - 2 + assert sum(rewards[1:]) == TOTAL_REWARD // 2 + + def test_only_pledge_agent_can_call(stake_hub): with brownie.reverts("the sender must be pledge agent contract"): stake_hub.proxyClaimReward(accounts[0]) @@ -463,15 +640,20 @@ def test_proxy_claim_reward_success(stake_hub, btc_agent, pledge_agent, set_cand assert tracker.delta() == BLOCK_REWARD +# _calculateReward def test_calculate_reward_success(stake_hub, btc_agent, core_agent, btc_stake, hash_power_agent, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) accounts[3].transfer(stake_hub, Web3.to_wei(1, 'ether')) reward = 10000 btc_reward = 6000 actual_rewards = [reward, reward, btc_reward] core_agent.setCoreRewardMap(accounts[0], reward, 0) hash_power_agent.setPowerRewardMap(accounts[0], reward, 0) - round_reward_manager.mock_btc_reward_map(accounts[0], reward, 0, MIN_INIT_DELEGATE_VALUE) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), reward, 0) btc_agent.setIsActive(True) stake_manager.set_lp_rates([[0, btc_reward]]) stake_hub.setOperators(accounts[3], True) @@ -483,41 +665,44 @@ def test_calculate_reward_success(stake_hub, btc_agent, core_agent, btc_stake, h @pytest.mark.parametrize("tests", [ {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 8000, 'expect_surplus': 2000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 8000, - 'expect_surplus': 3000}, - {'btc_reward': 10000, 'duration_unclaimed': 2000, 'btc_percentage': 8000, 'surplus': 2000, 'expect_reward': 8000, - 'expect_surplus': 6000}, - {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, - 'expect_surplus': 0, 'claim_rewards': 2000}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, - 'expect_surplus': 1000}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 12000, 'surplus': 2000, 'expect_reward': 12000, - 'expect_surplus': 3000}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 20000, 'surplus': 2000, 'expect_reward': 20000, - 'expect_surplus': 0, 'claim_rewards': 5000}, + {'btc_reward': 10000, 'duration_unclaimed': 6000, 'btc_percentage': 8000, 'surplus': 0, 'expect_reward': 8000, + 'expect_surplus': 8000}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 8000, 'surplus': 2000, 'expect_reward': 8000, + 'expect_surplus': 6500}, {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, 'expect_surplus': 0, 'claim_rewards': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 6000, 'btc_percentage': 12000, 'surplus': 0, 'expect_reward': 12000, + 'expect_surplus': 4000}, + {'btc_reward': 10000, 'duration_unclaimed': 6000, 'btc_percentage': 12000, 'surplus': 1000, 'expect_reward': 12000, + 'expect_surplus': 5000}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 20000, 'surplus': 2000, 'expect_reward': 20000, + 'expect_surplus': 0, 'claim_rewards': 5500}, {'btc_reward': 10000, 'duration_unclaimed': 0, 'btc_percentage': 10000, 'surplus': 1000, 'expect_reward': 10000, 'expect_surplus': 1000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 10000, 'surplus': 1000, 'expect_reward': 10000, - 'expect_surplus': 2000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 1000, 'expect_reward': 15000, - 'expect_surplus': 0, 'claim_rewards': 3000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, - 'expect_surplus': 0, 'claim_rewards': 4000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 6000, 'expect_reward': 15000, - 'expect_surplus': 2000}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 10000, 'surplus': 1000, 'expect_reward': 10000, + 'expect_surplus': 3500}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 15000, 'surplus': 1000, 'expect_reward': 15000, + 'expect_surplus': 0, 'claim_rewards': 1500}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, + 'expect_surplus': 0, 'claim_rewards': 2500}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 15000, 'surplus': 6000, 'expect_reward': 15000, + 'expect_surplus': 3500}, {'btc_reward': 10000, 'duration_unclaimed': 6000, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, - 'expect_surplus': 1000}, - {'btc_reward': 10000, 'duration_unclaimed': 3000, 'btc_percentage': 15000, 'surplus': 0, 'expect_reward': 15000, - 'expect_surplus': 0, 'claim_rewards': 2000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 15000, 'surplus': 1000, 'expect_reward': 15000, - 'expect_surplus': 0, 'claim_rewards': 3000}, - {'btc_reward': 10000, 'duration_unclaimed': 1000, 'btc_percentage': 0, 'surplus': 1000, 'expect_reward': 0, - 'expect_surplus': 12000} - + 'expect_surplus': 1000, 'claim_rewards': 0}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 0, 'surplus': 1000, 'expect_reward': 0, + 'expect_surplus': 13500}, + {'btc_reward': 10000, 'duration_unclaimed': 2500, 'btc_percentage': 5000, 'surplus': 1000, 'expect_reward': 5000, + 'expect_surplus': 8500}, + {'btc_reward': 10000, 'duration_unclaimed': 10000, 'btc_percentage': 20000, 'surplus': 0, 'expect_reward': 20000, + 'expect_surplus': 0}, + {'btc_reward': 10000, 'duration_unclaimed': 10000, 'btc_percentage': 15000, 'surplus': 2000, 'expect_reward': 15000, + 'expect_surplus': 7000} ]) -def test_calculate_reward_update_surplus(stake_hub, btc_agent, tests): +def test_calculate_reward_update_surplus(stake_hub, btc_agent, tests, set_candidate): + operators, consensuses = set_candidate + delegate_btc_success(operators[0], accounts[0], 1, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) stake_hub.setSurplus(tests['surplus']) btc_reward = tests['btc_reward'] duration_unclaimed = tests['duration_unclaimed'] @@ -526,7 +711,7 @@ def test_calculate_reward_update_surplus(stake_hub, btc_agent, tests): stake_manager.set_is_stake_hub_active(True) turn_round() actual_btc_reward = btc_reward * btc_percentage // Utils.DENOMINATOR - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward, duration_unclaimed, 100) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), btc_reward, duration_unclaimed) actual_reward = actual_btc_reward tx = stake_hub.calculateRewardMock(accounts[0]) if tests.get('claim_rewards'): @@ -537,17 +722,6 @@ def test_calculate_reward_update_surplus(stake_hub, btc_agent, tests): assert reward == [0, 0, actual_reward] -def test_system_reward_balance_insufficient(stake_hub, btc_agent, system_reward): - btc_reward = 100000e18 - rate = 20000 - stake_manager.set_lp_rates([[0, rate]]) - stake_manager.set_is_stake_hub_active(True) - turn_round() - round_reward_manager.mock_btc_reward_map(accounts[0], btc_reward * 2, 0, 100) - with brownie.reverts('SafeCast: value must be positive'): - stake_hub.calculateRewardMock(accounts[0]) - - @pytest.mark.parametrize("lp_rates", [ [(0, 1000), (1000, 5000), (30000, 10000)], [(0, 1000), (1000, 2000), (2000, 5000)], @@ -555,18 +729,28 @@ def test_system_reward_balance_insufficient(stake_hub, btc_agent, system_reward) [(0, 2000), (8000, 2000), (10000, 5000)], [(0, 2000), (10000, 5000), (12000, 2000)] ] -) -def test_claim_rewards_multiple_grades(stake_hub, core_agent, hash_power_agent, btc_agent, lp_rates): + ) +def test_claim_rewards_multiple_grades(stake_hub, core_agent, validator_set, btc_stake, hash_power_agent, btc_agent, + lp_rates, + set_candidate): + btc_agent.setAssetWeight(1e10) + accounts[3].transfer(validator_set.address, Web3.to_wei(10000, 'ether')) + operators, consensuses = set_candidate + delegate_coin_success(operators[1], accounts[0], 10000e18) + delegate_btc_success(operators[0], accounts[0], 1e8, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) accounts[3].transfer(stake_hub, Web3.to_wei(1, 'ether')) reward = 10000 actual_rewards = [reward, reward, reward // 2] - round_reward_manager.mock_btc_reward_map(accounts[0], reward, 0, 100) + round_reward_manager.mock_btc_reward_map(operators[0], get_current_round(), reward, 0, 1e8) core_agent.setCoreRewardMap(accounts[0], reward, 1000000) hash_power_agent.setPowerRewardMap(accounts[0], reward, 10) btc_agent.setIsActive(True) for lp in lp_rates: btc_agent.setLpRates(lp[0], lp[1]) - rewards = stake_hub.calculateRewardMock(accounts[0]).return_value + tx = stake_hub.calculateRewardMock(accounts[0]) + rewards = tx.return_value assert rewards == actual_rewards @@ -606,17 +790,21 @@ def test_onStakeChange_success(stake_hub, set_candidate): mock_delegate_btc_success(operators[1], accounts[0], btc_value) turn_round(consensuses, round_count=2) current_round = get_current_round() - stake_hub.onStakeChange(accounts[0]) + tx = stake_hub.onStakeChange(accounts[0]) + dual_staking_rate = 5000 + assert tx.events['storedRewardBtcTx']['dualStakingRate'] == dual_staking_rate assert stake_hub.getDelegatorMap(accounts[0])[0] == current_round tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - actual_reward = TOTAL_REWARD * 4 + # After the revision, stakeAmount and staking rounds are irrelevant + actual_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 assert tracker0.delta() == actual_reward turn_round(consensuses) stake_hub.calculateReward(accounts[0]) stake_manager.set_lp_rates([[0, 20000]]) stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 + turn_round(consensuses) @pytest.mark.parametrize("onStakeChange", [True, False]) @@ -898,42 +1086,21 @@ def test_stake_hup_get_hybrid_score(stake_hub, validator_set, candidate_hub, cor assert stake_hub.getCandidateScores(validator) == expect_score -def test_stake_hup_calculate_reward(stake_hub, btc_agent, validator_set, candidate_hub, core_agent, btc_light_client, - btc_stake): +@pytest.mark.parametrize('test', [ + {'add_core': 1000000, 'add_btc': 100, 'expect_rewards': (13545, 0, 13545)}, + {'add_core': 10000, 'add_btc': 1, 'expect_rewards': (13545, 0, 13545), 'is_active': True}, + {'add_core': 120000, 'add_btc': 10, 'expect_rewards': (13545, 0, 13545), 'is_active': True}, + {'add_core': 5000000, 'add_btc': 1000, 'expect_rewards': (13545, 0, 13545 * 8000 // 10000), 'is_active': True}, + {'add_core': 5000, 'add_btc': 10, 'expect_rewards': (13545, 0, 13545 // 2), 'is_active': True} +]) +def test_stake_hub_calculate_reward(stake_hub, btc_agent, candidate_hub, core_agent, btc_stake, set_candidate, test): + operators, consensuses = set_candidate turn_round() - register_candidate(operator=accounts[1]) - register_candidate(operator=accounts[2]) - - tests = [ - {'status': 'success', 'delegator': accounts[1], 'expect_rewards': (0, 0, 0), 'expect_debt_amount': 0}, - {'status': 'success', 'delegator': accounts[1], 'add_core': [(accounts[1], 100, 0)], - 'expect_rewards': (100, 0, 0), 'expect_debt_amount': 0}, - {'status': 'success', 'delegator': accounts[1], 'add_core': [(accounts[1], 10000, 0)], - 'add_btc': [(accounts[1], 10000, 0)], 'expect_rewards': (10000, 0, 10000), 'expect_debt_amount': 0}, - {'status': 'success', 'delegator': accounts[1], 'add_core': [(accounts[1], 10000, 10000)], - 'add_btc': [(accounts[1], 10000, 1)], 'set_grades': (1000, 5000, 4000, 7000, 5000, 8000, 10000, 10000), - 'expect_rewards': (10000, 0, 10000), 'expect_debt_amount': 0}, - {'status': 'success', 'delegator': accounts[1], 'add_core': [(accounts[1], 10000, 5000)], - 'add_btc': [(accounts[1], 20000, 1)], 'set_grades': (1000, 5000, 4000, 7000, 5000, 8000, 10000, 10000), - 'expect_rewards': (10000, 0, 16000), 'expect_debt_amount': 0}, - {'status': 'success', 'delegator': accounts[1], 'add_core': [(accounts[1], 10000, 500)], - 'add_btc': [(accounts[1], 100000, 1)], 'set_grades': (1000, 5000, 4000, 7000, 5000, 8000, 10000, 10000), - 'expect_rewards': (10000, 0, 50000), 'expect_debt_amount': 0} - - ] - - for test in tests: - print(f'case{tests.index(test)}:', test) - if 'add_core' in test: - for delegator, v1, v2 in test['add_core']: - core_agent.setCoreRewardMap(delegator, v1, v2) - if 'add_btc' in test: - for validator, v1, v2 in test['add_btc']: - btc_stake.setBtcRewardMap(validator, v1, v2, v2) - if 'set_grades' in test: - btc_agent.setIsActive(True) - btc_agent.setInitLpRates(*test['set_grades']) - - if test['status'] == 'success': - assert stake_hub.calculateRewardMock(test['delegator']).return_value == ( - test['expect_rewards']) + delegate_coin_success(operators[1], accounts[1], test['add_core']) + delegate_btc_success(operators[0], accounts[1], test['add_btc'], LOCK_SCRIPT, relay=accounts[1]) + turn_round(consensuses, round_count=2) + graders = [1000, 5000, 4000, 7000, 5000, 8000, 10000, 10000] + if test.get('is_active'): + btc_agent.setIsActive(True) + btc_agent.setInitLpRates(*graders) + assert stake_hub.calculateRewardMock(accounts[1]).return_value == (test['expect_rewards']) diff --git a/tests/test_validator_set.py b/tests/test_validator_set.py index 71dbf585c..6dc0199b8 100644 --- a/tests/test_validator_set.py +++ b/tests/test_validator_set.py @@ -1523,6 +1523,7 @@ def test_exit_maintenance_turn_round_with_maintenance_slash(validator_set, candi def test_exit_maintenance_turn_round_slash_then_exit(validator_set, slash_indicator, candidate_hub, set_candidate_maintenance): + accounts[99].transfer(candidate_hub.address, Web3.to_wei(1, 'ether')) operators, consensuses = set_candidate_maintenance turn_round() slash_indicator.slash(consensuses[0], {'from': accounts[0]}) @@ -1949,6 +1950,7 @@ def test_maintenance_with_less_alternate(validator_set, candidate_hub): turn_round(chain_get_validator_consensus()) enter_maintenance(operators[3]) + def test_delegate_and_transfer_during_maintenance(validator_set, candidate_hub, set_candidate_maintenance): stake_manager = StakeManager() stake_manager.set_lp_rates() @@ -1967,7 +1969,7 @@ def test_delegate_and_transfer_during_maintenance(validator_set, candidate_hub, delegate_coin_success(operator, delegator, delegate_amount) txid = delegate_btc_success(operator, btc_delegator, btc_amount, LOCK_SCRIPT, relay=btc_delegator) transfer_coin_success(operator, operators[1], delegator, delegate_amount // 2) - transfer_btc_success(txid,operators[2],btc_delegator) + transfer_btc_success(txid, operators[2], btc_delegator) undelegate_coin_success(operator, delegator, delegate_amount // 4) delegate_coin_success(operator, delegator, delegate_amount // 10) txid2 = delegate_btc_success(operator, btc_delegator, btc_amount // 10, LOCK_SCRIPT, relay=btc_delegator) @@ -1979,6 +1981,7 @@ def test_delegate_and_transfer_during_maintenance(validator_set, candidate_hub, stake_hub_claim_reward(delegator) turn_round(chain_get_validator_consensus()) + def test_validator_with_no_delegation_cannot_enter_maintenance(validator_set, candidate_hub, set_candidate_maintenance): operators, consensuses = set_candidate_maintenance turn_round() @@ -2565,6 +2568,98 @@ def test_get_validator_index_after_removal_success(validator_set, candidate_hub, assert index_after == 0 +# getLivingValidators +def test_get_living_validators_with_multiple_validators(validator_set): + operators = [] + consensuses = [] + vote_address_list = [random_vote_address() for _ in range(5)] + + for index, operator in enumerate(accounts[10:15]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + delegate_coin_success(operator, accounts[0], 1000 + index * 100) + + turn_round() + + result = validator_set.getLivingValidators() + consensus_addrs, vote_addrs = result + + assert len(consensus_addrs) == len(vote_addrs) + + current_validators = validator_set.getCurrentValidatorSet() + assert len(consensus_addrs) == len(current_validators) + + for consensus_addr in consensus_addrs: + found = False + for validator in current_validators: + if validator['consensusAddress'] == consensus_addr: + found = True + break + assert found + + +def test_get_living_validators_excludes_maintenance_validators(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + + for i, operator in enumerate(operators[:3]): + delegate_coin_success(operator, accounts[0], 5000 + i * 100) + + turn_round() + result_before = validator_set.getLivingValidators() + consensus_addrs_before, _ = result_before + tx = validator_set.enterMaintenance({'from': operators[0]}) + assert 'validatorEnterMaintenance' in tx.events + result_after = validator_set.getLivingValidators() + consensus_addrs_after, vote_addrs_after = result_after + current_validators = validator_set.getCurrentValidatorSet() + assert len(consensus_addrs_after) == len(current_validators) + assert consensuses[0] in consensus_addrs_after + + +def test_get_living_validators_correctness_of_vote_addresses(validator_set): + operators = [] + consensuses = [] + specific_vote_addresses = [random_vote_address() for _ in range(3)] + for index, operator in enumerate(accounts[20:23]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=specific_vote_addresses[index])) + delegate_coin_success(operator, accounts[0], 2000) + turn_round() + result = validator_set.getLivingValidators() + consensus_addrs, vote_addrs = result + for i, consensus_addr in enumerate(consensus_addrs): + if consensus_addr in consensuses: + consensus_index = consensuses.index(consensus_addr) + expected_vote_addr = specific_vote_addresses[consensus_index] + actual_vote_addr = vote_addrs[i] + assert actual_vote_addr == expected_vote_addr + + +def test_canceled_and_felony_validators_not_returned(validator_set, candidate_hub, slash_indicator): + operators = [accounts[30], accounts[31], accounts[32]] + consensuses = [] + for index, operator in enumerate(operators): + consensuses.append(register_candidate(operator=operator)) + delegate_coin_success(operators[0], accounts[0], 1e18) + delegate_coin_success(operators[1], accounts[0], 1e18) + turn_round() + + candidate_hub.refuseDelegate({'from': operators[0]}) + turn_round() + candidate_hub.unregister({'from': operators[0]}) + felony_threshold = slash_indicator.felonyThreshold() + for _ in range(felony_threshold): + slash_indicator.slash(consensuses[1]) + turn_round() + validators = validator_set.getValidators() + assert consensuses[0] not in validators + assert consensuses[1] not in validators + consensus_addrs, _ = validator_set.getLivingValidators() + assert consensuses[0] not in consensus_addrs + assert consensuses[1] not in consensus_addrs + assert consensus_addrs == [consensuses[2]] + + # update maintainSlashPercent def test_revert_when_not_gov_update_maintain_slash_percent(validator_set): hex_value = padding_left(Web3.to_hex(50), 64) diff --git a/tests/utils.py b/tests/utils.py index bc6e55ecd..c6cb9947e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,6 @@ import random import binascii import ecdsa -from _sha256 import sha256 from blspy import AugSchemeMPL from web3 import Web3 @@ -31,7 +30,7 @@ def random_vote_address(): def random_btc_tx_id(): rand_bytes = random.randbytes(32) - tx_id = hashlib.sha256(sha256(rand_bytes).digest()).hexdigest() + tx_id = hashlib.sha256(hashlib.sha256(rand_bytes).digest()).hexdigest() return '0x' + tx_id @@ -69,7 +68,7 @@ def expect_event(tx_receipt: TransactionReceipt, event_name, event_value: dict = def get_transaction_txid(btc_tx): try: - tx_id = '0x' + sha256(sha256(bytes.fromhex(btc_tx)).digest()).digest().hex() + tx_id = '0x' + hashlib.sha256(hashlib.sha256(bytes.fromhex(btc_tx)).digest()).digest().hex() except Exception: tx_id = '0x00' return tx_id @@ -187,7 +186,8 @@ def update_system_contract_address(update_contract, btc_agent=None, core_agent=None, hash_power_agent=None, - configuration=None + configuration=None, + channel=None ): if candidate_hub is None: candidate_hub = CandidateHubMock[0] @@ -221,10 +221,11 @@ def update_system_contract_address(update_contract, hash_power_agent = HashPowerAgentMock[0] if configuration is None: configuration = ConfigurationMock[0] - + if channel is None: + channel = Channel[0] contracts = [ validator_set, slash_indicator, system_reward, btc_light_client, relay_hub, candidate_hub, gov_hub, - pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, core_agent, hash_power_agent, configuration + pledge_agent, burn, foundation, stake_hub, btc_stake, btc_agent, core_agent, hash_power_agent, configuration, channel ] args = encode(['address'] * len(contracts), [c.address for c in contracts]) getattr(update_contract, "updateContractAddr")(args) From dc1ec7808ce578e634929f278dff0633087949e7 Mon Sep 17 00:00:00 2001 From: charles2023wood Date: Tue, 2 Sep 2025 14:44:19 +0800 Subject: [PATCH 15/27] Optimize algorithm for distributing reward when partner is unregistered Signed-off-by: charles2023wood --- contracts/Channel.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/Channel.sol b/contracts/Channel.sol index d28a72195..42d679299 100644 --- a/contracts/Channel.sol +++ b/contracts/Channel.sol @@ -74,7 +74,7 @@ contract Channel is IChannel, System, IParamSubscriber { /// @param reward the reward for total staked tx /// @return remainingReward the remain reward after pay commission. function payCommissionById(uint32 partnerId, uint256 reward) external override onlyCaller(BTC_STAKE_ADDR) returns (uint256 remainingReward) { - if (partnerId == 0 && partnerId > partnerIdGenerator) { + if (partners[partnerId].status == 0) { return reward; } Partner storage p = partners[partnerId]; @@ -97,6 +97,9 @@ contract Channel is IChannel, System, IParamSubscriber { remainingReward = reward; for (uint256 i = ids.length; i != 0; i--) { Partner storage p = partners[ids[i-1]]; + if (p.status == 0) { + continue; + } uint256 commission = p.delegatorAmounts[delegator] * p.coreCommissionRate / SatoshiPlusHelper.DENOMINATOR * reward / amount; p.commission += commission; remainingReward -= commission; @@ -110,7 +113,7 @@ contract Channel is IChannel, System, IParamSubscriber { /// @return feeAddress the fee address function resetCommission(address partner) external override onlyCaller(STAKE_HUB_ADDR) returns (uint256 commission, address feeAddress) { uint32 id = partnerIdMap[partner]; - if (id != 0) { + if (id != 0 && partners[id].status != 0) { feeAddress = partners[id].feeAddress; commission = partners[id].commission; if (commission != 0) { From 16aedc19b323f9cc4ae9d66b4dc0f60963947696 Mon Sep 17 00:00:00 2001 From: charles2023wood Date: Tue, 9 Sep 2025 17:22:31 +0800 Subject: [PATCH 16/27] Remove channel id permission when undelegateCoin from channel Signed-off-by: charles2023wood --- contracts/Channel.sol | 51 +++++++++-------------- contracts/CoreAgent.sol | 65 ++++++++++++++++-------------- contracts/PledgeAgent.sol | 2 +- contracts/interface/IChannel.sol | 6 +++ contracts/interface/ICoreAgent.sol | 3 +- 5 files changed, 62 insertions(+), 65 deletions(-) diff --git a/contracts/Channel.sol b/contracts/Channel.sol index 42d679299..a9d4878cd 100644 --- a/contracts/Channel.sol +++ b/contracts/Channel.sol @@ -122,6 +122,26 @@ contract Channel is IChannel, System, IParamSubscriber { } } + /// This method is a callback when delegate undelegate coin. + /// + /// @param delegator the delegator address + /// @param amount the undelegate amount + function onUndelegateCoin(address delegator, uint256 amount) external override onlyCaller(CORE_AGENT_ADDR) { + uint32[] storage ids = delegators[delegator]; + for (uint256 i = ids.length; amount != 0 && i != 0; i--) { + uint32 tempId = ids[i-1]; + uint256 stakedAmount = partners[tempId].delegatorAmounts[delegator]; + if (stakedAmount > amount) { + partners[tempId].delegatorAmounts[delegator] = stakedAmount - amount; + break; + } else { + amount -= stakedAmount; + delete partners[tempId].delegatorAmounts[delegator]; + ids.pop(); + } + } + } + /*********************** External methods ***************************/ /// Register as a channel partner function register(address feeAddress, uint16 coreCommissionRate, uint16 btcCommissionRate) external payable { @@ -221,37 +241,6 @@ contract Channel is IChannel, System, IParamSubscriber { p.delegatorAmounts[delegator] = existAmount + amount; } - /// unstake CORE from a partner channel - /// @param candidate The operator address of validator - /// @param amount The amount of CORE to undelegate - /// @param id The partner id - function undelegateCoin(address candidate, uint256 amount, uint32 id) external { - if (id == 0 || id > partnerIdGenerator) { - return; - } - uint256 stakedAmount = partners[id].delegatorAmounts[msg.sender]; - if (stakedAmount < amount) { - revert InsufficientAmount(amount, stakedAmount); - } - ICoreAgent(CORE_AGENT_ADDR).proxyUnDelegate(candidate, msg.sender, amount, id); - if (stakedAmount > amount) { - partners[id].delegatorAmounts[msg.sender] = stakedAmount - amount; - } else { - delete partners[id].delegatorAmounts[msg.sender]; - // remove id from Delegator - uint32[] storage ids = delegators[msg.sender]; - for (uint256 i = ids.length; i != 0; i--) { - if (ids[i-1] == id) { - if (i != ids.length) { - ids[i-1] = ids[ids.length-1]; - } - ids.pop(); - } - } - } - Address.sendValue(payable(msg.sender), amount); - } - /*********************** Governance ********************************/ /// Update parameters through governance vote /// @param key The name of the parameter diff --git a/contracts/CoreAgent.sol b/contracts/CoreAgent.sol index 73b9ce5a0..fdd9b0d19 100644 --- a/contracts/CoreAgent.sol +++ b/contracts/CoreAgent.sol @@ -183,16 +183,8 @@ contract CoreAgent is ICoreAgent, System, IParamSubscriber { /// @param candidate The operator address of validator /// @param amount The amount of CORE to undelegate function undelegateCoin(address candidate, uint256 amount) public { - Delegator storage d = delegatorMap[msg.sender]; - if (d.amount < d.channelAmount + amount) { - revert InsufficientTokens(0); - } - - IStakeHub(STAKE_HUB_ADDR).onStakeChange(msg.sender); - uint256 dAmount = _undelegateCoin(candidate, msg.sender, amount, false); - _deductTransferredAmount(msg.sender, dAmount); + amount = undelegate(candidate, msg.sender, amount); Address.sendValue(payable(msg.sender), amount); - emit undelegatedCoin(candidate, msg.sender, amount); } /// Transfer coin stake to a new validator @@ -331,29 +323,9 @@ contract CoreAgent is ICoreAgent, System, IParamSubscriber { /// @param candidate the validator candidate address /// @param delegator the delegator address /// @param amount the amount of CORE to unstake - /// @param channelId the channel id, 0 represents from PledgeAgent - function proxyUnDelegate(address candidate, address delegator, uint256 amount, uint32 channelId) external override onlyInternalCall returns(uint256) { - IStakeHub(STAKE_HUB_ADDR).onStakeChange(delegator); - if (amount == 0) { - amount = candidateMap[candidate].cDelegatorMap[delegator].realtimeAmount; - } - - Delegator storage d = delegatorMap[delegator]; - if (channelId == 0) { - if (d.amount < d.channelAmount + amount) { - revert InsufficientTokens(channelId); - } - } else { - if (d.channelAmount < amount) { - revert InsufficientTokens(channelId); - } - d.channelAmount -= amount; - } - - uint256 dAmount = _undelegateCoin(candidate, delegator, amount, false); - _deductTransferredAmount(delegator, dAmount); + function proxyUnDelegate(address candidate, address delegator, uint256 amount) external override onlyInternalCall returns(uint256) { + amount = undelegate(candidate, delegator, amount); Address.sendValue(payable(PLEDGE_AGENT_ADDR), amount); - emit undelegatedCoin(candidate, delegator, amount); return amount; } @@ -402,6 +374,37 @@ contract CoreAgent is ICoreAgent, System, IParamSubscriber { return cd.realtimeAmount; } + + /// for backward compatibility - allow users to unstake through PledgeAgent + /// support channel from v1.0.20 + /// @param candidate the validator candidate address + /// @param delegator the delegator address + /// @param amount the amount of CORE to unstake + function undelegate(address candidate, address delegator, uint256 amount) internal returns(uint256) { + IStakeHub(STAKE_HUB_ADDR).onStakeChange(delegator); + if (amount == 0) { + amount = candidateMap[candidate].cDelegatorMap[delegator].realtimeAmount; + } + + uint256 dAmount = _undelegateCoin(candidate, delegator, amount, false); + _deductTransferredAmount(delegator, dAmount); + emit undelegatedCoin(candidate, delegator, amount); + + Delegator storage d = delegatorMap[delegator]; + uint256 undelegateAmount = amount; + if (amount > d.channelAmount) { + undelegateAmount = d.channelAmount; + d.channelAmount = 0; + } else { + d.channelAmount -= amount; + } + if (undelegateAmount != 0) { + IChannel(CHANNEL_ADDR).onUndelegateCoin(delegator, undelegateAmount); + } + + return amount; + } + /// undelegate CORE tokens /// @param candidate the validator candidate to delegate to /// @param delegator the delegator address diff --git a/contracts/PledgeAgent.sol b/contracts/PledgeAgent.sol index 3bb7c09d4..001c63700 100644 --- a/contracts/PledgeAgent.sol +++ b/contracts/PledgeAgent.sol @@ -182,7 +182,7 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { _moveCOREData(agent, msg.sender); _distributeReward(msg.sender); - uint256 undelegateAmount = ICoreAgent(CORE_AGENT_ADDR).proxyUnDelegate(agent, msg.sender, amount, 0); + uint256 undelegateAmount = ICoreAgent(CORE_AGENT_ADDR).proxyUnDelegate(agent, msg.sender, amount); Address.sendValue(payable(msg.sender), undelegateAmount); } diff --git a/contracts/interface/IChannel.sol b/contracts/interface/IChannel.sol index 4126a2507..bab5922e2 100644 --- a/contracts/interface/IChannel.sol +++ b/contracts/interface/IChannel.sol @@ -24,4 +24,10 @@ interface IChannel { /// @return commission Amount claimed /// @return feeAddress the fee address function resetCommission(address partner) external returns (uint256 commission, address feeAddress); + + /// This method is a callback when delegate undelegate coin. + /// + /// @param delegator the delegator address + /// @param amount the undelegate amount + function onUndelegateCoin(address delegator, uint256 amount) external; } diff --git a/contracts/interface/ICoreAgent.sol b/contracts/interface/ICoreAgent.sol index 22002cd7a..eafa653c3 100644 --- a/contracts/interface/ICoreAgent.sol +++ b/contracts/interface/ICoreAgent.sol @@ -24,6 +24,5 @@ interface ICoreAgent is IAgent { /// @param candidate the validator candidate address /// @param delegator the delegator address /// @param amount the amount of CORE to unstake - /// @param channelId the channel id, 0 represents from PledgeAgent - function proxyUnDelegate(address candidate, address delegator, uint256 amount, uint32 channelId) external returns(uint256); + function proxyUnDelegate(address candidate, address delegator, uint256 amount) external returns(uint256); } \ No newline at end of file From bc9dfa2d239937a94ca937efd9f350ac0c0c77be Mon Sep 17 00:00:00 2001 From: charles2023wood Date: Fri, 12 Sep 2025 15:39:36 +0800 Subject: [PATCH 17/27] Remove code of moving data in PledgeAgent Signed-off-by: charles2023wood --- contracts/CoreAgent.sol | 75 ++----------- contracts/PledgeAgent.sol | 152 +-------------------------- contracts/interface/IPledgeAgent.sol | 1 - 3 files changed, 14 insertions(+), 214 deletions(-) diff --git a/contracts/CoreAgent.sol b/contracts/CoreAgent.sol index fdd9b0d19..b9e88b455 100644 --- a/contracts/CoreAgent.sol +++ b/contracts/CoreAgent.sol @@ -261,45 +261,6 @@ contract CoreAgent is ICoreAgent, System, IParamSubscriber { revert NotImplemented(); } - /*********************** Receive data from PledgeAgent ***************************/ - /// move staking data for a candidate/delegator pair from PledgeAgent - /// @param candidate the validator candidate address - /// @param delegator the delegator address - /// @param stakedAmount the staked amount of last round snapshot - /// @param transferredAmount the transferred out amount counted in reward calculation - /// @param round data of the round - function moveData(address candidate, address delegator, uint256 stakedAmount, uint256 transferredAmount, uint256 round) external payable onlyInternalCall { - uint256 realtimeAmount = msg.value; - require(stakedAmount <= realtimeAmount, "require stakedAmount <= realtimeAmount"); - Candidate storage a = candidateMap[candidate]; - CoinDelegator storage cd = a.cDelegatorMap[delegator]; - uint256 changeRound = cd.changeRound; - if (changeRound == 0) { - cd.changeRound = roundTag; - delegatorMap[delegator].candidates.push(candidate); - } else if (changeRound != roundTag) { - (uint256 reward, ,) = _collectRewardFromCandidate(candidate, cd); - if (reward != 0) { - rewardMap[delegator].reward += reward; - emit storedReward(candidate, delegator, reward, 0); - } - } - if (round < roundTag) { - (, uint256 reward, ,) = _collectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, round); - stakedAmount = realtimeAmount; - cd.changeRound = roundTag; - if (reward != 0) { - rewardMap[delegator].reward += reward; - emit storedReward(candidate, delegator, reward, 0); - } - } else { - cd.transferredAmount += transferredAmount; - } - cd.stakedAmount += stakedAmount; - cd.realtimeAmount += realtimeAmount; - delegatorMap[delegator].amount += realtimeAmount; - } - /// for backward compatibility - allow users to unstake through PledgeAgent /// support channel from v1.0.20 /// @param candidate the validator candidate address @@ -509,34 +470,12 @@ contract CoreAgent is ICoreAgent, System, IParamSubscriber { uint256 stakedAmount = cd.stakedAmount; uint256 realtimeAmount = cd.realtimeAmount; uint256 transferredAmount = cd.transferredAmount; - bool changed; - (changed, reward, stakedAmount1, stakedAmount2) = _collectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, cd.changeRound); - if (changed) { - if (transferredAmount != 0) { - cd.transferredAmount = 0; - } - if (realtimeAmount != stakedAmount) { - cd.stakedAmount = realtimeAmount; - } - cd.changeRound = roundTag; - } - } - /// collect rewards on a candidate with full parameters - /// @param candidate the candidate to collect reward from - /// @param stakedAmount CORE amount of last turn round snapshot - /// @param realtimeAmount realtime staked CORE amount - /// @param transferredAmount transferred in CORE amount, also eligible for rewards - /// @param changeRound the last round when the delegator acted - /// @return changed whether the changedRound value should be updated - /// @return reward the amount of rewards - /// @return stakedAmount1 the staked amount in the change round - /// @return stakedAmount2 the staked amount in the last round - function _collectReward(address candidate, uint256 stakedAmount, uint256 realtimeAmount, uint256 transferredAmount, uint256 changeRound) internal returns (bool changed, uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2) { + uint256 changeRound = cd.changeRound; require(changeRound != 0, "invalid delegator"); uint256 lastRound = roundTag - 1; - changed = changeRound <= lastRound; - if (changed) { + + if (changeRound <= lastRound) { uint256 changeRoundReward = _getRoundAccruedReward(candidate, changeRound); uint256 lastChangeRoundReward = _getRoundAccruedReward(candidate, changeRound - 1); stakedAmount1 = stakedAmount + transferredAmount; @@ -549,6 +488,14 @@ contract CoreAgent is ICoreAgent, System, IParamSubscriber { } else { stakedAmount2 = stakedAmount1; } + + if (transferredAmount != 0) { + cd.transferredAmount = 0; + } + if (realtimeAmount != stakedAmount) { + cd.stakedAmount = realtimeAmount; + } + cd.changeRound = roundTag; } } diff --git a/contracts/PledgeAgent.sol b/contracts/PledgeAgent.sol index 001c63700..c5c49ba09 100644 --- a/contracts/PledgeAgent.sol +++ b/contracts/PledgeAgent.sol @@ -162,8 +162,6 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { /// @param agent The operator address of validator /// HARDFORK V-1.0.12 Deprecated, the method is kept here for backward compatibility function delegateCoin(address agent) external payable override noReentrant{ - _moveCOREData(agent, msg.sender); - _distributeReward(msg.sender); ICoreAgent(CORE_AGENT_ADDR).proxyDelegate{value: msg.value}(agent, msg.sender, 0); } @@ -179,9 +177,6 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { /// @param amount The amount of CORE to undelegate /// HARDFORK V-1.0.12 Deprecated, the method is kept here for backward compatibility function undelegateCoin(address agent, uint256 amount) public override noReentrant{ - _moveCOREData(agent, msg.sender); - _distributeReward(msg.sender); - uint256 undelegateAmount = ICoreAgent(CORE_AGENT_ADDR).proxyUnDelegate(agent, msg.sender, amount); Address.sendValue(payable(msg.sender), undelegateAmount); } @@ -200,22 +195,13 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { /// @param amount The amount of CORE to transfer // HARDFORK V-1.0.12 Deprecated, the method is kept here for backward compatibility function transferCoin(address sourceAgent, address targetAgent, uint256 amount) public override noReentrant{ - _moveCOREData(sourceAgent, msg.sender); - _moveCOREData(targetAgent, msg.sender); - _distributeReward(msg.sender); - (bool success, ) = CORE_AGENT_ADDR.call(abi.encodeWithSignature("proxyTransfer(address,address,address,uint256)", sourceAgent, targetAgent, msg.sender, amount)); require (success, "call CORE_AGENT_ADDR.proxyTransfer() failed"); } /// Claim rewards for delegator - /// @param agentList The list of validators to claim rewards on, it can be empty /// @return (Amount claimed, Are all rewards claimed) - function claimReward(address[] calldata agentList) external override noReentrant returns (uint256, bool) { - uint256 agentSize = agentList.length; - for (uint256 i = 0; i < agentSize; ++i) { - _moveCOREData(agentList[i], msg.sender); - } + function claimReward(address[] calldata) external override noReentrant returns (uint256, bool) { uint256 rewardSum = rewardMap[msg.sender]; (bool success, bytes memory data) = STAKE_HUB_ADDR.call(abi.encodeWithSignature("proxyClaimReward(address)", msg.sender)); @@ -231,27 +217,6 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { return (rewardSum + proxyRewardSum, true); } - /// calculate reward for delegator - /// the rewards will be collected to rewardMap after execution - /// @param agentList The list of validators to calculate rewards - /// @param delegator the delegator to calculate rewards - function calculateReward(address[] calldata agentList, address delegator) external override returns (uint256) { - uint256 agentSize = agentList.length; - for (uint256 i = 0; i < agentSize; ++i) { - _moveCOREData(agentList[i], delegator); - } - return rewardMap[delegator]; - } - - // HARDFORK V-1.0.12 - /*********************** Move data ***************************/ - /// move delegator data to new contracts - /// @param candidate the validator candidate address - /// @param delegator the delegator address - function moveCOREData(address candidate, address delegator) external { - _moveCOREData(candidate, delegator); - } - /*********************** Internal methods ***************************/ /// send rewards to delegator and clear the record in rewardMap /// @param delegator the delegator address @@ -264,116 +229,6 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { } } - /// move historical CORE stake information from PledgeAgent to CoreAgent - /// the record will be removed in PledgeAgent after move - /// @param candidate the validator candidate address - /// @param delegator the delegator address - function _moveCOREData(address candidate, address delegator) internal returns(bool) { - Agent storage a = agentsMap[candidate]; - CoinDelegator storage d = a.cDelegatorMap[delegator]; - if (d.changeRound != 0) { - uint256 reward = _collectCoinReward(a, d, delegator); - if (reward != 0) { - rewardMap[delegator] += reward; - } - - uint256 deposit = d.deposit; - if (d.changeRound < roundTag) { - deposit = d.newDeposit; - } - (bool success, ) = CORE_AGENT_ADDR.call {value: d.newDeposit} (abi.encodeWithSignature("moveData(address,address,uint256,uint256,uint256)", candidate, delegator, deposit, d.transferOutDeposit, roundTag)); - require (success, "call CORE_AGENT_ADDR.moveData() failed"); - - a.totalDeposit -= d.newDeposit; - delete a.cDelegatorMap[delegator]; - delete debtDepositMap[roundTag][delegator]; - return true; - } - return false; - } - - /// collect rewards on a given reward map - /// @param r the reward map to collect - /// @param deposit the amount of stake - /// @return rewardAmount the amount of reward to claim - function _collectFromRoundReward(Reward storage r, uint256 deposit) internal returns (uint256 rewardAmount) { - require(r.coin >= deposit, "reward is not enough"); - uint256 curReward; - if (r.coin == deposit) { - curReward = r.remainReward; - r.coin = 0; - } else { - uint256 rsPower = stateMap[r.round].power; - curReward = (r.totalReward * deposit * rsPower) / r.score; - require(r.remainReward >= curReward, "there is not enough reward"); - r.coin -= deposit; - r.remainReward -= curReward; - } - return curReward; - } - - /// collect rewards on a candidate/delegator paris - /// @param a the validator candidate - /// @param d the delegator - /// @return rewardAmount the amount of reward to claim - function _collectCoinReward(Agent storage a, CoinDelegator storage d, address delegator) internal returns (uint256 rewardAmount) { - uint256 changeRound = d.changeRound; - uint256 curRound = roundTag; - if (changeRound < curRound) { - d.transferInDeposit = 0; - } - - uint256 rewardLength = a.rewardSet.length; - uint256 rewardIndex = d.rewardIndex; - if (rewardIndex >= rewardLength) { - return 0; - } - while (rewardIndex < rewardLength) { - Reward storage r = a.rewardSet[rewardIndex]; - uint256 rRound = r.round; - if (rRound == curRound) { - break; - } - uint256 deposit = d.newDeposit; - // HARDFORK V-1.0.3 - // d.deposit and d.transferOutDeposit are both eligible for claiming rewards - // however, d.transferOutDeposit will be used to pay the DEBT for the delegator before that - // the rewards from the DEBT will be collected and sent to the system reward contract - if (rRound == changeRound) { - uint256 transferOutDeposit = d.transferOutDeposit; - uint256 debt = debtDepositMap[rRound][delegator]; - if (transferOutDeposit > debt) { - transferOutDeposit -= debt; - debtDepositMap[rRound][delegator] = 0; - } else { - debtDepositMap[rRound][delegator] -= transferOutDeposit; - transferOutDeposit = 0; - } - if (transferOutDeposit != d.transferOutDeposit) { - uint256 undelegateReward = _collectFromRoundReward(r, d.transferOutDeposit - transferOutDeposit); - if (r.coin == 0) { - delete a.rewardSet[rewardIndex]; - } - ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{ value: undelegateReward }(); - } - deposit = d.deposit + transferOutDeposit; - d.deposit = d.newDeposit; - d.transferOutDeposit = 0; - } - if (deposit != 0) { - rewardAmount += _collectFromRoundReward(r, deposit); - if (r.coin == 0) { - delete a.rewardSet[rewardIndex]; - } - } - rewardIndex++; - } - - // update index whenever claim happens - d.rewardIndex = rewardIndex; - return rewardAmount; - } - /*********************** Governance ********************************/ /// Update parameters through governance vote /// @param key The name of the parameter @@ -407,11 +262,10 @@ contract PledgeAgent is IPledgeAgent, System, IParamSubscriber { require (success, "call CORE_AGENT_ADDR.getDelegator() failed"); (uint256 stakedAmount, uint256 realtimeAmount, uint256 transferredAmount, uint256 changeRound) = abi.decode(result, (uint256,uint256,uint256,uint256)); if (realtimeAmount != 0 || transferredAmount != 0) { - cd.deposit = cd.newDeposit + stakedAmount; - cd.newDeposit += realtimeAmount; + cd.deposit = stakedAmount; + cd.newDeposit = realtimeAmount; cd.changeRound = changeRound; cd.transferOutDeposit = transferredAmount; - cd.transferInDeposit = 0; } } diff --git a/contracts/interface/IPledgeAgent.sol b/contracts/interface/IPledgeAgent.sol index c34d77633..d6eae109b 100644 --- a/contracts/interface/IPledgeAgent.sol +++ b/contracts/interface/IPledgeAgent.sol @@ -8,5 +8,4 @@ interface IPledgeAgent { function transferCoin(address sourceAgent, address targetAgent) external; function transferCoin(address sourceAgent, address targetAgent, uint256 amount) external; function claimReward(address[] calldata agentList) external returns (uint256, bool); - function calculateReward(address[] calldata agentList, address delegator) external returns (uint256); } From 2f1bde5fb90b0f5854f29fff24964c38b023c176 Mon Sep 17 00:00:00 2001 From: zfliex Date: Tue, 16 Sep 2025 15:58:06 +0800 Subject: [PATCH 18/27] update config --- contracts/SlashIndicator.sol | 4 +-- contracts/ValidatorSet.sol | 45 ++++++++++++++++++++++++++++++--- contracts/ValidatorSet.template | 43 ++++++++++++++++++++++++++++--- generate-validator.js | 1 + generate-validatorset.js | 3 ++- genesis-template.json | 44 +++++++++++++++++++++++++++----- init_cycle.js | 3 ++- validators.js | 19 +++++++++++--- validators.template | 24 ++++++++++++++---- 9 files changed, 161 insertions(+), 25 deletions(-) diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 0cdf2f1ee..510441cbd 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -365,11 +365,11 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber{ bytesConcat(input, voteAddr, 128, 48); // call the precompiled contract to verify the BLS signature - // the precompiled contract's address is 0x66 + // the precompiled contract's address is 0x65 bytes memory output = new bytes(1); assembly { let len := mload(input) - if iszero(staticcall(not(0), 0x66, add(input, 0x20), len, add(output, 0x20), 0x01)) { + if iszero(staticcall(not(0), 0x65, add(input, 0x20), len, add(output, 0x20), 0x01)) { revert(0, 0) } } diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index b83189247..3869fddce 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -23,8 +23,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public constant BLOCK_REWARD_INCENTIVE_PERCENT = 10; uint256 public constant REDUCE_FACTOR = 9639; uint256 public constant SUBSIDY_REDUCE_INTERVAL = 10512000; + uint256 public constant INIT_TURN_LENGTH = 1; - bytes public constant INIT_VALIDATORSET_BYTES = hex"f90285ea944121f067b0f5135d77c29b2b329e8cb1bd96c96094f8b18cecc98d976ad253d38e4100a73d4e154726ea947f461f8a1c35edecd6816e76eb2e84eb661751ee94f8b18cecc98d976ad253d38e4100a73d4e154726ea94fd806ab93db5742944b7b50ce759e5eee5f6fe5094f8b18cecc98d976ad253d38e4100a73d4e154726ea947ef3a94ad1c443481fb3d86829355ca90477f8b594f8b18cecc98d976ad253d38e4100a73d4e154726ea9467d1ad48f91e131413bd0b04e823f3ae4f81e85394f8b18cecc98d976ad253d38e4100a73d4e154726ea943fb42cab4416024dc1b4c9e21b9acd0dfcef35f694f8b18cecc98d976ad253d38e4100a73d4e154726ea943511e3b8ac7336b99517d324145e9b5bb33e08a494f8b18cecc98d976ad253d38e4100a73d4e154726ea94729f39a54304fcc6ec279684c71491a385d7b9ae94f8b18cecc98d976ad253d38e4100a73d4e154726ea94f44a785fd9f23f0abd443541386e71356ce619dc94f8b18cecc98d976ad253d38e4100a73d4e154726ea942efd3cf0733421aec3e4202480d0a90bd157514994f8b18cecc98d976ad253d38e4100a73d4e154726ea94613b0f519ada008cb99b6130e89122ba416bf15994f8b18cecc98d976ad253d38e4100a73d4e154726ea94c0925eeb800ff6ba4695ded61562a10102152b5f94f8b18cecc98d976ad253d38e4100a73d4e154726ea9419e3c7d7e69f273f3f91c060bb438a007f6fc33c94f8b18cecc98d976ad253d38e4100a73d4e154726ea94e127f110d172a0c4c6209fe045dd71781e8fe9d494f8b18cecc98d976ad253d38e4100a73d4e154726ea94f778dc4a199a440dbe9f16d1e13e185bb179b3b794f8b18cecc98d976ad253d38e4100a73d4e154726"; + bytes public constant INIT_VALIDATORSET_BYTES = hex"f90573f85b944121f067b0f5135d77c29b2b329e8cb1bd96c96094f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b947f461f8a1c35edecd6816e76eb2e84eb661751ee94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94fd806ab93db5742944b7b50ce759e5eee5f6fe5094f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b947ef3a94ad1c443481fb3d86829355ca90477f8b594f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b9467d1ad48f91e131413bd0b04e823f3ae4f81e85394f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b943fb42cab4416024dc1b4c9e21b9acd0dfcef35f694f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b943511e3b8ac7336b99517d324145e9b5bb33e08a494f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94729f39a54304fcc6ec279684c71491a385d7b9ae94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94f44a785fd9f23f0abd443541386e71356ce619dc94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b942efd3cf0733421aec3e4202480d0a90bd157514994f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94613b0f519ada008cb99b6130e89122ba416bf15994f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94c0925eeb800ff6ba4695ded61562a10102152b5f94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b9419e3c7d7e69f273f3f91c060bb438a007f6fc33c94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94e127f110d172a0c4c6209fe045dd71781e8fe9d494f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94f778dc4a199a440dbe9f16d1e13e185bb179b3b794f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; /*********************** state of the contract **************************/ uint256 public blockReward; @@ -43,6 +44,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public validatorCount; address[] public rankedValidatorList; + uint256 public turnLength; + struct Validator { address operateAddress; address consensusAddress; @@ -98,6 +101,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } blockReward = BLOCK_REWARD; blockRewardIncentivePercent = BLOCK_REWARD_INCENTIVE_PERCENT; + turnLength = INIT_TURN_LENGTH; alreadyInit = true; } @@ -408,6 +412,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return currentValidatorSet[index - 1].income; } + /// Get the index of a validator in the current round + /// @param ops The operate address + /// @return The index of the validator function getValidatorIndexFromOps(address ops) public view returns (uint256) { uint256 len = currentValidatorSet.length; for (uint256 i = 0; i < len; i++) { @@ -418,6 +425,17 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return 0; } + /// Get the complete ranked validator list + /// @return List of ranked validator consensus addresses + function getRankedValidatorList() external view returns (address[] memory) { + uint256 length = rankedValidatorList.length; + address[] memory rankedValidators = new address[](length); + for (uint256 i = 0; i < length; i++) { + rankedValidators[i] = rankedValidatorList[i]; + } + return rankedValidators; + } + /// Get the list of validators that are still in the validator set /// @return (List of validator consensus addresses, List of voting addresses) function getLivingValidators() external view override returns (address[] memory, bytes[] memory) { @@ -528,6 +546,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { revert OutOfBounds(key, newMaintainSlashPercent, 0, 100); } maintainSlashPercent = newMaintainSlashPercent; + } else if (Memory.compareStrings(key, "turnLength")) { + uint256 newTurnLength = BytesToTypes.bytesToUint256(32, value); + if (newTurnLength == 0 || newTurnLength > 9) { + revert OutOfBounds(key, newTurnLength, 1, 9); + } + turnLength = newTurnLength; } else { revert UnsupportedGovParam(key); } @@ -609,6 +633,15 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return workingValidators; } + /// Get turn length + /// @return The turn length, returns INIT_TURN_LENGTH if turnLength is 0 + function getTurnLength() public view returns (uint256) { + if (turnLength == 0) { + return INIT_TURN_LENGTH; + } + return turnLength; + } + //rlp encode & decode function function decodeValidatorSet(bytes memory msgBytes) internal pure returns (Validator[] memory, bool) { RLPDecode.RLPItem[] memory items = msgBytes.toRLPItem().toList(); @@ -628,14 +661,18 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { function decodeValidator(RLPDecode.RLPItem memory itemValidator) internal pure returns (Validator memory, bool) { Validator memory validator; RLPDecode.Iterator memory iter = itemValidator.iterator(); - bool success = false; - while (iter.hasNext()) { + bool success = iter.hasNext(); + while (iter.hasNext() && success) { validator.consensusAddress = iter.next().toAddress(); validator.feeAddress = payable(iter.next().toAddress()); validator.operateAddress = validator.feeAddress; + validator.voteAddr = iter.next().toBytes(); validator.commissionThousandths = 1000; - success = true; + if (validator.voteAddr.length != 48) { + success = false; + } } + return (validator, success); } diff --git a/contracts/ValidatorSet.template b/contracts/ValidatorSet.template index 48886a824..220b5a26c 100644 --- a/contracts/ValidatorSet.template +++ b/contracts/ValidatorSet.template @@ -23,6 +23,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public constant BLOCK_REWARD_INCENTIVE_PERCENT = 10; uint256 public constant REDUCE_FACTOR = 9639; uint256 public {% if not mock %}constant{% endif %} SUBSIDY_REDUCE_INTERVAL = 10512000; + uint256 public constant INIT_TURN_LENGTH = {{initTurnLength}}; bytes public constant INIT_VALIDATORSET_BYTES = hex"{{initValidatorSetBytes}}"; @@ -43,6 +44,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public validatorCount; address[] public rankedValidatorList; + uint256 public turnLength; + struct Validator { address operateAddress; address consensusAddress; @@ -98,6 +101,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } blockReward = BLOCK_REWARD; blockRewardIncentivePercent = BLOCK_REWARD_INCENTIVE_PERCENT; + turnLength = INIT_TURN_LENGTH; alreadyInit = true; } @@ -408,6 +412,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return currentValidatorSet[index - 1].income; } + /// Get the index of a validator in the current round + /// @param ops The operate address + /// @return The index of the validator function getValidatorIndexFromOps(address ops) public view returns (uint256) { uint256 len = currentValidatorSet.length; for (uint256 i = 0; i < len; i++) { @@ -418,6 +425,17 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return 0; } + /// Get the complete ranked validator list + /// @return List of ranked validator consensus addresses + function getRankedValidatorList() external view returns (address[] memory) { + uint256 length = rankedValidatorList.length; + address[] memory rankedValidators = new address[](length); + for (uint256 i = 0; i < length; i++) { + rankedValidators[i] = rankedValidatorList[i]; + } + return rankedValidators; + } + /// Get the list of validators that are still in the validator set /// @return (List of validator consensus addresses, List of voting addresses) function getLivingValidators() external view override returns (address[] memory, bytes[] memory) { @@ -528,6 +546,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { revert OutOfBounds(key, newMaintainSlashPercent, 0, 100); } maintainSlashPercent = newMaintainSlashPercent; + } else if (Memory.compareStrings(key, "turnLength")) { + uint256 newTurnLength = BytesToTypes.bytesToUint256(32, value); + if (newTurnLength == 0 || newTurnLength > 9) { + revert OutOfBounds(key, newTurnLength, 1, 9); + } + turnLength = newTurnLength; } else { revert UnsupportedGovParam(key); } @@ -609,6 +633,15 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { return workingValidators; } + /// Get turn length + /// @return The turn length, returns INIT_TURN_LENGTH if turnLength is 0 + function getTurnLength() public view returns (uint256) { + if (turnLength == 0) { + return INIT_TURN_LENGTH; + } + return turnLength; + } + //rlp encode & decode function function decodeValidatorSet(bytes memory msgBytes) internal pure returns (Validator[] memory, bool) { RLPDecode.RLPItem[] memory items = msgBytes.toRLPItem().toList(); @@ -628,14 +661,18 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { function decodeValidator(RLPDecode.RLPItem memory itemValidator) internal pure returns (Validator memory, bool) { Validator memory validator; RLPDecode.Iterator memory iter = itemValidator.iterator(); - bool success = false; - while (iter.hasNext()) { + bool success = iter.hasNext(); + while (iter.hasNext() && success) { validator.consensusAddress = iter.next().toAddress(); validator.feeAddress = payable(iter.next().toAddress()); validator.operateAddress = validator.feeAddress; + validator.voteAddr = iter.next().toBytes(); validator.commissionThousandths = 1000; - success = true; + if (validator.voteAddr.length != 48) { + success = false; + } } + return (validator, success); } diff --git a/generate-validator.js b/generate-validator.js index 16ee21302..3c419cfa4 100644 --- a/generate-validator.js +++ b/generate-validator.js @@ -26,6 +26,7 @@ async function processValidatorConf(network) { validators.push({ consensusAddr: vs[0], feeAddr: vs[1], + voteAddr: vs[2] || '0x' + '00'.repeat(48), // 48 bytes of zeros }) } return validators diff --git a/generate-validatorset.js b/generate-validatorset.js index c3303a117..08d51adf1 100644 --- a/generate-validatorset.js +++ b/generate-validatorset.js @@ -1,7 +1,7 @@ const program = require("commander"); const fs = require("fs"); const nunjucks = require("nunjucks"); - +const init_cycle = require("./init_cycle") program.version("0.0.1"); program.option( @@ -35,6 +35,7 @@ if (initValidatorSetBytes == ""){ } const data = { initValidatorSetBytes: initValidatorSetBytes, + initTurnLength: init_cycle.turnLength, mock: program.mock, }; diff --git a/genesis-template.json b/genesis-template.json index aac1c4baf..557596232 100644 --- a/genesis-template.json +++ b/genesis-template.json @@ -12,6 +12,37 @@ "istanbulBlock": 0, "muirGlacierBlock": 0, "hashPowerBlock": 0, + "zeusBlock": 0, + "heraBlock": 0, + "poseidonBlock": 0, + "hertzBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "shanghaiTime": 0, + "keplerTime": 0, + "demeterTime": 0, + "athenaTime": 0, + "cancunTime": 0, + "theseusTime": 0, + "theseusFixTime": 0, + "lubanTime": 0, + "platoTime": 0, + "bohrTime": 0, + "pascalTime": 0, + "pragueTime": 0, + "hermesTime": 0, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + }, + "prague": { + "target": 6, + "max": 9, + "baseFeeUpdateFraction": 5007716 + } + }, "satoshi": { "period": {{initCycle.blockPeriod}}, "epoch": {{initCycle.epochLength}}, @@ -89,17 +120,18 @@ "balance": "0x0", "code": "0x{{bitcoinstake}}" }, - "0x0000000000000000000000000000000000001015": { - "balance": "0x0", - "code": "0x{{bitcoinLSTstake}}" - }, "0x0000000000000000000000000000000000001016": { "balance": "0x0", "code": "0x{{configuration}}" }, - "0x0000000000000000000000000000000000010001": { + "0x0000000000000000000000000000000000001017": { + "balance": "0x0", + "code": "0x{{channel}}" + }, + "0x0000F90827F1C53a10cb7A02335B175320002935": { "balance": "0x0", - "code": "0x{{bitcoinLSTtoken}}" + "nonce": "0x1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" }{% for v in initHolders %}, "{{ v.address.replace('0x', '') }}": { "balance": "0x{{ v.balance }}" diff --git a/init_cycle.js b/init_cycle.js index 5feb1cfc8..0735a72c5 100644 --- a/init_cycle.js +++ b/init_cycle.js @@ -3,7 +3,8 @@ const init_cycle = { blockPeriod: 3, epochLength: 200, roundInterval: 86400, - validatorCount: 21 + validatorCount: 21, + turnLength: 1 }; diff --git a/validators.js b/validators.js index 2d989e8d8..12d60ebfc 100644 --- a/validators.js +++ b/validators.js @@ -1,5 +1,6 @@ const web3 = require("web3") const RLP = require('rlp'); +const init_cycle = require("./init_cycle") // Configure const validators = [ @@ -7,6 +8,7 @@ const validators = [ { "consensusAddr": "0x4121F067B0F5135D77C29b2B329e8Cb1bd96C960", "feeAddr": "0xF8B18CeCC98D976ad253D38E4100a73D4e154726", + // "voteAddr": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" // optional voteAddr field }, { "consensusAddr": "0x7f461f8a1c35eDEcD6816e76Eb2E84eb661751eE", @@ -68,18 +70,25 @@ const validators = [ // =============== Do not edit below ==== function generateExtradata(validators) { - let extraVanity =Buffer.alloc(32); + let extraVanity = Buffer.alloc(32); let validatorsBytes = extraDataSerialize(validators); - let extraSeal =Buffer.alloc(65); - return Buffer.concat([extraVanity,validatorsBytes,extraSeal]); + let turnLengthByte = Buffer.from([init_cycle.turnLength]); // Initial turnLength from init_cycle.js + let extraSeal = Buffer.alloc(65); + return Buffer.concat([extraVanity,validatorsBytes,turnLengthByte,extraSeal]); } function extraDataSerialize(validators) { let n = validators.length; let arr = []; + const defaultVoteAddr = "0x" + "00".repeat(48); // 48 bytes of zeros + + arr.push(Buffer.from([n])); for (let i = 0;i Date: Tue, 16 Sep 2025 16:01:23 +0800 Subject: [PATCH 19/27] fix audit feedback --- contracts/BtcLightClient.sol | 3 +++ contracts/BtcLightClient.template | 3 +++ contracts/Configuration.sol | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/BtcLightClient.sol b/contracts/BtcLightClient.sol index 94f33fe68..e5f36c133 100644 --- a/contracts/BtcLightClient.sol +++ b/contracts/BtcLightClient.sol @@ -110,6 +110,7 @@ contract BtcLightClient is ILightClient, System, IParamSubscriber{ roundSize = ROUND_SIZE; maxWeight = MAXIMUM_WEIGHT; storeBlockGasPrice = INIT_STORE_BLOCK_GAS_PRICE; + height2HashMap[INIT_CHAIN_HEIGHT] = blockHash; alreadyInit = true; } @@ -120,6 +121,8 @@ contract BtcLightClient is ILightClient, System, IParamSubscriber{ require( tx.gasprice == (storeBlockGasPrice == 0 ? INIT_STORE_BLOCK_GAS_PRICE : storeBlockGasPrice), "must use limited gasprice"); + require(blockBytes.length >= 80, "block bytes length must >= 80"); + bytes memory headerBytes = slice(blockBytes, 0, 80); bytes32 blockHash = doubleShaFlip(headerBytes); require(submitters[blockHash] == address(0x0), "can't sync duplicated header"); diff --git a/contracts/BtcLightClient.template b/contracts/BtcLightClient.template index b06f16b9f..7579334b7 100644 --- a/contracts/BtcLightClient.template +++ b/contracts/BtcLightClient.template @@ -110,6 +110,7 @@ contract BtcLightClient is ILightClient, System, IParamSubscriber{ roundSize = ROUND_SIZE; maxWeight = MAXIMUM_WEIGHT; storeBlockGasPrice = INIT_STORE_BLOCK_GAS_PRICE; + height2HashMap[INIT_CHAIN_HEIGHT] = blockHash; alreadyInit = true; } @@ -120,6 +121,8 @@ contract BtcLightClient is ILightClient, System, IParamSubscriber{ require( tx.gasprice == (storeBlockGasPrice == 0 ? INIT_STORE_BLOCK_GAS_PRICE : storeBlockGasPrice), "must use limited gasprice"); + require(blockBytes.length >= 80, "block bytes length must >= 80"); + bytes memory headerBytes = slice(blockBytes, 0, 80); bytes32 blockHash = doubleShaFlip(headerBytes); require(submitters[blockHash] == address(0x0), "can't sync duplicated header"); diff --git a/contracts/Configuration.sol b/contracts/Configuration.sol index a9b686849..d18645152 100644 --- a/contracts/Configuration.sol +++ b/contracts/Configuration.sol @@ -210,8 +210,9 @@ contract Configuration is System { */ function toBytes32(RLPDecode.RLPItem memory item) internal pure returns (bytes32) { bytes memory data = item.toBytes(); - bytes32 result; + require(data.length >= 32, "invalid input length"); + bytes32 result; assembly { result := mload(add(data, 32)) } From b8ab831d56f029006531bc97b1554615b7943d53 Mon Sep 17 00:00:00 2001 From: eric Date: Wed, 3 Sep 2025 16:28:21 +0800 Subject: [PATCH 20/27] add tests for bug in 1.1.11 --- tests/test_channel.py | 377 +++++++++++++++++++++++------------------- 1 file changed, 207 insertions(+), 170 deletions(-) diff --git a/tests/test_channel.py b/tests/test_channel.py index b5be73fd9..1d17072d8 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -6,6 +6,7 @@ from eth_abi import encode from web3 import Web3 from .constant import Utils +from .utils import random_address from .common import execute_proposal, register_candidate, turn_round from .delegate import delegate_btc_success, StakeManager, delegate_power_success, delegate_coin_success @@ -78,6 +79,16 @@ def set_channel_partner(channel, required_margin): return partner, fee_address, partner_id, core_commission_rate, btc_commission_rate +@pytest.fixture +def set_channel(channel, required_margin): + def register_channel(partner, fee_address=None, core_commission_rate=1000, btc_commission_rate=1500): + if fee_address is None: + fee_address = random_address() + channel.register(fee_address, core_commission_rate, btc_commission_rate, {'from': partner, 'value': required_margin}) + return partner, fee_address, channel.partnerIdMap(partner), core_commission_rate, btc_commission_rate + return register_channel + + def test_init(channel): assert channel.alreadyInit() is True assert channel.requiredMargin() == channel.INIT_REQUIRED_MARGIN() @@ -272,7 +283,25 @@ def test_reset_commission_zero_commission(channel, set_channel_partner, stake_hu def test_reset_commission_nonexistent_partner(channel, stake_hub): non_existent_partner = accounts[99] - channel.resetCommission(non_existent_partner, {'from': stake_hub}) + commission, _ = channel.resetCommission.call(non_existent_partner, {'from': stake_hub}) + assert commission == 0 + + +def test_reset_commission_with_unregistered_partner(channel, stake_hub, btc_stake, set_channel_partner): + partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner + + reward = 10000 + channel.payCommissionById(partner_id, reward, {'from': btc_stake}) + partner_info = channel.partners(partner_id) + assert partner_info[-1] == reward * btc_commission_rate // 10000 + + commission, _ = channel.resetCommission.call(partner, {'from': stake_hub}) + assert commission == reward * btc_commission_rate // 10000 + + channel.unregister({'from': partner}) + + commission, _ = channel.resetCommission.call(partner, {'from': stake_hub}) + assert commission == 0 def test_reset_commission_unauthorized_caller(channel, set_channel_partner, stake_hub): @@ -639,125 +668,77 @@ def test_delegate_coin_invalid_channel_id(channel, required_margin): ) -def test_undelegate_coin_success(channel, required_margin, core_agent): - partner = accounts[1] - fee_address = accounts[2] - delegator = accounts[3] - operator = accounts[4] - register_candidate(operator=operator) - candidate = operator - core_commission_rate = 1000 - btc_commission_rate = 1500 - delegate_amount = Wei("10 ether") - undelegate_amount = Wei("3 ether") - - channel.register( - fee_address, core_commission_rate, btc_commission_rate, - {'from': partner, 'value': required_margin} - ) - partner_id = channel.partnerIdMap(partner) - - channel.delegateCoin( - candidate, partner_id, - {'from': delegator, 'value': delegate_amount} - ) - - initial_balance = delegator.balance() - channel.undelegateCoin( - candidate, undelegate_amount, partner_id, - {'from': delegator} - ) - - remaining_amount = channel.getDelegatorAmount(partner_id, delegator) - assert remaining_amount == delegate_amount - undelegate_amount - - assert delegator.balance() > initial_balance - - -def test_undelegate_coin_insufficient_amount(channel, required_margin): - partner = accounts[1] - fee_address = accounts[2] - delegator = accounts[3] - operator = accounts[4] +@pytest.mark.parametrize("_type", ["zero", "partial", "all", "above"]) +def test_on_undelegate_coin(channel, core_agent, _type, set_channel): + operator = accounts[1] register_candidate(operator=operator) - candidate = operator - core_commission_rate = 1000 - btc_commission_rate = 1500 - delegate_amount = Wei("10 ether") - undelegate_amount = Wei("15 ether") - - channel.register( - fee_address, core_commission_rate, btc_commission_rate, - {'from': partner, 'value': required_margin} - ) - partner_id = channel.partnerIdMap(partner) - - channel.delegateCoin( - candidate, partner_id, - {'from': delegator, 'value': delegate_amount} - ) - - with brownie.reverts(f"InsufficientAmount: {undelegate_amount}, {delegate_amount}"): - channel.undelegateCoin( - candidate, undelegate_amount, partner_id, - {'from': delegator} - ) + partner1, partner2, partner3 = accounts[2:5] + _,_, partner1_id,_,_ = set_channel(partner1) + _,_, partner2_id,_,_ = set_channel(partner2) + _,_, partner3_id,_,_ = set_channel(partner3) -def test_undelegate_coin_complete_removal(channel, required_margin, core_agent): - partner = accounts[1] - fee_address = accounts[2] - delegator = accounts[3] - operator = accounts[4] - register_candidate(operator=operator) - candidate = operator - core_commission_rate = 1000 - btc_commission_rate = 1500 - delegate_amount = Wei("10 ether") - - channel.register( - fee_address, core_commission_rate, btc_commission_rate, - {'from': partner, 'value': required_margin} - ) - partner_id = channel.partnerIdMap(partner) - - channel.delegateCoin( - candidate, partner_id, - {'from': delegator, 'value': delegate_amount} - ) - - channel_ids = channel.getIds(delegator) - assert partner_id in channel_ids - - channel.undelegateCoin( - candidate, delegate_amount, partner_id, - {'from': delegator} + delegator = accounts[0] + delegate_amount = 1000 + + channel.delegateCoin(operator, partner1_id, {'from': delegator, 'value': delegate_amount}) + channel.delegateCoin(operator, partner2_id, {'from': delegator, 'value': delegate_amount}) + channel.delegateCoin(operator, partner3_id, {'from': delegator, 'value': delegate_amount}) + + all_ids = channel.getIds(delegator) + for _id in (partner1_id, partner2_id, partner3_id): + assert _id in all_ids + + if _type == "zero": + channel.onUndelegateCoin(delegator, 0, {'from': core_agent}) + all_ids = channel.getIds(delegator) + for _id in (partner1_id, partner2_id, partner3_id): + assert _id in all_ids + assert channel.getDelegatorAmount(_id, delegator) == delegate_amount + elif _type == "partial": + channel.onUndelegateCoin(delegator, delegate_amount + delegate_amount // 2, {'from': core_agent}) + all_ids = channel.getIds(delegator) + assert len(all_ids) == 2 + assert partner2_id in all_ids + assert partner1_id in all_ids + assert channel.getDelegatorAmount(partner1_id, delegator) == delegate_amount + assert channel.getDelegatorAmount(partner2_id, delegator) == delegate_amount // 2 + assert channel.getDelegatorAmount(partner3_id, delegator) == 0 + elif _type == "all": + channel.onUndelegateCoin(delegator, delegate_amount * 3, {'from': core_agent}) + all_ids = channel.getIds(delegator) + assert all_ids == [] + for _id in (partner1_id, partner2_id, partner3_id): + assert channel.getDelegatorAmount(_id, delegator) == 0 + elif _type == "above": + channel.onUndelegateCoin(delegator, delegate_amount * 3 + 1, {'from': core_agent}) + all_ids = channel.getIds(delegator) + assert all_ids == [] + for _id in (partner1_id, partner2_id, partner3_id): + assert channel.getDelegatorAmount(_id, delegator) == 0 + + +def test_pay_commission_by_id_with_invalid_id(channel, btc_stake): + partner_id = 999 + reward_amount = 100 + + reward = channel.payCommissionById.call( + partner_id, reward_amount, + {'from': btc_stake.address} ) - - channel_ids = channel.getIds(delegator) - assert partner_id not in channel_ids - - delegator_amount = channel.getDelegatorAmount(partner_id, delegator) - assert delegator_amount == 0 - - delegator_map = core_agent.getDelegatorMap(delegator) - assert delegator_map[2] == 0 + assert reward == reward_amount -def test_undelegate_coin_invalid_channel_id(channel, required_margin): - delegator = accounts[3] - operator = accounts[4] - register_candidate(operator=operator) - candidate = operator - undelegate_amount = Wei("3 ether") - invalid_channel_id = 999 +def test_pay_commission_by_id_with_unregistered_partner(channel, btc_stake, set_channel_partner): + partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner + channel.unregister({'from': partner}) - initial_balance = delegator.balance() - channel.undelegateCoin( - candidate, undelegate_amount, invalid_channel_id, - {'from': delegator} + reward_amount = 100 + reward = channel.payCommissionById.call( + partner_id, reward_amount, + {'from': btc_stake.address} ) - assert delegator.balance() == initial_balance + assert reward == reward_amount def test_pay_commission_by_id_success(channel, required_margin, btc_stake): @@ -826,6 +807,55 @@ def test_pay_commission_by_id_unauthorized_caller(channel, required_margin): ) +def test_pay_commission_with_unregistered_partner(channel, set_channel_partner, core_agent, required_margin): + partner, fee_address, partner_id, core_commission_rate, btc_commission_rate = set_channel_partner + + partner2 = accounts[3] + fee_address2 = accounts[4] + core_commission_rate = 1000 + btc_commission_rate = 1500 + channel.register( + fee_address2, core_commission_rate, btc_commission_rate, + {'from': partner2, 'value': required_margin} + ) + partner_id2 = channel.partnerIdMap(partner2) + + delegator = accounts[5] + candidate = accounts[6] + delegate_amount = 1000 + + register_candidate(operator=candidate) + + channel.delegateCoin( + candidate, partner_id, + {'from': delegator, 'value': delegate_amount} + ) + channel.delegateCoin( + candidate, partner_id2, + {'from': delegator, 'value': delegate_amount} + ) + channel.unregister({'from': partner2}) + + reward_amount = 5000 + + total_delegate_amount = delegate_amount * 2 + expected_commission = (delegate_amount * core_commission_rate // 10000) * reward_amount // total_delegate_amount + expected_remaining = reward_amount - expected_commission + + remaining_reward = channel.payCommissions.call( + delegator, total_delegate_amount, reward_amount, + {'from': core_agent.address} + ) + assert remaining_reward == expected_remaining + + channel.payCommissions( + delegator, total_delegate_amount, reward_amount, + {'from': core_agent.address} + ) + assert channel.partners(partner_id2)[-1] == 0 + assert channel.partners(partner_id)[-1] == expected_commission + + def test_pay_commissions_success(channel, required_margin, core_agent): partner = accounts[1] fee_address = accounts[2] @@ -980,51 +1010,68 @@ def test_delegate_coin_integration_with_core_agent(channel, required_margin, cor assert partner_id in channel_ids -def test_undelegate_coin_integration_with_core_agent(channel, required_margin, core_agent): - partner = accounts[1] - fee_address = accounts[2] - delegator = accounts[3] - operator = accounts[4] +@pytest.mark.parametrize("_type", ["partial", "all", "above"]) +def test_undelegate_coin_integration_with_core_agent(channel, core_agent, _type, set_channel): + operator = accounts[1] register_candidate(operator=operator) - candidate = operator - core_commission_rate = 1000 - btc_commission_rate = 1500 - delegate_amount = Wei("10 ether") - undelegate_amount = Wei("3 ether") - - channel.register( - fee_address, core_commission_rate, btc_commission_rate, - {'from': partner, 'value': required_margin} - ) - partner_id = channel.partnerIdMap(partner) - - channel.delegateCoin( - candidate, partner_id, - {'from': delegator, 'value': delegate_amount} - ) - - initial_balance = delegator.balance() - initial_channel_delegation = channel.getDelegatorAmount(partner_id, delegator) - initial_channel_amount = core_agent.getDelegatorMap(delegator)[2] - - tx = channel.undelegateCoin( - candidate, undelegate_amount, partner_id, - {'from': delegator} - ) - - assert tx.status == 1 - - final_balance = delegator.balance() - assert final_balance > initial_balance - - final_channel_delegation = channel.getDelegatorAmount(partner_id, delegator) - assert final_channel_delegation == initial_channel_delegation - undelegate_amount - - channel_ids = channel.getIds(delegator) - assert partner_id in channel_ids - - final_channel_amount = core_agent.getDelegatorMap(delegator)[2] - assert final_channel_amount == initial_channel_amount - undelegate_amount + + partner1, partner2, partner3 = accounts[2:5] + _,_, partner1_id,_,_ = set_channel(partner1) + _,_, partner2_id,_,_ = set_channel(partner2) + _,_, partner3_id,_,_ = set_channel(partner3) + + delegator = accounts[0] + delegate_amount = 1000 + + channel.delegateCoin(operator, partner1_id, {'from': delegator, 'value': delegate_amount}) + channel.delegateCoin(operator, partner2_id, {'from': delegator, 'value': delegate_amount}) + channel.delegateCoin(operator, partner3_id, {'from': delegator, 'value': delegate_amount}) + core_agent.delegateCoin(operator, {'from': delegator, 'value': delegate_amount}) + total_channel_amount = delegate_amount * 3 + total_amount = total_channel_amount + delegate_amount + + all_ids = channel.getIds(delegator) + for _id in (partner1_id, partner2_id, partner3_id): + assert _id in all_ids + + if _type == "partial": + undelegate_amount = delegate_amount + delegate_amount // 2 + core_agent.undelegateCoin(operator, undelegate_amount, {'from': delegator}) + delegator_info = core_agent.delegatorMap(delegator) + _amount, _channel_amount = delegator_info + assert _amount == total_amount - undelegate_amount + assert _channel_amount == total_channel_amount - undelegate_amount + + all_ids = channel.getIds(delegator) + assert len(all_ids) == 2 + assert partner2_id in all_ids + assert partner1_id in all_ids + assert channel.getDelegatorAmount(partner1_id, delegator) == delegate_amount + assert channel.getDelegatorAmount(partner2_id, delegator) == delegate_amount // 2 + assert channel.getDelegatorAmount(partner3_id, delegator) == 0 + elif _type == "all": + core_agent.undelegateCoin(operator, 0, {'from': delegator}) + delegator_info = core_agent.delegatorMap(delegator) + _amount, _channel_amount = delegator_info + assert _amount == 0 + assert _channel_amount == 0 + + all_ids = channel.getIds(delegator) + assert all_ids == [] + for _id in (partner1_id, partner2_id, partner3_id): + assert channel.getDelegatorAmount(_id, delegator) == 0 + elif _type == "above": + undelegate_amount = total_channel_amount + delegate_amount // 2 + core_agent.undelegateCoin(operator, undelegate_amount, {'from': delegator}) + delegator_info = core_agent.delegatorMap(delegator) + _amount, _channel_amount = delegator_info + assert _amount == total_amount - undelegate_amount + assert _channel_amount == 0 + + all_ids = channel.getIds(delegator) + assert all_ids == [] + for _id in (partner1_id, partner2_id, partner3_id): + assert channel.getDelegatorAmount(_id, delegator) == 0 def test_delegate_coin_multiple_delegations(channel, required_margin, core_agent): @@ -1193,11 +1240,8 @@ def test_undelegate_coin_mixed_delegation_scenario(channel, required_margin, cor undelegate_channel_amount = Wei("3 ether") initial_balance = delegator.balance() - - channel.undelegateCoin( - candidate, undelegate_channel_amount, partner_id, - {'from': delegator} - ) + + core_agent.undelegateCoin(candidate, undelegate_channel_amount, {'from': delegator}) remaining_channel_amount = channel.getDelegatorAmount(partner_id, delegator) assert remaining_channel_amount == channel_amount - undelegate_channel_amount @@ -1592,12 +1636,8 @@ def test_core_agent_channel_amount_tracking(channel, required_margin, core_agent assert delegator_map[1] == expected_total_amount undelegate_amount = Wei("2 ether") + core_agent.undelegateCoin(candidate, undelegate_amount, {'from': delegator}) - channel.undelegateCoin( - candidate, undelegate_amount, partner_id, - {'from': delegator} - ) - delegator_map = core_agent.getDelegatorMap(delegator) final_channel_amount = expected_channel_amount - undelegate_amount final_total_amount = expected_total_amount - undelegate_amount @@ -1605,10 +1645,7 @@ def test_core_agent_channel_amount_tracking(channel, required_margin, core_agent assert delegator_map[1] == final_total_amount remaining_channel_amount = final_channel_amount - channel.undelegateCoin( - candidate, remaining_channel_amount, partner_id, - {'from': delegator} - ) + core_agent.undelegateCoin(candidate, remaining_channel_amount, {'from': delegator}) delegator_map = core_agent.getDelegatorMap(delegator) assert delegator_map[2] == 0 From bc196477859fafcdf85713f94df529587f62fa77 Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Wed, 10 Sep 2025 11:40:28 +0800 Subject: [PATCH 21/27] remove moveData tests; fix tests after audit --- contracts/mock/BitcoinAgentMock.sol | 29 +- contracts/mock/BitcoinStakeMock.sol | 154 +- contracts/mock/BtcLightClientMock.sol | 17 - contracts/mock/CandidateHubMock.sol | 155 +- contracts/mock/CoreAgentMock.sol | 164 +- contracts/mock/PledgeAgentMock.sol | 536 +--- contracts/mock/SlashIndicatorMock.sol | 16 +- contracts/mock/StakeHubMock.sol | 52 +- contracts/mock/ValidatorSetMock.sol | 159 +- contracts/mock/interface/ILightClientMock.sol | 12 - contracts/mock/interface/IPledgeAgentMock.sol | 21 - .../mock/interface/IValidatorSetMock.sol | 10 - tests/calc_reward.py | 16 +- tests/common.py | 9 - tests/delegate.py | 135 +- tests/test_btc_agent.py | 19 - tests/test_btc_light_client.py | 14 +- tests/test_btc_stake.py | 101 +- tests/test_claim_reward.py | 306 +-- tests/test_configuration.py | 100 +- tests/test_core_agent.py | 119 +- tests/test_core_agent_unit.py | 2 +- tests/test_delegate_btc.py | 2 + tests/test_delegate_coin.py | 5 +- tests/test_dual_staking.py | 395 +-- tests/test_gov_hub.py | 15 - tests/test_pledge_agent.py | 2430 ++--------------- tests/test_stake_hub_unit.py | 15 +- tests/test_validator_set.py | 61 + 29 files changed, 580 insertions(+), 4489 deletions(-) delete mode 100644 contracts/mock/interface/ILightClientMock.sol delete mode 100644 contracts/mock/interface/IPledgeAgentMock.sol delete mode 100644 contracts/mock/interface/IValidatorSetMock.sol diff --git a/contracts/mock/BitcoinAgentMock.sol b/contracts/mock/BitcoinAgentMock.sol index f32a2f837..f51607d19 100644 --- a/contracts/mock/BitcoinAgentMock.sol +++ b/contracts/mock/BitcoinAgentMock.sol @@ -5,30 +5,23 @@ import "../BitcoinAgent.sol"; contract BitcoinAgentMock is BitcoinAgent { - function initializeFromPledgeAgentMock(address[] memory candidates, uint256[] memory amounts) external { - uint256 s = candidates.length; - for (uint256 i = 0; i < s; ++i) { - candidateMap[candidates[i]].stakeAmount = amounts[i]; - } - } - - function setCandidateMap(address agent, uint256 value, uint256 value1) external { - candidateMap[agent] = StakeAmount(value, value1); + function setCandidateMap(address candidate, uint256 lstStakeAmount, uint256 stakeAmount) external { + candidateMap[candidate] = StakeAmount(lstStakeAmount, stakeAmount); } - function setInitLpRates(uint32 value1, uint32 value01, uint32 value2, uint32 value02, uint32 value3, uint32 value03, uint32 value4, uint32 value04) external { - while (grades.length > 0) { - grades.pop(); + function setInitLpRates( + uint32[4] calldata values, + uint32[4] calldata rates + ) external { + delete grades; + for (uint256 i = 0; i < 4; i++) { + grades.push(DualStakingGrade(values[i], rates[i])); } - grades.push(DualStakingGrade(value1, value01)); - grades.push(DualStakingGrade(value2, value02)); - grades.push(DualStakingGrade(value3, value03)); - grades.push(DualStakingGrade(value4, value04)); } - function setLpRates(uint32 value1, uint32 balue01) external { - grades.push(DualStakingGrade(value1, balue01)); + function setLpRates(uint32 stakeRate, uint32 percentage) external { + grades.push(DualStakingGrade(stakeRate, percentage)); } diff --git a/contracts/mock/BitcoinStakeMock.sol b/contracts/mock/BitcoinStakeMock.sol index 42220e6ad..cc44db9de 100644 --- a/contracts/mock/BitcoinStakeMock.sol +++ b/contracts/mock/BitcoinStakeMock.sol @@ -5,21 +5,9 @@ import "../BitcoinStake.sol"; import "../lib/BytesLib.sol"; contract BitcoinStakeMock is BitcoinStake { - uint256 public minBtcLockRound; uint64 public MONTH_TIMESTAMP = 2592000; - event migrated(bytes32 indexed txid); - event mockDelegatedBtc(bytes32 indexed txid, address indexed candidate, address indexed delegator, uint32 outputIndex, uint64 amount, uint256 endRound, uint32 channelId); - event mockTransferredBtc( - bytes32 indexed txid, - address sourceCandidate, - address targetCandidate, - address delegator, - uint256 amount - ); - function developmentInit() external { - minBtcLockRound = 1; gradeActive = true; } @@ -40,16 +28,17 @@ contract BitcoinStakeMock is BitcoinStake { roundTag = value; } - function setInitTlpRates(uint64 value1, uint32 value01, uint64 value2, uint32 value02, uint64 value3, uint32 value03, uint64 value4, uint32 value04, uint64 value5, uint32 value05) external { - grades.push(LockLengthGrade(value1 * MONTH_TIMESTAMP, value01)); - grades.push(LockLengthGrade(value2 * MONTH_TIMESTAMP, value02)); - grades.push(LockLengthGrade(value3 * MONTH_TIMESTAMP, value03)); - grades.push(LockLengthGrade(value4 * MONTH_TIMESTAMP, value04)); - grades.push(LockLengthGrade(value5 * MONTH_TIMESTAMP, value05)); + function setInitTlpRates( + uint64[5] calldata values, + uint32[5] calldata rates + ) external { + for (uint256 i = 0; i < 5; i++) { + grades.push(LockLengthGrade(values[i] * MONTH_TIMESTAMP, rates[i])); + } } - function setTlpRates(uint64 value1, uint32 value01) external { - grades.push(LockLengthGrade(value1, value01)); + function setTlpRates(uint64 lockDuration, uint32 percentage) external { + grades.push(LockLengthGrade(lockDuration, percentage)); } function popTtlpRates() external { @@ -100,130 +89,7 @@ contract BitcoinStakeMock is BitcoinStake { } return agentAddresses; } - - function initializeFromPledgeAgentMock(address[] memory candidates, uint256[] memory amounts, uint256[] memory realtimeAmounts) external { - uint256 s = candidates.length; - for (uint256 i = 0; i < s; ++i) { - Candidate storage c = candidateMap[candidates[i]]; - c.stakedAmount = amounts[i]; - c.realtimeAmount = realtimeAmounts[i]; - } - } - - - - - - function mockDelegateBtc(bytes32 txid, uint64 btcValue, address candidate, address delegator, uint32 lockTime, uint64 blockTimestamp, uint32 outputIndex, uint32 channelId) external nonReentrant { - BtcTx storage bt = btcTxMap[txid]; - require(bt.amount == 0, "btc tx is already delegated."); - { - uint256 endRound = lockTime / SatoshiPlusHelper.ROUND_INTERVAL; - require(endRound > roundTag + 1, "insufficient locking rounds"); - bt.lockTime = lockTime; - bt.blockTimestamp = blockTimestamp; - } - DepositReceipt storage dr = receiptMap[txid]; - uint64 btcAmount; - { - (btcAmount, outputIndex, delegator, candidate) = (btcValue, outputIndex, delegator, candidate); - bt.amount = btcAmount; - bt.outputIndex = outputIndex; - bt.channelId = channelId; - emit mockDelegatedBtc(txid, candidate, delegator, outputIndex, btcAmount, lockTime / SatoshiPlusHelper.ROUND_INTERVAL, channelId); - } - delegatorMap[delegator].txids.push(txid); - candidateMap[candidate].realtimeAmount += btcAmount; - dr.delegator = delegator; - dr.candidate = candidate; - dr.round = roundTag; - _addExpire(dr, lockTime, btcAmount); - } - - function _mockCollectReward(bytes32 txid) internal returns (uint256 reward, bool expired, uint256 accStakedAmount) { - BtcTx storage bt = btcTxMap[txid]; - DepositReceipt storage dr = receiptMap[txid]; - uint256 drRound = dr.round; - require(drRound != 0, "invalid deposit receipt"); - uint256 lastRound = roundTag - 1; - uint256 unlockRound1 = bt.lockTime / SatoshiPlusHelper.ROUND_INTERVAL - 1; - if (drRound < lastRound && drRound < unlockRound1) { - uint256 minRound = lastRound < unlockRound1 ? lastRound : unlockRound1; - // full reward - reward = (_getRoundAccruedReward(dr.candidate, minRound) - _getRoundAccruedReward(dr.candidate, drRound)) * bt.amount / SatoshiPlusHelper.BTC_DECIMAL; - accStakedAmount = bt.amount * (minRound - drRound); - - // apply time grading to BTC rewards - uint256 rewardUnclaimed = 0; - if (gradeActive && grades.length != 0) { - uint64 lockDuration = bt.lockTime - bt.blockTimestamp; - uint256 p = grades[0].percentage; - for (uint256 j = grades.length - 1; j != 0; j--) { - if (lockDuration >= grades[j].lockDuration) { - p = grades[j].percentage; - break; - } - } - uint256 rewardClaimed = reward * p / SatoshiPlusHelper.DENOMINATOR; - rewardUnclaimed = reward - rewardClaimed; - reward = rewardClaimed; - } - - dr.round = minRound; - if (reward != 0) { - rewardMap[dr.delegator].reward += reward; - } - if (rewardUnclaimed != 0) { - rewardMap[dr.delegator].unclaimedReward += rewardUnclaimed; - } - if (accStakedAmount != 0) { - rewardMap[dr.delegator].accStakedAmount += accStakedAmount; - } - } - - if (unlockRound1 < roundTag) { - emit btcExpired(txid, dr.delegator); - delete receiptMap[txid]; - return (reward, true, accStakedAmount); - } - return (reward, false, accStakedAmount); - } -/// transfer BTC delegate to a new validator -/// @param txid the staked BTC transaction to transfer -/// @param targetCandidate the new validator to stake to - function mockTransferBtc(bytes32 txid, address targetCandidate) external nonReentrant { - BtcTx storage bt = btcTxMap[txid]; - DepositReceipt storage dr = receiptMap[txid]; - uint64 amount = bt.amount; - require(amount != 0, "btc tx not found"); - require(dr.delegator == msg.sender, "not the delegator of this btc receipt"); - - address candidate = dr.candidate; - require(candidate != targetCandidate, "can not transfer to the same validator"); - uint256 endRound = bt.lockTime / SatoshiPlusHelper.ROUND_INTERVAL; - require(endRound > roundTag + 1, "insufficient locking rounds"); - - if (!ICandidateHub(CANDIDATE_HUB_ADDR).canDelegate(targetCandidate)) { - revert InactiveCandidate(targetCandidate); - } - _mockCollectReward(txid); - - Candidate storage c = candidateMap[candidate]; - c.realtimeAmount -= amount; - round2expireInfoMap[endRound].amountMap[candidate] -= amount; - - // Set candidate to targetCandidate - dr.candidate = targetCandidate; - dr.round = roundTag; - _addExpire(dr, bt.lockTime, amount); - - Candidate storage tc = candidateMap[targetCandidate]; - tc.realtimeAmount += amount; - - emit mockTransferredBtc(txid, candidate, targetCandidate, msg.sender, bt.amount); - } - - // mock contract for test + // for unit test function collectRewardMock(bytes32 txid, uint256 coreAmount, uint256 drRound, uint256 settleRound, bool claim) external returns (uint256 reward, bool expired, int256 floatReward, uint256 remainingCoreAmount) { return _collectReward(txid, coreAmount, drRound, settleRound, claim); } diff --git a/contracts/mock/BtcLightClientMock.sol b/contracts/mock/BtcLightClientMock.sol index a6c110adc..6d970785d 100644 --- a/contracts/mock/BtcLightClientMock.sol +++ b/contracts/mock/BtcLightClientMock.sol @@ -57,21 +57,4 @@ contract BtcLightClientMock is BtcLightClient { } } - function addMinerPowerMock(bytes32 blockHash) external { - addMinerPower(blockHash); - } - /// Get powers of given candidates (number of BTC blocks delegated to candidates) in a specific round - /// @param roundTimeTag The specific round time - /// @param candidates The given candidates to get their powers - /// @return powers The corresponding powers of given candidates - function getRoundPowersMock(uint256 roundTimeTag, address[] calldata candidates) external view returns (uint256[] memory powers) { - uint256 count = candidates.length; - powers = new uint256[](count); - - RoundPower storage r = roundPowerMap[roundTimeTag]; - for (uint256 i = 0; i < count; ++i) { - powers[i] = r.powerMap[candidates[i]].miners.length; - } - return powers; - } } diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index 0fc430d75..4f8965815 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -11,9 +11,6 @@ import "../interface/ISlashIndicator.sol"; import "../interface/IStakeHub.sol"; import "../interface/IPledgeAgent.sol"; import "../interface/ILightClient.sol"; -import "./interface/IPledgeAgentMock.sol"; -import "./interface/ILightClientMock.sol"; -import "./interface/IValidatorSetMock.sol"; import "../System.sol"; import "../lib/Address.sol"; import "../lib/SatoshiPlusHelper.sol"; @@ -138,14 +135,14 @@ contract CandidateHubMock is CandidateHub { uint256[] memory scoreList, uint256 count, uint256 sortedCount - ) public view returns (address[] memory validatorList) { + ) public pure returns (address[] memory validatorList) { return getValidators(candidateList, scoreList, count, sortedCount); } function getAlternateCountMock( uint256 maxAlternateCount, uint256 count, uint256 candidateSize - ) public view returns (uint256) { + ) public pure returns (uint256) { return getAlternateCount(maxAlternateCount, count, candidateSize); } @@ -197,153 +194,7 @@ contract CandidateHubMock is CandidateHub { super.nextRound(); } } - function getValidatorsOld(address[] memory candidateList, uint256[] memory scoreList, uint256 count) internal pure returns (address[] memory validatorList){ - uint256 candidateSize = candidateList.length; - // quicksort by scores O(nlogk) - uint256 l = 0; - uint256 r = 0; - if (count < candidateSize) { - r = candidateSize - 1; - } else { - count = candidateSize; - } - while (l < r) { - // partition - uint256 ll = l; - uint256 rr = r; - address back = candidateList[ll]; - uint256 p = scoreList[ll]; - while (ll < rr) { - while (ll < rr && scoreList[rr] < p) { - rr = rr - 1; - } - candidateList[ll] = candidateList[rr]; - scoreList[ll] = scoreList[rr]; - while (ll < rr && scoreList[ll] >= p) { - ll = ll + 1; - } - candidateList[rr] = candidateList[ll]; - scoreList[rr] = scoreList[ll]; - } - candidateList[ll] = back; - scoreList[ll] = p; - uint256 mid = ll; - // sub sort - if (mid < count) { - l = mid + 1; - } else if (mid > count) { - r = mid - 1; - } else { - break; - } - } - uint256 d = candidateSize - count; - if (d != 0) { - assembly { - mstore(candidateList, sub(mload(candidateList), d)) - } - } - return candidateList; - } - /// The `turn round` workflowf - /// @dev this method is called by Golang consensus engine at the end of a round - function turnRoundOld() external onlyCoinbase onlyInit onlyZeroGasPrice { - - if (turnroundFailed == true) { - require(false, "turnRound failed"); - } - - // distribute rewards for the about to end round - address[] memory lastCandidates = IValidatorSetMock(VALIDATOR_CONTRACT_ADDR).distributeRewardOld(); - - // fetch BTC miners who delegated hash power in the about to end round; - // and distribute rewards to them - uint256 lastCandidateSize = lastCandidates.length; - for (uint256 i = 0; i < lastCandidateSize; i++) { - address[] memory miners = ILightClient(LIGHT_CLIENT_ADDR).getRoundMiners(roundTag - 7, lastCandidates[i]); - IPledgeAgentMock(PLEDGE_AGENT_ADDR).distributePowerRewardOld(lastCandidates[i], miners); - } - - // update the system round tag; new round starts - - if (controlRoundTimeTag == false) { - - uint256 roundTimestamp = block.timestamp / roundInterval; - require(roundTimestamp > roundTag, "not allowed to turn round, wait for more time"); - roundTag = roundTimestamp; - - } else { - roundTag++; - } - - // reset validator flags for all candidates. - uint256 candidateSize = candidateSet.length; - uint256 validCount = 0; - uint256[] memory statusList = new uint256[](candidateSize); - for (uint256 i = 0; i < candidateSize; i++) { - statusList[i] = candidateSet[i].status & DEL_VALIDATOR; - if (statusList[i] == SET_CANDIDATE) validCount++; - } - - uint256[] memory powers; - address[] memory candidates = new address[](validCount); - uint256 j = 0; - for (uint256 i = 0; i < candidateSize; i++) { - if (statusList[i] == SET_CANDIDATE) { - candidates[j++] = candidateSet[i].operateAddr; - } - } - // fetch hash power delegated on list of candidates - // which is used to calculate hybrid score for validators in the new round - powers = ILightClientMock(LIGHT_CLIENT_ADDR).getRoundPowersMock(roundTag - 7, candidates); - - // calculate the hybrid score for all valid candidates and - // choose top ones to form the validator set of the new round - uint256[] memory hybridScores; - (hybridScores) = IPledgeAgentMock(PLEDGE_AGENT_ADDR).getHybridScoreOld(candidates, powers, roundTag); - address[] memory validatorList = getValidatorsOld(candidates, hybridScores, validatorCount); - - // prepare arguments, and notify ValidatorSet contract - uint256 totalCount = validatorList.length; - address[] memory consensusAddrList = new address[](totalCount); - address payable[] memory feeAddrList = new address payable[](totalCount); - uint256[] memory commissionThousandthsList = new uint256[](totalCount); - - for (uint256 i = 0; i < totalCount; ++i) { - uint256 index = operateMap[validatorList[i]]; - Candidate storage c = candidateSet[index - 1]; - consensusAddrList[i] = c.consensusAddr; - feeAddrList[i] = c.feeAddr; - if (hybridScores[i] == 0) { - commissionThousandthsList[i] = 1000; - } else { - commissionThousandthsList[i] = c.commissionThousandths; - } - statusList[index - 1] |= SET_VALIDATOR; - } - - IValidatorSetMock(VALIDATOR_CONTRACT_ADDR).updateValidatorSetOld(validatorList, consensusAddrList, feeAddrList, commissionThousandthsList); - - // clean slash contract - ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); - - // notify PledgeAgent contract - IPledgeAgentMock(PLEDGE_AGENT_ADDR).setNewRoundOld(validatorList, roundTag); - - // update validator jail status - for (uint256 i = 0; i < candidateSize; i++) { - address opAddr = candidateSet[i].operateAddr; - uint256 jailedRound = jailMap[opAddr]; - if (jailedRound != 0 && jailedRound <= roundTag) { - statusList[i] = statusList[i] & DEL_JAIL; - delete jailMap[opAddr]; - } - } - - for (uint256 i = 0; i < candidateSize; i++) { - changeStatus(candidateSet[i], statusList[i]); - } - } + function setMaxAlternateCount(uint256 _maxAlternateCount) external { maxAlternateCount = _maxAlternateCount; } diff --git a/contracts/mock/CoreAgentMock.sol b/contracts/mock/CoreAgentMock.sol index fd2127f88..1f20238bf 100644 --- a/contracts/mock/CoreAgentMock.sol +++ b/contracts/mock/CoreAgentMock.sol @@ -8,32 +8,11 @@ import "@openzeppelin/contracts/utils/Address.sol"; contract CoreAgentMock is CoreAgent { uint256 public rewardAmountM; - event mockDelegatedCoin(address indexed candidate, address indexed delegator, uint256 amount, uint256 realtimeAmount); - event mockUndelegatedCoin(address indexed candidate, address indexed delegator, uint256 amount); - event mockTransferredCoin( - address indexed sourceCandidate, - address indexed targetCandidate, - address indexed delegator, - uint256 amount, - uint256 realtimeAmount - ); - - function developmentInit() external { requiredCoinDeposit = requiredCoinDeposit / 1e16; roundTag = 7; } - function initializeFromPledgeAgentMock(address[] memory candidates, uint256[] memory amounts, uint256[] memory realtimeAmounts) external { - uint256 s = candidates.length; - for (uint256 i = 0; i < s; ++i) { - Candidate storage c = candidateMap[candidates[i]]; - c.amount = amounts[i]; - c.realtimeAmount = realtimeAmounts[i]; - } - } - - function setRoundTag(uint value) external { roundTag = value; } @@ -42,11 +21,6 @@ contract CoreAgentMock is CoreAgent { requiredCoinDeposit = newRequiredCoinDeposit; } - function deductTransferredAmountMock(address delegator, uint256 amount) external { - _deductTransferredAmount(delegator, amount); - } - - function getDelegatorMap(address delegator) external view returns (address[] memory, uint256, uint256) { address[] memory candidates = delegatorMap[delegator].candidates; uint256 amount = delegatorMap[delegator].amount; @@ -89,146 +63,14 @@ contract CoreAgentMock is CoreAgent { function getRewardAmount() external view returns (uint256) { return rewardAmountM; } - + // for unit test function collectCoinRewardMock(address agent, address delegator) external returns (uint256 reward, uint256 stakedAmount1, uint256 stakedAmount2) { Candidate storage a = candidateMap[agent]; CoinDelegator storage d = a.cDelegatorMap[delegator]; (reward, stakedAmount1, stakedAmount2) = _collectRewardFromCandidate(agent, d); } - - function _mockCollectReward(address candidate, uint256 stakedAmount, uint256 realtimeAmount, uint256 transferredAmount, uint256 changeRound) internal returns (uint256 reward, bool changed, uint256 accStakedAmount) { - require(changeRound != 0, "invalid delegator"); - uint256 lastRoundTag = roundTag - 1; - if (changeRound <= lastRoundTag) { - uint256 lastRoundReward = _getRoundAccruedReward(candidate, lastRoundTag); - uint256 lastChangeRoundReward = _getRoundAccruedReward(candidate, changeRound - 1); - uint256 changeRoundReward; - reward = stakedAmount * (lastRoundReward - lastChangeRoundReward); - accStakedAmount = stakedAmount * (lastRoundTag - changeRound + 1); - - if (transferredAmount != 0) { - changeRoundReward = _getRoundAccruedReward(candidate, changeRound); - reward += transferredAmount * (changeRoundReward - lastChangeRoundReward); - accStakedAmount += transferredAmount; - } - - if (realtimeAmount != stakedAmount) { - if (changeRound < lastRoundTag) { - if (changeRoundReward == 0) { - changeRoundReward = _getRoundAccruedReward(candidate, changeRound); - } - reward += (realtimeAmount - stakedAmount) * (lastRoundReward - changeRoundReward); - accStakedAmount += (realtimeAmount - stakedAmount) * (lastRoundTag - changeRound); - } - } - reward /= SatoshiPlusHelper.CORE_STAKE_DECIMAL; - return (reward, true, accStakedAmount); - } - return (0, false, 0); - } - - function _mockCollectRewardFromCandidate(address candidate, CoinDelegator storage cd) internal returns (uint256 reward, uint256 accStakedAmount) { - uint256 stakedAmount = cd.stakedAmount; - uint256 realtimeAmount = cd.realtimeAmount; - uint256 transferredAmount = cd.transferredAmount; - bool changed; - (reward, changed, accStakedAmount) = _mockCollectReward(candidate, stakedAmount, realtimeAmount, transferredAmount, cd.changeRound); - if (changed) { - if (transferredAmount != 0) { - cd.transferredAmount = 0; - } - if (realtimeAmount != stakedAmount) { - cd.stakedAmount = realtimeAmount; - } - cd.changeRound = roundTag; - } - } - - function _mockDelegateCoin(address candidate, address delegator, uint256 amount, bool isTransfer) internal returns (uint256) { - Candidate storage a = candidateMap[candidate]; - CoinDelegator storage cd = a.cDelegatorMap[delegator]; - uint256 changeRound = cd.changeRound; - if (changeRound == 0) { - cd.changeRound = roundTag; - delegatorMap[delegator].candidates.push(candidate); - } else if (changeRound != roundTag) { - (uint256 reward, uint256 accStakedAmount) = _mockCollectRewardFromCandidate(candidate, cd); - rewardMap[delegator].reward += reward; - rewardMap[delegator].accStakedAmount += accStakedAmount; - } - a.realtimeAmount += amount; - cd.realtimeAmount += amount; - if (!isTransfer) { - delegatorMap[delegator].amount += amount; - } - - return cd.realtimeAmount; - } - - function mockDelegateCoin(address candidate) external payable { - require(msg.value >= requiredCoinDeposit, "delegate amount is too small"); - uint256 realtimeAmount = _mockDelegateCoin(candidate, msg.sender, msg.value, false); - emit mockDelegatedCoin(candidate, msg.sender, msg.value, realtimeAmount); - } - - function _mockUndelegateCoin(address candidate, address delegator, uint256 amount, bool isTransfer) internal returns (uint256 undelegatedNewAmount) { - require(amount != 0, 'Undelegate zero coin'); - Candidate storage a = candidateMap[candidate]; - CoinDelegator storage cd = a.cDelegatorMap[delegator]; - uint256 changeRound = cd.changeRound; - require(changeRound != 0, 'no delegator information found'); - if (changeRound != roundTag) { - (uint256 reward, uint256 accStakedAmount) = _mockCollectRewardFromCandidate(candidate, cd); - rewardMap[delegator].reward += reward; - rewardMap[delegator].accStakedAmount += accStakedAmount; - } - - uint256 realtimeAmount = cd.realtimeAmount; - require(realtimeAmount >= amount, "Not enough staked tokens"); - if (amount != realtimeAmount) { - require(amount >= requiredCoinDeposit, "undelegate amount is too small"); - require(cd.realtimeAmount - amount >= requiredCoinDeposit, "remain amount is too small"); - } - - uint256 stakedAmount = cd.stakedAmount; - a.realtimeAmount -= amount; - if (isTransfer) { - if (stakedAmount > amount) { - cd.transferredAmount += amount; - } else if (stakedAmount != 0) { - cd.transferredAmount += stakedAmount; - } - } else { - delegatorMap[delegator].amount -= amount; - } - if (!isTransfer && cd.realtimeAmount == amount && cd.transferredAmount == 0) { - _removeDelegation(delegator, candidate); - } else { - cd.realtimeAmount -= amount; - if (stakedAmount > amount) { - cd.stakedAmount -= amount; - } else if (stakedAmount != 0) { - cd.stakedAmount = 0; - } - } - undelegatedNewAmount = amount - (stakedAmount - cd.stakedAmount); - } - - function mockUndelegateCoin(address candidate, uint256 amount) public { - uint256 dAmount = _mockUndelegateCoin(candidate, msg.sender, amount, false); - _deductTransferredAmount(msg.sender, dAmount); - Address.sendValue(payable(msg.sender), amount); - emit mockUndelegatedCoin(candidate, msg.sender, amount); - } - - function mockTransferCoin(address sourceCandidate, address targetCandidate, uint256 amount) public { - if (sourceCandidate == targetCandidate) { - revert SameCandidate(sourceCandidate); - } - _mockUndelegateCoin(sourceCandidate, msg.sender, amount, true); - uint256 newDeposit = _mockDelegateCoin(targetCandidate, msg.sender, amount, true); - emit mockTransferredCoin(sourceCandidate, targetCandidate, msg.sender, amount, newDeposit); + function deductTransferredAmountMock(address delegator, uint256 amount) external { + _deductTransferredAmount(delegator, amount); } - } diff --git a/contracts/mock/PledgeAgentMock.sol b/contracts/mock/PledgeAgentMock.sol index 19ecb47a3..33c74f5bc 100644 --- a/contracts/mock/PledgeAgentMock.sol +++ b/contracts/mock/PledgeAgentMock.sol @@ -7,54 +7,16 @@ import "../interface/ICandidateHub.sol"; contract PledgeAgentMock is PledgeAgent { int256 public constant CLAIM_ROUND_LIMIT = 500; uint256 public constant BTC_UNIT_CONVERSION_MOCK = 5; - uint256 public constant INIT_HASH_POWER_FACTOR = 20000; - uint256 public constant POWER_BLOCK_FACTOR = 1e18; - uint32 public constant INIT_BTC_CONFIRM_BLOCK = 6; - uint256 public constant INIT_MIN_BTC_LOCK_ROUND = 7; uint256 public constant ROUND_INTERVAL = 86400; - uint256 public constant INIT_MIN_BTC_VALUE = 1e6; - uint256 public constant INIT_BTC_FACTOR = 5e4; uint256 public constant BTC_STAKE_MAGIC = 0x5341542b; uint256 public constant CHAINID = 1116; - uint256 public constant FEE_FACTOR = 1e18; uint256 public constant BTC_UNIT_CONVERSION = 1e10; uint256 public constant INIT_DELEGATE_BTC_GAS_PRICE = 1e12; - - - event delegatedCoinOld(address indexed agent, address indexed delegator, uint256 amount, uint256 totalAmount); - event transferredBtcFee(bytes32 indexed txid, address payable feeReceiver, uint256 fee); - event failedTransferBtcFee(bytes32 indexed txid, address payable feeReceiver, uint256 fee); - event btcPledgeExpired(bytes32 indexed txid, address indexed delegator); - event transferredCoinOld( - address indexed sourceCandidate, - address indexed targetCandidate, - address indexed delegator, - uint256 amount, - uint256 realAmount - ); - event transferredBtcOld( - bytes32 indexed txid, - address sourceAgent, - address targetAgent, - address delegator, - uint256 amount, - uint256 totalAmount - ); - event undelegatedCoinOld(address indexed candidate, address indexed delegator, uint256 amount); - event roundReward(address indexed agent, uint256 coinReward, uint256 powerReward, uint256 btcReward); - event delegatedBtcOld(bytes32 indexed txid, address indexed agent, address indexed delegator, bytes script, uint256 btcvalue); - - error InactiveAgent(address candidate); - error InactiveCandidate(address candidate); - error SameCandidate(address candidate); - + function developmentInit() external { requiredCoinDeposit = requiredCoinDeposit / 1e16; btcFactor = 2; - minBtcLockRound = 3; - minBtcValue = 100; roundTag = 1; - powerFactor = INIT_HASH_POWER_FACTOR; alreadyInit = true; } @@ -66,29 +28,6 @@ contract PledgeAgentMock is PledgeAgent { } - function setAgentReward(address agent, uint index, - uint256 totalReward, - uint256 claimedReward, - uint256 totalScore, - uint256 coin, - uint256 power, - uint256 round) external {} - - - function setCoinDelegator(address agent) external {} - - function setBtcDelegator(address agent) external {} - - function getRewardLength(address agent) external view returns (uint) { - return agentsMap[agent].rewardSet.length; - } - - function getAgent2valueMap(uint256 round, address agent) external view returns (uint256 value) { - BtcExpireInfo storage expireInfo = round2expireInfoMap[round]; - value = expireInfo.agent2valueMap[agent]; - return value; - } - function getAgentAddrList(uint256 round) external view returns (address[] memory) { BtcExpireInfo storage expireInfo = round2expireInfoMap[round]; uint256 length = expireInfo.agentAddrList.length; @@ -105,481 +44,8 @@ contract PledgeAgentMock is PledgeAgent { return debt; } - function setPowerFactor(uint newPowerFactor) external { - powerFactor = newPowerFactor; - } - - function setBtcFactor(uint newBtcFactor) external { - btcFactor = newBtcFactor; - } - - function setRoundTag(uint value) external { roundTag = value; } - function undelegateCoinOld(address agent, address delegator, uint256 amount, bool isTransfer) internal returns (uint256, uint256) { - Agent storage a = agentsMap[agent]; - CoinDelegator storage d = a.cDelegatorMap[delegator]; - uint256 newDeposit = d.newDeposit; - if (amount == 0) { - amount = newDeposit; - } - require(newDeposit != 0, "delegator does not exist"); - if (newDeposit != amount) { - require(amount >= requiredCoinDeposit, "undelegate amount is too small"); - require(newDeposit >= requiredCoinDeposit + amount, "remaining amount is too small"); - } - uint256 rewardAmount = _collectCoinReward(a, d, delegator); - a.totalDeposit -= amount; - uint256 deposit = d.changeRound < roundTag ? newDeposit : d.deposit; - newDeposit -= amount; - uint256 deductedInDeposit; - uint256 deductedOutDeposit; - if (newDeposit < d.transferInDeposit) { - deductedInDeposit = d.transferInDeposit - newDeposit; - d.transferInDeposit = newDeposit; - if (!isTransfer) { - debtDepositMap[roundTag][msg.sender] += deductedInDeposit; - } - deductedOutDeposit = deposit; - } else if (newDeposit < d.transferInDeposit + deposit) { - deductedOutDeposit = d.transferInDeposit + deposit - newDeposit; - } - if (deductedOutDeposit != 0) { - deposit -= deductedOutDeposit; - if (a.rewardSet.length != 0) { - Reward storage r = a.rewardSet[a.rewardSet.length - 1]; - if (r.round == roundTag) { - if (isTransfer) { - d.transferOutDeposit += deductedOutDeposit; - } else { - r.coin -= deductedOutDeposit; - } - } else { - deductedOutDeposit = 0; - } - } else { - deductedOutDeposit = 0; - } - } - - if (newDeposit == 0 && d.transferOutDeposit == 0) { - delete a.cDelegatorMap[delegator]; - } else { - d.deposit = deposit; - d.newDeposit = newDeposit; - d.changeRound = roundTag; - } - - if (rewardAmount != 0) { - distributeRewardMock(payable(delegator), rewardAmount); - } - - return (amount, deductedInDeposit + deductedOutDeposit); - } - - function delegateCoinOld(address agent, address delegator, uint256 deposit, uint256 transferInDeposit) internal returns (uint256) { - require(deposit >= requiredCoinDeposit, "deposit is too small"); - Agent storage a = agentsMap[agent]; - CoinDelegator storage d = a.cDelegatorMap[delegator]; - uint256 rewardAmount; - if (d.changeRound != 0) { - rewardAmount = _collectCoinReward(a, d, delegator); - } - a.totalDeposit += deposit; - - if (d.newDeposit == 0 && d.transferOutDeposit == 0) { - d.newDeposit = deposit; - d.changeRound = roundTag; - d.rewardIndex = a.rewardSet.length; - } else { - if (d.changeRound < roundTag) { - d.deposit = d.newDeposit; - d.changeRound = roundTag; - } - d.newDeposit += deposit; - } - - if (transferInDeposit != 0) { - d.transferInDeposit += transferInDeposit; - } - - if (rewardAmount != 0) { - distributeRewardMock(payable(delegator), rewardAmount); - } - return d.newDeposit; - } - - function delegateCoinOld(address agent) external payable { - if (!ICandidateHub(CANDIDATE_HUB_ADDR).canDelegate(agent)) { - revert InactiveAgent(agent); - } - uint256 newDeposit = delegateCoinOld(agent, msg.sender, msg.value, 0); - emit delegatedCoinOld(agent, msg.sender, msg.value, newDeposit); - } - - - function undelegateCoinOld(address agent) external { - undelegateCoinOld(agent, 0); - } - - function undelegateCoinOld(address agent, uint256 amount) public { - (uint256 deposit,) = undelegateCoinOld(agent, msg.sender, amount, false); - Address.sendValue(payable(msg.sender), deposit); - emit undelegatedCoinOld(agent, msg.sender, deposit); - } - - function transferCoinOld(address sourceAgent, address targetAgent) external { - transferCoin(sourceAgent, targetAgent, 0); - } - - function transferCoinOld(address sourceAgent, address targetAgent, uint256 amount) public { - if (!ICandidateHub(CANDIDATE_HUB_ADDR).canDelegate(targetAgent)) { - revert InactiveAgent(targetAgent); - } - if (sourceAgent == targetAgent) { - revert SameCandidate(sourceAgent); - } - (uint256 deposit, uint256 deductedDeposit) = undelegateCoinOld(sourceAgent, msg.sender, amount, true); - uint256 newDeposit = delegateCoinOld(targetAgent, msg.sender, deposit, deductedDeposit); - - emit transferredCoinOld(sourceAgent, targetAgent, msg.sender, deposit, newDeposit); - } - - - function addExpire(BtcReceipt storage br) internal { - BtcExpireInfo storage expireInfo = round2expireInfoMap[br.endRound]; - if (expireInfo.agentExistMap[br.agent] == 0) { - expireInfo.agentAddrList.push(br.agent); - expireInfo.agentExistMap[br.agent] = 1; - } - expireInfo.agent2valueMap[br.agent] += br.value; - } - - function distributePowerRewardOld(address candidate, address[] calldata miners) external onlyCandidate { - Agent storage a = agentsMap[candidate]; - uint256 l = a.rewardSet.length; - if (l == 0) { - return; - } - Reward storage r = a.rewardSet[l - 1]; - if (r.totalReward == 0 || r.round != roundTag) { - return; - } - RoundState storage rs = stateMap[roundTag]; - uint256 reward = (rs.coin + rs.btc * rs.btcFactor) * POWER_BLOCK_FACTOR * rs.powerFactor / 10000 * r.totalReward / r.score; - uint256 minerSize = miners.length; - - uint256 powerReward = reward * minerSize; - uint256 undelegateCoinReward; - uint256 btcScore = a.btc * rs.btcFactor; - if (a.coin + btcScore > r.coin) { - undelegateCoinReward = r.totalReward * (a.coin + btcScore - r.coin) * rs.power / r.score; - } - uint256 remainReward = r.remainReward; - require(remainReward >= powerReward + undelegateCoinReward, "there is not enough reward"); - - for (uint256 i = 0; i < minerSize; i++) { - rewardMap[miners[i]] += reward; - } - - if (r.coin == 0) { - delete a.rewardSet[l - 1]; - undelegateCoinReward = remainReward - powerReward; - } else if (powerReward != 0 || undelegateCoinReward != 0) { - r.remainReward -= (powerReward + undelegateCoinReward); - } - - if (undelegateCoinReward != 0) { - ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{value: undelegateCoinReward}(); - } - } - function getHybridScoreOld( - address[] calldata candidates, - uint256[] calldata powers, - uint256 round - ) external onlyCandidate returns (uint256[] memory scores) { - uint256 candidateSize = candidates.length; - require(candidateSize == powers.length, "the length of candidates and powers should be equal"); - - for (uint256 r = roundTag + 1; r <= round; ++r) { - BtcExpireInfo storage expireInfo = round2expireInfoMap[r]; - uint256 j = expireInfo.agentAddrList.length; - while (j > 0) { - j--; - address agent = expireInfo.agentAddrList[j]; - agentsMap[agent].totalBtc -= expireInfo.agent2valueMap[agent]; - expireInfo.agentAddrList.pop(); - delete expireInfo.agent2valueMap[agent]; - delete expireInfo.agentExistMap[agent]; - } - delete round2expireInfoMap[r]; - } - - uint256 totalPower = 1; - uint256 totalCoin = 1; - uint256 totalBtc; - for (uint256 i = 0; i < candidateSize; ++i) { - Agent storage a = agentsMap[candidates[i]]; - a.power = powers[i] * POWER_BLOCK_FACTOR; - a.btc = a.totalBtc; - a.coin = a.totalDeposit; - totalPower += a.power; - totalCoin += a.coin; - totalBtc += a.btc; - } - - uint256 bf = (btcFactor == 0 ? INIT_BTC_FACTOR : btcFactor) * BTC_UNIT_CONVERSION_MOCK; - uint256 pf = powerFactor; - - scores = new uint256[](candidateSize); - for (uint256 i = 0; i < candidateSize; ++i) { - Agent storage a = agentsMap[candidates[i]]; - scores[i] = a.power * (totalCoin + totalBtc * bf) * pf / 10000 + (a.coin + a.btc * bf) * totalPower; - } - - RoundState storage rs = stateMap[round]; - rs.power = totalPower; - rs.coin = totalCoin; - rs.powerFactor = pf; - rs.btc = totalBtc; - rs.btcFactor = bf; - } - - function setNewRoundOld(address[] calldata validators, uint256 round) external onlyCandidate { - RoundState storage rs = stateMap[round]; - uint256 validatorSize = validators.length; - for (uint256 i = 0; i < validatorSize; ++i) { - Agent storage a = agentsMap[validators[i]]; - uint256 btcScore = a.btc * rs.btcFactor; - uint256 score = a.power * (rs.coin + rs.btc * rs.btcFactor) * rs.powerFactor / 10000 + (a.coin + btcScore) * rs.power; - a.rewardSet.push(Reward(0, 0, score, a.coin + btcScore, round)); - } - - roundTag = round; - } - - function addRoundRewardOld(address[] calldata agentList, uint256[] calldata rewardList) - external - payable - onlyValidator - { - uint256 agentSize = agentList.length; - require(agentSize == rewardList.length, "the length of agentList and rewardList should be equal"); - RoundState memory rs = stateMap[roundTag]; - for (uint256 i = 0; i < agentSize; ++i) { - Agent storage a = agentsMap[agentList[i]]; - if (a.rewardSet.length == 0) { - continue; - } - Reward storage r = a.rewardSet[a.rewardSet.length - 1]; - uint256 roundScore = r.score; - if (roundScore == 0) { - delete a.rewardSet[a.rewardSet.length - 1]; - continue; - } - if (rewardList[i] == 0) { - continue; - } - r.totalReward = rewardList[i]; - r.remainReward = rewardList[i]; - uint256 coinReward = rewardList[i] * a.coin * rs.power / roundScore; - uint256 powerReward = rewardList[i] * a.power * rs.coin / 10000 * rs.powerFactor / roundScore; - uint256 btcReward = rewardList[i] * a.btc * rs.btcFactor * rs.power / roundScore; - emit roundReward(agentList[i], coinReward, powerReward, btcReward); - } - } - /*********************** Internal methods ***************************/ - function distributeRewardMock(address payable delegator, uint256 reward) internal { - Address.sendValue(delegator, reward); - emit claimedReward(delegator, msg.sender, reward, true); - } - - function collectCoinRewardMock(Reward storage r, uint256 deposit) internal returns (uint256 rewardAmount) { - require(r.coin >= deposit, "reward is not enough"); - uint256 curReward; - if (r.coin == deposit) { - curReward = r.remainReward; - r.coin = 0; - } else { - uint256 rsPower = stateMap[r.round].power; - curReward = (r.totalReward * deposit * rsPower) / r.score; - require(r.remainReward >= curReward, "there is not enough reward"); - r.coin -= deposit; - r.remainReward -= curReward; - } - return curReward; - } - - function collectCoinRewardMock(Agent storage a, CoinDelegator storage d, int256 roundLimit) internal returns (uint256 rewardAmount) { - uint256 changeRound = d.changeRound; - uint256 curRound = roundTag; - if (changeRound < curRound) { - d.transferInDeposit = 0; - } - - uint256 rewardLength = a.rewardSet.length; - uint256 rewardIndex = d.rewardIndex; - if (rewardIndex >= rewardLength) { - return 0; - } - if (rewardIndex + uint256(roundLimit) < rewardLength) { - rewardLength = rewardIndex + uint256(roundLimit); - } - - while (rewardIndex < rewardLength) { - Reward storage r = a.rewardSet[rewardIndex]; - uint256 rRound = r.round; - if (rRound == curRound) { - break; - } - uint256 deposit = d.newDeposit; - // HARDFORK V-1.0.3 - // d.deposit and d.transferOutDeposit are both eligible for claiming rewards - // however, d.transferOutDeposit will be used to pay the DEBT for the delegator before that - // the rewards from the DEBT will be collected and sent to the system reward contract - if (rRound == changeRound) { - uint256 transferOutDeposit = d.transferOutDeposit; - uint256 debt = debtDepositMap[rRound][msg.sender]; - if (transferOutDeposit > debt) { - transferOutDeposit -= debt; - debtDepositMap[rRound][msg.sender] = 0; - } else { - debtDepositMap[rRound][msg.sender] -= transferOutDeposit; - transferOutDeposit = 0; - } - if (transferOutDeposit != d.transferOutDeposit) { - uint256 undelegateReward = collectCoinRewardMock(r, d.transferOutDeposit - transferOutDeposit); - if (r.coin == 0) { - delete a.rewardSet[rewardIndex]; - } - ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{value: undelegateReward}(); - } - deposit = d.deposit + transferOutDeposit; - d.deposit = d.newDeposit; - d.transferOutDeposit = 0; - } - if (deposit != 0) { - rewardAmount += collectCoinRewardMock(r, deposit); - if (r.coin == 0) { - delete a.rewardSet[rewardIndex]; - } - } - rewardIndex++; - } - - // update index whenever claim happens - d.rewardIndex = rewardIndex; - return rewardAmount; - } - - /// Claim reward for delegator - /// @param agentList The list of validators to claim rewards on, it can be empty - /// @return (Amount claimed, Are all rewards claimed) - function claimRewardMock(address[] calldata agentList) external returns (uint256, bool) { - // limit round count to control gas usage - int256 roundLimit = CLAIM_ROUND_LIMIT; - uint256 reward; - uint256 rewardSum = rewardMap[msg.sender]; - if (rewardSum != 0) { - rewardMap[msg.sender] = 0; - } - - uint256 agentSize = agentList.length; - for (uint256 i = 0; i < agentSize; ++i) { - Agent storage a = agentsMap[agentList[i]]; - if (a.rewardSet.length == 0) { - continue; - } - CoinDelegator storage d = a.cDelegatorMap[msg.sender]; - if (d.newDeposit == 0 && d.transferOutDeposit == 0) { - continue; - } - int256 roundCount = int256(a.rewardSet.length - d.rewardIndex); - reward = collectCoinRewardMock(a, d, roundLimit); - roundLimit -= roundCount; - rewardSum += reward; - if (d.newDeposit == 0 && d.transferOutDeposit == 0) { - delete a.cDelegatorMap[msg.sender]; - } - // if there are rewards to be collected, leave them there - if (roundLimit < 0) { - break; - } - } - if (rewardSum != 0) { - distributeRewardMock(payable(msg.sender), rewardSum); - } - return (rewardSum, roundLimit >= 0); - } - - function getDelegatorOld(address agent, address delegator) external view returns (CoinDelegator memory) { - CoinDelegator memory d = agentsMap[agent].cDelegatorMap[delegator]; - return d; - } - - function moveCandidateData(address[] memory candidates) external { - - uint256 l = candidates.length; - uint256 count; - for (uint256 i = 0; i < l; ++i) { - Agent storage agent = agentsMap[candidates[i]]; - if (!agent.moved && (agent.totalDeposit != 0 || agent.coin != 0 || agent.totalBtc != 0 || agent.btc != 0)) { - count++; - } - } - if (count == 0) { - return; - } - uint256[] memory amounts = new uint256[](count); - uint256[] memory realAmounts = new uint256[](count); - address[] memory targetCandidates = new address[](count); - uint j; - - // move CORE stake data to CoreAgent - for (uint256 i = 0; i < l; ++i) { - Agent storage agent = agentsMap[candidates[i]]; - if (!agent.moved && (agent.totalDeposit != 0 || agent.coin != 0 || agent.totalBtc != 0 || agent.btc != 0)) { - amounts[j] = agent.coin; - realAmounts[j] = agent.totalDeposit; - targetCandidates[j] = candidates[i]; - j++; - } - } - (bool success,) = CORE_AGENT_ADDR.call(abi.encodeWithSignature("initializeFromPledgeAgentMock(address[],uint256[],uint256[])", targetCandidates, amounts, realAmounts)); - require(success, "call CORE_AGENT_ADDR.initializeFromPledgeAgent() failed"); - - // move BTC stake data to BitcoinStake - j = 0; - for (uint256 i = 0; i < l; ++i) { - Agent storage agent = agentsMap[candidates[i]]; - if (!agent.moved && (agent.totalDeposit != 0 || agent.coin != 0 || agent.totalBtc != 0 || agent.btc != 0)) { - amounts[j] = agent.btc; - realAmounts[j] = agent.totalBtc; - agent.moved = true; - j++; - } - } - (success,) = BTC_STAKE_ADDR.call(abi.encodeWithSignature("initializeFromPledgeAgentMock(address[],uint256[],uint256[])", targetCandidates, amounts, realAmounts)); - require(success, "call BTC_STAKE_ADDR.initializeFromPledgeAgent() failed"); - - (success,) = BTC_AGENT_ADDR.call(abi.encodeWithSignature("initializeFromPledgeAgentMock(address[],uint256[])", targetCandidates, amounts)); - require(success, "call BTC_AGENT_ADDR.initializeFromPledgeAgent() failed"); - } - - function getStakeInfo(address[] memory candidates) external view returns (uint256[] memory cores, uint256[] memory hashs, uint256[] memory btcs) { - uint256 l = candidates.length; - cores = new uint256[](l); - hashs = new uint256[](l); - btcs = new uint256[](l); - - for (uint256 i = 0; i < l; ++i) { - Agent storage agent = agentsMap[candidates[i]]; - cores[i] = agent.coin; - hashs[i] = agent.power / POWER_BLOCK_FACTOR; - btcs[i] = agent.btc; - - } - } } diff --git a/contracts/mock/SlashIndicatorMock.sol b/contracts/mock/SlashIndicatorMock.sol index 816502d77..b26c8853c 100644 --- a/contracts/mock/SlashIndicatorMock.sol +++ b/contracts/mock/SlashIndicatorMock.sol @@ -32,9 +32,6 @@ contract SlashIndicatorMock is SlashIndicator { } } - function getSlashValidators() external view returns (address[] memory) { - return validators; - } function getIndicators() public view returns (address[] memory, uint256[] memory) { address[] memory v = new address[](validators.length); @@ -50,7 +47,14 @@ contract SlashIndicatorMock is SlashIndicator { return ecrecovery(hash, sig); } + function setMisdemeanorThreshold(uint256 _misdemeanorThreshold) external { + misdemeanorThreshold = _misdemeanorThreshold; + } + function setFelonyThreshold(uint256 _felonyThreshold) external { + felonyThreshold = _felonyThreshold; + } + // for uint test function mockSubmitFinalityViolationEvidence(FinalityEvidence memory evidence) external onlyInit { if (rewardForReportFinalityViolation == 0) { rewardForReportFinalityViolation = INIT_REWARD_FOR_REPORT_FINALITY_VIOLATION; @@ -87,12 +91,6 @@ contract SlashIndicatorMock is SlashIndicator { } } } - function setMisdemeanorThreshold(uint256 _misdemeanorThreshold) external { - misdemeanorThreshold = _misdemeanorThreshold; - } - function setFelonyThreshold(uint256 _felonyThreshold) external { - felonyThreshold = _felonyThreshold; - } function mockSlashWithBlockCount(address validator, uint256 blockCount) external { slashWithBlockCount(validator, blockCount); } diff --git a/contracts/mock/StakeHubMock.sol b/contracts/mock/StakeHubMock.sol index 12607b08f..cc1df295a 100644 --- a/contracts/mock/StakeHubMock.sol +++ b/contracts/mock/StakeHubMock.sol @@ -52,10 +52,7 @@ contract StakeHubMock is StakeHub { surplus = value; } - function initHybridScoreMock() external { - _initializeFromPledgeAgent(); - } - + // for unit test function calculateRewardMock(address delegator) external returns (uint256[] memory rewards) { (rewards) = _calculateReward(delegator, false); } @@ -64,51 +61,4 @@ contract StakeHubMock is StakeHub { IAgent(CORE_AGENT_ADDR).distributeReward(validators, rewardList, round); } - event _initializeFromPledgeAgent___(uint256 aa); - - function _initializeFromPledgeAgent() internal { - // get stake summary of current round (snapshot values of last turn round) - address[] memory validators = IValidatorSet(VALIDATOR_CONTRACT_ADDR).getValidatorOps(); - (bool success, bytes memory data) = PLEDGE_AGENT_ADDR.call(abi.encodeWithSignature("getStakeInfo(address[])", validators)); - require(success, "call PLEDGE_AGENT_ADDR.getStakeInfo() failed"); - (uint256[] memory cores, uint256[] memory hashs, uint256[] memory btcs) = abi.decode(data, (uint256[], uint256[], uint256[])); - - uint256[] memory factors = new uint256[](3); - factors[0] = 1; - // HASH_UNIT_CONVERSION * 1e6 - factors[1] = 1e18 * 1e6; - // BTC_UNIT_CONVERSION * 2e4 - factors[2] = 1e10 * 2e4; - // initialize hybrid score based on data migrated from PledgeAgent.getStakeInfo() - uint256 validatorSize = validators.length; - uint256[] memory totalAmounts = new uint256[](3); - emit _initializeFromPledgeAgent___(cores[0]); - for (uint256 i = 0; i < validatorSize; ++i) { - address validator = validators[i]; - totalAmounts[0] += cores[i]; - totalAmounts[1] += hashs[i]; - totalAmounts[2] += btcs[i]; - candidateScoresMap[validator].push(cores[i] * factors[0] + hashs[i] * factors[1] + btcs[i] * factors[2]); - candidateScoresMap[validator].push(cores[i] * factors[0]); - candidateScoresMap[validator].push(hashs[i] * factors[1]); - candidateScoresMap[validator].push(btcs[i] * factors[2]); - - } - - uint256 len = assets.length; - for (uint256 j = 0; j < len; j++) { - stateMap[assets[j].agent] = AssetState(totalAmounts[j], factors[j]); - } - - // get active candidates. - (success, data) = CANDIDATE_HUB_ADDR.call(abi.encodeWithSignature("getCandidates()")); - require(success, "call CANDIDATE_HUB.getCandidates() failed"); - address[] memory candidates = abi.decode(data, (address[])); - - // move candidate amount. - (success,) = PLEDGE_AGENT_ADDR.call(abi.encodeWithSignature("moveCandidateData(address[])", candidates)); - require(success, "call PLEDGE_AGENT_ADDR.moveCandidateData() failed"); - } - - } diff --git a/contracts/mock/ValidatorSetMock.sol b/contracts/mock/ValidatorSetMock.sol index baf7c1f04..9dd467ff0 100644 --- a/contracts/mock/ValidatorSetMock.sol +++ b/contracts/mock/ValidatorSetMock.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.4; import "../ValidatorSet.sol"; -import "./interface/IPledgeAgentMock.sol"; contract ValidatorSetMock is ValidatorSet { function developmentInit() external { @@ -14,7 +13,7 @@ contract ValidatorSetMock is ValidatorSet { } delete currentValidatorSet; - bytes memory initValidatorSet = hex"f8d7ea9401bca3615d24d3c638836691517b2b9b49b054b1943ae030dc3717c66f63d6e8f1d1508a5c941ff46dea94a458499604a85e90225a14946f36368ae24df16d94de442f5ba55687a24f04419424e0dc2593cc9f4cea945e00c0d5c4c10d4c805aba878d51129a89d513e094cb089be171e256acdaac1ebbeb32ffba0dd438eeea941cd652bc64af3f09b490daae27f46e53726ce230940a53b7e0ffd97357e444b85f4d683c1d8e22879aea94da37ccecbb2d7c83ae27ee2bebfe8ebce162c60094d82c24274ebbfe438788d684dc6034c3c67664a4"; + bytes memory initValidatorSet = hex"f901d1f85b9401bca3615d24d3c638836691517b2b9b49b054b1943ae030dc3717c66f63d6e8f1d1508a5c941ff46db099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b94a458499604a85e90225a14946f36368ae24df16d94de442f5ba55687a24f04419424e0dc2593cc9f4cb099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b945e00c0d5c4c10d4c805aba878d51129a89d513e094cb089be171e256acdaac1ebbeb32ffba0dd438eeb099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b941cd652bc64af3f09b490daae27f46e53726ce230940a53b7e0ffd97357e444b85f4d683c1d8e22879ab099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b94da37ccecbb2d7c83ae27ee2bebfe8ebce162c60094d82c24274ebbfe438788d684dc6034c3c67664a4b099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8"; bytes memory initVoteAddress = hex"99a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8"; (Validator[] memory validatorSet, bool valid) = decodeValidatorSet(initValidatorSet); require(valid, "failed to parse init validatorSet"); @@ -26,80 +25,7 @@ contract ValidatorSetMock is ValidatorSet { currentValidatorSetMap[validatorSet[i].consensusAddress] = i + 1; } } - - struct ValidatorOld { - address operateAddress; - address consensusAddress; - address payable feeAddress; - uint256 commissionThousandths; - uint256 income; - - } - - function checkValidatorSetOld( - address[] memory operateAddrList, - address[] memory consensusAddrList, - address payable[] memory feeAddrList, - uint256[] memory commissionThousandthsList - ) private pure { - require( - consensusAddrList.length == operateAddrList.length, - "the numbers of consensusAddresses and operateAddresses should be equal" - ); - require( - consensusAddrList.length == feeAddrList.length, - "the numbers of consensusAddresses and feeAddresses should be equal" - ); - require( - consensusAddrList.length == commissionThousandthsList.length, - "the numbers of consensusAddresses and commissionThousandthss should be equal" - ); - for (uint256 i = 0; i < consensusAddrList.length; i++) { - for (uint256 j = 0; j < i; j++) { - require(consensusAddrList[i] != consensusAddrList[j], "duplicate consensus address"); - } - require(commissionThousandthsList[i] <= 1000, "commissionThousandths out of bound"); - } - } - /// Update validator set of the new round with elected validators - /// @param operateAddrList List of validator operator addresses - /// @param consensusAddrList List of validator consensus addresses - /// @param feeAddrList List of validator fee addresses - /// @param commissionThousandthsList List of validator commission fees in thousandth - function updateValidatorSetOld( - address[] calldata operateAddrList, - address[] calldata consensusAddrList, - address payable[] calldata feeAddrList, - uint256[] calldata commissionThousandthsList - ) external onlyCandidate { - // do verify. - checkValidatorSetOld(operateAddrList, consensusAddrList, feeAddrList, commissionThousandthsList); - if (consensusAddrList.length == 0) { - return; - } - // do update validator set state - uint256 i; - uint256 lastLength = currentValidatorSet.length; - uint256 currentLength = consensusAddrList.length; - for (i = 0; i < lastLength; i++) { - delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; - } - for (i = currentLength; i < lastLength; i++) { - currentValidatorSet.pop(); - } - - for (i = 0; i < currentLength; ++i) { - if (i >= lastLength) { - currentValidatorSet.push(Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0, 0)); - } else { - currentValidatorSet[i] = Validator(operateAddrList[i], consensusAddrList[i], feeAddrList[i], commissionThousandthsList[i], 0, '', 0, 0); - } - currentValidatorSetMap[consensusAddrList[i]] = i + 1; - } - - emit validatorSetUpdated(); - } - + function updateBlockReward(uint256 _blockReward) external { blockReward = _blockReward; } @@ -108,15 +34,6 @@ contract ValidatorSetMock is ValidatorSet { SUBSIDY_REDUCE_INTERVAL = _internal; } - function addRoundRewardMock(address[] memory agentList, uint256[] memory rewardList, uint roundTag) - external payable { - uint256 rewardSum = 0; - for (uint256 i = 0; i < rewardList.length; i++) { - rewardSum += rewardList[i]; - } - IStakeHub(STAKE_HUB_ADDR).addRoundReward{value: rewardSum}(agentList, rewardList, roundTag); - } - function jailValidator(address operateAddress, uint256 round, uint256 fine) external { ICandidateHub(CANDIDATE_HUB_ADDR).jailValidator(operateAddress, round, fine); } @@ -135,72 +52,36 @@ contract ValidatorSetMock is ValidatorSet { function setValidatorSetMap(address validator) external { currentValidatorSetMap[validator] = 1; } - /// Distribute rewards to validators (and delegators through PledgeAgent) - /// @dev this method is called by the CandidateHub contract at the beginning of turn round - /// @dev this is where we deal with reward distribution logics - function distributeRewardOld() external onlyCandidate returns (address[] memory operateAddressList) { - address payable feeAddress; - uint256 validatorReward; - - uint256 incentiveSum = 0; - uint256 validatorSize = currentValidatorSet.length; - for (uint256 i = 0; i < validatorSize; i++) { - Validator storage v = currentValidatorSet[i]; - uint256 incentiveValue = (v.income * blockRewardIncentivePercent) / 100; - incentiveSum += incentiveValue; - v.income -= incentiveValue; - } - ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{value: incentiveSum}(); - operateAddressList = new address[](validatorSize); - uint256[] memory rewardList = new uint256[](validatorSize); - uint256 rewardSum = 0; - uint256 tempIncome; - for (uint256 i = 0; i < validatorSize; i++) { - Validator storage v = currentValidatorSet[i]; - operateAddressList[i] = v.operateAddress; - tempIncome = v.income; - if (tempIncome != 0) { - feeAddress = v.feeAddress; - validatorReward = (tempIncome * v.commissionThousandths) / 1000; - if (tempIncome > validatorReward) { - rewardList[i] = tempIncome - validatorReward; - rewardSum += rewardList[i]; - } - - v.income = 0; - bool success = feeAddress.send(validatorReward); - if (success) { - emit directTransfer(v.operateAddress, feeAddress, validatorReward, tempIncome); - } else { - emit directTransferFail(v.operateAddress, feeAddress, validatorReward, tempIncome); - } - } - } - - IPledgeAgentMock(PLEDGE_AGENT_ADDR).addRoundRewardOld{value: rewardSum}(operateAddressList, rewardList); - totalInCome = 0; - return operateAddressList; - } function setValidatorCount(uint256 _validatorCount) external { validatorCount = _validatorCount; } - function clearCurrentValidatorSet() external { - for (uint i = 0; i < currentValidatorSet.length; i++) { - delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; - } - delete currentValidatorSet; - } + function getCurrentValidatorSet() external view returns (Validator[] memory) { return currentValidatorSet; } function setMaintainSlashPercent(uint256 _maintainSlashPercent) external { maintainSlashPercent = _maintainSlashPercent; } + + // for unit test function mockUpdateRankedValidatorList(address[] calldata consensusAddrList) external { updateRankedValidatorList(consensusAddrList); } - function getRankedValidatorList() external view returns (address[] memory) { - return rankedValidatorList; + + function clearCurrentValidatorSet() external { + for (uint i = 0; i < currentValidatorSet.length; i++) { + delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; + } + delete currentValidatorSet; + } + + function addRoundRewardMock(address[] memory agentList, uint256[] memory rewardList, uint roundTag) + external payable { + uint256 rewardSum = 0; + for (uint256 i = 0; i < rewardList.length; i++) { + rewardSum += rewardList[i]; + } + IStakeHub(STAKE_HUB_ADDR).addRoundReward{value: rewardSum}(agentList, rewardList, roundTag); } } diff --git a/contracts/mock/interface/ILightClientMock.sol b/contracts/mock/interface/ILightClientMock.sol deleted file mode 100644 index 77d2a320b..000000000 --- a/contracts/mock/interface/ILightClientMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: Apache2.0 -pragma solidity 0.8.4; - -interface ILightClientMock { - function getRoundPowersMock(uint256 roundTimeTag, address[] calldata candidates) external view returns (uint256[] memory powers); - - function getRoundCandidates(uint256 roundTimeTag) external view returns (address[] memory candidates); - - function getRoundMiners(uint256 roundTimeTag, address candidate) external view returns (address[] memory miners); - - function checkTxProof(bytes32 txid, uint32 blockHeight, uint32 confirmBlock, bytes32[] calldata nodes, uint256 index) external view returns (bool, uint64); -} \ No newline at end of file diff --git a/contracts/mock/interface/IPledgeAgentMock.sol b/contracts/mock/interface/IPledgeAgentMock.sol deleted file mode 100644 index c1fee927c..000000000 --- a/contracts/mock/interface/IPledgeAgentMock.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache2.0 -pragma solidity 0.8.4; - -interface IPledgeAgentMock { - function addRoundReward(address[] calldata agentList, uint256[] calldata rewardList) payable external; - function onFelony(address agent) external; - function delegateCoin(address agent) external payable; - function undelegateCoin(address agent) external; - function undelegateCoin(address agent, uint256 amount) external; - function transferCoin(address sourceAgent, address targetAgent) external; - function transferCoin(address sourceAgent, address targetAgent, uint256 amount) external; - function claimReward(address[] calldata agentList) external returns (uint256, bool); - function delegateBtc(bytes calldata btcTx, uint32 blockHeight, bytes32[] memory nodes, uint256 index, bytes memory script) external; - function transferBtc(bytes32 txid, address targetAgent) external; - - function claimBtcReward(bytes32[] calldata txidList) external returns (uint256 rewardSum); - function distributePowerRewardOld(address candidate, address[] calldata miners) external; - function getHybridScoreOld(address[] calldata candidates, uint256[] calldata powers, uint256 round) external returns(uint256[] memory); - function setNewRoundOld(address[] calldata validatorList, uint256 round) external; - function addRoundRewardOld(address[] calldata agentList, uint256[] calldata rewardList) payable external; -} diff --git a/contracts/mock/interface/IValidatorSetMock.sol b/contracts/mock/interface/IValidatorSetMock.sol deleted file mode 100644 index 942f8a682..000000000 --- a/contracts/mock/interface/IValidatorSetMock.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache2.0 -pragma solidity 0.8.4; - -interface IValidatorSetMock { - function misdemeanor(address validator) external; - function felony(address validator, uint256 felonyRound, uint256 felonyDeposit) external; - function distributeRewardOld() external returns (address[] memory operateAddrList); - function updateValidatorSetOld(address[] calldata operateAddrList, address[] calldata consensusAddrList, address payable[] calldata feeAddrList, uint256[] calldata commissionThousandthsList) external; - function isValidator(address addr) external returns (bool); -} diff --git a/tests/calc_reward.py b/tests/calc_reward.py index c55ea706d..ab6ec6e73 100644 --- a/tests/calc_reward.py +++ b/tests/calc_reward.py @@ -25,15 +25,17 @@ class Discount: state_map = {} def get_init_discount(self): - tlp_rates = [] - lp_rates = [] + tlp_rates_keys = [] + tlp_rates_values = [] + lp_rates_keys = [] + lp_rates_values = [] for t in list(self.tlp_rates.keys())[::-1]: - tlp_rates.append(t) - tlp_rates.append(self.tlp_rates[t]) + tlp_rates_keys.append(t) + tlp_rates_values.append(self.tlp_rates[t]) for l in list(self.lp_rates.keys())[::-1]: - lp_rates.append(l) - lp_rates.append(self.lp_rates[l]) - return tlp_rates, lp_rates + lp_rates_keys.append(l) + lp_rates_values.append(self.lp_rates[l]) + return tlp_rates_keys, tlp_rates_values, lp_rates_keys, lp_rates_values def set_delegate(address, value, undelegate_amount=0, stake_duration=500): diff --git a/tests/common.py b/tests/common.py index fd4ec44ee..bcfc120a7 100644 --- a/tests/common.py +++ b/tests/common.py @@ -33,11 +33,9 @@ def chain_deposit(miners, tx_fee=100,deposit_count=1): for miner in miners: for _ in range(deposit_count): tx = ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) - print(f'chain_deposit>>>>>>>>>>deposit_count{deposit_count}:', tx.events) else: for _ in range(deposit_count): tx = ValidatorSetMock[0].deposit(miners, {"value": tx_fee, "from": accounts[99]}) - print(f'chain_deposit>>>>>>>>>>deposit_count{deposit_count}:',tx.events) @@ -51,7 +49,6 @@ def chain_vote(miners, weights): def chain_get_validator_consensus(): consensus, vote_addresses = ValidatorSetMock[0].getValidatorsAndVoteAddresses() - print(f'chain_get_validator_consensus>>>>>>>>consensus:{consensus}') return consensus def turn_round(miners: list = None, tx_fee=100, round_count=1, weights=None): @@ -108,11 +105,9 @@ def stake_hub_claim_reward(account): if isinstance(account, list): for i in account: tx = StakeHubMock[0].claimReward({'from': i}) - print(f'stake_hub_claim_reward>>>>>>>>account:{i}', tx.events) else: tx = StakeHubMock[0].claimReward({'from': account}) - print(f'stake_hub_claim_reward>>>>>>>>account:{account}', tx.events) return tx @@ -143,22 +138,18 @@ def slash_validator(consensus,slash_type='felony'): def refuse_delegate(operator): tx = CandidateHubMock[0].refuseDelegate({'from': operator}) - print(f'refuse_delegate>>>>>>>>operator:{operator}', tx.events) return tx def accept_delegate(operator): tx = CandidateHubMock[0].acceptDelegate({'from': operator}) - print(f'accept_delegate>>>>>>>>operator:{operator}', tx.events) return tx def enter_maintenance(operator): tx = ValidatorSetMock[0].enterMaintenance({'from': operator}) assert 'validatorEnterMaintenance' in tx.events - print(f'enter_maintenance>>>>>>>>operator:{operator}', tx.events) return tx def exit_maintenance(operator): tx = ValidatorSetMock[0].exitMaintenance({'from': operator}) assert 'validatorExitMaintenance' in tx.events - print(f'exit_maintenance>>>>>>>>operator:{operator}', tx.events) return tx diff --git a/tests/delegate.py b/tests/delegate.py index 6e90fd86c..18c9407ef 100644 --- a/tests/delegate.py +++ b/tests/delegate.py @@ -257,13 +257,13 @@ def run_stake_operation(operation, candidate, account, operation_amount, target_ def run_proxy_stake_operation(operation, candidate, account, operation_amount, target_agent=None, candidates=None): tx = None if operation == 'proxy_delegate': - tx = old_delegate_coin_success(candidate, account, operation_amount, False) + tx = proxy_delegate_coin_success(candidate, account, operation_amount) elif operation == 'proxy_undelegate': - tx = old_undelegate_coin_success(candidate, account, operation_amount, False) + tx = proxy_undelegate_coin_success(candidate, account, operation_amount) elif operation == 'proxy_transfer': - tx = old_transfer_coin_success(candidate, target_agent, account, operation_amount, False) + tx = proxy_transfer_coin_success(candidate, target_agent, account, operation_amount) elif operation == 'proxy_claim': - tx = old_claim_reward_success(candidates, account) + tx = proxy_claim_reward_success(candidates, account) return tx @@ -316,77 +316,26 @@ def delegate_power_success(candidate, delegator, value=1, stake_round=0): stake_round = get_current_round() - 6 + stake_round BtcLightClientMock[0].setMiners(stake_round, candidate, [delegator] * value) - -# mock delegate -def mock_delegate_btc_success(agent, delegator, btc_amount, lock_time=None, block_timestamp=0, output_index=0, - tx_id=None, channel_id=0): - if tx_id is None: - tx_id = random_btc_tx_id() - if lock_time is None: - lock_time = LOCK_TIME - tx = BitcoinStakeMock[0].mockDelegateBtc(tx_id, btc_amount, agent, delegator, lock_time, block_timestamp, - output_index, channel_id) - assert 'mockDelegatedBtc' in tx.events - return tx_id - - -def mock_transfer_btc_success(tx_id, target_candidate): - tx = BitcoinStakeMock[0].mockTransferBtc(tx_id, target_candidate) - assert 'mockTransferredBtc' in tx.events - - -def mock_delegate_coin_success(candidate, delegator, amount): - tx = CoreAgentMock[0].mockDelegateCoin(candidate, {'value': amount, 'from': delegator}) - assert 'mockDelegatedCoin' in tx.events - - -def mock_transfer_coin_success(source_agent, target_agent, delegator, amount): - tx = CoreAgentMock[0].mockTransferCoin(source_agent, target_agent, amount, {'from': delegator}) - assert 'mockTransferredCoin' in tx.events - - return tx - - -def mock_undelegate_coin_success(candidate, delegator, amount): - tx = CoreAgentMock[0].mockUndelegateCoin(candidate, amount, {'from': delegator}) - assert 'mockUndelegatedCoin' in tx.events - return tx - - # old delegate -def old_delegate_coin_success(candidate, account, amount, old=True): - if old is True: - tx = PledgeAgentMock[0].delegateCoinOld(candidate, {'value': amount, 'from': account}) - assert 'delegatedCoinOld' in tx.events - else: - tx = PledgeAgentMock[0].delegateCoin(candidate, {'value': amount, 'from': account}) - assert 'delegatedCoin' in tx.events - +def proxy_delegate_coin_success(candidate, account, amount): + tx = PledgeAgentMock[0].delegateCoin(candidate, {'value': amount, 'from': account}) + assert 'delegatedCoin' in tx.events return tx -def old_undelegate_coin_success(candidate, account, amount=0, old=True): - if old is True: - tx = PledgeAgentMock[0].undelegateCoinOld(candidate, amount, {'from': account}) - assert 'undelegatedCoinOld' in tx.events - - else: - tx = PledgeAgentMock[0].undelegateCoin(candidate, amount, {'from': account}) - assert 'undelegatedCoin' in tx.events +def proxy_undelegate_coin_success(candidate, account, amount=0): + tx = PledgeAgentMock[0].undelegateCoin(candidate, amount, {'from': account}) + assert 'undelegatedCoin' in tx.events return tx -def old_transfer_coin_success(source_agent, target_agent, account, amount=0, old=True): - if old is True: - tx = PledgeAgentMock[0].transferCoinOld(source_agent, target_agent, amount, {'from': account}) - assert 'transferredCoinOld' in tx.events - else: - tx = PledgeAgentMock[0].transferCoin(source_agent, target_agent, amount, {'from': account}) - assert 'transferredCoin' in tx.events +def proxy_transfer_coin_success(source_agent, target_agent, account, amount=0): + tx = PledgeAgentMock[0].transferCoin(source_agent, target_agent, amount, {'from': account}) + assert 'transferredCoin' in tx.events return tx -def old_claim_reward_success(candidates, account=None): +def proxy_claim_reward_success(candidates, account=None): if isinstance(account, list): for a in account: tx = PledgeAgentMock[0].claimReward(candidates, {'from': a}) @@ -396,42 +345,6 @@ def old_claim_reward_success(candidates, account=None): tx = PledgeAgentMock[0].claimReward(candidates, {'from': account}) return tx - -def old_claim_btc_reward_success(tx_ids, account=None): - if account is None: - account = accounts[0] - tx = PledgeAgentMock[0].claimBtcReward(tx_ids, {'from': account}) - return tx - - -def old_turn_round(miners: list = None, tx_fee=100, round_count=1): - if miners is None: - miners = [] - tx = None - for _ in range(round_count): - for miner in miners: - ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) - tx = CandidateHubMock[0].turnRoundOld() - chain.sleep(1) - return tx - - -def old_delegate_btc_success(btc_value, agent, delegator, lock_time=None, tx_id=None, script=None, fee=1): - if script is None: - script, _, timestamp = random_btc_lock_script() - if lock_time is None: - lock_time = timestamp - if tx_id is None: - tx_id = random_btc_tx_id() - PledgeAgentMock[0].delegateBtcMock(tx_id, btc_value, agent, delegator, script, lock_time, fee) - return tx_id - - -def old_transfer_btc_success(tx_id, agent): - tx = PledgeAgentMock[0].transferBtcOld(tx_id, agent) - return tx_id - - class BtcScript: @staticmethod def get_script_hash(script): @@ -588,26 +501,16 @@ def build_btc_lst(self, outputs, inputs=None, opreturn=None): self.tx_id = get_transaction_txid(btc_tx) return btc_tx - -def random_btc_lst_lock_script(): +def build_btc_lock_script(timestamp=None): private_key = generate_private_key() private_key_hex = get_public_key(private_key) - script_type = random.choice( - [AddressType.P2SH, AddressType.P2PKH, AddressType.P2WPKH, AddressType.P2WSH, AddressType.P2TAPROOT]) - script = BtcScript().k2_btc_lst_script(private_key_hex, script_type) - return script - - -def random_btc_lock_script(): - private_key = generate_private_key() - private_key_hex = get_public_key(private_key) - timestamp = random.randint(int(time.time()), int(time.time()) + 1000000) - scrip_type = random.choice(['hash', 'key']) - lock_script_type = random.choice(['p2sh', 'p2wsh']) + if timestamp is None: + timestamp = random.randint(int(time.time()), int(time.time()) + 1000000) + scrip_type = random.choice(['hash','key']) + lock_script_type = random.choice(['p2sh','p2wsh']) script, pay_address = BtcScript().k2_btc_script(private_key_hex, timestamp, scrip_type, lock_script_type) return script, pay_address, timestamp - class StakeManager: @staticmethod def set_lp_rates(rates=None): diff --git a/tests/test_btc_agent.py b/tests/test_btc_agent.py index bcd69eb30..29508d7f7 100644 --- a/tests/test_btc_agent.py +++ b/tests/test_btc_agent.py @@ -62,25 +62,6 @@ def test_btc_agent_init_once_only(btc_agent): with brownie.reverts("the contract already init"): btc_agent.init() - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_initialize_from_pledge_agent_success(btc_agent): - candidates = accounts[:3] - amounts = [100, 200, 300] - update_system_contract_address(btc_agent, pledge_agent=accounts[0]) - btc_agent._initializeFromPledgeAgent(candidates, amounts) - for index, candidate in enumerate(candidates): - assert btc_agent.candidateMap(candidate)[1] == amounts[index] - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_only_pledge_agent_can_call(btc_agent): - candidates = accounts[:3] - amounts = [100, 200, 300] - with brownie.reverts("the sender must be pledge agent contract"): - btc_agent._initializeFromPledgeAgent(candidates, amounts) - - @pytest.mark.parametrize('lst_btc_amount', [0, 4000]) def test_distribute_reward_success(btc_agent, btc_stake, lst_btc_amount): history_reward = 200 diff --git a/tests/test_btc_light_client.py b/tests/test_btc_light_client.py index d598de897..5f2d2ae7b 100644 --- a/tests/test_btc_light_client.py +++ b/tests/test_btc_light_client.py @@ -40,18 +40,20 @@ def update_default_block_gasprice(btc_light_client): btc_light_client.updateParam('storeBlockGasPrice', hex_value, {'from': accounts[0]}) +# storeBlockHeader + def test_store_zero_block(btc_light_client): - tx = btc_light_client.storeBlockHeader("0x0") + tx = btc_light_client.storeBlockHeader("0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") expect_event(tx, "StoreHeaderFailed", {'returnCode': "10030"}) - -def test_store_wrong_length_block(btc_light_client): - tx = btc_light_client.storeBlockHeader(btc_block_data[0][:80]) - expect_event(tx, "StoreHeaderFailed", {"returnCode": "10090"}) +@pytest.mark.parametrize("length", [79, 80, 81, 159, 160]) +def test_store_wrong_length_block(btc_light_client,length): + with brownie.reverts("block bytes length must >= 80"): + tx = btc_light_client.storeBlockHeader(btc_block_data[0][:length]) def test_store_change_nonce_block(btc_light_client): - tx = btc_light_client.storeBlockHeader(btc_block_data[0][:80] + '00') + tx = btc_light_client.storeBlockHeader(btc_block_data[0][:160] + '00') expect_event(tx, "StoreHeaderFailed", {"returnCode": "10090"}) diff --git a/tests/test_btc_stake.py b/tests/test_btc_stake.py index a14fcaa7b..495aeee14 100644 --- a/tests/test_btc_stake.py +++ b/tests/test_btc_stake.py @@ -75,29 +75,6 @@ def test_btc_stake_init_can_only_run_once(btc_stake): with brownie.reverts("the contract already init"): btc_stake.init() - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_revert_if_not_called_by_only_pledge_agent(btc_stake): - candidates = accounts[:3] - amounts = [1000, 2000, 3000] - realtime_amounts = [2000, 2000, 4000] - with brownie.reverts("the sender must be pledge agent contract"): - btc_stake._initializeFromPledgeAgent(candidates, amounts, realtime_amounts) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_initialize_from_pledge_agent_success(btc_stake): - update_system_contract_address(btc_stake, pledge_agent=accounts[0]) - candidates = accounts[:3] - amounts = [1000, 2000, 3000] - realtime_amounts = [2000, 2000, 4000] - btc_stake._initializeFromPledgeAgent(candidates, amounts, realtime_amounts) - for index, i in enumerate(candidates): - c = btc_stake.candidateMap(i) - assert c[0] == amounts[index] - assert c[1] == realtime_amounts[index] - - def test_delegate_btc_with_lock_time_in_tx(btc_stake, set_candidate, stake_hub, btc_agent): turn_round() operators, consensuses = set_candidate @@ -664,7 +641,7 @@ def test_undelegate_success(btc_stake, set_candidate): lock_script, btc_tx = __create_btc_stake_scrip_and_btc_tx(operators[0], accounts[0], BTC_VALUE) btc_stake.delegate(btc_tx, block_height, [], index, lock_script) turn_round(consensuses, round_count=2) - script, pay_address, _ = random_btc_lock_script() + script, pay_address, _ = build_btc_lock_script() btc_tx_info = {} inputs = [build_input(get_transaction_txid(btc_tx))] outputs = [build_output(BTC_VALUE // 2, pay_address)] @@ -692,7 +669,7 @@ def test_btc_undelegate_no_reward_impact(btc_stake, set_candidate, round_count): lock_script, btc_tx = __create_btc_stake_scrip_and_btc_tx(operators[0], accounts[0], BTC_VALUE) btc_stake.delegate(btc_tx, block_height, [], index, lock_script) turn_round(consensuses) - script, pay_address, _ = random_btc_lock_script() + script, pay_address, _ = build_btc_lock_script() btc_tx_info = {} inputs = [build_input(get_transaction_txid(btc_tx))] outputs = [build_output(BTC_VALUE // 2, pay_address)] @@ -718,7 +695,7 @@ def test_no_btc_tx_undelegated(btc_stake, set_candidate): lock_script, btc_tx = __create_btc_stake_scrip_and_btc_tx(operators[0], accounts[0], BTC_VALUE) btc_stake.delegate(btc_tx, block_height, [], index, lock_script) turn_round(consensuses) - script, pay_address, _ = random_btc_lock_script() + script, pay_address, _ = build_btc_lock_script() btc_tx_info = {} inputs = [build_input(get_transaction_txid(btc_tx), 1)] outputs = [build_output(BTC_VALUE // 2, pay_address)] @@ -738,7 +715,7 @@ def test_single_transaction_repeated_undelegate(btc_stake, set_candidate): index = 3000 tx_id0 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) pay_address0 = get_btc_script().k2_btc_pay_address(LOCK_SCRIPT, 'p2sh') - _, pay_address1, _ = random_btc_lock_script() + _, pay_address1, _ = build_btc_lock_script() btc_tx = btc_delegate.build_btc( set_outputs([BTC_VALUE, pay_address1], [BTC_VALUE, pay_address0]), opreturn=set_op_return([operators[1], accounts[1], LOCK_SCRIPT]) @@ -746,7 +723,7 @@ def test_single_transaction_repeated_undelegate(btc_stake, set_candidate): btc_stake.delegate(btc_tx, 201, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) tx_id1 = get_transaction_txid(btc_tx) turn_round(consensuses) - script, pay_address, _ = random_btc_lock_script() + script, pay_address, _ = build_btc_lock_script() btc_tx_info = {} inputs = [build_input(tx_id0, 0), build_input(tx_id1, 2)] outputs = [build_output(BTC_VALUE // 2, pay_address)] @@ -774,7 +751,7 @@ def test_failed_btc_transaction_repeated_undelegate(btc_stake, set_candidate): index = 3000 tx_id0 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) pay_address0 = get_btc_script().k2_btc_pay_address(LOCK_SCRIPT, 'p2sh') - _, pay_address1, _ = random_btc_lock_script() + _, pay_address1, _ = build_btc_lock_script() btc_tx = btc_delegate.build_btc( set_outputs([BTC_VALUE, pay_address1], [BTC_VALUE, pay_address0]), opreturn=set_op_return([operators[1], accounts[1], LOCK_SCRIPT]) @@ -782,7 +759,7 @@ def test_failed_btc_transaction_repeated_undelegate(btc_stake, set_candidate): btc_stake.delegate(btc_tx, 201, [], 0, LOCK_SCRIPT, {"from": accounts[1]}) tx_id1 = get_transaction_txid(btc_tx) turn_round(consensuses) - _, pay_address, _ = random_btc_lock_script() + _, pay_address, _ = build_btc_lock_script() btc_tx_info = {} inputs = [build_input(tx_id0, 1), build_input(tx_id1, 0)] outputs = [build_output(BTC_VALUE // 2, pay_address)] @@ -800,14 +777,14 @@ def test_undelegate_then_successful_stake(btc_stake, set_candidate): index = 3000 tx_id0 = delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) pay_address0 = get_btc_script().k2_btc_pay_address(LOCK_SCRIPT, 'p2sh') - _, pay_address1, _ = random_btc_lock_script() + _, pay_address1, _ = build_btc_lock_script() btc_tx = btc_delegate.build_btc( set_outputs([BTC_VALUE, pay_address1], [BTC_VALUE, pay_address0]), opreturn=set_op_return([operators[1], accounts[1], LOCK_SCRIPT]) ) tx_id1 = get_transaction_txid(btc_tx) turn_round(consensuses) - _, pay_address, _ = random_btc_lock_script() + _, pay_address, _ = build_btc_lock_script() btc_tx_info = {} inputs = [build_input(tx_id0, 0), build_input(tx_id1, 2)] outputs = [build_output(BTC_VALUE // 2, pay_address)] @@ -1486,19 +1463,15 @@ def test_calculateRewards_some_zero_rewards(btc_stake, set_candidate, btc_agent, # getCalculateRound def test_get_calculate_round_mock_basic(btc_stake, set_candidate): operators, _ = set_candidate - txid = Web3.to_bytes(hexstr='0x' + '11' * 32) lock_time = LOCK_TIME + Utils.ROUND_INTERVAL * 3 - block_ts = LOCK_TIME btc_amount = 1 * Utils.BTC_DECIMAL - btc_stake.mockDelegateBtc(txid, btc_amount, operators[0], accounts[0], lock_time, block_ts, 0, 0) - + lock_script, pay_address, _ = build_btc_lock_script(lock_time) + txid = delegate_btc_success(operators[0], accounts[0], btc_amount, lock_script,lock_data=lock_time) unlock_round_minus_1 = lock_time // Utils.ROUND_INTERVAL - 1 - settle_round_low = unlock_round_minus_1 - 1 calc_round, expired = btc_stake.getCalculateRoundMock(txid, settle_round_low) assert expired is False assert calc_round == settle_round_low - settle_round_high = unlock_round_minus_1 + 2 calc_round, expired = btc_stake.getCalculateRoundMock(txid, settle_round_high) assert expired is True @@ -1877,58 +1850,6 @@ def test_transfer_to_zero_address(btc_stake, set_candidate): with brownie.reverts(error_msg): transfer_btc_success(tx_id, ZERO_ADDRESS, accounts[0]) - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_move_data_success(btc_stake, pledge_agent, set_candidate): - operators, consensuses = set_candidate - btc_amount = BTC_VALUE // 2 - end_round = LOCK_TIME // Utils.ROUND_INTERVAL - tx_id = old_delegate_btc_success(btc_amount, operators[2], accounts[1], lock_time=LOCK_TIME, script=LOCK_SCRIPT) - btc_stake.moveData([tx_id]) - __check_receipt_map_info(tx_id, { - 'candidate': operators[2], - 'delegator': accounts[1], - 'round': 1 - }) - __check_btc_tx_map_info(tx_id, { - 'amount': btc_amount, - 'outputIndex': 0, - 'blockTimestamp': 0, - 'lockTime': LOCK_TIME // Utils.ROUND_INTERVAL * Utils.ROUND_INTERVAL, - 'usedHeight': 0 - }) - assert __get_delegator_btc_map(accounts[1])[0] == tx_id - tx_ids = [] - for i in range(2): - tx_id = old_delegate_btc_success(btc_amount + i, operators[i], accounts[i], lock_time=LOCK_TIME, - script=LOCK_SCRIPT) - tx_ids.append(tx_id) - btc_stake.moveData(tx_ids) - for i in range(2): - __check_receipt_map_info(tx_ids[i], { - 'candidate': operators[i], - 'delegator': accounts[i], - 'round': 1 - }) - agents, amounts = __get_round2_expire_info_map(end_round) - assert agents[0] == operators[2] - assert amounts[0] == btc_amount + 1 - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_move_data_with_btc_already_collateralized(btc_stake, pledge_agent, set_candidate): - operators, consensuses = set_candidate - btc_amount = BTC_VALUE // 2 - tx_id = old_delegate_btc_success(btc_amount, operators[1], accounts[1], lock_time=LOCK_TIME, script=LOCK_SCRIPT) - btc_stake.moveData([tx_id]) - __check_receipt_map_info(tx_id, { - 'candidate': operators[1], - 'delegator': accounts[1], - 'round': 1 - }) - btc_stake.moveData([tx_id]) - - def test_get_grades_success(btc_stake, pledge_agent, set_candidate): update_system_contract_address(btc_stake, gov_hub=accounts[0]) grades = [[0, 1], [1000, 2000]] diff --git a/tests/test_claim_reward.py b/tests/test_claim_reward.py index 62326c3f0..0dc7eb696 100644 --- a/tests/test_claim_reward.py +++ b/tests/test_claim_reward.py @@ -56,10 +56,10 @@ def set_block_reward(validator_set, candidate_hub, btc_stake, stake_hub, pledge_ candidate_hub.setControlRoundTimeTag(True) # The default staking time is 150 days set_block_time_stamp(150, LOCK_TIME) - tlp_rates, lp_rates = Discount().get_init_discount() + tlp_rates_keys, tlp_rates_values, lp_rates_keys, lp_rates_values = Discount().get_init_discount() btc_agent.setAssetWeight(1) - btc_stake.setInitTlpRates(*tlp_rates) - btc_agent.setInitLpRates(*lp_rates) + btc_stake.setInitTlpRates(tlp_rates_keys, tlp_rates_values) + btc_agent.setInitLpRates(lp_rates_keys, lp_rates_values) btc_stake.setIsActive(True) btc_agent.setIsActive(True) PLEDGE_AGENT = pledge_agent @@ -190,295 +190,6 @@ def test_claim_reward_after_hardcap_update(stake_hub, hard_cap, set_candidate): assert tracker2.delta() == account_rewards[accounts[2]] -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("tests", [ - {'slash_agent': 0, 'validator': True, 'slash_type': 'felony', 'newRequiredMargin': 2000000, 'Margin': 1100000, - 'claimed_reward': 13545}, - {'slash_agent': 0, 'validator': True, 'slash_type': 'felony', 'newRequiredMargin': 2000000, 'Margin': 1100001, - 'claimed_reward': 13545}, - {'slash_agent': 1, 'validator': False, 'slash_type': 'felony', 'newRequiredMargin': 2000000, 'Margin': 1099999, - 'claimed_reward': 0}, - {'slash_agent': 0, 'validator': False, 'slash_type': 'felony', 'newRequiredMargin': 2000000, 'Margin': 1000000, - 'claimed_reward': 0}, - {'slash_agent': 0, 'validator': True, 'slash_type': 'minor', 'newRequiredMargin': 2000000, 'Margin': 900000, - 'claimed_reward': 40635}, - {'slash_agent': 0, 'validator': True, 'slash_type': 'minor', 'newRequiredMargin': 2000000, 'Margin': 1000000, - 'claimed_reward': 40635} -]) -def test_update_bond_after_slash(slash_indicator, tests, candidate_hub, validator_set): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - operators = [] - consensuses = [] - for operator in accounts[5:10]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - old_delegate_coin_success(operators[tests['slash_agent']], accounts[0], delegate_value) - old_turn_round() - tx = None - if tests['slash_type'] == 'minor': - slash_threshold = slash_indicator.misdemeanorThreshold() - event_name = 'validatorMisdemeanor' - else: - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - for count in range(slash_threshold): - tx = slash_indicator.slash(consensuses[tests['slash_agent']]) - assert event_name in tx.events - old_turn_round(consensuses) - hex_value = padding_left(Web3.to_hex(int(tests['newRequiredMargin'])), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam('requiredMargin', hex_value) - init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - tx = candidate_hub.addMargin({'from': operators[tests['slash_agent']], 'value': tests['Margin']}) - turn_round(consensuses, round_count=3) - if tests['validator']: - assert consensuses[tests['slash_agent']] in validator_set.getValidators() - else: - assert consensuses[tests['slash_agent']] not in validator_set.getValidators() - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == tests['claimed_reward'] - turn_round(consensuses, round_count=2) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_upgrade_bond_does_not_affect_active_validators(slash_indicator, candidate_hub, set_candidate, validator_set): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_value) - old_turn_round() - old_turn_round(consensuses) - init_margin = 2000000 - hex_value = padding_left(Web3.to_hex(int(init_margin * 100)), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam('requiredMargin', hex_value) - init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses, round_count=3) - assert consensuses[0] in validator_set.getValidators() - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 3 - tx = candidate_hub.addMargin({'from': operators[0], 'value': 100}) - turn_round(consensuses, round_count=2) - assert consensuses[0] in validator_set.getValidators() - turn_round(consensuses, round_count=2) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_add_bond_multiple_times(slash_indicator, candidate_hub, set_candidate, validator_set): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_value) - old_turn_round() - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - tx = None - for count in range(slash_threshold): - tx = slash_indicator.slash(consensuses[0]) - assert event_name in tx.events - old_turn_round(consensuses) - assert consensuses[0] not in validator_set.getValidators() - init_margin = 2000000 - hex_value = padding_left(Web3.to_hex(init_margin * 10000), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam('requiredMargin', hex_value) - new_margin = 10000000 - candidate_hub.addMargin({'from': operators[0], 'value': new_margin}) - init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses, round_count=3) - assert consensuses[0] not in validator_set.getValidators() - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(new_margin)), 64) - candidate_hub.updateParam('requiredMargin', hex_value) - candidate_hub.addMargin({'from': operators[0], 'value': MIN_INIT_DELEGATE_VALUE}) - turn_round(consensuses, round_count=2) - assert consensuses[0] in validator_set.getValidators() - turn_round(consensuses, round_count=2) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("add_margin", [True, False]) -def test_bond_update_before_after_slash(slash_indicator, candidate_hub, set_candidate, validator_set, add_margin): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_value) - old_turn_round() - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - tx = None - init_margin = 2000000 - hex_value = padding_left(Web3.to_hex(init_margin * 10000), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam('requiredMargin', hex_value) - for count in range(slash_threshold): - tx = slash_indicator.slash(consensuses[0]) - assert event_name in tx.events - old_turn_round(consensuses) - assert consensuses[0] not in validator_set.getValidators() - init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses, round_count=3) - assert consensuses[0] not in validator_set.getValidators() - new_margin = 10002 - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(new_margin)), 64) - candidate_hub.updateParam('requiredMargin', hex_value) - if add_margin: - candidate_hub.addMargin({'from': operators[0], 'value': MIN_INIT_DELEGATE_VALUE}) - turn_round(consensuses, round_count=2) - if add_margin: - assert consensuses[0] in validator_set.getValidators() - else: - assert consensuses[0] not in validator_set.getValidators() - turn_round(consensuses, round_count=2) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("add_margin", [True, False]) -def test_slash_in_upgrade_round(slash_indicator, candidate_hub, set_candidate, validator_set, add_margin): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_value) - init_margin = 2000000 - hex_value = padding_left(Web3.to_hex(init_margin * 10000), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam('requiredMargin', hex_value) - old_turn_round() - assert consensuses[0] in validator_set.getValidators() - init_hybrid_score_mock() - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - tx = None - for count in range(slash_threshold): - tx = slash_indicator.slash(consensuses[0]) - assert event_name in tx.events - turn_round(consensuses, round_count=1) - assert consensuses[0] not in validator_set.getValidators() - new_margin = 10002 - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(new_margin)), 64) - candidate_hub.updateParam('requiredMargin', hex_value) - if add_margin: - candidate_hub.addMargin({'from': operators[0], 'value': MIN_INIT_DELEGATE_VALUE}) - turn_round(consensuses, round_count=2) - if add_margin: - assert consensuses[0] in validator_set.getValidators() - else: - assert consensuses[0] not in validator_set.getValidators() - turn_round(consensuses, round_count=2) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -@pytest.mark.parametrize("add_margin", [True, False]) -@pytest.mark.parametrize("round_count", [1, 2, 3]) -def test_slash_after_upgrade(slash_indicator, candidate_hub, set_candidate, validator_set, add_margin, round_count): - delegate_value = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_value) - old_delegate_coin_success(operators[0], accounts[1], delegate_value) - tx_id = old_delegate_btc_success(BTC_VALUE, operators[0], accounts[1]) - init_margin = 2000000 - hex_value = padding_left(Web3.to_hex(init_margin * 10000), 64) - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - candidate_hub.updateParam('requiredMargin', hex_value) - old_turn_round() - assert consensuses[0] in validator_set.getValidators() - init_hybrid_score_mock() - move_btc_data([tx_id]) - turn_round(consensuses, round_count=round_count) - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - tx = None - for count in range(slash_threshold): - tx = slash_indicator.slash(consensuses[0]) - assert event_name in tx.events - turn_round(consensuses, round_count=1) - assert consensuses[0] not in validator_set.getValidators() - new_margin = 10002 - update_system_contract_address(candidate_hub, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(new_margin)), 64) - candidate_hub.updateParam('requiredMargin', hex_value) - if add_margin: - candidate_hub.addMargin({'from': operators[0], 'value': MIN_INIT_DELEGATE_VALUE}) - turn_round(consensuses, round_count=2) - if add_margin: - assert consensuses[0] in validator_set.getValidators() - else: - assert consensuses[0] not in validator_set.getValidators() - turn_round(consensuses, round_count=2) - - -def test_stake_reward_claim_after_upgrade(stake_hub, system_reward, set_candidate, init_system_reward_balance): - surplus = 50000 - accounts[3].transfer(stake_hub.address, surplus) - stake_hub.setSurplus(surplus) - assert stake_hub.balance() == surplus - delegate_amount = 500000 - btc_amount = 100 - operators, consensuses = set_candidate - old_turn_round() - old_delegate_coin_success(operators[0], accounts[10], delegate_amount) - old_delegate_coin_success(operators[0], accounts[11], delegate_amount) - old_delegate_coin_success(operators[1], accounts[12], delegate_amount) - old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[10], delegate_amount // 2) - old_undelegate_coin_success(operators[0], accounts[10], delegate_amount // 2) - old_turn_round(consensuses) - init_hybrid_score_mock() - old_turn_round(consensuses, round_count=2) - mock_delegate_coin_success(operators[2], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[2], accounts[1], delegate_amount) - mock_delegate_btc_success(operators[2], accounts[2], btc_amount) - turn_round(consensuses) - mock_transfer_coin_success(operators[2], operators[1], accounts[0], delegate_amount // 2) - mock_undelegate_coin_success(operators[1], accounts[0], delegate_amount // 2) - turn_round(consensuses) - # hardfork - # gov surplus - system_reward_tracker = get_tracker(system_reward) - hex_value = padding_left(Web3.to_hex(surplus), 64) - execute_proposal( - stake_hub.address, 0, - "updateParam(string,bytes)", - encode(['string', 'bytes'], ['surplus', Web3.to_bytes(hexstr=hex_value)]), - "update surplus" - ) - assert system_reward_tracker.delta() == surplus - # add whiteList - percentage = 2000 - white_list0 = [to_bytes(hexstr=accounts[50].address), percentage] - white_list_encode = rlp.encode(white_list0).hex() - execute_proposal( - system_reward.address, 0, - "updateParam(string,bytes)", - encode(['string', 'bytes'], ['addWhiteList', Web3.to_bytes(hexstr=white_list_encode)]), - "update addWhiteList0" - ) - white_list1 = [to_bytes(hexstr=stake_hub.address), percentage * 2] - white_list_encode1 = rlp.encode(white_list1).hex() - execute_proposal( - system_reward.address, 0, - "updateParam(string,bytes)", - encode(['string', 'bytes'], ['addWhiteList', Web3.to_bytes(hexstr=white_list_encode1)]), - "update addWhiteList1" - ) - assert system_reward.getWhiteListSet() == [[accounts[50].address, percentage], [stake_hub.address, percentage * 2]] - tracker = get_tracker(accounts[50]) - burn_reward = 9030 - delegate_coin_success(operators[2], accounts[3], delegate_amount) - turn_round(consensuses) - assert tracker.delta() == (surplus + burn_reward) * percentage // Utils.DENOMINATOR - delegate_coin_success(operators[0], accounts[2], delegate_amount) - turn_round(consensuses) - for account in accounts[:4]: - tx = stake_hub_claim_reward(account) - assert 'claimedReward' in tx.events - turn_round(consensuses) - - def init_hybrid_score_mock(): STAKE_HUB.initHybridScoreMock() set_round_tag(get_current_round()) @@ -488,17 +199,6 @@ def move_btc_data(tx_ids): BTC_STAKE.moveData(tx_ids) -def old_turn_round(miners: list = None, tx_fee=100, round_count=1): - if miners is None: - miners = [] - tx = None - for _ in range(round_count): - for miner in miners: - ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) - tx = CandidateHubMock[0].turnRoundOld() - chain.sleep(1) - return tx - # bep127 & bep131 def test_bep127_alternate_replace_after_major_offense(candidate_hub, validator_set): diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d7589445a..36a23105f 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -50,6 +50,7 @@ def test_only_gov_can_call(configuration, core_agent): def test_add_config_success(configuration, core_agent): event_prototype = "Transfer(address,address,uint256)" signature = Web3.keccak(text=event_prototype) + signature1 = Web3.keccak(text="btcExpired") value = [ to_bytes(hexstr=accounts[0].address), [ @@ -65,7 +66,7 @@ def test_add_config_success(configuration, core_agent): [to_bytes(hexstr=accounts[3].address), 7000], [to_bytes(hexstr=accounts[4].address), 3000] ], - 'btcExpired', + signature1, 100000 ] ], @@ -80,7 +81,7 @@ def test_add_config_success(configuration, core_agent): assert config_fee['events'] == [ [signature.hex(), 20000, (('0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a', 2000), ('0x97e9fA3b2AeA5aa56376a5FB5Cbf153ae91b0660', 8000))], - [hex_32bytes, 100000, + [signature1.hex(), 100000, (('0xA904540818AC9c47f2321F97F1069B9d8746c6DB', 7000), ('0x316b2Fa7C8a2ab7E21110a4B3f58771C01A71344', 3000))] ] assert configuration.configAddresses(0) == accounts[0] @@ -127,6 +128,25 @@ def test_add_config_invalid_length(configuration, length): with brownie.reverts("MismatchParamLength: addConfig"): configuration.updateParam('addConfig', value_encode) +def test_add_config_events_length_error(configuration): + value = [ + to_bytes(hexstr=accounts[0].address), + [ + [ + [ + [to_bytes(hexstr=accounts[3].address), 7000], + [to_bytes(hexstr=accounts[4].address), 3000] + ], + 'btcExpired', + 100000 + ] + ], + 1000 + ] + value_encode = rlp.encode(value) + with brownie.reverts("invalid input length"): + configuration.updateParam('addConfig', value_encode) + def test_invalid_parameter_format(configuration): value = to_bytes(hexstr=accounts[0].address).hex() @@ -168,7 +188,7 @@ def test_contract_address_already_exists(configuration): [to_bytes(hexstr=accounts[3].address), 7000], [to_bytes(hexstr=accounts[4].address), 3000] ], - 'btcExpired', + Web3.keccak(text='btcExpired'), 100000 ] ], @@ -196,7 +216,7 @@ def test_events_length_zero(configuration): def test_events_length_exceeds_max(configuration, events_length): bytes_length = 64 event = [ - [[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 100000 + [[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 100000 ] events = [] for i in range(events_length): @@ -221,7 +241,7 @@ def test_ratio_exceeds_maximum(configuration): [[[to_bytes(hexstr=accounts[3].address), 4000], [to_bytes(hexstr=accounts[3].address), 2000], [to_bytes(hexstr=accounts[3].address), 2000], [to_bytes(hexstr=accounts[3].address), 1000], [to_bytes(hexstr=accounts[3].address), 500], [to_bytes(hexstr=accounts[3].address), 500]], - 'transfer', 100000] + Web3.keccak(text='transfer'), 100000] ], 1000 ] @@ -241,7 +261,7 @@ def test_ratio_must_equal_10000(configuration, percentages): [ [[[to_bytes(hexstr=accounts[3].address), percentage1], [to_bytes(hexstr=accounts[3].address), percentage2], [to_bytes(hexstr=accounts[3].address), percentage3]], - 'transfer', 100000] + Web3.keccak(text='transfer'), 100000] ], 1000 ] @@ -257,11 +277,12 @@ def test_ratio_must_equal_10000(configuration, percentages): @pytest.mark.parametrize("gas", [[0, 1000001], [1, 1000002], [2, 1000003], [3, 1000003]]) def test_gas_exceeds_maximum(configuration, gas): events = [ - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], 'transfer', + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], + Web3.keccak(text='transfer'), 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], ] events[gas[0]][-1] = gas[1] value = [ @@ -275,7 +296,7 @@ def test_gas_exceeds_maximum(configuration, gas): def test_rewards_invalid_format(configuration): - event1 = [[['test1', 10000]], 'transfer', 100000] + event1 = [[['test1', 10000]], Web3.keccak(text='transfer'), 100000] event2 = [[[to_bytes(hexstr=accounts[3].address), 'transfertransfertransfertransfertransfer']], 'transfer', 100000] value1 = [ to_bytes(hexstr=accounts[0].address), @@ -325,7 +346,7 @@ def test_add_config_multiple_times(configuration): [ [ [[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], - 'transfer', 100000 + Web3.keccak(text='transfer'), 100000 ] ], 1000 @@ -347,7 +368,7 @@ def test_add_config_rewards_empty(configuration): [ [ [], - 'transfer', 100000 + Web3.keccak(text='transfer'), 100000 ] ], 1000 @@ -465,15 +486,16 @@ def test_governance_after_limit_with_excess_events(configuration): padding_value = Web3.to_bytes(hexstr=padding_left(Web3.to_hex(maximum_reward), bytes_length)) configuration.updateParam('updateMaxEvents', padding_value) events = [ - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], 'transfer', + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], + Web3.keccak(text='transfer'), 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000] + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000] ] value = [ to_bytes(hexstr=accounts[0].address), @@ -490,7 +512,7 @@ def test_total_configurations_no_limit(configuration): padding_value = Web3.to_bytes(hexstr=padding_left(Web3.to_hex(maximum_reward), bytes_length)) configuration.updateParam('updateMaxEvents', padding_value) events = [ - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000] + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000] ] value = [ to_bytes(hexstr=accounts[0].address), @@ -730,7 +752,7 @@ def test_update_nonexistent_contract_address(configuration): [[[ [to_bytes(hexstr=accounts[3].address), 7000], [to_bytes(hexstr=accounts[4].address), 3000]], - 'btcExpired', + Web3.keccak(text='btcExpired'), 100000]], 1000 ] value_encode = rlp.encode(value) @@ -752,7 +774,7 @@ def test_update_events_zero_length_success(configuration): @pytest.mark.parametrize("events_length", [3, 4, 5]) def test_update_events_exceed_max_length(configuration, events_length): - event = [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 100000] + event = [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 100000] events = [] for i in range(events_length): events.append(event) @@ -804,7 +826,7 @@ def test_update_ratio_must_equal_10000(configuration, percentages): [ [[[to_bytes(hexstr=accounts[3].address), percentage1], [to_bytes(hexstr=accounts[3].address), percentage2], [to_bytes(hexstr=accounts[3].address), percentage3]], - 'transfer', 100000] + Web3.keccak(text='transfer'), 100000] ], 1000 ] @@ -819,11 +841,12 @@ def test_update_ratio_must_equal_10000(configuration, percentages): @pytest.mark.parametrize("gas", [[0, 1000001], [1, 1000002], [2, 1000003], [3, 1000003]]) def test_update_gas_exceeds_maximum(configuration, gas): events = [ - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], 'transfer', + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], + Web3.keccak(text='transfer'), 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], - [[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], + [[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000], ] events[gas[0]][-1] = gas[1] value = [ @@ -861,14 +884,14 @@ def test_update_events_reward_invalid_format(configuration): @pytest.mark.parametrize("is_multiple_data", [True, False]) @pytest.mark.parametrize("event", [ - [[[]], 'transfer', 100000], + [[[]], Web3.keccak(text='transfer'), 100000], [[[to_bytes(hexstr=ZERO_ADDRESS)]]], [[[10000]]], [[[to_bytes(hexstr=ZERO_ADDRESS), 10000]]], [[[to_bytes(hexstr=ZERO_ADDRESS), 10000]], 100000], [[[to_bytes(hexstr=ZERO_ADDRESS), 10000]], 'transfer']]) def test_update_config_governance_invalid_parameter(configuration, event, is_multiple_data): - events = [[[[to_bytes(hexstr=accounts[3].address), 10000]], 'transfer', 1000000]] + events = [[[[to_bytes(hexstr=accounts[3].address), 10000]], Web3.keccak(text='transfer'), 1000000]] if is_multiple_data: events.append(event) else: @@ -887,7 +910,8 @@ def test_update_config_multiple_times(configuration): _add_config(accounts[1]) value = [ to_bytes(hexstr=accounts[1].address), - [[[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], 'transfer', + [[[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], + Web3.keccak(text='transfer'), 100000] ], [] @@ -907,7 +931,8 @@ def test_update_config_multiple_times(configuration): def test_update_after_readding_deleted_config(configuration): value = [ to_bytes(hexstr=accounts[4].address), - [[[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], 'transfer', + [[[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], + Web3.keccak(text='transfer'), 100000] ], [] @@ -925,7 +950,8 @@ def test_update_after_readding_deleted_config(configuration): def test_add_config_function(configuration): function = [ - [[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], 'transfer', + [[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], + Web3.keccak(text='transfer'), 100000]] value = [ to_bytes(hexstr=accounts[0].address), @@ -936,7 +962,8 @@ def test_add_config_function(configuration): config_fee = configuration.getConfig(accounts[0]) assert len(config_fee['functions']) == 0 function = [ - [[[to_bytes(hexstr=accounts[2].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], 'transfer', + [[[to_bytes(hexstr=accounts[2].address), 6000], [to_bytes(hexstr=accounts[3].address), 4000]], + Web3.keccak(text='transfer'), 100000]] value[-1] = function configuration.updateParam('updateConfig', list2_elp_encode(value)) @@ -946,7 +973,8 @@ def test_add_config_function(configuration): def test_update_function_and_events(configuration): function = [ - [[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], 'transfer', + [[[to_bytes(hexstr=accounts[2].address), 5000], [to_bytes(hexstr=accounts[3].address), 5000]], + Web3.keccak(text='transfer'), 100000]] value = [ to_bytes(hexstr=accounts[0].address), diff --git a/tests/test_core_agent.py b/tests/test_core_agent.py index bbac455a1..e0f6e611b 100644 --- a/tests/test_core_agent.py +++ b/tests/test_core_agent.py @@ -68,29 +68,6 @@ def test_core_agent_init_can_only_run_once(core_agent): with brownie.reverts("the contract already init"): core_agent.init() - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_revert_if_not_called_by_only_pledge_agent(core_agent): - candidates = accounts[:3] - amounts = [1000, 2000, 3000] - realtime_amounts = [2000, 2000, 4000] - with brownie.reverts("the sender must be PledgeAgent contract"): - core_agent._initializeFromPledgeAgent(candidates, amounts, realtime_amounts) - - -@pytest.mark.skip(reason="the data migration part has been removed, skip it.") -def test_initialize_from_pledge_agent_success(core_agent): - update_system_contract_address(core_agent, pledge_agent=accounts[0]) - candidates = accounts[:3] - amounts = [1000, 2000, 3000] - realtime_amounts = [2000, 2000, 4000] - core_agent._initializeFromPledgeAgent(candidates, amounts, realtime_amounts) - for index, i in enumerate(candidates): - c = core_agent.candidateMap(i) - assert c['amount'] == amounts[index] - assert c['realtimeAmount'] == realtime_amounts[index] - - def test_distribute_reward_success(core_agent): validators = accounts[:3] staked_amounts = [3000, 5000, 6000] @@ -396,28 +373,6 @@ def test_delegate_transfer_reward_calculation(core_agent, stake_hub, set_candida # undelegateCoin -def test_cannot_undelegate_directly_if_staked_via_channel(core_agent, set_candidate, channel): - operators, consensuses = set_candidate - turn_round() - update_system_contract_address(core_agent, channel=accounts[0]) - core_agent.proxyDelegate(operators[0], accounts[0], 1, {"value": MIN_INIT_DELEGATE_VALUE}) - turn_round() - turn_round(consensuses) - update_system_contract_address(core_agent, channel=channel) - with brownie.reverts("InsufficientTokens: 0"): - core_agent.undelegateCoin(operators[0], MIN_INIT_DELEGATE_VALUE * 2) - with brownie.reverts("InsufficientTokens: 0"): - core_agent.undelegateCoin(operators[0], MIN_INIT_DELEGATE_VALUE) - with brownie.reverts("Undelegate zero coin"): - core_agent.undelegateCoin(operators[0], 0) - update_system_contract_address(core_agent, channel=channel) - delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - assert core_agent.candidateMap(operators[0])['realtimeAmount'] == MIN_INIT_DELEGATE_VALUE - core_agent.undelegateCoin(operators[0], MIN_INIT_DELEGATE_VALUE) - assert core_agent.candidateMap(operators[0])['realtimeAmount'] == 0 - assert core_agent.delegatorMap(accounts[0]) == [MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] - - def test_undelegate_amount_small(core_agent, validator_set): undelegate_value = MIN_INIT_DELEGATE_VALUE - 1 operator = accounts[1] @@ -435,8 +390,12 @@ def test_undelegate_cannot_be_zero(core_agent, validator_set): core_agent.delegateCoin(operator, {"value": MIN_INIT_DELEGATE_VALUE}) turn_round() assert consensus in validator_set.getValidators() - with brownie.reverts("Undelegate zero coin"): - core_agent.undelegateCoin(operator, 0) + tx = core_agent.undelegateCoin(operator, 0) + expect_event(tx, "undelegatedCoin", { + 'candidate': operator, + 'delegator': accounts[0], + 'amount': MIN_INIT_DELEGATE_VALUE + }) def test_undelegate_coin_success(core_agent): @@ -463,7 +422,7 @@ def test_undelegate_coin_failed_with_no_delegate(core_agent): agent = accounts[1] delegator = accounts[2] __candidate_register(agent) - with brownie.reverts("InsufficientTokens: 0"): + with brownie.reverts("no delegator information found"): core_agent.undelegateCoin(agent, required_coin_deposit, {'from': delegator}) @@ -1064,7 +1023,7 @@ def test_stake_transfer_and_cancel_in_same_round(core_agent, set_candidate): operators, consensuses = set_candidate core_agent.delegateCoin(operators[0], {'value': MIN_INIT_DELEGATE_VALUE * 2}) core_agent.transferCoin(operators[0], operators[1], MIN_INIT_DELEGATE_VALUE * 2, {'from': accounts[0]}) - undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + undelegate_coin_success(operators[1], accounts[0], 0) turn_round() tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) @@ -1228,6 +1187,7 @@ def test_acc_stake_amount_cross_round_success(core_agent, set_candidate, stake_h @pytest.mark.parametrize("round_count", [1, 2]) @pytest.mark.parametrize("tests", [ {'transfer': 500, 'undelagate': 0, 'amount': 500, 'stake_amount0': 2500, 'stake_amount1': 2500}, + {'transfer': 500, 'undelagate': 0, 'amount': 0, 'stake_amount0': 2500, 'stake_amount1': 2500}, {'transfer': 500, 'undelagate': 1, 'amount': 1000, 'stake_amount0': 2000, 'stake_amount1': 2000}, {'transfer': 500, 'undelagate': 2, 'amount': 1500, 'stake_amount0': 1500, 'stake_amount1': 1500}, ]) @@ -1259,6 +1219,8 @@ def test_check_acc_amount_after_cancel_stake_current_round(core_agent, validator 'expect_stake_amount': 3500}, {'delegate': 2000, 'transfer': 1000, 'undelagate': 1, 'amount': 2000, 'expect_reward': 13545 + 6772, 'expect_stake_amount': 2000}, + {'delegate': 2000, 'transfer': 1000, 'undelagate': 1, 'amount': 0, 'expect_reward': 13545 + 6772, + 'expect_stake_amount': 2000}, {'delegate': 3000, 'transfer': 1500, 'undelagate': 2, 'amount': 3000, 'expect_reward': 13545 * 2 // 3, 'expect_stake_amount': 2000} ]) @@ -1605,51 +1567,6 @@ def test_multi_round_coin_stake_success(core_agent, set_candidate, round): reward = core_agent.collectCoinRewardMock(operators[0], accounts[0]).return_value assert reward == (TOTAL_REWARD * 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE * 2) - -def test_move_data_success(core_agent, pledge_agent): - turn_round() - operators, consensuses = __register_candidates(accounts[2:5]) - pledge_agent.delegateCoinOld(operators[1], {"value": Web3.to_wei(100000, 'ether')}) - candidate = accounts[2] - delegator0 = accounts[0] - delegator1 = accounts[1] - staked_amount = MIN_INIT_DELEGATE_VALUE * 10 - transferred_amount = MIN_INIT_DELEGATE_VALUE * 5 - round_tag = 6 - real_amount = MIN_INIT_DELEGATE_VALUE * 20 - # scenario 1: Reward settlement is required - core_agent.moveData(candidate, delegator0, staked_amount, transferred_amount, round_tag, - {'from': pledge_agent, 'value': real_amount}) - # the transfer part of the reward has been settled, so it is cleared to 0 - __check_coin_delegator(candidate, delegator0, real_amount, real_amount, get_current_round(), 0) - assert __get_candidate_list_by_delegator(delegator0)[0] == candidate - assert core_agent.delegatorMap(delegator0) == [real_amount, 0] - # scenario 2: No reward settlement - staked_amount = MIN_INIT_DELEGATE_VALUE * 10 + 1 - transferred_amount = MIN_INIT_DELEGATE_VALUE * 5 + 1 - real_amount = MIN_INIT_DELEGATE_VALUE * 20 + 1 - - core_agent.moveData(candidate, delegator1, staked_amount, transferred_amount, get_current_round(), - {'from': pledge_agent, 'value': real_amount}) - __check_coin_delegator(candidate, delegator1, staked_amount, real_amount, get_current_round(), - transferred_amount) - assert __get_candidate_list_by_delegator(delegator1)[0] == candidate - assert core_agent.delegatorMap(delegator1) == [real_amount, 0] - - -def test_only_pledge_agent_can_call_move_data(core_agent, set_candidate): - operators, consensuses = set_candidate - turn_round() - with brownie.reverts("the sender must be PledgeAgent or Channel contracts"): - core_agent.moveData(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE, 0, 0) - - -def test_move_data_with_excessive_stake_amount_reverts(core_agent): - update_system_contract_address(core_agent, pledge_agent=accounts[0]) - with brownie.reverts("require stakedAmount <= realtimeAmount"): - core_agent.moveData(accounts[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE, 0) - - @pytest.mark.parametrize("operate", ['delegateCoin', 'undelegateCoin', 'transferCoin']) def test_only_pledge_agent_can_call_proxy(core_agent, validator_set, operate): delegate_amount = required_coin_deposit * 10 @@ -1660,7 +1577,7 @@ def test_only_pledge_agent_can_call_proxy(core_agent, validator_set, operate): core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[2], 'value': delegate_amount}) elif operate == 'undelegateCoin': with brownie.reverts(error): - core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, 0, {'from': accounts[2]}) + core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, {'from': accounts[2]}) else: with brownie.reverts(error): core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': accounts[2]}) @@ -1670,18 +1587,18 @@ def test_only_pledge_agent_can_call_proxy(core_agent, validator_set, operate): def test_successful_proxy_method_call(core_agent, validator_set, operate, pledge_agent): delegate_amount = required_coin_deposit * 10 operators, consensuses = __register_candidates(accounts[2:5]) - pledge_agent.delegateCoinOld(operators[1], {"value": Web3.to_wei(100000, 'ether')}) - tx = core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': pledge_agent, 'value': delegate_amount}) + update_system_contract_address(core_agent, pledge_agent=accounts[1]) + tx = core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[1], 'value': delegate_amount}) assert tx.events['delegatedCoin']['amount'] == delegate_amount coin_reward = TOTAL_REWARD turn_round() if operate == 'delegateCoin': - core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': pledge_agent, 'value': delegate_amount}) + core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[1], 'value': delegate_amount}) elif operate == 'undelegateCoin': - core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, 0, {'from': pledge_agent}) + core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, {'from': accounts[1]}) coin_reward = 0 else: - core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': pledge_agent}) + core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': accounts[1]}) turn_round(consensuses) tracker0 = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) @@ -1699,7 +1616,7 @@ def test_proxy_operation_with_insufficient_amount_reverts(core_agent, validator_ core_agent.proxyDelegate(operators[0], accounts[0], 0, {'from': accounts[1], 'value': delegate_amount}) elif operate == 'undelegateCoin': with brownie.reverts("undelegate amount is too small"): - core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, 0, {'from': accounts[1]}) + core_agent.proxyUnDelegate(operators[0], accounts[0], delegate_amount, {'from': accounts[1]}) else: with brownie.reverts("undelegate amount is too small"): core_agent.proxyTransfer(operators[0], operators[1], accounts[0], delegate_amount, {'from': accounts[1]}) diff --git a/tests/test_core_agent_unit.py b/tests/test_core_agent_unit.py index 23da04049..aee3e7b4c 100644 --- a/tests/test_core_agent_unit.py +++ b/tests/test_core_agent_unit.py @@ -136,7 +136,7 @@ def test_core_agent_undelegate_coin(core_agent, candidate_hub): 'value': required_coin_deposit}, {'status': 'success', 'candidate': accounts[1], 'value': required_coin_deposit, 'expect_cd': (0, required_coin_deposit * 2, 0, round_tag)}, - {'status': 'failed', 'err': 'InsufficientTokens: 0', 'candidate': accounts[1], + {'status': 'failed', 'err': 'Not enough staked tokens', 'candidate': accounts[1], 'value': required_coin_deposit * 2 + 1, 'turn_round': 1}, {'status': 'failed', 'err': 'remain amount is too small', 'candidate': accounts[1], 'value': required_coin_deposit * 2 - 1}, diff --git a/tests/test_delegate_btc.py b/tests/test_delegate_btc.py index e666f61ca..2667224ec 100644 --- a/tests/test_delegate_btc.py +++ b/tests/test_delegate_btc.py @@ -940,6 +940,8 @@ def test_btc_transaction_with_witness_as_output_address(btc_stake, set_candidate "000000000017a9144c35996fbf4026de7c8fe79c4320c248a10e4bf28702483045022100e32dd040238c19321407b7dfbba957e5988755779030dbcc52e6ae22a2a2088402202eeb497ae61aee9eba97cc4f5d34ba814c3ad1c0bf3286edaba05f044ab4bba401210386f359aa5a42d821370bf07a5ad86c1ff2d892662699103e462ae04d082d83ac00000000") lock_script = '041e28fd65b17576a914a808bc3c1ba547b0ba2df4abf1396f35c4d23b4f88ac' scrip_pubkey = 'a9144c35996fbf4026de7c8fe79c4320c248a10e4bf287' + with brownie.reverts("BitcoinHelper: invalid tx"): + tx = btc_stake.delegate(btc_tx, 200, [], 22, lock_script) btc_tx = remove_witness_data_from_raw_tx(btc_tx, scrip_pubkey) tx = btc_stake.delegate(btc_tx, 200, [], 22, lock_script) assert 'delegated' in tx.events diff --git a/tests/test_delegate_coin.py b/tests/test_delegate_coin.py index c05d1e5bd..5a99fcb11 100644 --- a/tests/test_delegate_coin.py +++ b/tests/test_delegate_coin.py @@ -563,7 +563,10 @@ def test_transfer_with_partial_undelegate_and_claimed_rewards(core_agent, valida delegate_coin_success(operators[1], accounts[2], delegate_amount) turn_round() transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount) - undelegate_coin_success(operators[0], accounts[1], undelegate_amount) + amount = undelegate_amount + if undelegate_type == 'all': + amount = 0 + undelegate_coin_success(operators[0], accounts[1], amount) transfer_coin_success(operators[1], operators[2], accounts[2], transfer_amount) turn_round(consensuses, round_count=1) tracker1 = get_tracker(accounts[1]) diff --git a/tests/test_dual_staking.py b/tests/test_dual_staking.py index 9a4e6e5b1..11f1e6d97 100644 --- a/tests/test_dual_staking.py +++ b/tests/test_dual_staking.py @@ -59,9 +59,9 @@ def set_block_reward(validator_set, candidate_hub, btc_light_client, btc_stake, BTC_REWARD_NO_POWER = TOTAL_REWARD * HardCap.BTC_HARD_CAP // (HardCap.SUM_HARD_CAP - HardCap.POWER_HARD_CAP) # The default staking time is 150 days set_block_time_stamp(150, LOCK_TIME) - tlp_rates, lp_rates = Discount().get_init_discount() - btc_stake.setInitTlpRates(*tlp_rates) - btc_agent.setInitLpRates(*lp_rates) + tlp_rates_keys, tlp_rates_values, lp_rates_keys, lp_rates_values = Discount().get_init_discount() + btc_stake.setInitTlpRates(tlp_rates_keys, tlp_rates_values) + btc_agent.setInitLpRates(lp_rates_keys, lp_rates_values) btc_agent.setIsActive(True) btc_agent.setAssetWeight(1) system_reward.setOperator(stake_hub.address) @@ -794,9 +794,9 @@ def test_dual_staking_without_duration_discount_success(btc_stake, stake_hub, se turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value) + tx_id = delegate_btc_success(operators[1], accounts[0], btc_value,LOCK_SCRIPT) turn_round(consensuses, round_count=2) current_round = get_current_round() if tests[0] in ['undelegate', 'transfer']: @@ -837,9 +837,9 @@ def test_staking_with_duration_discount_claim_reward_success(btc_stake, stake_hu stake_duration = 30 set_block_time_stamp(stake_duration, LOCK_TIME) time_stamp = LOCK_TIME - stake_duration * Utils.ROUND_INTERVAL - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value, block_timestamp=time_stamp) + delegate_btc_success(operators[1], accounts[0], btc_value,LOCK_SCRIPT,stake_duration=0,lock_time=time_stamp) turn_round(consensuses, round_count=2) tx = delegate_coin_success(operators[2], accounts[0], delegate_amount) assert tx.events['storedRewardBtcTx']['lockLengthRate'] == Utils.DENOMINATOR // 2 @@ -872,9 +872,9 @@ def test_dual_staking_claim_then_operate_success(btc_stake, stake_hub, core_agen turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value) + tx_id = delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) @@ -914,9 +914,9 @@ def test_dual_staking_multiple_rounds_operations_success(btc_stake, stake_hub, c turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) @@ -949,11 +949,11 @@ def test_dual_staking_uneven_with_transfer_and_cancel_rewards(btc_stake, stake_h turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 4 * 3) - mock_undelegate_coin_success(operators[2], accounts[0], delegate_amount // 4 * 2) + tx_id = delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) + transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 4 * 3) + undelegate_coin_success(operators[2], accounts[0], delegate_amount // 4 * 2) turn_round(consensuses, round_count=3) actual_reward = TOTAL_REWARD // 2 + TOTAL_REWARD * 4 + TOTAL_REWARD // 4 * 2 event_name = ['storedRewardBtcTx', 'storedCoinReward'] @@ -990,10 +990,10 @@ def test_claim_reward_clears_change_round(btc_stake, stake_hub, core_agent, set_ turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round(consensuses, round_count=3) - mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) + undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) turn_round(consensuses) undelegate_coin_success(operators[0], accounts[0], delegate_amount // 4) tracker = get_tracker(accounts[0]) @@ -1011,10 +1011,10 @@ def test_btc_transfer_triggers_dual_staking(btc_stake, stake_hub, core_agent, se turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round(consensuses, round_count=2) - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value) - mock_transfer_btc_success(tx_id, operators[0]) + tx_id = delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) + transfer_btc_success(tx_id, operators[0], accounts[0]) turn_round(consensuses) tx = run_stake_operation(tests, operators[0], accounts[0], delegate_amount, operators[2], tx_id, LOCK_SCRIPT, btc_value=btc_value) @@ -1037,9 +1037,9 @@ def test_claim_dual_staking_reward_after_btc_expiration(btc_stake, stake_hub, co btc_value = 100 set_last_round_tag(3) get_current_round() - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=3) get_current_round() tx = delegate_coin_success(operators[0], accounts[0], delegate_amount) @@ -1058,14 +1058,14 @@ def test_coin_staking_reward_settlement(set_candidate, pledge_agent, stake_hub): operators, consensuses = set_candidate turn_round() delegate_amount = 500000 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round(consensuses) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round(consensuses) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_coin_success(operators[0], accounts[1], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round(consensuses, round_count=3) delegate_coin_success(operators[2], accounts[0], delegate_amount) tracker = get_tracker(accounts[0]) @@ -1090,32 +1090,6 @@ def test_dual_staking_after_expired_btc(stake_hub, set_candidate): tx = stake_hub_claim_reward(accounts[0]) assert tx.events['claimedRewardBtcTx']['dualStakingRate'] == 10000 - -def test_stake_btc_then_move_data(stake_hub, btc_stake, core_agent, set_candidate): - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - stake_manager.set_lp_rates([[0, 1000], [1, 10000]]) - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - btc_value = 100000 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - old_turn_round(consensuses, round_count=6) - __init_hybrid_score_mock() - turn_round(consensuses) - mock_delegate_btc_success(operators[0], accounts[0], btc_value) - turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) - assert tx.events['storedRewardBtcTx']['dualStakingRate'] == 1000 - turn_round(consensuses, round_count=2) - old_claim_reward_success(operators, accounts[0]) - turn_round(consensuses, round_count=2) - tx = delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, events=True) - assert tx.events['storedRewardBtcTx'][0]['dualStakingRate'] == 10000 - - @pytest.mark.parametrize("tests", [ ['delegate', 'transfer', 4063 + 5416 * 2 // 3 // 2], ['transfer', 'undelegate', 4063 // 2 + 5416 * 2 // 3 // 10], @@ -1132,10 +1106,10 @@ def test_reconcile_diff_operations_in_current_round(stake_hub, btc_stake, core_a delegate_amount = 500000 btc_value = 100 for account in accounts[:2]: - mock_delegate_coin_success(operators[0], account, delegate_amount) + delegate_coin_success(operators[0], account, delegate_amount) turn_round(consensuses) - tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value * 2) - mock_delegate_btc_success(operators[0], accounts[1], btc_value) + tx_id = delegate_btc_success(operators[0], accounts[0], btc_value * 2, LOCK_SCRIPT) + delegate_btc_success(operators[0], accounts[1], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=3) for test in tests: run_stake_operation(test, operators[0], accounts[0], delegate_amount // 2, operators[2], tx_id, @@ -1164,18 +1138,21 @@ def test_claim_reward_across_different_changeRound(stake_hub, set_candidate, rou btc_value = 100 for account in accounts[:2]: for op in operators[:2]: - mock_delegate_coin_success(op, account, delegate_amount) + delegate_coin_success(op, account, delegate_amount) turn_round(consensuses, round_count=2) for index, account in enumerate(accounts[:2]): for op in operators[:2]: delegate_btc_value = btc_value if index == 0: delegate_btc_value = btc_value * 2 - mock_delegate_btc_success(op, account, delegate_btc_value) + delegate_btc_success(op, account, delegate_btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=round_reward[0]) tracker = get_tracker(accounts[0]) stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == round_reward[1] + actual_reward = round_reward[1] -2 + if round_reward[0] == 0: + actual_reward = round_reward[1] + assert tracker.delta() == actual_reward turn_round(consensuses) stake_hub_claim_reward(accounts[0]) account_reward = COIN_REWARD_NO_POWER // 2 * 2 + (BTC_REWARD_NO_POWER - 1) * 4 // 6 * 2 @@ -1194,11 +1171,11 @@ def test_change_round_diff_with_power_stake(stake_hub, set_candidate): btc_value = 100 for account in accounts[:2]: for op in operators[:2]: - mock_delegate_coin_success(op, account, delegate_amount) + delegate_coin_success(op, account, delegate_amount) turn_round(consensuses, round_count=2) for account in accounts[:2]: for op in operators[:2]: - mock_delegate_btc_success(op, account, btc_value) + delegate_btc_success(op, account, btc_value, LOCK_SCRIPT) delegate_power_success(operators[0], accounts[2]) delegate_power_success(operators[1], accounts[2]) turn_round(consensuses) @@ -1210,7 +1187,7 @@ def test_change_round_diff_with_power_stake(stake_hub, set_candidate): trackers = get_trackers(accounts[:3]) tx = stake_hub_claim_reward(accounts[:3]) # coin reward + btc reward - account_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) + account_reward = TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + (TOTAL_REWARD // 6 * 2) - 2 assert trackers[0].delta() == account_reward assert trackers[1].delta() == account_reward assert trackers[2].delta() == TOTAL_REWARD // 6 * 2 @@ -1292,11 +1269,11 @@ def test_validator_power_reward_on_upgrade(stake_hub, set_candidate): btc_value = 100 for account in accounts[:2]: for op in operators[:2]: - mock_delegate_coin_success(op, account, delegate_amount) + delegate_coin_success(op, account, delegate_amount) turn_round(consensuses, round_count=3) for account in accounts[:2]: for op in operators[:2]: - mock_delegate_btc_success(op, account, btc_value) + delegate_btc_success(op, account, btc_value, LOCK_SCRIPT) delegate_power_success(operators[0], accounts[2]) delegate_power_success(operators[1], accounts[2]) turn_round(consensuses) @@ -1342,16 +1319,16 @@ def test_proxy_claim_reward_after_current_round_operation(stake_hub, set_candida delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round(consensuses) delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) if claim: - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) else: stake_hub_claim_reward(accounts[0]) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) stake_manager.set_lp_rates([[4999, 1000], [5000, 20000], [5001, 1000]]) if claim: - tx = old_claim_reward_success(operators, accounts[0]) + tx = proxy_claim_reward_success(operators, accounts[0]) else: tx = stake_hub_claim_reward(accounts[0]) event_name = ['claimedCoinReward', 'claimedRewardBtcTx'] @@ -1403,9 +1380,9 @@ def test_calculateReward_success(btc_stake, stake_hub, core_agent, set_candidate turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) current_round = get_current_round() stake_hub.calculateReward(accounts[0]) @@ -1429,9 +1406,9 @@ def test_calculateReward_repeated(btc_stake, stake_hub, core_agent, set_candidat turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) current_round = get_current_round() delegate_coin_success(operators[0], accounts[0], delegate_amount) @@ -1716,9 +1693,9 @@ def test_delete_data_for_expired_btc_in_operation_round(btc_stake, btc_agent, st delegate_amount = 500000 btc_value = 100 set_last_round_tag(3) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round(consensuses) - tx_id = mock_delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_TIME) + tx_id = delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=4) stake_hub_calculate_reward(accounts[0], accounts[3]) assert btc_stake.receiptMap(tx_id)['round'] == 0 @@ -1732,11 +1709,9 @@ def test_calculateReward_generates_rewards(btc_stake, btc_agent, stake_hub, core delegate_amount = 500000 btc_amount = 1000 set_last_round_tag(3) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_power_success(operators[1], accounts[0]) - mock_delegate_btc_success(operators[2], accounts[0], btc_amount) - delegator_map = stake_hub.getDelegatorMap(accounts[0]) - assert delegator_map == [0, []] + delegate_btc_success(operators[2], accounts[0], btc_amount, LOCK_SCRIPT) stake_hub_calculate_reward(accounts[0], accounts[3]) delegator_map = stake_hub.getDelegatorMap(accounts[0]) assert delegator_map == [get_current_round(), [0, 0, 0]] @@ -1780,153 +1755,6 @@ def test_calculateReward_after_current_round_operation(btc_stake, btc_agent, sta assert trackers[2].delta() == 0 turn_round(consensuses) - -def test_manual_moveCoreData(pledge_agent, stake_hub, set_candidate): - stake_manager.set_lp_rates([[0, 1000], [4999, 1000], [5000, 10000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - old_turn_round() - delegate_amount = 500000 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_turn_round(consensuses) - old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, 0, 0) - pledge_agent.moveCOREData(operators[0], accounts[0], {'from': accounts[1]}) - pledge_agent.moveCOREData(operators[0], accounts[1], {'from': accounts[2]}) - turn_round(consensuses, round_count=2) - delegate_coin_success(operators[0], accounts[0], delegate_amount) - rewards = [TOTAL_REWARD - 1, 0, 0] - __check_stake_hub_delegator_map(accounts[0], {'changeRound': get_current_round(), 'rewards': rewards}) - trackers = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == sum(rewards) - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert trackers.delta() == TOTAL_REWARD // 2 - turn_round(consensuses) - - -def test_rewardMap_with_historical_rewards(btc_stake, core_agent, hash_power_agent, stake_hub, - set_candidate): - stake_manager.set_lp_rates([[0, 1000], [5000, 10000], [5001, 1000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - turn_round() - delegate_amount = 500000 - btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - tx_id = mock_delegate_btc_success(operators[0], accounts[0], btc_value) - delegate_power_success(operators[1], accounts[0]) - turn_round(consensuses, round_count=2) - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_transfer_btc_success(tx_id, operators[2]) - assert core_agent.rewardMap(accounts[0]) == [COIN_REWARD_NO_POWER, delegate_amount] - assert btc_stake.rewardMap(accounts[0]) == [BTC_REWARD_NO_POWER - 1, 0, btc_value] - assert hash_power_agent.rewardMap(accounts[0]) == [TOTAL_REWARD, 0] - delegate_coin_success(operators[1], accounts[0], delegate_amount) - assert stake_hub.getDelegatorMap(accounts[0])[1] == [COIN_REWARD_NO_POWER, TOTAL_REWARD, 0] - tracker = get_tracker(accounts[0]) - tx = stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == COIN_REWARD_NO_POWER + TOTAL_REWARD - - -def __move_btc_data(tx_ids): - return BTC_STAKE.moveDataMock(tx_ids) - - -def __init_hybrid_score_mock(): - tx = STAKE_HUB.initHybridScoreMock() - set_round_tag(get_current_round()) - return tx - - -def __get_candidate_list_by_delegator(delegator): - candidate_info = CORE_AGENT.getCandidateListByDelegator(delegator) - return candidate_info - - -def __get_stake_hub_delegator_map(delegator): - delegator_map = STAKE_HUB.getDelegatorMap(delegator) - return delegator_map - - -def __check_stake_hub_delegator_map(delegator, result): - delegator_map = STAKE_HUB.getDelegatorMap(delegator) - assert delegator_map[0] == result['changeRound'] - assert delegator_map[1] == result['rewards'] - return delegator_map - - -def __get_reward_map_info(delegate): - rewards, unclaimed_reward = BTC_STAKE.getRewardMap(delegate) - return rewards, unclaimed_reward - - -def __get_receipt_map_info(tx_id): - receipt_map = BTC_STAKE.receiptMap(tx_id) - return receipt_map - - -def __calc_stake_amount_discount(asset_reward, asset_stake_amount, coin_stake_amount, reward_pool=0, - system_reward=100000000, - asset_weight=1): - lp_rates = Discount.lp_rates - discount = Utils.DENOMINATOR - stake_rate = int(coin_stake_amount / (asset_stake_amount / asset_weight)) - for i in lp_rates: - if stake_rate >= i: - discount = lp_rates[i] - break - actual_account_btc_reward = asset_reward * discount // Utils.DENOMINATOR - if discount > Utils.DENOMINATOR: - actual_bonus = actual_account_btc_reward - asset_reward - if actual_bonus > reward_pool: - system_reward -= actual_bonus - reward_pool += actual_bonus - reward_pool -= actual_bonus - if asset_reward > actual_account_btc_reward: - reward_pool += asset_reward - actual_account_btc_reward - return actual_account_btc_reward, reward_pool - - -def __update_core_stake_amount(delegator, core_rate, asset_stake_amount): - core_acc_stake_amount = asset_stake_amount * core_rate - CORE_AGENT.setCoreRewardMap(delegator, 0, core_acc_stake_amount) - return core_acc_stake_amount - - -def __calc_btc_deducted_stake_duration_reward(validator_reward, duration, tlp_rates=None): - duration = duration * Utils.ROUND_INTERVAL - if tlp_rates is None: - tlp_rates = Discount.tlp_rates - stake_duration = Utils.DENOMINATOR - if len(tlp_rates) == 1: - for t in tlp_rates: - stake_duration = tlp_rates[t] - for i in tlp_rates: - time_stamp = i * Utils.MONTH_TIMESTAMP - if duration >= time_stamp: - stake_duration = tlp_rates[i] - break - if validator_reward is None: - validator_reward = BLOCK_REWARD // 2 - btc_reward_claimed = validator_reward * stake_duration // Utils.DENOMINATOR - unclaim_amount = validator_reward - btc_reward_claimed - return btc_reward_claimed, unclaim_amount - - -def stake_hub_calculate_reward(account, sender=None): - if sender is None: - sender = accounts[0] - tx = STAKE_HUB.calculateReward(account, {'from': sender}) - return tx - - - def test_dual_staking_various_operations_flow(btc_stake, stake_hub, core_agent, set_candidate): stake_manager.set_lp_rates([[0, 1000], [2500, 5000], [5000, 10000], [10000, 15000]]) @@ -2138,36 +1966,6 @@ def test_dual_staking_with_slashing_current_round(btc_stake, stake_hub, core_age stake_hub_claim_reward(accounts[0]) -def test_dual_staking_legacy_contract_move_flow(pledge_agent, stake_hub, core_agent, set_candidate): - - stake_manager.set_lp_rates([[0, 1000], [1, 2000], [4000, 10000], [10000, 25000]]) - stake_manager.set_is_stake_hub_active(True) - stake_manager.set_tlp_rates() - operators, consensuses = set_candidate - - old_turn_round() - delegate_amount = 400000 - btc_value = 100 - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[2], delegate_amount) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - turn_round(consensuses) - delegate_btc_success(operators[0], accounts[0], btc_value, LOCK_SCRIPT, stake_duration=YEAR) - delegate_btc_success(operators[0], accounts[2], btc_value, LOCK_SCRIPT, stake_duration=YEAR) - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == BTC_REWARD_NO_POWER // 2 * 1000 // 10000 - turn_round(consensuses) - pledge_agent.moveCOREData(operators[0], accounts[0]) - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses) - tx = stake_hub_claim_reward(accounts[0]) - assert tx.events['claimedReward']['amounts'][2] == (BTC_REWARD_NO_POWER - 1) // 2 - - - def test_dual_staking_adjustment_and_flow(btc_stake, btc_agent, stake_hub, core_agent, set_candidate): stake_manager.set_lp_rates([[0, 1000], [4000, 10000], [4001, 1000]]) @@ -2248,3 +2046,88 @@ def test_dual_staking_with_current_round_power_reward(btc_stake, btc_agent, stak tx = stake_hub_claim_reward(accounts[0]) assert tx.events['claimedReward']['amounts'] == [TOTAL_REWARD // 2, TOTAL_REWARD // 2 // 3, TOTAL_REWARD // 2 // 3 * 2] + + + + + +def __get_candidate_list_by_delegator(delegator): + candidate_info = CORE_AGENT.getCandidateListByDelegator(delegator) + return candidate_info + + +def __get_stake_hub_delegator_map(delegator): + delegator_map = STAKE_HUB.getDelegatorMap(delegator) + return delegator_map + + +def __check_stake_hub_delegator_map(delegator, result): + delegator_map = STAKE_HUB.getDelegatorMap(delegator) + assert delegator_map[0] == result['changeRound'] + assert delegator_map[1] == result['rewards'] + return delegator_map + + +def __get_reward_map_info(delegate): + rewards, unclaimed_reward = BTC_STAKE.getRewardMap(delegate) + return rewards, unclaimed_reward + + +def __get_receipt_map_info(tx_id): + receipt_map = BTC_STAKE.receiptMap(tx_id) + return receipt_map + + +def __calc_stake_amount_discount(asset_reward, asset_stake_amount, coin_stake_amount, reward_pool=0, + system_reward=100000000, + asset_weight=1): + lp_rates = Discount.lp_rates + discount = Utils.DENOMINATOR + stake_rate = int(coin_stake_amount / (asset_stake_amount / asset_weight)) + for i in lp_rates: + if stake_rate >= i: + discount = lp_rates[i] + break + actual_account_btc_reward = asset_reward * discount // Utils.DENOMINATOR + if discount > Utils.DENOMINATOR: + actual_bonus = actual_account_btc_reward - asset_reward + if actual_bonus > reward_pool: + system_reward -= actual_bonus + reward_pool += actual_bonus + reward_pool -= actual_bonus + if asset_reward > actual_account_btc_reward: + reward_pool += asset_reward - actual_account_btc_reward + return actual_account_btc_reward, reward_pool + + +def __update_core_stake_amount(delegator, core_rate, asset_stake_amount): + core_acc_stake_amount = asset_stake_amount * core_rate + CORE_AGENT.setCoreRewardMap(delegator, 0, core_acc_stake_amount) + return core_acc_stake_amount + + +def __calc_btc_deducted_stake_duration_reward(validator_reward, duration, tlp_rates=None): + duration = duration * Utils.ROUND_INTERVAL + if tlp_rates is None: + tlp_rates = Discount.tlp_rates + stake_duration = Utils.DENOMINATOR + if len(tlp_rates) == 1: + for t in tlp_rates: + stake_duration = tlp_rates[t] + for i in tlp_rates: + time_stamp = i * Utils.MONTH_TIMESTAMP + if duration >= time_stamp: + stake_duration = tlp_rates[i] + break + if validator_reward is None: + validator_reward = BLOCK_REWARD // 2 + btc_reward_claimed = validator_reward * stake_duration // Utils.DENOMINATOR + unclaim_amount = validator_reward - btc_reward_claimed + return btc_reward_claimed, unclaim_amount + + +def stake_hub_calculate_reward(account, sender=None): + if sender is None: + sender = accounts[0] + tx = STAKE_HUB.calculateReward(account, {'from': sender}) + return tx diff --git a/tests/test_gov_hub.py b/tests/test_gov_hub.py index bc8549aed..73760be30 100644 --- a/tests/test_gov_hub.py +++ b/tests/test_gov_hub.py @@ -594,21 +594,6 @@ def test_execute_early_then_propose(gov_hub, pledge_agent, is_pre_exec): chain.mine(gov_hub.votingPeriod() * 2) gov_hub.execute(1) assert pledge_agent.btcFactor() == 0 - pledge_agent.setBtcFactor(20) - gov_hub.propose( - [pledge_agent.address], - [0], - ["updateParam(string,bytes)"], - [encode(['string', 'bytes'], ['clearDeprecatedMembers', padding_value])], - ['pledgeAgent clearDeprecatedMembers'] - ) - chain.mine(1) - for member in gov_hub.getMembers()[:3]: - gov_hub.castVote(2, True, {'from': member}) - if is_pre_exec is False: - chain.mine(gov_hub.votingPeriod() * 2) - gov_hub.execute(2) - assert pledge_agent.btcFactor() == 0 @pytest.mark.parametrize("is_pre_exec", [True, False]) diff --git a/tests/test_pledge_agent.py b/tests/test_pledge_agent.py index e5b701d57..652f95dc1 100644 --- a/tests/test_pledge_agent.py +++ b/tests/test_pledge_agent.py @@ -68,1990 +68,130 @@ def test_reinit(pledge_agent): pledge_agent.init() -@pytest.mark.parametrize("store_old_data", [True, False]) -def test_delegate_coin(pledge_agent, set_candidate, store_old_data: bool): - operators, consensuses = set_candidate - if store_old_data: - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - tx = pledge_agent.delegateCoin(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - assert 'delegatedCoin' in tx.events - tx = pledge_agent.delegateCoin(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - assert 'delegatedCoin' in tx.events - else: - tx = pledge_agent.delegateCoin(operators[0], {"value": web3.to_wei(1, 'ether')}) - assert 'delegatedCoin' in tx.events - - -@pytest.mark.parametrize("operate", ['delegate', 'undelegate', 'transfer', 'claim']) -def test_reentry_stake_hub_claim(pledge_agent, stake_hub, set_candidate, validator_set, operate): - operators, consensuses = set_candidate - reentry_ = ClaimRewardReentry.deploy(pledge_agent.address, stake_hub, {'from': accounts[0]}) - accounts[2].transfer(reentry_, ONE_ETHER) - accounts[2].transfer(pledge_agent, ONE_ETHER) - accounts[2].transfer(stake_hub, ONE_ETHER) - turn_round(consensuses, round_count=2) - delegate_coin_success(operators[0], reentry_, MIN_INIT_DELEGATE_VALUE) - pledge_agent.setRewardMap(reentry_, TOTAL_REWARD) - if operate == 'delegate': - tx = reentry_.delegateCoin(operators[0], {'value': MIN_INIT_DELEGATE_VALUE}) - elif operate == 'undelegate': - tx = reentry_.undelegateCoin(operators[0]) - elif operate == 'transfer': - tx = reentry_.transferCoin(operators[0], operators[1]) - else: - tx = reentry_.claimReward([operators[0]]) - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD - assert len(tx.events['claimedReward']) == 1 - - -@pytest.mark.parametrize("operate", ['delegate', 'undelegate', 'transfer', 'claim']) -def test_reentry_pledge_agent_claim(pledge_agent, stake_hub, set_candidate, validator_set, operate): - operators, consensuses = set_candidate - reentry_ = OldClaimRewardReentry.deploy(pledge_agent.address, stake_hub, {'from': accounts[0]}) - accounts[2].transfer(reentry_, ONE_ETHER) - accounts[2].transfer(pledge_agent, ONE_ETHER) - old_delegate_coin_success(operators[0], reentry_, MIN_INIT_DELEGATE_VALUE) - old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - reentry_.setAgents(operators) - after = reentry_.balance() - amount = 0 - if operate == 'delegate': - tx = reentry_.delegateCoin(operators[0], {'value': MIN_INIT_DELEGATE_VALUE}) - assert tx.events['proxyDelegate']['success'] is False - amount = MIN_INIT_DELEGATE_VALUE - elif operate == 'undelegate': - tx = reentry_.undelegateCoin(operators[0]) - assert tx.events['proxyUndelegate']['success'] is False - elif operate == 'transfer': - tx = reentry_.transferCoin(operators[0], operators[1]) - assert len(tx.events) == 0 - else: - tx = reentry_.claimReward([operators[0]]) - assert len(tx.events) == 0 - assert reentry_.balance() == after + amount - - -@pytest.mark.parametrize("operate", ['delegate', 'undelegate', 'transfer']) -def test_auto_issue_historical_rewards(pledge_agent, set_candidate, core_agent, operate): - old_turn_round() - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - if operate == 'delegate': - tx = old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE, False) - elif operate == 'undelegate': - tx = old_undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE, False) - else: - tx = old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE, False) - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD - - -@pytest.mark.parametrize("is_validator", [True, False]) -@pytest.mark.parametrize("partial", [True, False]) -def test_undelegate_coin(pledge_agent, candidate_hub, is_validator: bool, partial: bool): - operators = accounts[1:3] - consensus = [] - for operator in operators: - consensus.append(register_candidate(operator=operator)) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE * 2}) - __old_turn_round() - __old_turn_round(consensus) - __init_hybrid_score_mock() - turn_round(round_count=2) - - undelegate_amount = MIN_INIT_DELEGATE_VALUE if partial else 0 - if is_validator: - pledge_agent.undelegateCoin(operators[0], undelegate_amount) - if partial: - tx = pledge_agent.undelegateCoin(operators[0], undelegate_amount) - assert 'undelegatedCoin' in tx.events - else: - candidate_hub.refuseDelegate({'from': operators[0]}) - turn_round() - candidate_hub.unregister({'from': operators[0]}) - turn_round() - pledge_agent.undelegateCoin(operators[0], undelegate_amount) - if partial: - tx = pledge_agent.undelegateCoin(operators[0], undelegate_amount) - assert 'undelegatedCoin' in tx.events - - -@pytest.mark.parametrize("is_validator", [True, False]) -@pytest.mark.parametrize("partial", [True, False]) -def test_transfer_coin(pledge_agent, candidate_hub, is_validator: bool, partial: bool): - operators = accounts[1:3] - consensus = [] - for operator in operators: - consensus.append(register_candidate(operator=operator)) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE * 2}) - __old_turn_round() - __old_turn_round(consensus) - __init_hybrid_score_mock() - turn_round(round_count=2) - - transfer_amount = MIN_INIT_DELEGATE_VALUE if partial else 0 - if is_validator: - tx = pledge_agent.transferCoin(operators[0], operators[1], transfer_amount) - assert 'transferredCoin' in tx.events - - if partial: - tx = pledge_agent.transferCoin(operators[0], operators[1], transfer_amount) - assert 'transferredCoin' in tx.events - else: - candidate_hub.refuseDelegate({'from': operators[0]}) - turn_round() - candidate_hub.unregister({'from': operators[0]}) - turn_round() - tx = pledge_agent.transferCoin(operators[0], operators[1], transfer_amount) - assert 'transferredCoin' in tx.events - - if partial: - tx = pledge_agent.transferCoin(operators[0], operators[1], transfer_amount) - assert 'transferredCoin' in tx.events - - -@pytest.mark.parametrize("agents_type", ["empty", "all", "partial", "none"]) -def test_claim_reward(pledge_agent, candidate_hub, agents_type: str): - operators = accounts[1:4] - consensus = [] - for operator in operators: - consensus.append(register_candidate(operator=operator)) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - __old_turn_round(consensus) - __init_hybrid_score_mock() - turn_round(round_count=2) - if agents_type == "empty": - tx = pledge_agent.claimReward([]) - assert 'claimedReward' not in tx.events - elif agents_type == "all": - tx = pledge_agent.claimReward(operators) - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD * 3 - elif agents_type == "none": - tx = pledge_agent.claimReward([random_address()]) - assert 'claimedReward' not in tx.events - else: - tx = pledge_agent.claimReward(operators[:2] + [random_address()]) - assert tx.events['claimedReward']['amount'] == TOTAL_REWARD * 2 - - -def test_claim_reward_success(btc_agent, pledge_agent, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[0]) - actual_reward = TOTAL_REWARD - pledge_agent.claimReward(operators) - assert tracker.delta() == actual_reward - FEE - - -def test_claim_reward_validator_address_empty(btc_agent, pledge_agent, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) - delegate_btc_success(operators[2], accounts[0], BTC_VALUE, LOCK_SCRIPT) - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[0]) - actual_reward = TOTAL_REWARD * 3 - pledge_agent.claimReward([]) - assert tracker.delta() == actual_reward - - -def test_claim_validator_reward_individually(btc_agent, pledge_agent, set_candidate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - undelegate_amount = delegate_amount // 2 - operators, consensuses = set_candidate - __old_turn_round() - for op in operators[:2]: - old_delegate_coin_success(op, accounts[0], delegate_amount) - __old_turn_round(consensuses) - old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) - old_transfer_coin_success(operators[0], operators[2], accounts[0], undelegate_amount) - __init_hybrid_score_mock() - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - old_claim_reward_success(operators[:1], accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 - - -@pytest.mark.parametrize("agents_type", ["empty", "all", "partial", "none"]) -def test_calculate_reward(pledge_agent, candidate_hub, agents_type: str): - operators = accounts[1:4] - consensus = [] - for operator in operators: - consensus.append(register_candidate(operator=operator)) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - __old_turn_round(consensus) - __init_hybrid_score_mock() - turn_round(round_count=2) - actual_reward = 0 - calc_reward = 0 - if agents_type == "empty": - calc_reward = pledge_agent.calculateReward([], accounts[0]) - elif agents_type == "all": - calc_reward = pledge_agent.calculateReward(operators, accounts[0]) - actual_reward = TOTAL_REWARD * 3 - elif agents_type == "none": - calc_reward = pledge_agent.calculateReward([random_address()], accounts[0]) - elif agents_type == "partial": - calc_reward = pledge_agent.calculateReward(operators[:2] + [random_address()], accounts[0]) - actual_reward = TOTAL_REWARD * 2 - assert calc_reward.return_value == actual_reward - - -@pytest.mark.parametrize("operate", [ - ['undelegate', 'delegate'], - ['delegate', 'undelegate'], - ['delegate', 'transfer'], - ['transfer', 'undelegate'], - ['delegate', 'delegate', 'transfer'], - ['delegate', 'delegate', 'undelegate'], - ['undelegate', 'transfer', 'delegate'], - ['undelegate', 'undelegate', 'delegate'], - ["delegate", "undelegate", "transfer"], - ['transfer', 'undelegate', 'delegate'], - ['undelegate', 'transfer', 'delegate'], - ['delegate', 'delegate', 'undelegate', 'undelegate'], - ['undelegate', 'delegate', 'transfer', 'transfer'], - ['transfer', 'transfer', 'delegate', 'undelegate'] -]) -def test_calculate_reward_withdraw_transfer_reward(pledge_agent, set_candidate, operate): - operators, consensus = set_candidate - for index, op in enumerate(operators): - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - __old_turn_round() - total_undelegate_amount = 0 - delegate_count = [] - for index, o in enumerate(operate): - if o == 'delegate': - tx = old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - assert 'delegatedCoinOld' in tx.events - delegate_count.append(index) - elif o == 'undelegate': - if len(delegate_count) == 0: - total_undelegate_amount += MIN_INIT_DELEGATE_VALUE - else: - delegate_count.pop() - tx = old_undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - assert 'undelegatedCoinOld' in tx.events - elif o == 'transfer': - tx = old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - assert 'transferredCoinOld' in tx.events - __old_turn_round(consensus) - __init_hybrid_score_mock() - debt_reward = (TOTAL_REWARD * total_undelegate_amount // (MIN_INIT_DELEGATE_VALUE * 2)) - calc_reward = pledge_agent.calculateReward(operators, accounts[0]) - assert calc_reward.return_value == TOTAL_REWARD * 3 - debt_reward - - -def test_claim_reward_after_calculate_reward(pledge_agent, candidate_hub): - operators = accounts[1:4] - consensus = [] - for operator in operators: - consensus.append(register_candidate(operator=operator)) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - __old_turn_round(consensus) - __init_hybrid_score_mock() - calc_reward = pledge_agent.calculateReward(operators, accounts[0]) - actual_reward = TOTAL_REWARD * 3 - assert calc_reward.return_value == actual_reward - tracker0 = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) - assert tracker0.delta() == actual_reward - __check_delegate_info(operators[0], accounts[0], { - 'stakedAmount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE - }) - - -def test_stake_new_then_move_core_data(pledge_agent, validator_set, candidate_hub, set_candidate): - operators, consensuses = set_candidate - core_value = 10000 - set_round_tag(LOCK_TIME // Utils.ROUND_INTERVAL - 10) - old_delegate_coin_success(operators[0], accounts[0], core_value) - old_delegate_coin_success(operators[0], accounts[1], core_value) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - delegate_coin_success(operators[0], accounts[0], core_value) - tracker = get_tracker(accounts[0]) - tx = old_delegate_coin_success(operators[0], accounts[0], core_value, False) - assert tracker.delta() == 0 - (core_value - TOTAL_REWARD // 2) - expect_event(tx, 'claimedReward', { - 'amount': TOTAL_REWARD // 2 - }) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 - turn_round(consensuses) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "coin": [set_delegate(accounts[0], core_value * 3), set_delegate(accounts[1], core_value)] - }], TOTAL_REWARD) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == account_rewards[accounts[0]] - old_claim_reward_success(operators, accounts[0]) - - -def test_data_migration_with_cancelled_validator_registration(pledge_agent, validator_set, candidate_hub, - set_candidate): - operators, consensuses = set_candidate - for i in range(2): - for op in operators[:3]: - old_delegate_coin_success(op, accounts[i], MIN_INIT_DELEGATE_VALUE) - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - assert len(validator_set.getValidators()) == 2 - __init_hybrid_score_mock() - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': 0 - }) - for op in operators[1:3]: - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE * 2, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - - -def test_data_migration_for_queued_validators(pledge_agent, validator_set, candidate_hub, - set_candidate): - candidate_hub.setValidatorCount(1) - operators, consensuses = set_candidate - for i in range(2): - for op in operators[:3]: - old_delegate_coin_success(op, accounts[i], MIN_INIT_DELEGATE_VALUE) - delegate_power_success(op, accounts[i], MIN_INIT_DELEGATE_VALUE) - __old_turn_round() - assert consensuses[1] in validator_set.getValidators() - __init_hybrid_score_mock() - for op in operators[:3]: - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE * 2, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - turn_round(consensuses) - - -def test_data_migration_for_validators_in_multiple_states(pledge_agent, validator_set, slash_indicator, candidate_hub): - candidate_hub.setValidatorCount(15) - operators = [] - consensuses = [] - for operator in accounts[5:20]: - operators.append(operator) - consensuses.append(register_candidate(operator=operator)) - for op in operators: - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE) - candidate_hub.unregister({'from': operators[0]}) - candidate_hub.refuseDelegate({'from': operators[1]}) - __old_turn_round() - felony_threshold = slash_indicator.felonyThreshold() - for _ in range(felony_threshold): - slash_indicator.slash(consensuses[2]) - __old_turn_round(consensuses, round_count=2) - candidate_hub.refuseDelegate({'from': operators[3]}) - for c in operators[:2]: - assert c not in validator_set.getValidatorOps() - assert operators[3] in validator_set.getValidatorOps() - __init_hybrid_score_mock() - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': 0 - }) - __check_candidate_map_info(operators[1], { - 'amount': 0, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE - }) - for op in operators[2:]: - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE - }) - - -def test_move_candidate_data(pledge_agent, core_agent, btc_stake, btc_agent, candidate_hub): - operator = accounts[1] - consensus = register_candidate(operator=operator) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - assert operator in candidate_hub.getCandidates() - __old_turn_round([consensus]) - pledge_agent.moveCandidateData([operator]) - agent = pledge_agent.agentsMap(operator) - assert agent[-1] is True - - candidate_in_core_agent = core_agent.candidateMap(operator) - assert candidate_in_core_agent[0] == MIN_INIT_DELEGATE_VALUE - assert candidate_in_core_agent[1] == MIN_INIT_DELEGATE_VALUE - -def test_validator_cancel_registration_in_second_round(pledge_agent, set_candidate, validator_set, slash_indicator, - candidate_hub): - candidate_hub.setValidatorCount(1) - operators, consensuses = set_candidate - for op in operators: - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE) - __old_turn_round() - assert operators[1] in validator_set.getValidatorOps() - candidate_hub.refuseDelegate({'from': operators[0]}) - __old_turn_round() - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - __init_hybrid_score_mock() - pledge_agent.moveCandidateData([operators[0]]) - turn_round(consensuses) - __check_candidate_map_info(operators[0], { - 'amount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE - }) - - -def test_move_cancelled_validator_candidate_data(pledge_agent, validator_set, candidate_hub, - set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - for op in operators[:3]: - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - assert len(validator_set.getValidators()) == 2 - __init_hybrid_score_mock() - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': 0 - }) - pledge_agent.moveCandidateData([operators[0]]) - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - turn_round(consensuses, round_count=4) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - - -def test_deregister_multiple_rounds_then_move_candidate(pledge_agent, validator_set, candidate_hub, - set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - for op in operators[:3]: - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - candidate_hub.unregister({'from': operators[0]}) - __old_turn_round() - __old_turn_round(consensuses, round_count=2) - assert len(validator_set.getValidators()) == 2 - __init_hybrid_score_mock() - pledge_agent.moveCandidateData([operators[0]]) - __check_candidate_map_info(operators[0], { - 'amount': 0, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - consensuses.append(register_candidate(operator=operators[0])) - old_claim_reward_success([operators[0]], accounts[0]) - turn_round(consensuses, round_count=4) - assert len(validator_set.getValidators()) == 3 - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 * 3 + 1 - turn_round(consensuses) - - -def test_single_stake_data_move_success(pledge_agent, set_candidate): - operators, consensuses = set_candidate - for index, op in enumerate(operators): - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE + index) - __old_turn_round() - pledge_agent.moveCandidateData(operators) - for index, op in enumerate(operators): - agent_map = pledge_agent.agentsMap(op) - assert agent_map['moved'] is True - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE + index, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE + index - }) - - -def test_move_candidate_success(pledge_agent, set_candidate): - operators, consensuses = set_candidate - for index, op in enumerate(operators): - old_delegate_coin_success(op, accounts[0], MIN_INIT_DELEGATE_VALUE + index) - __old_turn_round() - __old_turn_round(consensuses) - pledge_agent.moveCandidateData(operators) - for index, op in enumerate(operators): - agent_map = pledge_agent.agentsMap(op) - assert agent_map['moved'] is True - __check_candidate_map_info(op, { - 'amount': MIN_INIT_DELEGATE_VALUE + index, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE + index - }) - - -def test_move_core_data(pledge_agent, core_agent): - operator = accounts[1] - operator2 = accounts[2] - consensus = register_candidate(operator=operator) - register_candidate(operator=operator2) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE * 2}) - pledge_agent.delegateCoinOld(operator2, {"value": MIN_INIT_DELEGATE_VALUE}) - __old_turn_round() - __old_turn_round([consensus]) - tracker = get_tracker(accounts[0]) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - pledge_agent.transferCoinOld(operator, operator2, MIN_INIT_DELEGATE_VALUE * 2) - assert tracker.delta() == TOTAL_REWARD - MIN_INIT_DELEGATE_VALUE - __init_hybrid_score_mock() - turn_round([consensus]) - turn_round([consensus]) - pledge_agent.moveCOREData(operator, accounts[0]) - old_claim_reward_success([operator], accounts[0]) - assert tracker.delta() == TOTAL_REWARD * 2 - delegator_info_in_core_agent = core_agent.getDelegator(operator, accounts[0]) - _staked_amount, _realtime_amount, _transferred_amount, changeRound = delegator_info_in_core_agent - assert _staked_amount == MIN_INIT_DELEGATE_VALUE - assert _realtime_amount == MIN_INIT_DELEGATE_VALUE - assert _transferred_amount == 0 - assert changeRound == get_current_round() - - -@pytest.mark.parametrize("round", [0, 1, 2, 3]) -def test_move_core_success(pledge_agent, core_agent, set_candidate, round): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - old_delegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - round_tag = 9 - set_round_tag(round_tag) - turn_round(consensuses, round_count=round) - if round == 0: - change_round = get_current_round() - else: - change_round = get_current_round() - tx = pledge_agent.moveCOREData(operators[0], accounts[0]) - if round > 0: - expect_event(tx, 'storedReward', { - 'candidate': operators[0], - 'delegator': accounts[0], - 'reward': TOTAL_REWARD * round, - }) - else: - assert 'storedReward' not in tx.events - __check_delegate_info(operators[0], accounts[0], { - 'stakedAmount': MIN_INIT_DELEGATE_VALUE * 2, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2, - 'changeRound': change_round, - 'transferredAmount': 0, - }) - reward_map = __get_reward_map_info(accounts[0]) - assert reward_map == [TOTAL_REWARD * round, 0] - __check_old_delegate_info(operators[0], accounts[0], { - 'changeRound': 0 - }) - - -def test_move_core_success_after_staking(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - set_round_tag(get_current_round()) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - turn_round(consensuses, round_count=4) - tx = pledge_agent.moveCOREData(operators[0], accounts[0]) - expect_event(tx, 'storedReward', { - 'candidate': operators[0], - 'delegator': accounts[0], - 'reward': TOTAL_REWARD + TOTAL_REWARD // 2 - }) - expect_event(tx, 'storedReward', { - 'candidate': operators[0], - 'delegator': accounts[0], - 'reward': TOTAL_REWARD * 2 + TOTAL_REWARD // 2 - }, idx=1) - - -def test_repeat_move_core_data(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - old_delegate_coin_success(operators[1], accounts[1], MIN_INIT_DELEGATE_VALUE * 2) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - for i in range(3): - pledge_agent.moveCOREData(operators[0], accounts[0]) - __check_delegate_info(operators[0], accounts[0], { - 'stakedAmount': MIN_INIT_DELEGATE_VALUE * 2, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2, - 'changeRound': core_agent.roundTag(), - 'transferredAmount': 0, - }) - tracker = get_tracker(accounts[0]) - pledge_agent.claimReward(operators) - assert tracker.delta() == TOTAL_REWARD - turn_round(consensuses) - pledge_agent.moveCOREData(operators[0], accounts[0]) - pledge_agent.claimReward([]) - assert tracker.delta() == TOTAL_REWARD - - -def test_move_core_data_with_reward(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0]) - __check_delegate_info(operators[0], accounts[0], { - 'stakedAmount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE, - 'changeRound': core_agent.roundTag(), - 'transferredAmount': 0, - }) - tracker = get_tracker(accounts[0]) - pledge_agent.claimReward([operators[0]]) - assert tracker.delta() == TOTAL_REWARD - turn_round(consensuses) - candidate_list = core_agent.getCandidateListByDelegator(accounts[0]) - assert len(candidate_list) == 1 - pledge_agent.moveCOREData(operators[1], accounts[0]) - __check_delegate_info(operators[1], accounts[0], { - 'stakedAmount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE, - 'changeRound': get_current_round(), - 'transferredAmount': 0, - }) - - -def test_transfer_and_undelegate_before_upgrade_round(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 12) - old_delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE * 12) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) - old_transfer_coin_success(operators[0], operators[2], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) - old_undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) - old_undelegate_coin_success(operators[2], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[2], accounts[0]) - pledge_agent.moveCOREData(operators[0], accounts[0]) - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - pledge_agent.claimReward(operators) - assert tracker.delta() == TOTAL_REWARD // 2 - turn_round(consensuses) - pledge_agent.claimReward(operators) - assert tracker.delta() == TOTAL_REWARD // 3 - - -def test_cancel_move_data_after_transfer(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - old_undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0]) - __check_delegate_info(operators[0], accounts[0], { - 'stakedAmount': MIN_INIT_DELEGATE_VALUE, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE, - 'changeRound': core_agent.roundTag(), - 'transferredAmount': 0, - }) - tracker = get_tracker(accounts[0]) - pledge_agent.claimReward(operators) - assert tracker.delta() == TOTAL_REWARD - TOTAL_REWARD // 2 - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD - - -def test_move_core_data_check_acc_stake_amount(pledge_agent, stake_hub, core_agent, set_candidate): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - old_delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - old_undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0]) - __check_core_reward_map(accounts[0], { - 'reward': 0, - 'accStakedAmount': 0 - }) - turn_round(consensuses) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], True) - assert reward_map == [TOTAL_REWARD // 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] - update_system_contract_address(core_agent, stake_hub=stake_hub) - turn_round(consensuses) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], True) - assert reward_map == [TOTAL_REWARD // 2 * 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] - update_system_contract_address(core_agent, stake_hub=stake_hub) - stake_hub_claim_reward(accounts[0]) - turn_round(consensuses) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], True) - assert reward_map == [TOTAL_REWARD // 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] - update_system_contract_address(core_agent, stake_hub=stake_hub) - old_claim_reward_success(operators, accounts[0]) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], True) - assert reward_map == [0, 0, 0] - update_system_contract_address(core_agent, stake_hub=stake_hub) - turn_round(consensuses) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[0], True) - assert reward_map == [TOTAL_REWARD // 2, MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE] - - -def test_stake_current_round_move_core_no_reward(pledge_agent, stake_hub, core_agent, set_candidate): - operators, consensuses = set_candidate - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE * 2) - __old_turn_round() - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0]) - pledge_agent.moveCOREData(operators[0], accounts[1]) - turn_round(consensuses, round_count=1) - update_system_contract_address(core_agent, stake_hub=accounts[0]) - reward_map = core_agent.claimReward.call(accounts[1], True) - assert reward_map == [TOTAL_REWARD, MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE * 2] - reward_map = core_agent.claimReward.call(accounts[0], True) - assert reward_map == [0, 0, 0] - update_system_contract_address(core_agent, stake_hub=stake_hub) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[1]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - assert tracker1.delta() == TOTAL_REWARD - turn_round(consensuses, round_count=1) - stake_hub_claim_reward(accounts[1]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD // 2 - assert tracker1.delta() == TOTAL_REWARD // 2 - - -def test_move_data_with_debt_caller_no_debt(pledge_agent, stake_hub, core_agent, set_candidate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[2], accounts[1], delegate_amount) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2) - old_undelegate_coin_success(operators[1], accounts[0], delegate_amount // 4) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0], {'from': accounts[1]}) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - old_claim_reward_success(operators, accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD - TOTAL_REWARD // 4 - assert tracker1.delta() == TOTAL_REWARD - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 2 - assert tracker1.delta() == TOTAL_REWARD - - -def test_move_data_all_with_debt(pledge_agent, stake_hub, core_agent, set_candidate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[2], accounts[1], delegate_amount) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2) - old_undelegate_coin_success(operators[1], accounts[0], delegate_amount // 4) - old_transfer_coin_success(operators[2], operators[1], accounts[1], delegate_amount // 2) - old_undelegate_coin_success(operators[1], accounts[1], delegate_amount // 4) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0], {'from': accounts[1]}) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - old_claim_reward_success(operators, accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD - TOTAL_REWARD // 4 - assert tracker1.delta() == TOTAL_REWARD - TOTAL_REWARD // 4 - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 - assert tracker1.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 - - -def test_move_data_no_debt(pledge_agent, stake_hub, core_agent, set_candidate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[2], accounts[1], delegate_amount) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - pledge_agent.moveCOREData(operators[0], accounts[0], {'from': accounts[1]}) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - old_claim_reward_success(operators, accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD - assert tracker1.delta() == TOTAL_REWARD - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD - assert tracker1.delta() == TOTAL_REWARD - - -def test_batch_move_core(pledge_agent, stake_hub, core_agent, set_candidate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 - operators, consensuses = set_candidate - for account in accounts[:2]: - for op in operators: - old_delegate_coin_success(op, account, delegate_amount) - __old_turn_round() - __old_turn_round(consensuses) - __init_hybrid_score_mock() - turn_round(consensuses, round_count=4) - old_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2, False) - for account in accounts[:2]: - for op in operators: - pledge_agent.moveCOREData(op, account, {'from': accounts[2]}) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[0]) - stake_hub_claim_reward(accounts[1]) - assert tracker0.delta() == (TOTAL_REWARD + TOTAL_REWARD // 2) * 5 - 2 - assert tracker1.delta() == (TOTAL_REWARD + TOTAL_REWARD // 2) * 5 - 2 - turn_round(consensuses) - - -def test_get_stake_info(pledge_agent): - operator = accounts[1] - consensus = register_candidate(operator=operator) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - delegate_power_success(operator, accounts[0], 10) - __old_turn_round() - __check_old_agent_map_info(operator, { - 'power': int(10e18), - 'coin': MIN_INIT_DELEGATE_VALUE - }) - stake_info = pledge_agent.getStakeInfo([operator]) - assert stake_info[1][0] == 10 - __old_turn_round([consensus]) - stake_info = pledge_agent.getStakeInfo([operator]) - assert stake_info[0][0] == MIN_INIT_DELEGATE_VALUE - - -def test_get_power_stake_info(pledge_agent): - power_value = 10 - operator = accounts[1] - consensus = register_candidate(operator=operator) - pledge_agent.delegateCoinOld(operator, {"value": MIN_INIT_DELEGATE_VALUE}) - delegate_power_success(operator, accounts[0], power_value) - delegate_power_success(operator, accounts[0], power_value, 1) - delegate_power_success(operator, accounts[0], power_value, 2) - __old_turn_round() - __check_old_agent_map_info(operator, { - 'power': int(10e18), - 'coin': MIN_INIT_DELEGATE_VALUE - }) - stake_info = pledge_agent.getStakeInfo([operator]) - assert stake_info[1][0] == 10 - __old_turn_round([consensus]) - stake_info = pledge_agent.getStakeInfo([operator]) - assert stake_info[0][0] == MIN_INIT_DELEGATE_VALUE - assert stake_info[1][0] == power_value - - -def test_get_delegator_success(pledge_agent, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, - MIN_INIT_DELEGATE_VALUE * 2, get_current_round(), 0, - MIN_INIT_DELEGATE_VALUE, 0] - delegator1 = pledge_agent.getDelegator(operators[1], accounts[0]) - assert delegator1 == [0, MIN_INIT_DELEGATE_VALUE, get_current_round(), 1, 0, MIN_INIT_DELEGATE_VALUE] - - -def test_query_data_in_new_contract(pledge_agent, set_candidate): - operators, consensuses = set_candidate - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) - turn_round() - transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, - MIN_INIT_DELEGATE_VALUE * 2, get_current_round(), 0, - MIN_INIT_DELEGATE_VALUE, 0] - delegator1 = pledge_agent.getDelegator(operators[1], accounts[0]) - assert delegator1 == [0, MIN_INIT_DELEGATE_VALUE, get_current_round(), 0, 0, 0] - - -def test_query_no_data_found(pledge_agent, set_candidate): - operators, consensuses = set_candidate - delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert sum(delegator0) == 0 - - -def test_query_after_data_migration(pledge_agent, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - __init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 3, - MIN_INIT_DELEGATE_VALUE * 3, get_current_round(), 0, - 0, 0] - - -@pytest.mark.parametrize("is_turn_round", [True, False]) -def test_getDelegator_returns_both_old_and_new_data(pledge_agent, set_candidate, is_turn_round): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - __init_hybrid_score_mock() - mock_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - stake_amount = MIN_INIT_DELEGATE_VALUE * 3 - if is_turn_round: - turn_round() - stake_amount += MIN_INIT_DELEGATE_VALUE - stake_hub_claim_reward(accounts[0]) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [stake_amount, - MIN_INIT_DELEGATE_VALUE * 4, get_current_round(), 0, - 0, 0] - - -def test_add_stake_after_existing_old_data(pledge_agent, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - __init_hybrid_score_mock() - mock_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - change_round = get_current_round() - turn_round(consensuses) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 3, - MIN_INIT_DELEGATE_VALUE * 5, change_round, 0, 0, 0] - change_round = get_current_round() - transfer_coin_success(operators[0], operators[2], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 4, - MIN_INIT_DELEGATE_VALUE * 4, change_round, 0, MIN_INIT_DELEGATE_VALUE, 0] - - -def test_cancel_getDelegator_after_transfer(pledge_agent, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - __init_hybrid_score_mock() - mock_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 4) - mock_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 4) - turn_round(consensuses) - transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - undelegate_coin_success(operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - change_round = get_current_round() - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 7, - MIN_INIT_DELEGATE_VALUE * 7, change_round, 0, MIN_INIT_DELEGATE_VALUE, 0] - delegator0 = pledge_agent.getDelegator(operators[1], accounts[0]) - assert delegator0 == [0, MIN_INIT_DELEGATE_VALUE, change_round, 0, 0, 0] - stake_hub_claim_reward(accounts[0]) - delegator0 = pledge_agent.getDelegator(operators[1], accounts[0]) - assert delegator0 == [0, MIN_INIT_DELEGATE_VALUE, change_round, 0, 0, 0] - - -def test_getDelegator_after_migration(pledge_agent, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - __init_hybrid_score_mock() - old_claim_reward_success(operators, accounts[0]) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 3, MIN_INIT_DELEGATE_VALUE * 3, get_current_round(), 0, - 0, 0] - turn_round(consensuses) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 3, MIN_INIT_DELEGATE_VALUE * 4, get_current_round(), 0, - 0, 0] - turn_round(consensuses) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE * 3, get_current_round(), 0, - 0, 0] - - -def test_get_result_after_full_transfer(pledge_agent, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - turn_round(consensuses) - transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [0, 0, get_current_round(), 0, MIN_INIT_DELEGATE_VALUE, 0] - - -def test_query_info_after_reoffender_verifier(pledge_agent, slash_indicator, set_candidate): - operators, consensuses = set_candidate - turn_round() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) - change_round = get_current_round() - turn_round(consensuses) - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - tx = None - for count in range(slash_threshold): - tx = slash_indicator.slash(consensuses[0]) - assert event_name in tx.events - turn_round(consensuses) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [0, MIN_INIT_DELEGATE_VALUE * 3, change_round, 0, 0, 0] - undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE, get_current_round(), 0, 0, 0] - - -def test_query_result_with_old_transfer_data(pledge_agent, slash_indicator, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __init_hybrid_score_mock() - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE * 2, get_current_round(), 0, - MIN_INIT_DELEGATE_VALUE, 0] - turn_round(consensuses) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - # if there is data in the new contract, only the new data will be transferred - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE * 3, get_current_round(), 0, - 0, 0] - old_claim_reward_success(operators, accounts[0]) - delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) - assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, MIN_INIT_DELEGATE_VALUE * 3, get_current_round(), 0, - 0, 0] - delegator0 = pledge_agent.getDelegatorOld(operators[0], accounts[0]) - assert sum(delegator0) == 0 - - -def test_query_result_with_new_transfer(pledge_agent, set_candidate): - operators, consensuses = set_candidate - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - __old_turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __init_hybrid_score_mock() - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': MIN_INIT_DELEGATE_VALUE * 2, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 2, - 'changeRound': get_current_round(), - 'transferOutDeposit': MIN_INIT_DELEGATE_VALUE, - }) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 4) - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': MIN_INIT_DELEGATE_VALUE * 2, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 6, - 'changeRound': get_current_round(), - 'transferOutDeposit': 0, - }) - transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': MIN_INIT_DELEGATE_VALUE * 2, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 4, - 'changeRound': get_current_round(), - 'transferOutDeposit': 0, - }) - turn_round(consensuses, round_count=2) - transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': MIN_INIT_DELEGATE_VALUE * 3, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 3, - 'changeRound': get_current_round(), - 'transferOutDeposit': MIN_INIT_DELEGATE_VALUE - }) - transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': MIN_INIT_DELEGATE_VALUE * 2, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 2, - 'changeRound': get_current_round(), - 'transferOutDeposit': MIN_INIT_DELEGATE_VALUE * 2, - }) - - -def test_query_after_current_round_stake_in_old_contract(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_turn_round() - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - change_round = get_current_round() - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': 0, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 3, - 'changeRound': change_round - }) - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': MIN_INIT_DELEGATE_VALUE * 3, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 4, - 'changeRound': core_agent.roundTag() - }) - - -def test_query_after_cancel_all_stakes(pledge_agent, set_candidate): - operators, consensuses = set_candidate - old_turn_round() - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - change_round = get_current_round() - old_turn_round() - __init_hybrid_score_mock() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': 0, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 3, - 'changeRound': change_round - }) - - -def test_stake_then_transfer(pledge_agent, core_agent, set_candidate): - operators, consensuses = set_candidate - old_turn_round() - pledge_agent.delegateCoinOld(operators[0], {"value": MIN_INIT_DELEGATE_VALUE * 3}) - change_round = get_current_round() - old_turn_round() - __init_hybrid_score_mock() - delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - transfer_coin_success(operators[0], operators[2], accounts[0], MIN_INIT_DELEGATE_VALUE) - # realtimeAmount transferredAmount is 0 - __check_pledge_agent_delegate_info(operators[0], accounts[0], { - 'deposit': 0, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 3, - 'changeRound': change_round - }) - turn_round(consensuses) - - -def test_clear_deprecated_members_success(pledge_agent, set_candidate): - update_system_contract_address(pledge_agent, gov_hub=accounts[0]) - hex_value = padding_left(Web3.to_hex(int(0)), 64) - pledge_agent.updateParam('clearDeprecatedMembers', hex_value) - assert pledge_agent.btcFactor() == 0 - - -@pytest.mark.parametrize("operate", ['delegate', 'undelegate', 'transfer', 'claim']) -def test_move2_core_agent_execution_success(pledge_agent, validator_set, stake_hub, core_agent, operate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 5) - old_delegate_coin_success(operators[1], accounts[1], MIN_INIT_DELEGATE_VALUE) - __check_old_delegate_info(operators[0], accounts[0], { - 'deposit': 0, - 'newDeposit': MIN_INIT_DELEGATE_VALUE * 5, - 'changeRound': get_current_round(), - 'rewardIndex': 1, - 'transferOutDeposit': 0, - 'transferInDeposit': 0, - }) - __old_turn_round() - __init_hybrid_score_mock() - real_amount = delegate_amount - transferred_amount = 0 - if operate == 'delegate': - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE, old=False) - staked_amount = delegate_amount - real_amount = MIN_INIT_DELEGATE_VALUE * 6 - change_round = core_agent.roundTag() - elif operate == 'undelegate': - turn_round() - tx = old_undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE, old=False) - assert 'undelegatedCoin' in tx.events - staked_amount = delegate_amount - MIN_INIT_DELEGATE_VALUE - real_amount = staked_amount - change_round = get_current_round() - elif operate == 'transfer': - turn_round() - tx = old_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE, old=False) - assert 'transferredCoin' in tx.events - staked_amount = delegate_amount - MIN_INIT_DELEGATE_VALUE - real_amount = staked_amount - change_round = get_current_round() - transferred_amount = MIN_INIT_DELEGATE_VALUE - else: - staked_amount = MIN_INIT_DELEGATE_VALUE * 5 - change_round = core_agent.roundTag() - old_claim_reward_success(operators) - __check_old_delegate_info(operators[0], accounts[0], { - 'deposit': 0, - 'newDeposit': 0, - 'changeRound': 0, - 'rewardIndex': 0, - 'transferOutDeposit': 0, - 'transferInDeposit': 0, - - }) - __check_delegate_info(operators[0], accounts[0], { - 'stakedAmount': staked_amount, - 'realtimeAmount': real_amount, - 'changeRound': change_round, - 'transferredAmount': transferred_amount - }) - - -def test_init_hybrid_score_success(): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 3 - for i in range(candidate_size - 1): - old_delegate_coin_success(operators[i], accounts[i], MIN_INIT_DELEGATE_VALUE * 5 + i) - __old_turn_round() - operators1, consensuses1 = __register_candidates(accounts[4:5]) - operators.append(operators1[0]) - consensuses.append(consensuses1[0]) - old_delegate_coin_success(operators[2], accounts[2], MIN_INIT_DELEGATE_VALUE * 5 + 2) - for i in range(candidate_size): - coin = delegate_amount + i - if operators[i] == operators[-1]: - coin = 0 - __check_old_agent_map_info(operators[i], { - 'totalDeposit': delegate_amount + i, - 'power': 0, - 'coin': coin, - 'btc': 0, - 'totalBtc': 0, - 'moved': False, - }) - __init_hybrid_score_mock() - for i in range(candidate_size): - core_amount = delegate_amount + i - if operators[i] == operators[-1]: - core_amount = 0 - __check_candidate_amount_map_info(operators[i], [core_amount, core_amount, 0, 0]) - - -def test_move_agent_success(pledge_agent, validator_set, stake_hub, core_agent): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 2 - for i in range(candidate_size): - old_delegate_coin_success(operators[i], accounts[i], delegate_amount + i) - __get_old_delegator_info(operators[i], accounts[i]) - __old_turn_round() - for i in range(candidate_size): - __check_old_agent_map_info(operators[i], { - 'totalDeposit': delegate_amount + i, - 'coin': delegate_amount + i, - 'moved': False, - }) - pledge_agent.moveCandidateData(operators) - for i in range(candidate_size): - __check_candidate_map_info(operators[i], { - 'amount': delegate_amount + i, - 'realtimeAmount': delegate_amount + i - }) - __check_old_agent_map_info(operators[i], { - # because the contract also has a staked core - 'totalDeposit': delegate_amount + i, - 'coin': delegate_amount + i, - 'moved': True, - }) - - -@pytest.mark.parametrize("claim", ['old', 'new']) -def test_migration_scenario_1(pledge_agent, validator_set, stake_hub, claim, core_agent): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 2 - for i in range(candidate_size): - old_delegate_coin_success(operators[i], accounts[0], delegate_amount + i) - __old_turn_round(consensuses) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - tracker0 = get_tracker(accounts[0]) - reward = BLOCK_REWARD - if claim == 'old': - old_claim_reward_success(operators, accounts[0]) - else: - stake_hub_claim_reward(accounts[0]) - reward = 0 - assert tracker0.delta() == reward - - -@pytest.mark.parametrize("operate", ['undelegate', 'transfer']) -def test_migration_scenario_2(pledge_agent, validator_set, stake_hub, operate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 2 - for i in range(candidate_size): - old_delegate_coin_success(operators[i], accounts[0], delegate_amount + i) - __old_turn_round(consensuses) - reward = BLOCK_REWARD - if operate == 'undelegate': - old_undelegate_coin_success(operators[0], accounts[0], delegate_amount) - reward = reward // 2 - else: - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - tracker0 = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) - assert tracker0.delta() == reward - - -@pytest.mark.parametrize("operate", ['undelegate', 'transfer']) -def test_migration_scenario_3(pledge_agent, validator_set, stake_hub, operate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 2 - for i in range(candidate_size): - old_delegate_coin_success(operators[i], accounts[0], delegate_amount) - __old_turn_round(consensuses) - reward = BLOCK_REWARD - if operate == 'undelegate': - old_undelegate_coin_success(operators[0], accounts[0]) - reward = reward // 2 - else: - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount) - __init_hybrid_score_mock() - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == reward - - -@pytest.mark.parametrize("operate", ['undelegate', 'transfer']) -def test_migration_scenario_4(pledge_agent, validator_set, stake_hub, operate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - undelegate_amount = delegate_amount // 2 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 2 - for i in range(candidate_size): - old_delegate_coin_success(operators[i], accounts[0], delegate_amount) - __old_turn_round(consensuses) - reward = BLOCK_REWARD - if operate == 'undelegate': - old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) - reward = TOTAL_REWARD + BLOCK_REWARD // 4 - else: - old_transfer_coin_success(operators[0], operators[1], accounts[0], undelegate_amount) - __init_hybrid_score_mock() - turn_round(consensuses, round_count=1) - tracker0 = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) - assert tracker0.delta() == reward - assert stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - - -@pytest.mark.parametrize("operate", ['undelegate', 'transfer']) -def test_migration_scenario_5(pledge_agent, validator_set, stake_hub, operate): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - undelegate_amount = delegate_amount // 2 - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - candidate_size = 2 - for i in range(candidate_size): - old_delegate_coin_success(operators[i], accounts[0], delegate_amount) - __old_turn_round(consensuses) - reward = BLOCK_REWARD - if operate == 'undelegate': - old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) - reward = TOTAL_REWARD + TOTAL_REWARD - TOTAL_REWARD // 2 - delegate_amount0 = delegate_amount // 2 - delegate_amount1 = delegate_amount - else: - old_transfer_coin_success(operators[0], operators[1], accounts[0], undelegate_amount) - delegate_amount0 = delegate_amount // 2 - delegate_amount1 = delegate_amount + delegate_amount // 2 - __old_turn_round(consensuses) - __init_hybrid_score_mock() - turn_round(consensuses, round_count=1) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == 0 - old_claim_reward_success(operators, accounts[0]) - assert tracker0.delta() == reward + BLOCK_REWARD - turn_round(consensuses, round_count=1) - stake_hub_claim_reward(accounts[0]) - _, _, account_rewards, _ = parse_delegation([{ - "address": operators[0], - "active": True, - "power": [], - "coin": [set_delegate(accounts[0], delegate_amount0)], - "btc": [], - }, { - "address": operators[1], - "active": True, - "power": [], - "coin": [set_delegate(accounts[0], delegate_amount1)], - "btc": [], - }], BLOCK_REWARD // 2) - - assert tracker0.delta() == account_rewards[accounts[0]] - -@pytest.mark.parametrize("round_count", [0, 1]) -@pytest.mark.parametrize("tests", [ - {'delegator': ['tr', 'de', 'un'], 'actual_reward': 27090, 'reward_after_round_switch': 40635}, - {'delegator': ['de', 'un', 'un', 'un'], 'actual_reward': 20317, 'reward_after_round_switch': 27090}, - {'delegator': ['de', 'un', 'un'], 'actual_reward': 27090, 'reward_after_round_switch': 27090}, - {'delegator': ['de', 'tr', 'un', 'un'], 'actual_reward': 20317, 'reward_after_round_switch': 40635}, - {'delegator': ['un', 'un', 'de'], 'actual_reward': 13545, 'reward_after_round_switch': 27090}, - {'delegator': ['un', 'de', 'tr'], 'actual_reward': 20317, 'reward_after_round_switch': 40635}, -]) -def test_move_data_claim_reward_correct(pledge_agent, validator_set, stake_hub, tests, round_count): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - undelegate_amount = delegate_amount // 2 - operators, consensuses = __register_candidates(accounts[2:5]) - __old_turn_round() - for op in operators[:2]: - old_delegate_coin_success(op, accounts[0], delegate_amount) - __old_turn_round(consensuses) - for d in tests['delegator']: - if d == 'de': - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - elif d == 'un': - old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) - elif d == 'tr': - old_transfer_coin_success(operators[0], operators[2], accounts[0], undelegate_amount) - __init_hybrid_score_mock() - turn_round(consensuses, round_count=round_count) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - old_claim_reward_success(operators, accounts[0]) - actual_reward = tests['actual_reward'] - if round_count == 0: - actual_reward = 0 - assert tracker.delta() == actual_reward - if round_count > 0: - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == tests['reward_after_round_switch'] - - -@pytest.mark.parametrize("tests", [ - {'delegator': ['tr', 'de', 'un'], 'actual_reward': 67725}, - {'delegator': ['de', 'un', 'un'], 'actual_reward': 54180}, - {'delegator': ['un', 'un', 'de'], 'actual_reward': 40635}, - {'delegator': ['un', 'tr', 'de'], 'actual_reward': 60952} -]) -def test_upgrade_claim_reward_after_skip_round(pledge_agent, validator_set, stake_hub, tests): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 5 - undelegate_amount = delegate_amount // 2 - operators, consensuses = __register_candidates(accounts[2:5]) - __old_turn_round() - for op in operators[:2]: - old_delegate_coin_success(op, accounts[0], delegate_amount) - __old_turn_round(consensuses) - for d in tests['delegator']: - if d == 'de': - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - elif d == 'un': - old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) - elif d == 'tr': - old_transfer_coin_success(operators[0], operators[2], accounts[0], undelegate_amount) - __init_hybrid_score_mock() - turn_round(consensuses, round_count=2) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 - old_claim_reward_success(operators, accounts[0]) - actual_reward = tests['actual_reward'] - assert tracker.delta() == actual_reward - - -@pytest.mark.parametrize("tests", [ - {'delegator': ['tr', 'un']}, - {'delegator': ['tr', 'de', 'un', 'un', 'un']}, - {'delegator': ['tr', 'un', 'tr', 'un']}, -]) -def test_cancel_claim_reward_after_transfer(pledge_agent, validator_set, stake_hub, set_candidate, tests): - btc_value = 1e8 - delegate_amount = 2e18 - undelegate_amount = delegate_amount // 2 +def test_reentry_stake_hub_claim(pledge_agent, stake_hub, set_candidate, validator_set): operators, consensuses = set_candidate - __old_turn_round() - delegate_power_success(operators[0], accounts[2], 1) - delegate_power_success(operators[1], accounts[2], 1) - delegate_power_success(operators[2], accounts[2], 1) - for op in operators[:2]: - old_delegate_coin_success(op, accounts[1], delegate_amount) - __old_turn_round(consensuses) - for d in tests['delegator']: - if d == 'de': - old_delegate_coin_success(operators[2], accounts[1], delegate_amount) - elif d == 'un': - old_undelegate_coin_success(operators[2], accounts[1], undelegate_amount) - elif d == 'tr': - old_transfer_coin_success(operators[0], operators[2], accounts[1], undelegate_amount) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - trackers = [] - for i in range(3): - new_reward = get_tracker(accounts[i]) - trackers.append(new_reward) - old_reward = __get_old_reward(operators, accounts[i]) - old_claim_reward_success(operators, accounts[i]) - assert new_reward.delta() == old_reward + reentry_ = ClaimRewardReentry.deploy(pledge_agent.address, stake_hub, {'from': accounts[0]}) + accounts[2].transfer(reentry_, ONE_ETHER) + accounts[2].transfer(pledge_agent, ONE_ETHER) + accounts[2].transfer(stake_hub, ONE_ETHER) turn_round(consensuses, round_count=2) + delegate_coin_success(operators[0], reentry_, MIN_INIT_DELEGATE_VALUE) + pledge_agent.setRewardMap(reentry_, TOTAL_REWARD) + tx = reentry_.claimReward([operators[0]]) + assert tx.events['claimedReward']['amount'] == TOTAL_REWARD + assert len(tx.events['claimedReward']) == 1 -def test_power_claim_reward_success(pledge_agent, set_candidate): + +@pytest.mark.parametrize("operate", ['delegate', 'undelegate', 'transfer']) +def test_auto_issue_historical_rewards(pledge_agent, set_candidate, core_agent, operate): operators, consensuses = set_candidate - __old_turn_round() - delegate_power_success(operators[0], accounts[0]) - __old_turn_round(consensuses) - delegate_power_success(operators[0], accounts[1]) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[0], TOTAL_REWARD) - turn_round(consensuses) - tracker = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[1]) - assert tracker.delta() == TOTAL_REWARD - turn_round(consensuses) - stake_hub_claim_reward(accounts[1]) - assert tracker.delta() == 0 + accounts[0].transfer(pledge_agent, ONE_ETHER) + pledge_agent.setRewardMap(accounts[0], TOTAL_REWARD) + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) + if operate == 'delegate': + tx = proxy_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) + elif operate == 'undelegate': + tx = proxy_undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) + else: + tx = proxy_transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + assert 'claimedReward' not in tx.events -def test_new_validator_join_current_round(pledge_agent, set_candidate): - operators, consensuses = __register_candidates(accounts[2:4]) - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - __old_turn_round(consensuses) - agent, consensus = __register_candidates(accounts[4:5]) - operators.append(agent[0]) - consensuses.append(consensus[0]) - old_delegate_coin_success(operators[-1], accounts[0], MIN_INIT_DELEGATE_VALUE) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[0], 0) - __check_old_reward(operators, accounts[1], 0) - turn_round(consensuses, round_count=3) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD * 2 - assert tracker1.delta() == TOTAL_REWARD * 3 - - -def test_cancel_allowed_current_round_after_move_data(pledge_agent, set_candidate): - operators, consensuses = __register_candidates(accounts[2:4]) - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - agent, consensus = __register_candidates(accounts[4:5]) - operators.append(agent[0]) - consensuses.append(consensus[0]) - old_delegate_coin_success(operators[-1], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[0], 0) - __check_old_reward(operators, accounts[1], 0) - undelegate_coin_success(operators[0], accounts[1], delegate_amount) - undelegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - undelegate_coin_success(operators[-1], accounts[0], MIN_INIT_DELEGATE_VALUE) - turn_round(consensuses) - undelegate_coin_success(operators[0], accounts[1], MIN_INIT_DELEGATE_VALUE) - undelegate_coin_success(operators[-1], accounts[0], MIN_INIT_DELEGATE_VALUE) - - -def test_claim_upgrade_current_round_stake_reward(pledge_agent, stake_hub, set_candidate): - operators, consensuses = __register_candidates(accounts[2:4]) - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1], 0) - __check_old_reward(operators, accounts[0], 0) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD // 2 - assert tracker1.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 -def test_claim_new_effective_reward_after_skip_round(pledge_agent, stake_hub, set_candidate): +def test_claim_reward_success(btc_agent, pledge_agent, set_candidate): operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1], TOTAL_REWARD + TOTAL_REWARD // 2) - __check_old_reward(operators, accounts[0], TOTAL_REWARD - TOTAL_REWARD // 2) + turn_round() + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD // 2 * 2 - assert tracker1.delta() == TOTAL_REWARD // 2 * 2 + tracker = get_tracker(accounts[0]) + actual_reward = TOTAL_REWARD + pledge_agent.claimReward(operators) + assert tracker.delta() == actual_reward - FEE -def test_upgrade_current_round_cancel(pledge_agent, stake_hub, set_candidate): +def test_claim_reward_validator_address_empty(btc_agent, pledge_agent, set_candidate): operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - old_undelegate_coin_success(operators[0], accounts[1], delegate_amount // 2) - old_undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2) - assert tracker0.delta() == TOTAL_REWARD - TOTAL_REWARD // 2 + delegate_amount // 2 - assert tracker1.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 + delegate_amount // 2 - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1], 0) - __check_old_reward(operators, accounts[0], 0) + turn_round() + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], BTC_VALUE, LOCK_SCRIPT) + delegate_btc_success(operators[2], accounts[0], BTC_VALUE, LOCK_SCRIPT) turn_round(consensuses, round_count=2) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD // 4 + TOTAL_REWARD // 2 - assert tracker1.delta() == TOTAL_REWARD // 4 + TOTAL_REWARD // 2 + tracker = get_tracker(accounts[0]) + actual_reward = TOTAL_REWARD * 3 + pledge_agent.claimReward([]) + assert tracker.delta() == actual_reward -def test_candidate_becomes_validator_after_upgrade(pledge_agent, stake_hub, set_candidate, validator_set, - candidate_hub): - candidate_hub.setValidatorCount(1) +def test_claim_validator_reward_individually(btc_agent, pledge_agent, set_candidate): + accounts[0].transfer(pledge_agent, ONE_ETHER) operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_delegate_coin_success(operators[1], accounts[2], delegate_amount) - old_delegate_coin_success(operators[2], accounts[3], delegate_amount) - __old_turn_round(consensuses) - assert consensuses[0] not in validator_set.getValidators() - old_transfer_coin_success(operators[0], operators[2], accounts[1], delegate_amount) - assert consensuses[1] in validator_set.getValidators() - assert consensuses[2] not in validator_set.getValidators() - __init_hybrid_score_mock() - candidate_hub.setValidatorCount(3) - __check_old_reward(operators, accounts[1]) - turn_round(consensuses, round_count=4) - tracker = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[1]) - assert tracker.delta() == TOTAL_REWARD // 2 * 3 + 1 - turn_round(consensuses) - + pledge_agent.setRewardMap(accounts[0], TOTAL_REWARD // 2) + tracker = get_tracker(accounts[0]) + stake_hub_claim_reward(accounts[0]) + assert tracker.delta() == 0 + proxy_claim_reward_success(operators[:1], accounts[0]) + assert tracker.delta() == TOTAL_REWARD // 2 -def test_upgrade_current_round_transfer(pledge_agent, btc_agent, stake_hub, set_candidate): +# getDelegator +def test_query_data_in_new_contract(pledge_agent, set_candidate): operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - old_transfer_coin_success(operators[0], operators[1], accounts[1]) - old_transfer_coin_success(operators[1], operators[2], accounts[1]) - old_transfer_coin_success(operators[0], operators[1], accounts[0]) - assert tracker0.delta() == TOTAL_REWARD - TOTAL_REWARD // 2 - assert tracker1.delta() == TOTAL_REWARD + TOTAL_REWARD // 2 - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1], 0) - __check_old_reward(operators, accounts[0], 0) - turn_round(consensuses, round_count=2) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD // 2 + TOTAL_REWARD - assert tracker1.delta() == TOTAL_REWARD // 2 + TOTAL_REWARD - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD - assert tracker1.delta() == TOTAL_REWARD + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) + turn_round() + transfer_coin_success(operators[0], operators[1], accounts[0], MIN_INIT_DELEGATE_VALUE) + delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) + assert delegator0 == [MIN_INIT_DELEGATE_VALUE * 2, + MIN_INIT_DELEGATE_VALUE * 2, get_current_round(), 0, + MIN_INIT_DELEGATE_VALUE, 0] + delegator1 = pledge_agent.getDelegator(operators[1], accounts[0]) + assert delegator1 == [0, MIN_INIT_DELEGATE_VALUE, get_current_round(), 0, 0, 0] -def test_refuse_validator_data_migration(pledge_agent, btc_agent, candidate_hub, stake_hub, set_candidate): +def test_query_no_data_found(pledge_agent, set_candidate): operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses, round_count=2) - candidate_hub.refuseDelegate({'from': operators[0]}) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1], TOTAL_REWARD + TOTAL_REWARD // 2) - __check_old_reward(operators, accounts[0], TOTAL_REWARD - TOTAL_REWARD // 2) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == TOTAL_REWARD // 2 - assert tracker1.delta() == TOTAL_REWARD // 2 - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == 0 + delegate_btc_success(operators[0], accounts[0], BTC_VALUE, LOCK_SCRIPT) + delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) + assert sum(delegator0) == 0 -@pytest.mark.parametrize("round_count", [0, 1, 2]) -def test_non_validator_stake_data_migration(pledge_agent, btc_agent, candidate_hub, stake_hub, set_candidate, - round_count): - operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - candidate_hub.refuseDelegate({'from': operators[0]}) - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1]) - __check_old_reward(operators, accounts[0]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - actual_reward0 = 0 - actual_reward1 = 0 - if round_count == 0: - actual_reward0 = TOTAL_REWARD - assert tracker0.delta() == actual_reward0 - assert tracker1.delta() == actual_reward1 - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == 0 - assert tracker1.delta() == 0 -@pytest.mark.parametrize('round_count', [0, 1]) -@pytest.mark.parametrize("threshold_type", ['minor', 'major']) -def test_slash_validator_during_data_migration(pledge_agent, btc_agent, slash_indicator, candidate_hub, stake_hub, - set_candidate, - threshold_type, round_count): +def test_query_info_after_reoffender_verifier(pledge_agent, slash_indicator, set_candidate): operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses, round_count=1) + turn_round() + delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 3) + change_round = get_current_round() + turn_round(consensuses) + slash_threshold = slash_indicator.felonyThreshold() + event_name = 'validatorFelony' tx = None - if threshold_type == 'minor': - slash_threshold = slash_indicator.misdemeanorThreshold() - event_name = 'validatorMisdemeanor' - actual_reward = TOTAL_REWARD // 2 - else: - slash_threshold = slash_indicator.felonyThreshold() - event_name = 'validatorFelony' - actual_reward = 0 for count in range(slash_threshold): tx = slash_indicator.slash(consensuses[0]) assert event_name in tx.events - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1]) - __check_old_reward(operators, accounts[0]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == actual_reward - assert tracker1.delta() == actual_reward - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == actual_reward - assert tracker1.delta() == actual_reward - - -@pytest.mark.parametrize('round_count', [0, 1]) -def test_major_offense_validators_before_data_migration(pledge_agent, validator_set, btc_agent, slash_indicator, - candidate_hub, - stake_hub, - set_candidate, round_count): - operators, consensuses = set_candidate - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses, round_count=1) - slash_threshold = slash_indicator.felonyThreshold() - actual_reward = 0 - for count in range(slash_threshold): - slash_indicator.slash(consensuses[0]) - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[1]) - __check_old_reward(operators, accounts[0]) - turn_round(consensuses) - tracker0 = get_tracker(accounts[0]) - tracker1 = get_tracker(accounts[1]) - stake_hub_claim_reward(accounts[:2]) - assert tracker0.delta() == actual_reward - assert tracker1.delta() == actual_reward - required_margin = candidate_hub.requiredMargin() - candidate_hub.addMargin({'value': required_margin, 'from': operators[0]}) - assert len(validator_set.getValidators()) == 2 - turn_round(consensuses) - validator_length = 3 - if round_count == 0: - validator_length = 2 - assert len(validator_set.getValidators()) == validator_length - turn_round(consensuses) - stake_hub_claim_reward(accounts[:2]) - if round_count > 0: - actual_reward = TOTAL_REWARD // 2 - assert tracker0.delta() == actual_reward - assert tracker1.delta() == actual_reward - - -def test_major_offense_validator_penalty_sufficient(validator_set, slash_indicator, candidate_hub, set_candidate): - operators, consensuses = set_candidate - required_margin = candidate_hub.requiredMargin() - candidate_hub.addMargin({'value': required_margin, 'from': operators[0]}) - delegate_amount = MIN_INIT_DELEGATE_VALUE * 4 - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - __old_turn_round(consensuses) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - __check_old_reward(operators, accounts[0], TOTAL_REWARD) - turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - slash_threshold = slash_indicator.felonyThreshold() - for count in range(slash_threshold): - slash_indicator.slash(consensuses[0]) - turn_round(consensuses) - assert consensuses[0] not in validator_set.getValidators() - turn_round(consensuses) - assert consensuses[0] not in validator_set.getValidators() - turn_round(consensuses) - assert consensuses[0] in validator_set.getValidators() - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == 0 turn_round(consensuses) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 - - -def test_stake_immediately_after_upgrade(stake_hub, core_agent, set_candidate): - btc_value = 1000 - delegate_amount = 10000 - operators, consensuses = set_candidate - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - old_delegate_coin_success(operators[1], accounts[3], delegate_amount) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - delegate_coin_success(operators[0], accounts[0], delegate_amount) - turn_round(consensuses, round_count=2) - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker0.delta() == TOTAL_REWARD // 3 - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - assert tracker0.delta() == TOTAL_REWARD // 2 * 2 + TOTAL_REWARD // 3 - - -def test_power_claim_reward_current_round_after_move_data(stake_hub, pledge_agent, core_agent, set_candidate): - power_value = 1 - delegate_amount = 1000000e18 - operators, consensuses = set_candidate - __old_turn_round() - delegate_power_success(operators[0], accounts[0], power_value) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses, round_count=1) - __init_hybrid_score_mock() - tx = turn_round(consensuses, round_count=1) - assert tx.events['roundReward'][0]['amount'][0] == TOTAL_REWARD // 2 - tracker0 = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - old_claim_reward_success(operators, accounts[1]) - assert tracker0.delta() == TOTAL_REWARD // 2 - - -def test_claim_old_contract_reward_after_move_data(stake_hub, pledge_agent, core_agent, set_candidate): - delegate_amount = 1000 - power_value = 1 - operators, consensuses = set_candidate - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - delegate_power_success(operators[0], accounts[2], power_value, stake_round=0) - delegate_power_success(operators[0], accounts[2], power_value, stake_round=1) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount * 2, power_value * 1000, 0) - turn_round(consensuses, round_count=1) - trackers = get_trackers(accounts[:2]) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) - for tracker in trackers: - assert tracker.delta() == TOTAL_REWARD // 3 - turn_round(consensuses, round_count=1) - delegator0 = pledge_agent.getDelegatorOld(operators[0], accounts[0]) - delegator1 = pledge_agent.getDelegatorOld(operators[0], accounts[1]) - assert delegator0['changeRound'] == 0 - assert delegator1['changeRound'] == 0 + delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) + assert delegator0 == [0, MIN_INIT_DELEGATE_VALUE * 3, change_round, 0, 0, 0] + undelegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE * 2) + delegator0 = pledge_agent.getDelegator(operators[0], accounts[0]) + assert delegator0 == [MIN_INIT_DELEGATE_VALUE, MIN_INIT_DELEGATE_VALUE, get_current_round(), 0, 0, 0] @pytest.mark.parametrize("tests", ['delegate', 'undelgate', 'transfer']) def test_proxy_staking_success(stake_hub, pledge_agent, core_agent, set_candidate, tests): - btc_value = 100 delegate_amount = 1000 - power_value = 1 operators, consensuses = set_candidate - __old_turn_round() - old_delegate_coin_success(operators[0], accounts[1], delegate_amount) - __old_turn_round(consensuses) - __init_hybrid_score_mock() - stake_hub.setCandidateScoresMap(operators[0], delegate_amount, power_value * 1000, btc_value * 10) + delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round(consensuses, round_count=1) - old_claim_reward_success(operators, accounts[0]) - old_claim_reward_success(operators, accounts[1]) if tests == 'delegate': - tx = old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + tx = proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) assert 'delegatedCoin' in tx.events elif tests == 'undelgate': - tx = old_undelegate_coin_success(operators[0], accounts[1], 0, old=False) + tx = proxy_undelegate_coin_success(operators[0], accounts[1], 0) assert 'undelegatedCoin' in tx.events elif tests == 'transfer': - tx = old_transfer_coin_success(operators[0], operators[1], accounts[1], delegate_amount, old=False) + tx = proxy_transfer_coin_success(operators[0], operators[1], accounts[1], delegate_amount) assert 'transferredCoin' in tx.events @@ -2060,8 +200,8 @@ def test_proxy_staking_success(stake_hub, pledge_agent, core_agent, set_candidat def test_proxy_unstaking_success(stake_hub, pledge_agent, core_agent, set_candidate, part, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() if part: undelegate_amount = delegate_amount // 2 @@ -2074,10 +214,10 @@ def test_proxy_unstaking_success(stake_hub, pledge_agent, core_agent, set_candid undelegate_value = delegate_amount round_reward = 0 tracker = get_tracker(accounts[0]) - tx = old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount, old=False) + tx = proxy_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) assert 'undelegatedCoin' in tx.events turn_round(consensuses, round_count=round_count) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) actual_reward = actual_reward + round_reward + undelegate_value if round_count == 0: actual_reward = undelegate_value @@ -2089,8 +229,8 @@ def test_proxy_unstaking_success(stake_hub, pledge_agent, core_agent, set_candid def test_proxy_unstaking_after_multiple_rounds(stake_hub, pledge_agent, core_agent, set_candidate, part, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() turn_round(consensuses, round_count=round_count) accrued_reward = 6772500 @@ -2105,10 +245,10 @@ def test_proxy_unstaking_after_multiple_rounds(stake_hub, pledge_agent, core_age undelegate_value = delegate_amount round_reward = delegate_amount * round_count * accrued_reward // Utils.CORE_STAKE_DECIMAL tracker = get_tracker(accounts[0]) - old_undelegate_coin_success(operators[0], accounts[0], undelegate_amount, old=False) - old_claim_reward_success(operators, accounts[0]) + proxy_undelegate_coin_success(operators[0], accounts[0], undelegate_amount) + proxy_claim_reward_success(operators, accounts[0]) turn_round(consensuses) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) actual_reward = actual_reward + round_reward + undelegate_value if round_count == 0: actual_reward = undelegate_value @@ -2122,8 +262,8 @@ def test_proxy_unstaking_after_multiple_rounds(stake_hub, pledge_agent, core_age def test_proxy_transfer_success(stake_hub, pledge_agent, core_agent, set_candidate, part, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() if part: transfer_amount = delegate_amount // 2 @@ -2136,10 +276,10 @@ def test_proxy_transfer_success(stake_hub, pledge_agent, core_agent, set_candida round_reward = TOTAL_REWARD * (round_count - 1) old_candidate_reward = 0 tracker = get_tracker(accounts[0]) - tx = old_transfer_coin_success(operators[0], operators[1], accounts[0], transfer_amount, old=False) + tx = proxy_transfer_coin_success(operators[0], operators[1], accounts[0], transfer_amount) assert 'transferredCoin' in tx.events turn_round(consensuses, round_count=round_count) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) actual_reward = actual_reward + round_reward + old_candidate_reward if round_count == 0: actual_reward = 0 @@ -2151,8 +291,8 @@ def test_proxy_transfer_success(stake_hub, pledge_agent, core_agent, set_candida def test_proxy_transfer_after_multiple_rounds(stake_hub, pledge_agent, core_agent, set_candidate, part, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() turn_round(consensuses, round_count=round_count) accrued_reward = 6772500 @@ -2163,10 +303,10 @@ def test_proxy_transfer_after_multiple_rounds(stake_hub, pledge_agent, core_agen else: transfer_amount = delegate_amount tracker = get_tracker(accounts[0]) - old_transfer_coin_success(operators[0], operators[1], accounts[0], transfer_amount, old=False) - old_claim_reward_success(operators, accounts[0]) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], transfer_amount) + proxy_claim_reward_success(operators, accounts[0]) turn_round(consensuses) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) actual_reward = actual_reward + round_reward if round_count == 0: actual_reward = TOTAL_REWARD // 2 @@ -2177,11 +317,11 @@ def test_proxy_transfer_after_multiple_rounds(stake_hub, pledge_agent, core_agen def test_proxy_delegate_success(stake_hub, pledge_agent, core_agent, set_candidate, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() tracker = get_tracker(accounts[0]) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round(consensuses, round_count=round_count) stake_hub_claim_reward(accounts[0]) actual_reward = TOTAL_REWARD // 2 @@ -2195,19 +335,19 @@ def test_proxy_delegate_success(stake_hub, pledge_agent, core_agent, set_candida def test_proxy_delegate_after_multiple_rounds(stake_hub, pledge_agent, core_agent, set_candidate, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() turn_round(consensuses, round_count=round_count) accrued_reward = 6772500 actual_reward = TOTAL_REWARD // 2 round_reward = delegate_amount * round_count * accrued_reward // Utils.CORE_STAKE_DECIMAL tracker = get_tracker(accounts[0]) - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_claim_reward_success(operators, accounts[0]) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_claim_reward_success(operators, accounts[0]) turn_round(consensuses, round_count=2) actual_reward += TOTAL_REWARD * 2 // 3 - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) actual_reward = actual_reward + round_reward - delegate_amount if round_count == 0: actual_reward = TOTAL_REWARD // 2 + TOTAL_REWARD * 2 // 3 - delegate_amount @@ -2220,19 +360,19 @@ def test_proxy_delegate_current_round_cancel_and_transfer_allowed(stake_hub, cor round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) if tests == 'undelgate': - old_undelegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_undelegate_coin_success(operators[0], accounts[0], delegate_amount) elif tests == 'transfer': - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount, old=False) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount) turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round(consensuses, round_count=round_count) if tests == 'undelgate': - old_undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2, old=False) + proxy_undelegate_coin_success(operators[0], accounts[0], delegate_amount // 2) elif tests == 'transfer': - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2, old=False) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount // 2) @pytest.mark.parametrize("round_count", [0, 1, 2]) @@ -2240,15 +380,15 @@ def test_proxy_delegate_current_round_cancel_and_transfer_allowed(stake_hub, cor def test_current_round_transfer_can_cancel_and_transfer(stake_hub, core_agent, set_candidate, tests, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount, old=False) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount) turn_round(consensuses, round_count=round_count) if tests == 'undelgate': - old_undelegate_coin_success(operators[1], accounts[0], delegate_amount, old=False) + proxy_undelegate_coin_success(operators[1], accounts[0], delegate_amount) elif tests == 'transfer': - old_transfer_coin_success(operators[1], operators[2], accounts[0], delegate_amount, old=False) + proxy_transfer_coin_success(operators[1], operators[2], accounts[0], delegate_amount) @pytest.mark.parametrize("round_count", [0, 1, 2]) @@ -2256,16 +396,16 @@ def test_current_round_transfer_can_cancel_and_transfer(stake_hub, core_agent, s def test_proxy_undelegate_cancel_all(stake_hub, core_agent, set_candidate, tests, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_coin_success(operators[1], accounts[0], delegate_amount) turn_round() if tests == 'delegate': delegate_coin_success(operators[1], accounts[0], delegate_amount) else: - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount, old=False) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount) turn_round(consensuses, round_count=round_count) tracker = get_tracker(accounts[0]) - old_undelegate_coin_success(operators[1], accounts[0], 0, old=False) + proxy_undelegate_coin_success(operators[1], accounts[0], 0) actual_amount = delegate_amount * 2 assert tracker.delta() == actual_amount turn_round(consensuses) @@ -2276,15 +416,15 @@ def test_proxy_undelegate_cancel_all(stake_hub, core_agent, set_candidate, tests def test_proxy_transfer_cancel_all(stake_hub, core_agent, set_candidate, tests, round_count): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_coin_success(operators[1], accounts[0], delegate_amount) turn_round() if tests == 'delegate': delegate_coin_success(operators[1], accounts[0], delegate_amount) else: - old_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount, old=False) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], delegate_amount) turn_round(consensuses, round_count=round_count) - tx = old_transfer_coin_success(operators[1], operators[2], accounts[0], 0, old=False) + tx = proxy_transfer_coin_success(operators[1], operators[2], accounts[0], 0) actual_amount = delegate_amount * 2 assert tx.events['transferredCoin']['amount'] == actual_amount assert tx.events['transferredCoin']['realtimeAmount'] == actual_amount @@ -2294,13 +434,13 @@ def test_proxy_transfer_cancel_all(stake_hub, core_agent, set_candidate, tests, def test_cancel_after_all_already_cancelled(stake_hub, core_agent, set_candidate): delegate_amount = 1000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_coin_success(operators[1], accounts[0], delegate_amount) turn_round() - old_undelegate_coin_success(operators[1], accounts[0], 0, old=False) + proxy_undelegate_coin_success(operators[1], accounts[0], 0) turn_round(consensuses, round_count=2) with brownie.reverts("Undelegate zero coin"): - old_undelegate_coin_success(operators[1], accounts[0], 0, old=False) + proxy_undelegate_coin_success(operators[1], accounts[0], 0) __check_delegate_info(operators[1], accounts[0], { 'stakedAmount': 0, 'realtimeAmount': 0, @@ -2312,11 +452,11 @@ def test_cancel_after_all_already_cancelled(stake_hub, core_agent, set_candidate def test_cancel_stake_with_zero_amount(stake_hub, core_agent, set_candidate): delegate_amount = 10000 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_transfer_coin_success(operators[0], operators[2], accounts[0], 0, False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_transfer_coin_success(operators[0], operators[2], accounts[0], 0) with brownie.reverts("Undelegate zero coin"): undelegate_coin_success(operators[0], accounts[0], 0) turn_round(consensuses) @@ -2332,22 +472,22 @@ def test_claim_reward_with_both_proxy_and_regular_staking(stake_hub, btc_stake, power_value = 5 operators, consensuses = set_candidate turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) delegate_coin_success(operators[0], accounts[1], delegate_amount) delegate_btc_success(operators[0], accounts[0], btc_amount, LOCK_SCRIPT) tx_id = delegate_btc_success(operators[0], accounts[1], btc_amount, LOCK_SCRIPT, relay=accounts[1]) delegate_power_success(operators[0], accounts[3], power_value) turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, old=False) - old_undelegate_coin_success(operators[0], accounts[0], 0, old=False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_undelegate_coin_success(operators[0], accounts[0], 0) transfer_btc_success(tx_id, operators[2], accounts[1]) - old_transfer_coin_success(operators[0], operators[2], accounts[1], 0, old=False) + proxy_transfer_coin_success(operators[0], operators[2], accounts[1], 0) turn_round(consensuses) trackers = get_trackers(accounts[:4]) stake_hub_claim_reward(accounts[0]) - old_claim_reward_success(operators, accounts[1]) + proxy_claim_reward_success(operators, accounts[1]) stake_hub_claim_reward(accounts[2]) - old_claim_reward_success(operators, accounts[3]) + proxy_claim_reward_success(operators, accounts[3]) _, _, account_rewards, _ = parse_delegation([{ "address": operators[0], "power": [set_delegate(accounts[3], power_value)], @@ -2390,16 +530,16 @@ def test_cancel_stake_after_transfer_with_validator(pledge_agent, validator_set, agent_index = tests['undelagate'] operators, consensuses = set_candidate for op in operators: - old_delegate_coin_success(op, accounts[0], delegate_amount, False) - old_delegate_coin_success(op, accounts[1], delegate_amount, False) + proxy_delegate_coin_success(op, accounts[0], delegate_amount) + proxy_delegate_coin_success(op, accounts[1], delegate_amount) turn_round() tracker0 = get_tracker(accounts[0]) if inter_round_cancel: turn_round(consensuses) stake_hub_claim_reward(accounts[0]) expect_reward += TOTAL_REWARD // 2 * 3 - old_transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount, False) - tx = old_undelegate_coin_success(operators[agent_index], accounts[0], undelegate_amount, False) + proxy_transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount) + tx = proxy_undelegate_coin_success(operators[agent_index], accounts[0], undelegate_amount) turn_round(consensuses) stake_hub_claim_reward(accounts[0]) assert tracker0.delta() == expect_reward + undelegate_amount @@ -2432,25 +572,25 @@ def test_cancel_stake_from_validators_after_multiple_additions(core_agent, valid delegate_value = delegate_amount if index == 0: delegate_value = delegate_amount * 3 - old_delegate_coin_success(op, accounts[0], delegate_value, False) - old_delegate_coin_success(op, accounts[1], delegate_value, False) + proxy_delegate_coin_success(op, accounts[0], delegate_value) + proxy_delegate_coin_success(op, accounts[1], delegate_value) turn_round() for index, op in enumerate(operators): delegate_value = delegate_amount - old_delegate_coin_success(op, accounts[0], delegate_value, False) + proxy_delegate_coin_success(op, accounts[0], delegate_value) if old: delegate_coin_success(op, accounts[1], delegate_value) else: - old_delegate_coin_success(op, accounts[1], delegate_value, False) + proxy_delegate_coin_success(op, accounts[1], delegate_value) if old: - old_transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount, False) + proxy_transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount) else: transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount) for index, a in enumerate(agent_index): - old_undelegate_coin_success(operators[a], accounts[0], undelegate_amount[index], False) + proxy_undelegate_coin_success(operators[a], accounts[0], undelegate_amount[index]) turn_round(consensuses) tracker0 = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) assert tracker0.delta() == expect_reward @@ -2470,14 +610,14 @@ def test_claim_reward_after_cancel_coin_transfer(core_agent, validator_set, set_ expect_reward = tests['expect_reward'] agent_index = tests['candidate'] operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() - old_transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount, False) + proxy_transfer_coin_success(operators[0], operators[2], accounts[0], transfer_amount) tracker0 = get_tracker(accounts[0]) - old_undelegate_coin_success(operators[agent_index], accounts[0], undelegate_amount, False) + proxy_undelegate_coin_success(operators[agent_index], accounts[0], undelegate_amount) turn_round(consensuses, round_count=round_count) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) if round_count == 0: expect_reward = 0 elif round_count > 1: @@ -2488,18 +628,18 @@ def test_claim_reward_after_cancel_coin_transfer(core_agent, validator_set, set_ def test_claim_rewards_after_cancel_all_in_two_rounds(core_agent, validator_set, set_candidate): delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() - old_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2, False) - old_undelegate_coin_success(operators[0], accounts[0], 0, False) - old_undelegate_coin_success(operators[2], accounts[0], 0, False) + proxy_transfer_coin_success(operators[0], operators[2], accounts[0], delegate_amount // 2) + proxy_undelegate_coin_success(operators[0], accounts[0], 0) + proxy_undelegate_coin_success(operators[2], accounts[0], 0) tracker0 = get_tracker(accounts[0]) turn_round(consensuses, round_count=2) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) assert tracker0.delta() == 0 turn_round(consensuses, round_count=2) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) assert tracker0.delta() == 0 @@ -2507,13 +647,13 @@ def test_claim_rewards_after_cancel_all_in_two_rounds(core_agent, validator_set, def test_cancel_and_transfer_after_adding_stake(core_agent, validator_set, set_candidate, tests): delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - old_delegate_coin_success(operators[0], accounts[1], delegate_amount, False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[0], accounts[1], delegate_amount) turn_round() - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - old_delegate_coin_success(operators[1], accounts[0], delegate_amount, False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[1], accounts[0], delegate_amount) if tests == 'transfer': - old_transfer_coin_success(operators[0], operators[1], accounts[0], 0, False) + proxy_transfer_coin_success(operators[0], operators[1], accounts[0], 0) __check_delegate_info(operators[0], accounts[0], { 'stakedAmount': 0, 'realtimeAmount': 0, @@ -2526,7 +666,7 @@ def test_cancel_and_transfer_after_adding_stake(core_agent, validator_set, set_c }) expect_reward = TOTAL_REWARD // 2 else: - old_undelegate_coin_success(operators[0], accounts[0], 0, False) + proxy_undelegate_coin_success(operators[0], accounts[0], 0) __check_delegate_info(operators[0], accounts[0], { 'stakedAmount': 0, 'realtimeAmount': 0, @@ -2536,7 +676,7 @@ def test_cancel_and_transfer_after_adding_stake(core_agent, validator_set, set_c expect_reward = 0 turn_round(consensuses) tracker = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) assert tracker.delta() == expect_reward @@ -2544,12 +684,12 @@ def test_cancel_and_transfer_after_adding_stake(core_agent, validator_set, set_c def test_cancel_all_after_transfer(core_agent, validator_set, set_candidate, tests): delegate_amount = MIN_INIT_DELEGATE_VALUE * 10 operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], delegate_amount, False) - old_delegate_coin_success(operators[1], accounts[0], delegate_amount, False) + proxy_delegate_coin_success(operators[0], accounts[0], delegate_amount) + proxy_delegate_coin_success(operators[1], accounts[0], delegate_amount) turn_round() - old_transfer_coin_success(operators[1], operators[0], accounts[0], 0, False) + proxy_transfer_coin_success(operators[1], operators[0], accounts[0], 0) if tests == 'transfer': - old_transfer_coin_success(operators[0], operators[2], accounts[0], 0, False) + proxy_transfer_coin_success(operators[0], operators[2], accounts[0], 0) __check_delegate_info(operators[0], accounts[0], { 'stakedAmount': 0, 'realtimeAmount': 0, @@ -2562,7 +702,7 @@ def test_cancel_all_after_transfer(core_agent, validator_set, set_candidate, tes }) expect_reward = TOTAL_REWARD * 2 else: - old_undelegate_coin_success(operators[0], accounts[0], 0, False) + proxy_undelegate_coin_success(operators[0], accounts[0], 0) __check_delegate_info(operators[0], accounts[0], { 'stakedAmount': 0, 'realtimeAmount': 0, @@ -2572,219 +712,10 @@ def test_cancel_all_after_transfer(core_agent, validator_set, set_candidate, tes expect_reward = 0 turn_round(consensuses) tracker = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) + proxy_claim_reward_success(operators, accounts[0]) assert tracker.delta() == expect_reward -@pytest.mark.parametrize("round_count", [0, 1, 2]) -def test_reregister_and_claim_rewards_after_upgrade(pledge_agent, set_candidate, validator_set, slash_indicator, - candidate_hub, round_count): - operators, consensuses = set_candidate - old_delegate_coin_success(operators[0], accounts[0], MIN_INIT_DELEGATE_VALUE) - for op in operators: - old_delegate_coin_success(op, accounts[1], MIN_INIT_DELEGATE_VALUE) - __old_turn_round() - candidate_hub.refuseDelegate({'from': operators[0]}) - __old_turn_round() - candidate_hub.unregister({'from': operators[0]}) - candidate_hub.refuseDelegate({'from': operators[1]}) - __old_turn_round(consensuses, round_count=2) - __init_hybrid_score_mock() - pledge_agent.moveCandidateData([operators[0]]) - assert operators[2] in validator_set.getValidatorOps() - assert len(validator_set.getValidatorOps()) == 1 - turn_round(consensuses, round_count=round_count) - __check_candidate_map_info(operators[0], { - 'amount': MIN_INIT_DELEGATE_VALUE * 2, - 'realtimeAmount': MIN_INIT_DELEGATE_VALUE * 2 - }) - operators.append(operators[0]) - consensuses.append(register_candidate(operator=operators[0])) - old_claim_reward_success([operators[0]], accounts[0]) - turn_round(consensuses) - assert operators[0] in validator_set.getValidatorOps() - assert len(validator_set.getValidatorOps()) == 2 - turn_round(consensuses) - tracker = get_tracker(accounts[0]) - stake_hub_claim_reward(accounts[0]) - assert tracker.delta() == TOTAL_REWARD // 2 - - -@pytest.mark.skip(reason="skip gas-related tests") -def test_data_migration_with_large_core_reward_round(pledge_agent, set_candidate, validator_set, candidate_hub): - delegate_amount = 20000 - btc_amount = 2000 - operators, consensuses = set_candidate - tx_ids = [] - round_count = 500 - for op in operators: - old_delegate_coin_success(op, accounts[0], delegate_amount) - tx_id = old_delegate_btc_success(btc_amount, op, accounts[0], fee=0) - tx_ids.append(tx_id) - old_delegate_coin_success(op, accounts[1], delegate_amount * 2) - __old_turn_round() - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - single_validator_reward = 6772 - actual_reward = single_validator_reward * len(operators) * round_count - tracker = get_tracker(accounts[0]) - old_claim_reward_success(operators, accounts[0]) - assert tracker.delta() == actual_reward - - -@pytest.mark.skip(reason="skip gas-related tests") -def test_move_core_data_gas_check(pledge_agent, set_candidate, validator_set, candidate_hub): - delegate_amount = 20000 - btc_amount = 2000 - operators, consensuses = set_candidate - tx_ids = [] - round_count = 650 - for op in operators: - old_delegate_coin_success(op, accounts[0], delegate_amount) - tx_id = old_delegate_btc_success(btc_amount, op, accounts[0], fee=0) - tx_ids.append(tx_id) - old_delegate_coin_success(op, accounts[1], delegate_amount * 2) - __old_turn_round() - __old_turn_round(consensuses, round_count=round_count) - __init_hybrid_score_mock() - __move_btc_data(tx_ids) - old_claim_reward_success(operators, accounts[0]) - - -def __init_hybrid_score_mock(): - STAKE_HUB.initHybridScoreMock() - set_round_tag(get_current_round()) - - -def __move_btc_data(tx_ids): - return BTC_STAKE.moveDataMock(tx_ids) - - -def __old_turn_round(miners: list = None, tx_fee=100, round_count=1): - if miners is None: - miners = [] - tx = None - for _ in range(round_count): - for miner in miners: - ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) - tx = CandidateHubMock[0].turnRoundOld() - chain.sleep(1) - return tx - - -def __get_btc_tx_map_info(tx_id): - data = BTC_STAKE.btcTxMap(tx_id) - return data - - -def __check_btc_tx_map_info(tx_id, result: dict): - data = __get_btc_tx_map_info(tx_id) - for i in result: - assert data[i] == result[i] - - -def __get_old_reward_index_info(candidate, index): - reward_index = PLEDGE_AGENT.getReward(candidate, index) - return reward_index - - -def __get_old_agent_map_info(candidate): - agent_map = PLEDGE_AGENT.agentsMap(candidate) - return agent_map - - -def __get_old_delegator_info(candidate, delegator): - delegator_info = PLEDGE_AGENT.getDelegatorOld(candidate, delegator) - return delegator_info - - -def __get_pledge_agent_delegator_info(candidate, delegator): - delegator_info = PLEDGE_AGENT.getDelegator(candidate, delegator) - return delegator_info - - -def __get_delegator_info(candidate, delegator): - delegator_info = CoreAgentMock[0].getDelegator(candidate, delegator) - return delegator_info - - -def __get_reward_map_info(delegator): - delegator_info = CoreAgentMock[0].rewardMap(delegator) - return delegator_info - - -def __get_candidate_map_info(candidate): - candidate_info = CoreAgentMock[0].candidateMap(candidate) - return candidate_info - - -def __check_candidate_map_info(candidate, result: dict): - old_info = __get_candidate_map_info(candidate) - for i in result: - assert old_info[i] == result[i] - - -def __get_candidate_amount_map_info(candidate): - # The order is core, hash, btc. - candidate_score = STAKE_HUB.getCandidateScoresMap(candidate) - return candidate_score - - -def __check_old_delegate_info(candidate, delegator, result: dict): - old_info = __get_old_delegator_info(candidate, delegator) - for i in result: - assert old_info[i] == result[i] - - -def __check_pledge_agent_delegate_info(candidate, delegator, result: dict): - old_info = __get_pledge_agent_delegator_info(candidate, delegator) - for i in result: - assert old_info[i] == result[i] - - -def __check_delegate_info(candidate, delegator, result: dict): - new_info = __get_delegator_info(candidate, delegator) - for i in result: - assert new_info[i] == result[i] - - -def __check_candidate_amount_map_info(candidate, result: list): - candidate_amounts = __get_candidate_amount_map_info(candidate) - if candidate_amounts == (): - candidate_amounts = [0, 0, 0, 0] - for index, r in enumerate(result): - assert candidate_amounts[index] == r - - -def __get_btc_receipt_map(tx_id): - receipt_map = BTC_STAKE.receiptMap(tx_id) - return receipt_map - - -def __check_btc_receipt_map(tx_id, result: dict): - receipt = __get_btc_receipt_map(tx_id) - for i in result: - assert receipt[i] == result[i] - - -def __get_core_agent_reward_map(delegator): - reward_map = CORE_AGENT.rewardMap(delegator) - return reward_map - - -def __check_core_reward_map(delegator, result: dict): - reward_map = __get_core_agent_reward_map(delegator) - for i in result: - assert reward_map[i] == result[i] - - -def __check_old_agent_map_info(candidate, result: dict): - old_info = __get_old_agent_map_info(candidate) - for i in result: - assert old_info[i] == result[i] - - def __register_candidates(agents=None): operators = [] consensuses = [] @@ -2796,20 +727,13 @@ def __register_candidates(agents=None): return operators, consensuses -def __get_btc_candidate_map_info(candidate): - candidate_map = BTC_STAKE.candidateMap(candidate) - return candidate_map - +def __get_delegator_info(candidate, delegator): + delegator_info = CoreAgentMock[0].getDelegator(candidate, delegator) + return delegator_info -def __get_old_reward(operators, delegator): - old_reward, _ = PLEDGE_AGENT.claimRewardMock.call(operators, {'from': delegator}) - return old_reward +def __check_delegate_info(candidate, delegator, result: dict): + new_info = __get_delegator_info(candidate, delegator) + for i in result: + assert new_info[i] == result[i] -def __check_old_reward(operators, delegator, actual_reward=None): - old_reward = __get_old_reward(operators, delegator) - tracker = get_tracker(delegator) - old_claim_reward_success(operators, delegator) - assert tracker.delta() == old_reward - if actual_reward is not None: - assert old_reward == actual_reward diff --git a/tests/test_stake_hub_unit.py b/tests/test_stake_hub_unit.py index d8c0f799d..b79cb6dfa 100644 --- a/tests/test_stake_hub_unit.py +++ b/tests/test_stake_hub_unit.py @@ -631,7 +631,7 @@ def test_proxy_claim_reward_success(stake_hub, btc_agent, pledge_agent, set_cand operators, consensuses = set_candidate turn_round() delegate_coin_success(operators[0], accounts[2], delegate_amount) - script, pay_address, timestamp = random_btc_lock_script() + script, pay_address, timestamp = build_btc_lock_script() delegate_btc_success(operators[1], accounts[2], 100, script, timestamp, relay=accounts[2]) turn_round(consensuses, round_count=2) tracker = get_tracker(accounts[0]) @@ -785,9 +785,9 @@ def test_onStakeChange_success(stake_hub, set_candidate): turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - mock_delegate_btc_success(operators[1], accounts[0], btc_value) + delegate_btc_success(operators[1], accounts[0], btc_value,LOCK_SCRIPT) turn_round(consensuses, round_count=2) current_round = get_current_round() tx = stake_hub.onStakeChange(accounts[0]) @@ -816,8 +816,8 @@ def test_calculateReward_invalid_after_operation(stake_hub, set_candidate, onSta turn_round() delegate_amount = 500000 btc_value = 100 - mock_delegate_coin_success(operators[0], accounts[0], delegate_amount) - mock_delegate_btc_success(operators[1], accounts[0], btc_value) + delegate_coin_success(operators[0], accounts[0], delegate_amount) + delegate_btc_success(operators[1], accounts[0], btc_value,LOCK_SCRIPT) turn_round(consensuses, round_count=2) if onStakeChange: stake_hub.onStakeChange(accounts[0]) @@ -1099,8 +1099,9 @@ def test_stake_hub_calculate_reward(stake_hub, btc_agent, candidate_hub, core_ag delegate_coin_success(operators[1], accounts[1], test['add_core']) delegate_btc_success(operators[0], accounts[1], test['add_btc'], LOCK_SCRIPT, relay=accounts[1]) turn_round(consensuses, round_count=2) - graders = [1000, 5000, 4000, 7000, 5000, 8000, 10000, 10000] + graders_keys = [1000,4000,5000,10000] + graders_values = [5000,7000,8000,10000] if test.get('is_active'): btc_agent.setIsActive(True) - btc_agent.setInitLpRates(*graders) + btc_agent.setInitLpRates(graders_keys,graders_values) assert stake_hub.calculateRewardMock(accounts[1]).return_value == (test['expect_rewards']) diff --git a/tests/test_validator_set.py b/tests/test_validator_set.py index 6dc0199b8..52285ba20 100644 --- a/tests/test_validator_set.py +++ b/tests/test_validator_set.py @@ -2568,6 +2568,39 @@ def test_get_validator_index_after_removal_success(validator_set, candidate_hub, assert index_after == 0 +# getRankedValidatorList +def test_get_ranked_validator_list_success(validator_set, set_candidate): + operators, consensuses = set_candidate + turn_round() + ranked_list = validator_set.getRankedValidatorList() + assert ranked_list == consensuses + + +def test_get_ranked_validator_list_after_update(validator_set): + operate_addr_list = [account for account in accounts[:3]] + consensus_addr_list = [account for account in accounts[3:6]] + fee_addr_list = [account for account in accounts[6:9]] + vote_addr_list = [random_vote_address() for _ in range(3)] + commission_thousandths = [100 for _ in range(3)] + validator_count = 3 + update_system_contract_address(validator_set, candidate_hub=accounts[0]) + validator_set.updateValidatorSet( + operate_addr_list, + consensus_addr_list, + fee_addr_list, + commission_thousandths, + vote_addr_list, + validator_count + ) + ranked_list = validator_set.getRankedValidatorList() + assert ranked_list == consensus_addr_list + + +def test_get_ranked_validator_list_empty(validator_set): + ranked_list = validator_set.getRankedValidatorList() + assert len(ranked_list) == 0 + + # getLivingValidators def test_get_living_validators_with_multiple_validators(validator_set): operators = [] @@ -2692,6 +2725,23 @@ def test_update_maintain_slash_percent_success(validator_set, value): assert validator_set.maintainSlashPercent() == value +# update turnLength +@pytest.mark.parametrize("value", [1, 2, 3, 7, 8, 9]) +def test_update_turn_length_success(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + tx = validator_set.updateParam('turnLength', hex_value) + assert validator_set.turnLength() == value + + +@pytest.mark.parametrize("value", [0, 10]) +def test_update_turn_length_invalid_value(validator_set, value): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(value), 64) + with brownie.reverts(f"OutOfBounds: turnLength, {value}, 1, 9"): + validator_set.updateParam('turnLength', hex_value) + + # updateRankedValidatorList def test_update_ranked_validator_list_basic_success(validator_set, set_candidate_maintenance): turn_round() @@ -2956,3 +3006,14 @@ def test_get_working_validators_consistency(validator_set, set_candidate_mainten working_count = validator_set.getWorkingCount() working_validators = validator_set.getWorkingValidators() assert working_count == len(working_validators) + +# getTurnLength +def test_get_turn_length_success(validator_set): + assert validator_set.getTurnLength() == 1 + +def test_get_turn_length_after_update(validator_set): + __update_gov_address() + hex_value = padding_left(Web3.to_hex(2), 64) + tx = validator_set.updateParam('turnLength', hex_value) + assert validator_set.turnLength() == 2 + \ No newline at end of file From 39f99f4bcade2a0544a28c536b808131d2fe2e1d Mon Sep 17 00:00:00 2001 From: zfliex Date: Thu, 18 Sep 2025 10:36:53 +0800 Subject: [PATCH 22/27] fix audit feedback --- contracts/BitcoinStake.sol | 12 ++++++------ contracts/Channel.sol | 2 ++ contracts/mock/BitcoinStakeMock.sol | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/BitcoinStake.sol b/contracts/BitcoinStake.sol index f4ec8bc4c..785e72f99 100644 --- a/contracts/BitcoinStake.sol +++ b/contracts/BitcoinStake.sol @@ -634,11 +634,11 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar } } - function _applyDualStaking(uint256 coreAmount, uint256 bctAmount) internal view returns (uint256, uint256) { + function _applyDualStaking(uint256 coreAmount, uint256 btcAmount) internal view returns (uint256, uint256) { uint256 dsPercentage; - uint256 stakeRate = coreAmount / bctAmount; + uint256 stakeRate = coreAmount / btcAmount; (dsPercentage, stakeRate) = IBtcAgent(BTC_AGENT_ADDR).getGrade(stakeRate); - uint256 dualAmount = stakeRate * bctAmount; + uint256 dualAmount = stakeRate * btcAmount; if (coreAmount > dualAmount) { coreAmount -= dualAmount; } else { @@ -660,7 +660,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar function _collectReward(bytes32 txid, uint256 coreAmount, uint256 drRound, uint256 settleRound, bool claim) internal returns (uint256 reward, bool expired, int256 floatReward, uint256 remainingCoreAmount) { require(drRound != 0, "invalid deposit receipt"); require(settleRound < roundTag, "invalid settle round"); - uint256 bctAmount; + uint256 btcAmount; (settleRound, expired) = _getCalculateRound(txid, settleRound); uint256 ldPercentage; uint256 dsPercentage; @@ -670,7 +670,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar DepositReceipt storage dr = receiptMap[txid]; // full reward reward = (_getRoundAccruedReward(dr.candidate, settleRound) - _getRoundAccruedReward(dr.candidate, drRound)) * bt.amount / SatoshiPlusHelper.BTC_DECIMAL; - bctAmount = bt.amount; + btcAmount = bt.amount; dr.round = settleRound; if (reward != 0) { @@ -690,7 +690,7 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar reward = pReward; } - (remainingCoreAmount, dsPercentage) = _applyDualStaking(coreAmount, bctAmount); + (remainingCoreAmount, dsPercentage) = _applyDualStaking(coreAmount, btcAmount); pReward = reward * dsPercentage / SatoshiPlusHelper.DENOMINATOR; floatReward += pReward.toInt256() - reward.toInt256(); reward = pReward; diff --git a/contracts/Channel.sol b/contracts/Channel.sol index a9d4878cd..cd5f2e7fe 100644 --- a/contracts/Channel.sol +++ b/contracts/Channel.sol @@ -16,6 +16,7 @@ contract Channel is IChannel, System, IParamSubscriber { uint256 public constant INIT_REQUIRED_MARGIN = 1e22; uint256 public constant DEFAULT_COMMISSION_LIMIT = 2000; + uint256 public constant INIT_DUES = 1e20; // the refundable deposit uint256 public requiredMargin; @@ -64,6 +65,7 @@ contract Channel is IChannel, System, IParamSubscriber { function init() external onlyNotInit { requiredMargin = INIT_REQUIRED_MARGIN; commissionLimit = DEFAULT_COMMISSION_LIMIT; + dues = INIT_DUES; alreadyInit = true; } diff --git a/contracts/mock/BitcoinStakeMock.sol b/contracts/mock/BitcoinStakeMock.sol index cc44db9de..2f461e17a 100644 --- a/contracts/mock/BitcoinStakeMock.sol +++ b/contracts/mock/BitcoinStakeMock.sol @@ -96,8 +96,8 @@ contract BitcoinStakeMock is BitcoinStake { function getCalculateRoundMock(bytes32 txid, uint256 settleRound) external view returns (uint calculateRound, bool expired) { return _getCalculateRound(txid, settleRound); } - function applyDualStakingMock(uint256 coreAmount, uint256 bctAmount) external view returns (uint256, uint256) { - return _applyDualStaking(coreAmount, bctAmount); + function applyDualStakingMock(uint256 coreAmount, uint256 btcAmount) external view returns (uint256, uint256) { + return _applyDualStaking(coreAmount, btcAmount); } From ab52141327c443f282b3bff4896d02b239bbd6a9 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 18 Sep 2025 11:13:35 +0800 Subject: [PATCH 23/27] adjusting test cases based on audit feedback --- tests/test_channel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_channel.py b/tests/test_channel.py index 1d17072d8..4e6cde9d6 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -92,7 +92,7 @@ def register_channel(partner, fee_address=None, core_commission_rate=1000, btc_c def test_init(channel): assert channel.alreadyInit() is True assert channel.requiredMargin() == channel.INIT_REQUIRED_MARGIN() - assert channel.dues() == 0 + assert channel.dues() == channel.INIT_DUES() assert channel.commissionLimit() == channel.DEFAULT_COMMISSION_LIMIT() @@ -313,7 +313,7 @@ def test_reset_commission_unauthorized_caller(channel, set_channel_partner, stak def test_governance_update_required_margin(channel, gov_hub): - new_margin = Wei("15 ether") + new_margin = channel.dues() + Wei("15 ether") execute_proposal( channel.address, 0, From 46f6b03f553d75179e3af63a294f45319a674799 Mon Sep 17 00:00:00 2001 From: charles2023wood Date: Wed, 24 Sep 2025 18:30:48 +0800 Subject: [PATCH 24/27] Fix issue of member storage offset Signed-off-by: charles2023wood --- contracts/CandidateHub.sol | 101 +++++++++++++-------------- contracts/ValidatorSet.sol | 112 ++++++++++++++++-------------- contracts/ValidatorSet.template | 116 ++++++++++++++++++-------------- generate-validatorset.js | 1 + 4 files changed, 175 insertions(+), 155 deletions(-) diff --git a/contracts/CandidateHub.sol b/contracts/CandidateHub.sol index 87e4d85f7..a2beabeac 100644 --- a/contracts/CandidateHub.sol +++ b/contracts/CandidateHub.sol @@ -63,6 +63,9 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { uint256 public maxAlternateCount; mapping(address => uint256) public agentMap; + // Key is operate address. + // Value is CandidateEx + mapping(address => CandidateEx) public exMap; struct Candidate { address operateAddr; @@ -73,16 +76,11 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { uint256 status; uint256 commissionLastChangeRound; uint256 commissionLastRoundValue; - bytes voteAddr; - address agent; - Description description; } - struct Description { - string moniker; - string identity; - string website; - string details; + struct CandidateEx { + address agent; + bytes voteAddr; } modifier onlyOperator() { @@ -102,7 +100,6 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { event ConsensusAddressEdited(address indexed operateAddr, address newConsensusAddr); event CommissionRateEdited(address indexed operateAddr, uint256 newRate); event VoteAddressEdited(address indexed operateAddr, bytes newVoteAddr); - event DescriptionEdited(address indexed operateAddr, string moniker, string identity, string website, string details); event FeeAddressEdited(address indexed operateAddr, address newFeeAddr); /*********************** init **************************/ @@ -237,7 +234,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { Candidate storage c = candidateSet[index - 1]; consensusAddrList[i] = c.consensusAddr; feeAddrList[i] = c.feeAddr; - voteAddrList[i] = c.voteAddr; + voteAddrList[i] = exMap[validatorList[i]].voteAddr; if (scores[i] == 0) { commissionThousandthsList[i] = 1000; } else { @@ -294,7 +291,7 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { require(voteAddr.length == 48, "vote address length should be 48"); for (uint256 i = 0; i < candidateSize; i++) { - require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); + require(!BytesLib.equal(exMap[candidateSet[i].operateAddr].voteAddr, voteAddr), "vote address already exists"); } uint256 status = SET_CANDIDATE; @@ -306,11 +303,12 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { msg.value, status, roundTag, - commissionThousandths, - voteAddr, - address(0), - Description("", "", "", "") + commissionThousandths )); + exMap[msg.sender] = CandidateEx( + address(0), + voteAddr + ); operateMap[msg.sender] = candidateSize + 1; consensusMap[consensusAddr] = candidateSize + 1; @@ -336,41 +334,41 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { } function updateAgent(address newAgent) external onlyOperator { - uint256 index = operateMap[msg.sender]; - Candidate storage c = candidateSet[index - 1]; + address operateAddr = msg.sender; + uint256 index = operateMap[operateAddr]; require(newAgent != address(0), "agent address cannot be zero"); require(agentMap[newAgent] == 0, "agent address already exists"); - if (c.agent != address(0)) { - delete agentMap[c.agent]; + address oldAgent = exMap[operateAddr].agent; + if (oldAgent != address(0)) { + delete agentMap[oldAgent]; } agentMap[newAgent] = index; - c.agent = newAgent; - emit AgentUpdated(msg.sender, newAgent); + exMap[operateAddr].agent = newAgent; + emit AgentUpdated(operateAddr, newAgent); } - function removeAgent() external onlyOperator { - uint256 index = operateMap[msg.sender]; - Candidate storage c = candidateSet[index - 1]; - require(c.agent != address(0), "agent address does not exist"); - delete agentMap[c.agent]; - c.agent = address(0); + function removeAgent() external onlyOperator { + address operateAddr = msg.sender; + require(exMap[operateAddr].agent != address(0), "agent address does not exist"); + delete agentMap[exMap[operateAddr].agent]; + exMap[operateAddr].agent = address(0); } function editConsensusAddress(address newConsensusAddr) external { - (uint256 index, Candidate storage c) = getCandidate(); - + Candidate storage c = getCandidate(); + require(consensusMap[newConsensusAddr] == 0, "consensus already exists"); + consensusMap[newConsensusAddr] = consensusMap[c.consensusAddr]; delete consensusMap[c.consensusAddr]; c.consensusAddr = newConsensusAddr; - consensusMap[newConsensusAddr] = index; - emit ConsensusAddressEdited(msg.sender, newConsensusAddr); + emit ConsensusAddressEdited(c.operateAddr, newConsensusAddr); } function editCommissionRate(uint32 newRate) external { - (, Candidate storage c) = getCandidate(); + Candidate storage c = getCandidate(); require(newRate != 0 && newRate < 1000, "commissionThousandths should in range (0, 1000)"); @@ -387,31 +385,23 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { c.commissionLastRoundValue = c.commissionThousandths; } c.commissionThousandths = newRate; - emit CommissionRateEdited(msg.sender, newRate); + emit CommissionRateEdited(c.operateAddr, newRate); } function editVoteAddress(bytes calldata voteAddr) external { - (, Candidate storage c) = getCandidate(); - + Candidate storage c = getCandidate(); require(voteAddr.length == 48, "vote address length should be 48"); - + uint256 candidateSize = candidateSet.length; for (uint256 i = 0; i < candidateSize; i++) { - require(!BytesLib.equal(candidateSet[i].voteAddr, voteAddr), "vote address already exists"); + require(!BytesLib.equal(exMap[candidateSet[i].operateAddr].voteAddr, voteAddr), "vote address already exists"); } - c.voteAddr = voteAddr; - emit VoteAddressEdited(msg.sender, voteAddr); - } - - function editDescription(string calldata moniker, string calldata identity, string calldata website, string calldata details) external { - (, Candidate storage c) = getCandidate(); - - c.description = Description(moniker, identity, website, details); - emit DescriptionEdited(msg.sender, moniker, identity, website, details); + exMap[c.operateAddr].voteAddr = voteAddr; + emit VoteAddressEdited(c.operateAddr, voteAddr); } function editFeeAddress(address payable newFeeAddr) external onlyOperator { - (, Candidate storage c) = getCandidate(); + Candidate storage c = getCandidate(); require(newFeeAddr != address(0), "fee address cannot be zero"); c.feeAddr = newFeeAddr; @@ -453,14 +443,14 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { /*************************** internal methods ******************************/ - function getCandidate() internal view returns (uint256, Candidate storage) { + function getCandidate() internal view returns (Candidate storage) { uint256 index = operateMap[msg.sender]; if (index == 0) { // Check if sender is agent index = agentMap[msg.sender]; require(index != 0, "candidate does not exist"); } - return (index, candidateSet[index - 1]); + return candidateSet[index - 1]; } function changeStatus(Candidate storage c, uint256 newStatus) internal { @@ -476,19 +466,20 @@ contract CandidateHub is ICandidateHub, System, IParamSubscriber { emit unregistered(c.operateAddr, c.consensusAddr); - if (c.agent != address(0)) { - delete agentMap[c.agent]; + if (exMap[c.operateAddr].agent != address(0)) { + delete agentMap[exMap[c.operateAddr].agent]; } delete operateMap[c.operateAddr]; delete consensusMap[c.consensusAddr]; + delete exMap[c.operateAddr]; if (index != candidateSet.length) { candidateSet[index-1] = candidateSet[candidateSet.length - 1]; - operateMap[candidateSet[index-1].operateAddr] = index; - consensusMap[candidateSet[index-1].consensusAddr] = index; - if (candidateSet[index-1].agent != address(0)) { - agentMap[candidateSet[index-1].agent] = index; + operateMap[c.operateAddr] = index; + consensusMap[c.consensusAddr] = index; + if (exMap[c.operateAddr].agent != address(0)) { + agentMap[exMap[c.operateAddr].agent] = index; } } candidateSet.pop(); diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 3869fddce..45323f338 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -46,12 +46,19 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public turnLength; + // key is the `consensusAddress` of `Validator`, + // value is the extension information of Validator. + mapping(address => ValidatorEx) public exMap; + struct Validator { address operateAddress; address consensusAddress; address payable feeAddress; uint256 commissionThousandths; uint256 income; + } + + struct ValidatorEx { bytes voteAddr; uint256 voteWeight; uint256 enterMaintenanceHeight; @@ -92,13 +99,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { /*********************** init **************************/ function init() external onlyNotInit { - (Validator[] memory validatorSet, bool valid) = decodeValidatorSet(INIT_VALIDATORSET_BYTES); + (bool valid) = decodeValidatorSet(INIT_VALIDATORSET_BYTES); require(valid, "failed to parse init validatorSet"); - uint256 validatorSize = validatorSet.length; - for (uint256 i = 0; i < validatorSize; i++) { - currentValidatorSet.push(validatorSet[i]); - currentValidatorSetMap[validatorSet[i].consensusAddress] = i + 1; - } + blockReward = BLOCK_REWARD; blockRewardIncentivePercent = BLOCK_REWARD_INCENTIVE_PERCENT; turnLength = INIT_TURN_LENGTH; @@ -146,8 +149,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { for (uint256 i; i < valAddrs.length; ++i) { uint256 index = currentValidatorSetMap[valAddrs[i]]; - if (index > 0) { - currentValidatorSet[index - 1].voteWeight += weights[i]; + if (index != 0) { + exMap[valAddrs[i]].voteWeight += weights[i]; } } } @@ -156,14 +159,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 len = currentValidatorSet.length; address[] memory valAddrs = new address[](len); uint256 j; + address consensusAddress; for (uint256 i; i < len; ++i) { - if (currentValidatorSet[i].enterMaintenanceHeight != 0) { - valAddrs[j++] = currentValidatorSet[i].consensusAddress; + consensusAddress = currentValidatorSet[i].consensusAddress; + if (exMap[consensusAddress].enterMaintenanceHeight != 0) { + valAddrs[j++] = consensusAddress; } } for (uint256 i; i < j; ++i) { - _exitMaintenance(valAddrs[i], currentValidatorSetMap[valAddrs[i]]); + _exitMaintenance(valAddrs[i]); } } @@ -181,7 +186,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 incentiveValue = (v.income * blockRewardIncentivePercent) / 100; incentiveSum += incentiveValue; v.income -= incentiveValue; - voteWeightSum += v.voteWeight; + voteWeightSum += exMap[v.consensusAddress].voteWeight; } ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{ value: incentiveSum }(); @@ -220,8 +225,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { if (voteRewardSum != 0) { for (uint256 i = 0; i < currentValidatorSet.length; i++) { Validator memory v = currentValidatorSet[i]; - if (v.voteWeight != 0) { - uint256 reward = voteRewardSum * v.voteWeight / voteWeightSum; + uint256 voteWeight = exMap[v.consensusAddress].voteWeight; + if (voteWeight != 0) { + uint256 reward = voteRewardSum * voteWeight / voteWeightSum; bool success = v.feeAddress.send(reward); if(success) { emit voteRewardTransfer(v.operateAddress, v.feeAddress, reward); @@ -265,6 +271,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 currentLength = consensusAddrList.length; for (i = 0; i < lastLength; i++) { + delete exMap[currentValidatorSet[i].consensusAddress]; delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; } for (i = currentLength; i < lastLength; i++) { @@ -278,9 +285,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { v.feeAddress = feeAddrList[i]; v.commissionThousandths = commissionThousandthsList[i]; v.income = 0; - v.voteAddr = voteAddrList[i]; - v.voteWeight = 0; - v.enterMaintenanceHeight = 0; + exMap[v.consensusAddress].voteAddr = voteAddrList[i]; if (i >= lastLength) { currentValidatorSet.push(v); @@ -297,9 +302,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { if (index == 0) { return false; } - Validator storage val = currentValidatorSet[index-1]; + address consensusAddress = currentValidatorSet[index-1].consensusAddress; uint256 working = getWorkingCount(); - if (val.enterMaintenanceHeight != 0 || working <= 1 || validatorCount >= working || validatorCount == 0) { + if (exMap[consensusAddress].enterMaintenanceHeight != 0 || working <= 1 || validatorCount >= working || validatorCount == 0) { return false; } @@ -310,7 +315,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 index = getValidatorIndexFromOps(msg.sender); require(index > 0, "not a validator"); require(canEnterMaintenance(index), "can not enter Temporary Maintenance"); - _enterMaintenance(currentValidatorSet[index-1].consensusAddress, index); + _enterMaintenance(currentValidatorSet[index-1].consensusAddress); } function enterMaintenance(address val) external override onlySlash { @@ -320,16 +325,17 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } if (canEnterMaintenance(index)) { - _enterMaintenance(val, index); + _enterMaintenance(val); } } function exitMaintenance() external { uint256 index = getValidatorIndexFromOps(msg.sender); - require(index > 0, "not a validator"); - require(currentValidatorSet[index-1].enterMaintenanceHeight != 0, "not in Temporary Maintenance"); + require(index != 0, "not a validator"); + address consensusAddress = currentValidatorSet[index-1].consensusAddress; + require(exMap[consensusAddress].enterMaintenanceHeight != 0, "not in Temporary Maintenance"); - _exitMaintenance(currentValidatorSet[index-1].consensusAddress, index); + _exitMaintenance(consensusAddress); } /// Get list of validators in the current round @@ -368,7 +374,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 len = rankedValidatorList.length; for (uint256 i = 0; i < len; ++i) { uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; - if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + if (index != 0 && exMap[rankedValidatorList[i]].enterMaintenanceHeight == 0) { ++workingRankedValidatorLen; } } @@ -376,7 +382,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 j = 0; for (uint256 i = 0; i < len; ++i) { uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; - if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + if (index != 0 && exMap[rankedValidatorList[i]].enterMaintenanceHeight == 0) { workingRankedValidatorList[j++] = currentValidatorSet[index-1].consensusAddress; } } @@ -391,10 +397,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { bytes[] memory voteAddrs = new bytes[](validatorSize); uint256 pushedCount; + address consensusAddress; for (uint256 i; pushedCount < validatorSize && i < workingRankedValidatorLen; ++i) { uint256 index = currentValidatorSetMap[workingRankedValidatorList[i]]; - consensusAddrs[pushedCount] = currentValidatorSet[index-1].consensusAddress; - voteAddrs[pushedCount] = currentValidatorSet[index-1].voteAddr; + consensusAddress = currentValidatorSet[index-1].consensusAddress; + consensusAddrs[pushedCount] = consensusAddress; + voteAddrs[pushedCount] = exMap[consensusAddress].voteAddr; ++pushedCount; } @@ -445,7 +453,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { for (uint256 i = 0; i < len; i++) { consensusAddrs[i] = currentValidatorSet[i].consensusAddress; - voteAddrs[i] = currentValidatorSet[i].voteAddr; + voteAddrs[i] = exMap[consensusAddrs[i]].voteAddr; } return (consensusAddrs, voteAddrs); @@ -504,6 +512,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { address operateAddress = currentValidatorSet[index].operateAddress; emit validatorFelony(operateAddress, income); delete currentValidatorSetMap[validator]; + delete exMap[validator]; // It is ok that the validatorSet is not in order. if (index != currentValidatorSet.length - 1) { currentValidatorSet[index] = currentValidatorSet[currentValidatorSet.length - 1]; @@ -612,8 +621,10 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { function getWorkingCount() public view returns (uint256) { uint256 len = currentValidatorSet.length; uint256 working; + address consensusAddress; for (uint256 i; i < len; ++i) { - if (currentValidatorSet[i].enterMaintenanceHeight == 0) { + consensusAddress = currentValidatorSet[i].consensusAddress; + if (exMap[consensusAddress].enterMaintenanceHeight == 0) { ++working; } } @@ -625,9 +636,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 working = getWorkingCount(); address[] memory workingValidators = new address[](working); uint256 j; + address consensusAddress; for (uint256 i ; i < len; ++i) { - if (currentValidatorSet[i].enterMaintenanceHeight == 0) { - workingValidators[j++] = currentValidatorSet[i].consensusAddress; + consensusAddress = currentValidatorSet[i].consensusAddress; + if (exMap[consensusAddress].enterMaintenanceHeight == 0) { + workingValidators[j++] = consensusAddress; } } return workingValidators; @@ -643,22 +656,23 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } //rlp encode & decode function - function decodeValidatorSet(bytes memory msgBytes) internal pure returns (Validator[] memory, bool) { + function decodeValidatorSet(bytes memory msgBytes) internal returns (bool) { RLPDecode.RLPItem[] memory items = msgBytes.toRLPItem().toList(); uint256 itemSize = items.length; - Validator[] memory validatorSet = new Validator[](itemSize); - for (uint256 j = 0; j < itemSize; j++) { - (Validator memory val, bool ok) = decodeValidator(items[j]); + for (uint256 i = 0; i < itemSize; i++) { + (Validator memory val, bytes memory voteAddr, bool ok) = decodeValidator(items[i]); if (!ok) { - return (validatorSet, false); + return false; } - validatorSet[j] = val; + currentValidatorSet.push(val); + currentValidatorSetMap[val.consensusAddress] = i + 1; + exMap[val.consensusAddress].voteAddr = voteAddr; } bool success = itemSize != 0; - return (validatorSet, success); + return success; } - function decodeValidator(RLPDecode.RLPItem memory itemValidator) internal pure returns (Validator memory, bool) { + function decodeValidator(RLPDecode.RLPItem memory itemValidator) internal pure returns (Validator memory, bytes memory voteAddr, bool) { Validator memory validator; RLPDecode.Iterator memory iter = itemValidator.iterator(); bool success = iter.hasNext(); @@ -666,30 +680,30 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { validator.consensusAddress = iter.next().toAddress(); validator.feeAddress = payable(iter.next().toAddress()); validator.operateAddress = validator.feeAddress; - validator.voteAddr = iter.next().toBytes(); + voteAddr = iter.next().toBytes(); validator.commissionThousandths = 1000; - if (validator.voteAddr.length != 48) { + if (voteAddr.length != 48) { success = false; } } - return (validator, success); + return (validator, voteAddr, success); } - function _enterMaintenance(address validator, uint256 index) internal { - currentValidatorSet[index-1].enterMaintenanceHeight = block.number; + function _enterMaintenance(address validator) internal { + exMap[validator].enterMaintenanceHeight = block.number; emit validatorEnterMaintenance(validator); } - function _exitMaintenance(address validator, uint256 index) internal { + function _exitMaintenance(address validator) internal { uint256 working = currentValidatorSet.length; if (working > validatorCount) { working = validatorCount; } - uint256 slashCount = (block.number - currentValidatorSet[index-1].enterMaintenanceHeight) / working * maintainSlashPercent / 100; + uint256 slashCount = (block.number - exMap[validator].enterMaintenanceHeight) / working * maintainSlashPercent / 100; + + exMap[validator].enterMaintenanceHeight = 0; - currentValidatorSet[index-1].enterMaintenanceHeight = 0; - if (slashCount != 0) { ISlashIndicator(SLASH_CONTRACT_ADDR).exitMaintenanceSlash(validator, slashCount); } diff --git a/contracts/ValidatorSet.template b/contracts/ValidatorSet.template index 220b5a26c..a02339b4c 100644 --- a/contracts/ValidatorSet.template +++ b/contracts/ValidatorSet.template @@ -22,8 +22,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public constant BLOCK_REWARD = 3e18; uint256 public constant BLOCK_REWARD_INCENTIVE_PERCENT = 10; uint256 public constant REDUCE_FACTOR = 9639; - uint256 public {% if not mock %}constant{% endif %} SUBSIDY_REDUCE_INTERVAL = 10512000; - uint256 public constant INIT_TURN_LENGTH = {{initTurnLength}}; + uint256 public constant SUBSIDY_REDUCE_INTERVAL = 10512000; + uint256 public constant INIT_TURN_LENGTH = 1; bytes public constant INIT_VALIDATORSET_BYTES = hex"{{initValidatorSetBytes}}"; @@ -46,12 +46,19 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 public turnLength; + // key is the `consensusAddress` of `Validator`, + // value is the extension information of Validator. + mapping(address => ValidatorEx) public exMap; + struct Validator { address operateAddress; address consensusAddress; address payable feeAddress; uint256 commissionThousandths; uint256 income; + } + + struct ValidatorEx { bytes voteAddr; uint256 voteWeight; uint256 enterMaintenanceHeight; @@ -92,13 +99,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { /*********************** init **************************/ function init() external onlyNotInit { - (Validator[] memory validatorSet, bool valid) = decodeValidatorSet(INIT_VALIDATORSET_BYTES); + (bool valid) = decodeValidatorSet(INIT_VALIDATORSET_BYTES); require(valid, "failed to parse init validatorSet"); - uint256 validatorSize = validatorSet.length; - for (uint256 i = 0; i < validatorSize; i++) { - currentValidatorSet.push(validatorSet[i]); - currentValidatorSetMap[validatorSet[i].consensusAddress] = i + 1; - } + blockReward = BLOCK_REWARD; blockRewardIncentivePercent = BLOCK_REWARD_INCENTIVE_PERCENT; turnLength = INIT_TURN_LENGTH; @@ -146,8 +149,8 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { for (uint256 i; i < valAddrs.length; ++i) { uint256 index = currentValidatorSetMap[valAddrs[i]]; - if (index > 0) { - currentValidatorSet[index - 1].voteWeight += weights[i]; + if (index != 0) { + exMap[valAddrs[i]].voteWeight += weights[i]; } } } @@ -156,14 +159,16 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 len = currentValidatorSet.length; address[] memory valAddrs = new address[](len); uint256 j; + address consensusAddress; for (uint256 i; i < len; ++i) { - if (currentValidatorSet[i].enterMaintenanceHeight != 0) { - valAddrs[j++] = currentValidatorSet[i].consensusAddress; + consensusAddress = currentValidatorSet[i].consensusAddress; + if (exMap[consensusAddress].enterMaintenanceHeight != 0) { + valAddrs[j++] = consensusAddress; } } for (uint256 i; i < j; ++i) { - _exitMaintenance(valAddrs[i], currentValidatorSetMap[valAddrs[i]]); + _exitMaintenance(valAddrs[i]); } } @@ -181,7 +186,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 incentiveValue = (v.income * blockRewardIncentivePercent) / 100; incentiveSum += incentiveValue; v.income -= incentiveValue; - voteWeightSum += v.voteWeight; + voteWeightSum += exMap[v.consensusAddress].voteWeight; } ISystemReward(SYSTEM_REWARD_ADDR).receiveRewards{ value: incentiveSum }(); @@ -220,8 +225,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { if (voteRewardSum != 0) { for (uint256 i = 0; i < currentValidatorSet.length; i++) { Validator memory v = currentValidatorSet[i]; - if (v.voteWeight != 0) { - uint256 reward = voteRewardSum * v.voteWeight / voteWeightSum; + uint256 voteWeight = exMap[v.consensusAddress].voteWeight; + if (voteWeight != 0) { + uint256 reward = voteRewardSum * voteWeight / voteWeightSum; bool success = v.feeAddress.send(reward); if(success) { emit voteRewardTransfer(v.operateAddress, v.feeAddress, reward); @@ -265,6 +271,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 currentLength = consensusAddrList.length; for (i = 0; i < lastLength; i++) { + delete exMap[currentValidatorSet[i].consensusAddress]; delete currentValidatorSetMap[currentValidatorSet[i].consensusAddress]; } for (i = currentLength; i < lastLength; i++) { @@ -278,9 +285,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { v.feeAddress = feeAddrList[i]; v.commissionThousandths = commissionThousandthsList[i]; v.income = 0; - v.voteAddr = voteAddrList[i]; - v.voteWeight = 0; - v.enterMaintenanceHeight = 0; + exMap[v.consensusAddress].voteAddr = voteAddrList[i]; if (i >= lastLength) { currentValidatorSet.push(v); @@ -297,9 +302,9 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { if (index == 0) { return false; } - Validator storage val = currentValidatorSet[index-1]; + address consensusAddress = currentValidatorSet[index-1].consensusAddress; uint256 working = getWorkingCount(); - if (val.enterMaintenanceHeight != 0 || working <= 1 || validatorCount >= working || validatorCount == 0) { + if (exMap[consensusAddress].enterMaintenanceHeight != 0 || working <= 1 || validatorCount >= working || validatorCount == 0) { return false; } @@ -310,7 +315,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 index = getValidatorIndexFromOps(msg.sender); require(index > 0, "not a validator"); require(canEnterMaintenance(index), "can not enter Temporary Maintenance"); - _enterMaintenance(currentValidatorSet[index-1].consensusAddress, index); + _enterMaintenance(currentValidatorSet[index-1].consensusAddress); } function enterMaintenance(address val) external override onlySlash { @@ -320,16 +325,17 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } if (canEnterMaintenance(index)) { - _enterMaintenance(val, index); + _enterMaintenance(val); } } function exitMaintenance() external { uint256 index = getValidatorIndexFromOps(msg.sender); - require(index > 0, "not a validator"); - require(currentValidatorSet[index-1].enterMaintenanceHeight != 0, "not in Temporary Maintenance"); + require(index != 0, "not a validator"); + address consensusAddress = currentValidatorSet[index-1].consensusAddress; + require(exMap[consensusAddress].enterMaintenanceHeight != 0, "not in Temporary Maintenance"); - _exitMaintenance(currentValidatorSet[index-1].consensusAddress, index); + _exitMaintenance(consensusAddress); } /// Get list of validators in the current round @@ -368,7 +374,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 len = rankedValidatorList.length; for (uint256 i = 0; i < len; ++i) { uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; - if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + if (index != 0 && exMap[rankedValidatorList[i]].enterMaintenanceHeight == 0) { ++workingRankedValidatorLen; } } @@ -376,7 +382,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 j = 0; for (uint256 i = 0; i < len; ++i) { uint256 index = currentValidatorSetMap[rankedValidatorList[i]]; - if (index != 0 && currentValidatorSet[index-1].enterMaintenanceHeight == 0) { + if (index != 0 && exMap[rankedValidatorList[i]].enterMaintenanceHeight == 0) { workingRankedValidatorList[j++] = currentValidatorSet[index-1].consensusAddress; } } @@ -391,10 +397,12 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { bytes[] memory voteAddrs = new bytes[](validatorSize); uint256 pushedCount; + address consensusAddress; for (uint256 i; pushedCount < validatorSize && i < workingRankedValidatorLen; ++i) { uint256 index = currentValidatorSetMap[workingRankedValidatorList[i]]; - consensusAddrs[pushedCount] = currentValidatorSet[index-1].consensusAddress; - voteAddrs[pushedCount] = currentValidatorSet[index-1].voteAddr; + consensusAddress = currentValidatorSet[index-1].consensusAddress; + consensusAddrs[pushedCount] = consensusAddress; + voteAddrs[pushedCount] = exMap[consensusAddress].voteAddr; ++pushedCount; } @@ -445,7 +453,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { for (uint256 i = 0; i < len; i++) { consensusAddrs[i] = currentValidatorSet[i].consensusAddress; - voteAddrs[i] = currentValidatorSet[i].voteAddr; + voteAddrs[i] = exMap[consensusAddrs[i]].voteAddr; } return (consensusAddrs, voteAddrs); @@ -504,6 +512,7 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { address operateAddress = currentValidatorSet[index].operateAddress; emit validatorFelony(operateAddress, income); delete currentValidatorSetMap[validator]; + delete exMap[validator]; // It is ok that the validatorSet is not in order. if (index != currentValidatorSet.length - 1) { currentValidatorSet[index] = currentValidatorSet[currentValidatorSet.length - 1]; @@ -612,8 +621,10 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { function getWorkingCount() public view returns (uint256) { uint256 len = currentValidatorSet.length; uint256 working; + address consensusAddress; for (uint256 i; i < len; ++i) { - if (currentValidatorSet[i].enterMaintenanceHeight == 0) { + consensusAddress = currentValidatorSet[i].consensusAddress; + if (exMap[consensusAddress].enterMaintenanceHeight == 0) { ++working; } } @@ -625,9 +636,11 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { uint256 working = getWorkingCount(); address[] memory workingValidators = new address[](working); uint256 j; + address consensusAddress; for (uint256 i ; i < len; ++i) { - if (currentValidatorSet[i].enterMaintenanceHeight == 0) { - workingValidators[j++] = currentValidatorSet[i].consensusAddress; + consensusAddress = currentValidatorSet[i].consensusAddress; + if (exMap[consensusAddress].enterMaintenanceHeight == 0) { + workingValidators[j++] = consensusAddress; } } return workingValidators; @@ -643,22 +656,23 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { } //rlp encode & decode function - function decodeValidatorSet(bytes memory msgBytes) internal pure returns (Validator[] memory, bool) { + function decodeValidatorSet(bytes memory msgBytes) internal returns (bool) { RLPDecode.RLPItem[] memory items = msgBytes.toRLPItem().toList(); uint256 itemSize = items.length; - Validator[] memory validatorSet = new Validator[](itemSize); - for (uint256 j = 0; j < itemSize; j++) { - (Validator memory val, bool ok) = decodeValidator(items[j]); + for (uint256 i = 0; i < itemSize; i++) { + (Validator memory val, bytes memory voteAddr, bool ok) = decodeValidator(items[i]); if (!ok) { - return (validatorSet, false); + return false; } - validatorSet[j] = val; + currentValidatorSet.push(val); + currentValidatorSetMap[val.consensusAddress] = i + 1; + exMap[val.consensusAddress].voteAddr = voteAddr; } bool success = itemSize != 0; - return (validatorSet, success); + return success; } - function decodeValidator(RLPDecode.RLPItem memory itemValidator) internal pure returns (Validator memory, bool) { + function decodeValidator(RLPDecode.RLPItem memory itemValidator) internal pure returns (Validator memory, bytes memory voteAddr, bool) { Validator memory validator; RLPDecode.Iterator memory iter = itemValidator.iterator(); bool success = iter.hasNext(); @@ -666,30 +680,30 @@ contract ValidatorSet is IValidatorSet, System, IParamSubscriber { validator.consensusAddress = iter.next().toAddress(); validator.feeAddress = payable(iter.next().toAddress()); validator.operateAddress = validator.feeAddress; - validator.voteAddr = iter.next().toBytes(); + voteAddr = iter.next().toBytes(); validator.commissionThousandths = 1000; - if (validator.voteAddr.length != 48) { + if (voteAddr.length != 48) { success = false; } } - return (validator, success); + return (validator, voteAddr, success); } - function _enterMaintenance(address validator, uint256 index) internal { - currentValidatorSet[index-1].enterMaintenanceHeight = block.number; + function _enterMaintenance(address validator) internal { + exMap[validator].enterMaintenanceHeight = block.number; emit validatorEnterMaintenance(validator); } - function _exitMaintenance(address validator, uint256 index) internal { + function _exitMaintenance(address validator) internal { uint256 working = currentValidatorSet.length; if (working > validatorCount) { working = validatorCount; } - uint256 slashCount = (block.number - currentValidatorSet[index-1].enterMaintenanceHeight) / working * maintainSlashPercent / 100; + uint256 slashCount = (block.number - exMap[validator].enterMaintenanceHeight) / working * maintainSlashPercent / 100; + + exMap[validator].enterMaintenanceHeight = 0; - currentValidatorSet[index-1].enterMaintenanceHeight = 0; - if (slashCount != 0) { ISlashIndicator(SLASH_CONTRACT_ADDR).exitMaintenanceSlash(validator, slashCount); } diff --git a/generate-validatorset.js b/generate-validatorset.js index 08d51adf1..f5e1b458a 100644 --- a/generate-validatorset.js +++ b/generate-validatorset.js @@ -43,3 +43,4 @@ const templateString = fs.readFileSync(program.template).toString(); const resultString = nunjucks.renderString(templateString, data); fs.writeFileSync(program.output, resultString); console.log("ValidatorSet file updated."); + From b37234afcaa61c3d147d67a5aea16c7bd325803c Mon Sep 17 00:00:00 2001 From: falcon Date: Mon, 29 Sep 2025 11:47:33 +0800 Subject: [PATCH 25/27] Fix issue of member storage offset --- contracts/BitcoinStake.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/BitcoinStake.sol b/contracts/BitcoinStake.sol index 785e72f99..8d82db3de 100644 --- a/contracts/BitcoinStake.sol +++ b/contracts/BitcoinStake.sol @@ -78,10 +78,10 @@ contract BitcoinStake is IBitcoinStake, System, IParamSubscriber, ReentrancyGuar struct BtcTx { uint64 amount; uint32 outputIndex; - uint32 channelId; uint64 blockTimestamp; uint32 lockTime; uint32 usedHeight; + uint32 channelId; } struct Delegator { From eec3201b7c2c6671f79b8a2dc021204633e392f5 Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Thu, 25 Sep 2025 14:00:16 +0800 Subject: [PATCH 26/27] fix tests for storage offset change --- contracts/mock/CandidateHubMock.sol | 13 +- contracts/mock/ValidatorSetMock.sol | 43 ++++-- tests/common.py | 24 +-- tests/test_candidate_hub.py | 189 +++++++++++++---------- tests/test_channel.py | 4 +- tests/test_claim_reward.py | 36 ----- tests/test_slash_indicator.py | 50 +++++-- tests/test_stake_hub_unit.py | 101 ++++++++++++- tests/test_validator_set.py | 223 +++++++++++++++++++--------- 9 files changed, 452 insertions(+), 231 deletions(-) diff --git a/contracts/mock/CandidateHubMock.sol b/contracts/mock/CandidateHubMock.sol index 4f8965815..62584f7a5 100644 --- a/contracts/mock/CandidateHubMock.sol +++ b/contracts/mock/CandidateHubMock.sol @@ -168,12 +168,13 @@ contract CandidateHubMock is CandidateHub { margin: msg.value, status: status, commissionLastChangeRound: roundTag, - commissionLastRoundValue: commissionThousandths, - voteAddr: voteAddr, - agent: address(0), - description: Description("", "", "", "") + commissionLastRoundValue: commissionThousandths }) ); + exMap[operateAddr] = CandidateEx({ + voteAddr: voteAddr, + agent: address(0) + }); uint256 index = candidateSet.length; operateMap[operateAddr] = index; consensusMap[consensusAddr] = index; @@ -202,4 +203,8 @@ contract CandidateHubMock is CandidateHub { return getAlternateCount(maxAlternateCount, count, candidateSize); } receive() external payable {} + // for unit test + function removeCandidateMock(uint256 index) external { + removeCandidate(index); + } } diff --git a/contracts/mock/ValidatorSetMock.sol b/contracts/mock/ValidatorSetMock.sol index 9dd467ff0..712782370 100644 --- a/contracts/mock/ValidatorSetMock.sol +++ b/contracts/mock/ValidatorSetMock.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.4; import "../ValidatorSet.sol"; contract ValidatorSetMock is ValidatorSet { + uint256 public subsidyReduceInterval = 10512000; + function developmentInit() external { blockReward = blockReward / 1e14; voteRewardPercent = 10; @@ -14,24 +16,16 @@ contract ValidatorSetMock is ValidatorSet { delete currentValidatorSet; bytes memory initValidatorSet = hex"f901d1f85b9401bca3615d24d3c638836691517b2b9b49b054b1943ae030dc3717c66f63d6e8f1d1508a5c941ff46db099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b94a458499604a85e90225a14946f36368ae24df16d94de442f5ba55687a24f04419424e0dc2593cc9f4cb099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b945e00c0d5c4c10d4c805aba878d51129a89d513e094cb089be171e256acdaac1ebbeb32ffba0dd438eeb099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b941cd652bc64af3f09b490daae27f46e53726ce230940a53b7e0ffd97357e444b85f4d683c1d8e22879ab099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8f85b94da37ccecbb2d7c83ae27ee2bebfe8ebce162c60094d82c24274ebbfe438788d684dc6034c3c67664a4b099a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8"; - bytes memory initVoteAddress = hex"99a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8"; - (Validator[] memory validatorSet, bool valid) = decodeValidatorSet(initValidatorSet); + (bool valid) = decodeValidatorSet(initValidatorSet); require(valid, "failed to parse init validatorSet"); - uint256 validatorSize = validatorSet.length; - for (uint256 i = 0; i < validatorSize; i++) { - validatorSet[i].voteAddr = initVoteAddress; - validatorSet[i].voteWeight = 0; - currentValidatorSet.push(validatorSet[i]); - currentValidatorSetMap[validatorSet[i].consensusAddress] = i + 1; - } } function updateBlockReward(uint256 _blockReward) external { blockReward = _blockReward; } - function updateSubsidyReduceInterval(uint256 _internal) external { - SUBSIDY_REDUCE_INTERVAL = _internal; + function updateSubsidyReduceInterval(uint256 _subsidyReduceInterval) external { + subsidyReduceInterval = _subsidyReduceInterval; } function jailValidator(address operateAddress, uint256 round, uint256 fine) external { @@ -43,6 +37,11 @@ contract ValidatorSetMock is ValidatorSet { require(index > 0, "no match validator"); return currentValidatorSet[index - 1]; } + function getValidatorExByConsensus(address consensus) external view returns (ValidatorEx memory) { + uint index = currentValidatorSetMap[consensus]; + require(index > 0, "no match validator"); + return exMap[consensus]; + } function getVoteRewardPercent() external view returns (uint256) { return voteRewardPercent; @@ -84,4 +83,26 @@ contract ValidatorSetMock is ValidatorSet { } IStakeHub(STAKE_HUB_ADDR).addRoundReward{value: rewardSum}(agentList, rewardList, roundTag); } + function decodeValidatorSetMock(bytes memory initValidatorSet) external returns (bool) { + return decodeValidatorSet(initValidatorSet); + } + function depositMock(address valAddr) external payable onlyCoinbase onlyInit onlyZeroGasPrice { + if (block.number % subsidyReduceInterval == 0) { + blockReward = blockReward * REDUCE_FACTOR / 10000; + } + uint256 value = msg.value; + if (address(this).balance >= totalInCome + value + blockReward) { + value += blockReward; + } + uint256 index = currentValidatorSetMap[valAddr]; + if (index != 0) { + Validator storage validator = currentValidatorSet[index - 1]; + totalInCome = totalInCome + value; + validator.income = validator.income + value; + emit validatorDeposit(valAddr, value); + } else { + emit deprecatedDeposit(valAddr, value); + } + } + } diff --git a/tests/common.py b/tests/common.py index bcfc120a7..59a38d8cf 100644 --- a/tests/common.py +++ b/tests/common.py @@ -28,29 +28,25 @@ def get_candidate(operator=None): return CandidateHubMock[0].candidateSet(idx - 1).dict() -def chain_deposit(miners, tx_fee=100,deposit_count=1): +def chain_deposit(miners, tx_fee=100, deposit_count=1): if isinstance(miners, list): for miner in miners: for _ in range(deposit_count): - tx = ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) + tx = ValidatorSetMock[0].deposit(miner, {"value": tx_fee, "from": accounts[99]}) else: for _ in range(deposit_count): tx = ValidatorSetMock[0].deposit(miners, {"value": tx_fee, "from": accounts[99]}) - - + def chain_vote(miners, weights): - if isinstance(miners, list): - for miner in miners: - ValidatorSetMock[0].vote(miner, weights, {"from": accounts[99]}) - else: - ValidatorSetMock[0].vote(miners, weights, {"from": accounts[99]}) + ValidatorSetMock[0].vote(miners, weights, {"from": accounts[99]}) def chain_get_validator_consensus(): consensus, vote_addresses = ValidatorSetMock[0].getValidatorsAndVoteAddresses() return consensus + def turn_round(miners: list = None, tx_fee=100, round_count=1, weights=None): if miners is None: miners = [] @@ -77,11 +73,13 @@ def execute_proposal(target, value, signature, calldata, msg): GovHubMock[0].execute(proposal_id) return proposal_id + def expect_validator_consensus(consensuses): assert len(chain_get_validator_consensus()) == len(consensuses) for consensus in chain_get_validator_consensus(): assert consensus in consensuses + def register_relayer(relayer_address=None): if relayer_address is None: relayer_address = accounts[0] @@ -121,8 +119,8 @@ def claim_stake_and_relay_reward(account): return tx0 -def slash_validator(consensus,slash_type='felony'): - tx =None +def slash_validator(consensus, slash_type='felony'): + tx = None slash_indicator = SlashIndicatorMock[0] if slash_type == 'felony': slash_threshold = slash_indicator.felonyThreshold() @@ -136,19 +134,23 @@ def slash_validator(consensus,slash_type='felony'): assert 'validatorMisdemeanor' in tx.events return tx + def refuse_delegate(operator): tx = CandidateHubMock[0].refuseDelegate({'from': operator}) return tx + def accept_delegate(operator): tx = CandidateHubMock[0].acceptDelegate({'from': operator}) return tx + def enter_maintenance(operator): tx = ValidatorSetMock[0].enterMaintenance({'from': operator}) assert 'validatorEnterMaintenance' in tx.events return tx + def exit_maintenance(operator): tx = ValidatorSetMock[0].exitMaintenance({'from': operator}) assert 'validatorExitMaintenance' in tx.events diff --git a/tests/test_candidate_hub.py b/tests/test_candidate_hub.py index 7fce456a1..72dc2b6ea 100644 --- a/tests/test_candidate_hub.py +++ b/tests/test_candidate_hub.py @@ -208,6 +208,7 @@ def test_getRoundTag_success(candidate_hub): assert round_tag == init_round +# turnRound def test_turnRound_after_new_validator(candidate_hub, set_candidate): operators, consensuses = set_candidate turn_round() @@ -278,8 +279,36 @@ def test_turnround_update_voteaddrlist_success(candidate_hub, validator_set, sla assert validator_set.getValidatorsAndVoteAddresses()[0] == consensuses assert validator_set.getValidatorsAndVoteAddresses()[1] == vote_address_list turn_round(consensuses) - assert validator_set.currentValidatorSet(0) == [operators[0], consensuses[0], operators[0], 1000, 0, - vote_address_list[0], 0, 0] + assert validator_set.currentValidatorSet(0) == [operators[0], consensuses[0], operators[0], 1000, 0] + assert validator_set.exMap(consensuses[0])['voteAddr'] == vote_address_list[0] + assert validator_set.exMap(consensuses[0])['voteWeight'] == 0 + assert validator_set.exMap(consensuses[0])['enterMaintenanceHeight'] == 0 + + +def test_turnRound_update_validatorEx_success(candidate_hub, validator_set, slash_indicator): + operators = [] + consensuses = [] + vote_address_list = [random_vote_address() for _ in range(3)] + for index, operator in enumerate(accounts[5:8]): + operators.append(operator) + consensuses.append(register_candidate(operator=operator, vote_address=vote_address_list[index])) + turn_round(consensuses) + weights = [30, 50, 80] + chain_vote(consensuses, weights) + for i in range(3): + assert validator_set.exMap(consensuses[i])['voteAddr'] == vote_address_list[i] + assert validator_set.exMap(consensuses[i])['voteWeight'] == weights[i] + assert validator_set.exMap(consensuses[i])['enterMaintenanceHeight'] == 0 + turn_round(consensuses) + for i in range(3): + assert validator_set.exMap(consensuses[i])['voteAddr'] == vote_address_list[i] + assert validator_set.exMap(consensuses[i])['voteWeight'] == 0 + assert validator_set.exMap(consensuses[i])['enterMaintenanceHeight'] == 0 + chain_vote(consensuses, weights) + for i in range(3): + assert validator_set.exMap(consensuses[i])['voteAddr'] == vote_address_list[i] + assert validator_set.exMap(consensuses[i])['voteWeight'] == weights[i] + assert validator_set.exMap(consensuses[i])['enterMaintenanceHeight'] == 0 @pytest.mark.parametrize("times", [ @@ -478,11 +507,10 @@ def test_registration_index_correct_after_success(candidate_hub, required_margin required_margin, status, get_current_round(), - commission_last_round, - vote_address, - ZERO_ADDRESS, - ("", "", "", "") + commission_last_round ) + assert candidate_hub.exMap(accounts[1])['voteAddr'] == vote_address + assert candidate_hub.exMap(accounts[1])['agent'] == ZERO_ADDRESS assert candidate_hub.operateMap(accounts[1]) == 1 assert candidate_hub.getConsensusMap(consensus_addr) == 1 consensus = register_candidate(operator=accounts[2]) @@ -1350,7 +1378,7 @@ def test_update_agent_success(candidate_hub, accounts): assert len(tx.events['AgentUpdated']) > 0 assert tx.events['AgentUpdated']['operateAddr'] == operator assert tx.events['AgentUpdated']['newAgent'] == new_agent - + assert candidate_hub.exMap(operator)['agent'] == new_agent assert candidate_hub.agentMap(new_agent) != 0 @@ -1395,11 +1423,14 @@ def test_update_agent_already_exists(candidate_hub): ) candidate_hub.updateAgent(agent, {'from': operator1}) - with brownie.reverts("agent address already exists"): candidate_hub.updateAgent(agent, {'from': operator2}) candidate_hub.updateAgent(accounts[13], {'from': operator2}) assert candidate_hub.agentMap(accounts[13]) == 2 + candidate_hub.updateAgent(accounts[11], {'from': operator1}) + candidate_hub.updateAgent(agent, {'from': operator2}) + assert candidate_hub.agentMap(accounts[11]) == 1 + assert candidate_hub.agentMap(agent) == 2 def test_update_agent_multiple_times(candidate_hub, accounts): @@ -1429,6 +1460,16 @@ def test_update_agent_multiple_times(candidate_hub, accounts): def test_remove_agent_permission(candidate_hub, accounts): with brownie.reverts("candidate does not exist"): candidate_hub.removeAgent({'from': accounts[1]}) + candidate_hub.register( + accounts[8], + accounts[9], + 100, + random_vote_address(), + {'from': accounts[1], 'value': 1e18} + ) + candidate_hub.updateAgent(accounts[2], {'from': accounts[1]}) + with brownie.reverts("candidate does not exist"): + candidate_hub.removeAgent({'from': accounts[2]}) def test_remove_agent_no_agent(candidate_hub, accounts): @@ -1446,10 +1487,11 @@ def test_remove_agent_success(candidate_hub, accounts): candidate_hub.updateAgent(agent, {'from': operator}) assert candidate_hub.agentMap(agent) != 0 + assert candidate_hub.exMap(operator)['agent'] == agent candidate_hub.removeAgent({'from': operator}) assert candidate_hub.agentMap(agent) == 0 - assert candidate_hub.candidateSet(0).dict()['agent'] == ZERO_ADDRESS + assert candidate_hub.exMap(operator)['agent'] == ZERO_ADDRESS # editConsensusAddress @@ -1480,8 +1522,7 @@ def test_edit_consensus_address_success(candidate_hub, accounts): assert tx.events['ConsensusAddressEdited']['newConsensusAddr'] == new_consensus assert candidate_hub.isCandidateByConsensus(old_consensus) is False assert candidate_hub.isCandidateByConsensus(new_consensus) - assert candidate_hub.candidateSet( - 0).dict()['consensusAddr'] == new_consensus + assert candidate_hub.candidateSet(0).dict()['consensusAddr'] == new_consensus def test_edit_consensus_address_by_agent_success(candidate_hub, accounts): @@ -1611,9 +1652,12 @@ def test_remove_candidate_agent_map_index(candidate_hub, slash_indicator): for i in range(5): assert candidate_hub.operateMap(operators[i]) == i + 1 assert candidate_hub.getConsensusMap(consensuses[i]) == i + 1 - for i in range(1, 4): + for i in range(1, 5): assert candidate_hub.agentMap(agents[i]) == i + 1 assert candidate_hub.agentMap(agents[0]) == 0 + vote_addr = random_vote_address() + candidate_hub.editVoteAddress(vote_addr, {'from': operators[0]}) + assert candidate_hub.exMap(operators[0])['voteAddr'] == vote_addr felony_threshold = slash_indicator.felonyThreshold() for _ in range(felony_threshold): tx = slash_indicator.slash(consensuses[0]) @@ -1626,6 +1670,7 @@ def test_remove_candidate_agent_map_index(candidate_hub, slash_indicator): assert candidate_hub.agentMap(agents[0]) == 0 assert candidate_hub.operateMap(operators[0]) == 0 assert candidate_hub.getConsensusMap(consensuses[0]) == 0 + assert candidate_hub.exMap(operators[0])['voteAddr'] == ZERO_ADDRESS def test_remove_candidate_agent_map_index_with_all_agents(candidate_hub, slash_indicator): @@ -1649,7 +1694,7 @@ def test_remove_candidate_agent_map_index_with_all_agents(candidate_hub, slash_i assert candidate_hub.agentMap(agents[-1]) == 3 vote_addr = random_vote_address() candidate_hub.editVoteAddress(vote_addr, {'from': agents[-1]}) - assert candidate_hub.candidateSet(2).dict()['voteAddr'] == vote_addr + assert candidate_hub.exMap(operators[-1])['voteAddr'] == vote_addr assert candidate_hub.candidateSet(2).dict()['operateAddr'] == operators[-1] @@ -1672,18 +1717,22 @@ def test_edit_vote_address_length(candidate_hub, accounts): def test_edit_vote_address_duplicate(candidate_hub, accounts): operator1 = accounts[5] operator2 = accounts[6] + operator3 = accounts[7] consensus1 = register_candidate(operator=operator1) - vote_addr1 = candidate_hub.candidateSet(0).dict()['voteAddr'] + vote_addr1 = candidate_hub.exMap(operator1)['voteAddr'] register_candidate(operator=operator2) - + register_candidate(operator=operator3) with brownie.reverts("vote address already exists"): candidate_hub.editVoteAddress(vote_addr1, {'from': operator2}) + vote_addr2 = candidate_hub.exMap(operator3)['voteAddr'] + with brownie.reverts("vote address already exists"): + candidate_hub.editVoteAddress(vote_addr2, {'from': operator2}) def test_edit_vote_address_success(candidate_hub, accounts): operator = accounts[5] consensus = register_candidate(operator=operator) - old_vote_addr = candidate_hub.candidateSet(0).dict()['voteAddr'] + old_vote_addr = candidate_hub.exMap(operator)['voteAddr'] new_vote_addr = random_vote_address() tx = candidate_hub.editVoteAddress(new_vote_addr, {'from': operator}) @@ -1691,6 +1740,7 @@ def test_edit_vote_address_success(candidate_hub, accounts): assert 'VoteAddressEdited' in tx.events assert tx.events['VoteAddressEdited']['operateAddr'] == operator assert tx.events['VoteAddressEdited']['newVoteAddr'] == new_vote_addr + assert candidate_hub.exMap(operator)['voteAddr'] == new_vote_addr def test_edit_vote_address_by_agent_success(candidate_hub, accounts): @@ -1705,57 +1755,6 @@ def test_edit_vote_address_by_agent_success(candidate_hub, accounts): assert 'VoteAddressEdited' in tx.events -# editDescription -def test_edit_description_permission(candidate_hub, accounts): - with brownie.reverts("candidate does not exist"): - candidate_hub.editDescription( - "test", "test", "test", "test", {'from': accounts[1]}) - - -def test_edit_description_success(candidate_hub, accounts): - operator = accounts[5] - register_candidate(operator=operator) - - moniker = "Test Validator" - identity = "test-identity" - website = "https://test.com" - details = "Test validator details" - - tx = candidate_hub.editDescription( - moniker, identity, website, details, {'from': operator}) - - assert 'DescriptionEdited' in tx.events - assert tx.events['DescriptionEdited']['operateAddr'] == operator - assert tx.events['DescriptionEdited']['moniker'] == moniker - assert tx.events['DescriptionEdited']['identity'] == identity - assert tx.events['DescriptionEdited']['website'] == website - assert tx.events['DescriptionEdited']['details'] == details - assert candidate_hub.candidateSet( - 0).dict()['description']['moniker'] == moniker - assert candidate_hub.candidateSet( - 0).dict()['description']['identity'] == identity - assert candidate_hub.candidateSet( - 0).dict()['description']['website'] == website - assert candidate_hub.candidateSet( - 0).dict()['description']['details'] == details - - -def test_edit_description_by_agent_success(candidate_hub, accounts): - operator = accounts[5] - agent = accounts[6] - register_candidate(operator=operator) - new_moniker = "Test Validator" - new_identity = "test-identity" - new_website = "https://test.com" - new_details = "Test validator details" - - candidate_hub.updateAgent(agent, {'from': operator}) - - tx = candidate_hub.editDescription( - new_moniker, new_identity, new_website, new_details, {'from': agent}) - assert 'DescriptionEdited' in tx.events - - # editFeeAddress def test_edit_fee_address_permission(candidate_hub, accounts): with brownie.reverts("candidate does not exist"): @@ -1808,13 +1807,7 @@ def test_candidate_update_and_turn_round(candidate_hub, accounts, validator_set, for i in range(validator_count): candidate_hub.updateAgent(accounts[i], {'from': operators[i]}) turn_round(consensuses) - for i in range(validator_count): - moniker = f"validator{i}" - identity = f"identity{i}" - website = f"https://validator{i}.com" - details = f"details{i}" - tx = candidate_hub.editDescription( - moniker, identity, website, details, {'from': accounts[i]}) + candidate_hub.editVoteAddress(random_vote_address(), {'from': operators[0]}) turn_round(consensuses) for i in range(validator_count): candidate_hub.editFeeAddress(accounts[i], {'from': operators[i]}) @@ -1825,13 +1818,9 @@ def test_candidate_update_and_turn_round(candidate_hub, accounts, validator_set, for i, operator in enumerate(operators): index = candidate_hub.operateMap(operator) c = candidate_hub.candidateSet(index - 1).dict() - assert c['agent'] == accounts[i] + assert candidate_hub.exMap(operator)['agent'] == accounts[i] assert c['feeAddr'] == accounts[i] assert c['commissionThousandths'] == 500 + i - assert c['description']['moniker'] == f"validator{i}" - assert c['description']['identity'] == f"identity{i}" - assert c['description']['website'] == f"https://validator{i}.com" - assert c['description']['details'] == f"details{i}" tx = stake_hub_claim_reward(accounts[0]) assert 'claimedReward' in tx.events turn_round(consensuses) @@ -1890,3 +1879,45 @@ def test_get_alternate_count_typical_scenarios(candidate_hub): # candidateSize < validatorCount, return 0 alternate_count = candidate_hub.mockGetAlternateCount(5, 10, 8) assert alternate_count == 0 + + +# removeCandidate +def test_remove_candidate_success(candidate_hub, validator_set): + operators = [acc for acc in accounts[5:10]] + vote_addresses = [random_vote_address() for _ in range(5)] + update_agents = [f'0x{"0" * 39}{i + 1}' for i in range(5)] + consensuses = [register_candidate(operator=op) for op in operators] + for op, agent, vote_addr in zip(operators, update_agents, vote_addresses): + candidate_hub.updateAgent(agent, {'from': op}) + candidate_hub.editVoteAddress(vote_addr, {'from': op}) + + turn_round() + for i, op in enumerate(operators): + candidate = candidate_hub.candidateSet(i) + assert candidate == [op, consensuses[i], op, 500, 1000000, 17, 7, 500] + assert candidate_hub.operateMap(op) == i + 1 + assert candidate_hub.getConsensusMap(consensuses[i]) == i + 1 + assert candidate_hub.exMap(op) == [update_agents[i], vote_addresses[i]] + assert candidate_hub.agentMap(update_agents[i]) == i + 1 + + candidate_hub.removeCandidateMock(2, {'from': operators[0]}) + candidate_set_order = [0,4,2,3] + for index,i in enumerate(candidate_set_order): + candidate = candidate_hub.candidateSet(index) + assert candidate == [operators[i], consensuses[i], operators[i], 500, 1000000, 17, 7, 500] + assert candidate_hub.operateMap(operators[i]) == index + 1 + assert candidate_hub.getConsensusMap(consensuses[i]) == index + 1 + assert candidate_hub.exMap(operators[i]) == [update_agents[i], vote_addresses[i]] + assert candidate_hub.agentMap(update_agents[i]) == index + 1 + + candidate_hub.removeCandidateMock(1, {'from': operators[0]}) + + assert len(candidate_hub.getCandidates()) == 3 + candidate_set_order = [3, 4, 2] + for index, i in enumerate(candidate_set_order): + candidate = candidate_hub.candidateSet(index) + assert candidate == [operators[i], consensuses[i], operators[i], 500, 1000000, 17, 7, 500] + assert candidate_hub.operateMap(operators[i]) == index + 1 + assert candidate_hub.getConsensusMap(consensuses[i]) == index + 1 + assert candidate_hub.exMap(operators[i]) == [update_agents[i], vote_addresses[i]] + assert candidate_hub.agentMap(update_agents[i]) == index + 1 diff --git a/tests/test_channel.py b/tests/test_channel.py index 4e6cde9d6..8e41cc321 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -1481,7 +1481,7 @@ def test_pay_commission_by_id_through_btc_stake_claim_reward( btc_tx_id = delegate_btc_success( candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=partner_id) tx_map = btc_stake.btcTxMap(btc_tx_id) - assert tx_map[2] == partner_id + assert tx_map[-1] == partner_id turn_round() turn_round(consensuses) @@ -1505,7 +1505,7 @@ def test_pay_commission_by_id_btc_stake_invalid_channel(channel, btc_stake, set_ btc_tx_id = delegate_btc_success(candidate, delegator, BTC_VALUE, LOCK_SCRIPT, lock_data=LOCK_TIMESTAMP, channel_id=channel_id) tx_map = btc_stake.btcTxMap(btc_tx_id) - assert tx_map[2] == channel_id + assert tx_map[-1] == channel_id turn_round() turn_round(consensuses) diff --git a/tests/test_claim_reward.py b/tests/test_claim_reward.py index 0dc7eb696..4ddfbb2d1 100644 --- a/tests/test_claim_reward.py +++ b/tests/test_claim_reward.py @@ -1215,37 +1215,6 @@ def test_edit_vote_address_and_claim_reward(candidate_hub): assert tracker.delta() == TOTAL_REWARD // 2 - 1 turn_round(chain_get_validator_consensus()) - -def test_edit_description_and_claim_reward(candidate_hub): - delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 - btc_amount = 1000 - operators, consensuses = setup_validators_and_delegates(validator_count=5, - alternate_count=2, - candidate_count=7) - for i in range(7): - delegate_validator(operators, i, accounts[50 + i], delegate_amount - i, btc_amount) - delegate_validator(operators, i, accounts[57 + i], delegate_amount - i, btc_amount) - turn_round() - expect_validator_consensus(consensuses[:5]) - turn_round(chain_get_validator_consensus()) - stake_hub_claim_reward(accounts[50]) - moniker = "Test Validator" - identity = "test-identity" - website = "https://test.com" - details = "Test validator details" - candidate_hub.editDescription(moniker, identity, website, details, {'from': operators[0]}) - turn_round(chain_get_validator_consensus()) - tracker = get_tracker(accounts[50]) - stake_hub_claim_reward(accounts[50]) - assert tracker.delta() == TOTAL_REWARD // 2 - 1 - turn_round(chain_get_validator_consensus()) - candidate_hub.editDescription('test1', identity, website, details, {'from': operators[0]}) - turn_round(chain_get_validator_consensus()) - stake_hub_claim_reward(accounts[50]) - assert tracker.delta() == (TOTAL_REWARD // 2) * 2 - 1 - turn_round(chain_get_validator_consensus()) - - def test_edit_fee_address_and_claim_reward(candidate_hub): delegate_amount = MIN_INIT_DELEGATE_VALUE * 100 btc_amount = 1000 @@ -1312,16 +1281,11 @@ def test_edit_agent_info_and_claim_reward(candidate_hub, validator_set): delegate_validator(operators, i, accounts[77 + i], delegate_amount - i, btc_amount) turn_round() validator_set.setValidatorCount(0) - moniker = "Test Validator" - identity = "test-identity" - website = "https://test.com" - details = "Test validator details" candidate_hub.updateAgent(accounts[72], {'from': operators[0]}) candidate_hub.editFeeAddress(accounts[72], {'from': operators[0]}) candidate_hub.editVoteAddress(random_vote_address(), {'from': operators[0]}) candidate_hub.editCommissionRate(550, {'from': operators[0]}) candidate_hub.editConsensusAddress(accounts[72], {'from': operators[0]}) - candidate_hub.editDescription(moniker, identity, website, details, {'from': operators[0]}) stake_hub_claim_reward(accounts[70]) turn_round(chain_get_validator_consensus(), round_count=2) diff --git a/tests/test_slash_indicator.py b/tests/test_slash_indicator.py index 2941b290b..08995e3d4 100644 --- a/tests/test_slash_indicator.py +++ b/tests/test_slash_indicator.py @@ -16,12 +16,13 @@ def set_up(slash_indicator): encode(['string', 'bytes'], ['felonyThreshold', Web3.to_bytes(hexstr=hex_value)]), "update felonyThreshold" ) + + @pytest.fixture(scope="module", autouse=True) def deposit_for_reward(validator_set, gov_hub): accounts[99].transfer(validator_set.address, Web3.to_wei(100000, 'ether')) - @pytest.fixture() def set_candidate_maintenance(candidate_hub): candidate_hub.setValidatorCount(3) @@ -35,6 +36,7 @@ def set_candidate_maintenance(candidate_hub): delegate_coin_success(operator, accounts[1], 1e18) return operators, consensuses + # slash def test_slash(slash_indicator, validator_set): for slash_address, times, count in ( @@ -48,7 +50,8 @@ def test_slash(slash_indicator, validator_set): result = slash_indicator.getSlashIndicator(slash_address) assert result[1] == count -def test_slash_force_enter_maintenance(slash_indicator,candidate_hub, validator_set, set_candidate_maintenance): + +def test_slash_force_enter_maintenance(slash_indicator, candidate_hub, validator_set, set_candidate_maintenance): candidate_hub.setMaxAlternateCount(5) operators, consensuses = set_candidate_maintenance validator = consensuses[0] @@ -59,21 +62,44 @@ def test_slash_force_enter_maintenance(slash_indicator,candidate_hub, validator_ validator_set.deposit(validator, {"value": 10000, "from": accounts[99]}) for _ in range(2): - tx= slash_indicator.slash(validator) + tx = slash_indicator.slash(validator) assert 'validatorEnterMaintenance' in tx.events - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 old_enter_maintenance_height = validator['enterMaintenanceHeight'] result = slash_indicator.getSlashIndicator(consensuses[0]) - assert result[1] == 2 + assert result[1] == 2 slash_indicator.slash(consensuses[0]) - tx = slash_indicator.slash(consensuses[0]) - assert 'validatorEnterMaintenance' not in tx.events - validator = validator_set.getValidatorByConsensus(consensuses[0]) + tx = slash_indicator.slash(consensuses[0]) + assert 'validatorEnterMaintenance' not in tx.events + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] == old_enter_maintenance_height - +@pytest.mark.parametrize("slash", ['felony', 'misdemeanor']) +def test_slash_force_update_voteWeight_success(slash_indicator,validator_set,set_candidate,slash): + operators, consensuses = set_candidate + turn_round() + vote_weight = [10, 20, 30] + chain_vote(consensuses, vote_weight) + for i in range(3): + assert validator_set.getValidatorExByConsensus(consensuses[i])['voteWeight'] == vote_weight[i] + if slash == 'felony': + slash_indicator.setFelonyThreshold(3) + event_name = 'validatorFelony' + actual_weight = 0 + else: + event_name = 'validatorMisdemeanor' + slash_indicator.setFelonyThreshold(10) + slash_indicator.setMisdemeanorThreshold(3) + actual_weight = 10 + validator = validator_set.exMap(consensuses[0]) + assert validator['voteWeight'] == vote_weight[0] + for _ in range(3): + tx = slash_indicator.slash(consensuses[0]) + assert event_name in tx.events + validator = validator_set.exMap(consensuses[0]) + assert validator['voteWeight'] == actual_weight def test_double_sign_slash(slash_indicator, candidate_hub): tests = [( @@ -285,8 +311,8 @@ def test_slash_with_block_count_new_validator_success(slash_indicator, validator slash_indicator.mockSlashWithBlockCount(validator, block_count) result = slash_indicator.getSlashIndicator(validator) - assert result[0] == chain.height - assert result[1] == block_count + assert result[0] == chain.height + assert result[1] == block_count assert slash_indicator.validators(0) == validator @@ -302,7 +328,7 @@ def test_slash_with_block_count_existing_validator_success(slash_indicator, set_ slash_indicator.mockSlashWithBlockCount(validator, 3) result2 = slash_indicator.getSlashIndicator(validator) - assert result2[1] == 8 + assert result2[1] == 8 assert result2[0] == chain.height diff --git a/tests/test_stake_hub_unit.py b/tests/test_stake_hub_unit.py index b79cb6dfa..9c9531dae 100644 --- a/tests/test_stake_hub_unit.py +++ b/tests/test_stake_hub_unit.py @@ -787,7 +787,7 @@ def test_onStakeChange_success(stake_hub, set_candidate): btc_value = 100 delegate_coin_success(operators[0], accounts[0], delegate_amount) turn_round() - delegate_btc_success(operators[1], accounts[0], btc_value,LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) current_round = get_current_round() tx = stake_hub.onStakeChange(accounts[0]) @@ -817,7 +817,7 @@ def test_calculateReward_invalid_after_operation(stake_hub, set_candidate, onSta delegate_amount = 500000 btc_value = 100 delegate_coin_success(operators[0], accounts[0], delegate_amount) - delegate_btc_success(operators[1], accounts[0], btc_value,LOCK_SCRIPT) + delegate_btc_success(operators[1], accounts[0], btc_value, LOCK_SCRIPT) turn_round(consensuses, round_count=2) if onStakeChange: stake_hub.onStakeChange(accounts[0]) @@ -828,6 +828,97 @@ def test_calculateReward_invalid_after_operation(stake_hub, set_candidate, onSta assert len(tx.events) == 0 +# calculateReward +def test_calculate_reward_only_btc_stakes(stake_hub, btc_agent, core_agent, set_candidate): + operators, consensuses = set_candidate + delegator = accounts[0] + delegate_btc_success(operators[0], delegator, 100, LOCK_SCRIPT) + turn_round() + turn_round(consensuses) + btc_agent.setIsActive(True) + stake_manager.set_lp_rates([[0, 5000], [5000, 8000], [10000, 15000]]) + stake_manager.set_is_stake_hub_active(True) + tx = stake_hub.calculateReward(delegator, {'from': accounts[1]}) + delegator_info = stake_hub.getDelegator(delegator) + assert delegator_info[0] == get_current_round() + assert len(delegator_info[1]) == 3 + assert delegator_info[1][2] == TOTAL_REWARD // 2 + + +def test_calculate_reward_only_core_stakes(stake_hub, btc_agent, core_agent, set_candidate): + operators, consensuses = set_candidate + delegator = accounts[0] + delegate_coin_success(operators[0], delegator, 10000) + turn_round() + turn_round(consensuses, round_count=2) + stake_manager.set_is_stake_hub_active(True) + tx = stake_hub.calculateReward(delegator, {'from': accounts[1]}) + delegator_info = stake_hub.getDelegator(delegator) + assert delegator_info[0] == get_current_round() + assert len(delegator_info[1]) == 3 + assert delegator_info[1][0] == TOTAL_REWARD * 2 + + +def test_calculate_reward_multiple_btc_and_core_stakes(stake_hub, btc_agent, core_agent, set_candidate): + operators, consensuses = set_candidate + delegator = accounts[0] + delegate_coin_success(operators[2], delegator, 500000) + delegate_coin_success(operators[2], delegator, 1000000) + turn_round() + delegate_btc_success(operators[0], delegator, 100, LOCK_SCRIPT) + delegate_btc_success(operators[1], delegator, 100, LOCK_SCRIPT) + turn_round() + delegate_coin_success(operators[2], delegator, 800000) + turn_round(consensuses) + turn_round(consensuses) + btc_agent.setIsActive(True) + stake_manager.set_lp_rates([[0, 3000], [1, 4000], [5000, 6000], [8000, 10000], [15000, 20000]]) + stake_manager.set_is_stake_hub_active(True) + + tx = stake_hub.calculateReward(delegator, {'from': accounts[1]}) + assert tx.events['storedRewardBtcTx'][0]['dualStakingRate'] == Utils.DENOMINATOR * 2 + assert tx.events['storedRewardBtcTx'][1]['dualStakingRate'] == 3000 + + assert tx.events['storedRewardBtcTx'][2]['dualStakingRate'] == Utils.DENOMINATOR * 2 + assert tx.events['storedRewardBtcTx'][3]['dualStakingRate'] == Utils.DENOMINATOR + delegator_info = stake_hub.getDelegator(delegator) + assert delegator_info[1][0] == TOTAL_REWARD * 2 - 1 + assert delegator_info[1][2] == TOTAL_REWARD * 2 + TOTAL_REWARD * 3000 // Utils.DENOMINATOR + TOTAL_REWARD * 3 + tracker0 = get_tracker(delegator) + stake_hub_claim_reward(delegator) + assert tracker0.delta() == sum(delegator_info[1]) + delegator_info = stake_hub.getDelegator(delegator) + assert sum(delegator_info[1]) == 0 + + +def test_calculate_reward_various_stake_combinations(stake_hub, btc_agent, core_agent, set_candidate): + operators, consensuses = set_candidate + delegator = accounts[0] + delegate_btc_success(operators[0], delegator, 200, LOCK_SCRIPT) + delegate_btc_success(operators[1], delegator, 100, LOCK_SCRIPT) + + delegate_coin_success(operators[2], delegator, 1000000) + delegate_coin_success(operators[2], delegator, 500000) + delegate_coin_success(operators[2], delegator, 800000) + turn_round(consensuses, round_count=2) + btc_agent.setIsActive(True) + stake_manager.set_lp_rates([[0, 5000], [8000, 10000], [15000, 20000]]) + stake_manager.set_is_stake_hub_active(True) + stake_hub.calculateReward(delegator, {'from': accounts[1]}) + stake_manager.set_lp_rates([[0, 30000]]) + delegator_info = stake_hub.getDelegator(delegator) + assert delegator_info[1][0] == TOTAL_REWARD - 1 + assert delegator_info[1][2] == TOTAL_REWARD * 2 + TOTAL_REWARD // 2 + tracker0 = get_tracker(delegator) + stake_hub_claim_reward(delegator) + assert tracker0.delta() == sum(delegator_info[1]) + delegator_info = stake_hub.getDelegator(delegator) + assert sum(delegator_info[1]) == 0 + turn_round(consensuses) + stake_hub_claim_reward(delegator) + assert tracker0.delta() == TOTAL_REWARD - 1 + TOTAL_REWARD * 6 + + def test_only_govhub_can_call(stake_hub): grades_encode = rlp.encode([]) with brownie.reverts("the msg sender must be governance contract"): @@ -1099,9 +1190,9 @@ def test_stake_hub_calculate_reward(stake_hub, btc_agent, candidate_hub, core_ag delegate_coin_success(operators[1], accounts[1], test['add_core']) delegate_btc_success(operators[0], accounts[1], test['add_btc'], LOCK_SCRIPT, relay=accounts[1]) turn_round(consensuses, round_count=2) - graders_keys = [1000,4000,5000,10000] - graders_values = [5000,7000,8000,10000] + graders_keys = [1000, 4000, 5000, 10000] + graders_values = [5000, 7000, 8000, 10000] if test.get('is_active'): btc_agent.setIsActive(True) - btc_agent.setInitLpRates(graders_keys,graders_values) + btc_agent.setInitLpRates(graders_keys, graders_values) assert stake_hub.calculateRewardMock(accounts[1]).return_value == (test['expect_rewards']) diff --git a/tests/test_validator_set.py b/tests/test_validator_set.py index 52285ba20..7fa3f2897 100644 --- a/tests/test_validator_set.py +++ b/tests/test_validator_set.py @@ -106,6 +106,45 @@ def __update_slash_address(): update_system_contract_address(validator_set_instance, slash_indicator=accounts[0]) +def test_init_success(validator_set): + init_operators = ['0x3aE030Dc3717C66f63D6e8f1d1508a5C941ff46D', '0xDE442f5BA55687a24F04419424E0dc2593cC9f4C', + '0xcb089Be171e256acdaac1eBBEb32fFbA0dd438ee', '0x0A53B7e0Ffd97357e444B85f4D683c1d8e22879A', + '0xd82c24274EBbfe438788D684dC6034c3C67664A4'] + init_consensuses = ['0x01Bca3615D24d3c638836691517b2B9b49b054B1', '0xa458499604A85E90225a14946f36368Ae24df16D', + '0x5E00C0D5C4C10d4c805aba878D51129A89d513e0', '0x1Cd652bC64Af3f09B490dAae27f46e53726ce230', + '0xDA37ccECBB2D7C83aE27eE2BeBFE8EBCe162c600'] + init_vote_address = '0x99a1dbde53606922478636c65b06f9683e10bde7f6cbee8f0ebbb803d0beef91fa47f2727ef8533cb5166e54a52d08b8' + assert validator_set.blockRewardIncentivePercent() == 10 + assert validator_set.turnLength() == 1 + assert validator_set.alreadyInit() is True + for index, validator in enumerate(validator_set.getCurrentValidatorSet()): + assert validator == [init_operators[index], init_consensuses[index], init_operators[index], 1000, 0] + assert validator_set.getValidatorExByConsensus(init_consensuses[index]) == [init_vote_address, 0, 0] + validator_set.clearCurrentValidatorSet() + init_validator_data = 'f90573f85b944121f067b0f5135d77c29b2b329e8cb1bd96c96094f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b947f461f8a1c35edecd6816e76eb2e84eb661751ee94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94fd806ab93db5742944b7b50ce759e5eee5f6fe5094f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b947ef3a94ad1c443481fb3d86829355ca90477f8b594f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b9467d1ad48f91e131413bd0b04e823f3ae4f81e85394f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b943fb42cab4416024dc1b4c9e21b9acd0dfcef35f694f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b943511e3b8ac7336b99517d324145e9b5bb33e08a494f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94729f39a54304fcc6ec279684c71491a385d7b9ae94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94f44a785fd9f23f0abd443541386e71356ce619dc94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b942efd3cf0733421aec3e4202480d0a90bd157514994f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94613b0f519ada008cb99b6130e89122ba416bf15994f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94c0925eeb800ff6ba4695ded61562a10102152b5f94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b9419e3c7d7e69f273f3f91c060bb438a007f6fc33c94f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94e127f110d172a0c4c6209fe045dd71781e8fe9d494f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85b94f778dc4a199a440dbe9f16d1e13e185bb179b3b794f8b18cecc98d976ad253d38e4100a73d4e154726b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + validator_set.decodeValidatorSetMock(init_validator_data) + init_consensuses2 = ['0x4121F067B0F5135D77C29b2B329e8Cb1bd96C960', + '0x7f461f8a1c35eDEcD6816e76Eb2E84eb661751eE', + '0xfD806AB93db5742944B7B50Ce759E5EeE5f6FE50', + '0x7Ef3a94AD1c443481fb3d86829355CA90477F8b5', + '0x67D1ad48f91E131413BD0b04e823F3AE4F81E853', + '0x3Fb42caB4416024dC1B4C9e21B9acD0DFcef35f6', + '0x3511E3b8aC7336B99517D324145e9b5Bb33e08a4', + '0x729f39a54304fCc6eC279684c71491A385d7b9aE', + '0xF44a785Fd9F23F0abd443541386E71356Ce619dC', + '0x2EFd3CF0733421aec3E4202480d0A90bd1575149', + '0x613b0F519aDA008CB99B6130E89122BA416Bf159', + '0xc0925eeb800fF6Ba4695DED61562A10102152B5f', + '0x19e3C7D7E69F273f3F91C060Bb438a007f6Fc33c', + '0xE127f110D172a0c4C6209fE045dd71781e8fe9d4', + '0xF778dc4A199A440dBE9f16d1e13e185bB179B3b7'] + for index, validator in enumerate(validator_set.getCurrentValidatorSet()): + assert validator == ['0xF8B18CeCC98D976ad253D38E4100a73D4e154726', init_consensuses2[index], + '0xF8B18CeCC98D976ad253D38E4100a73D4e154726', 1000, 0] + assert validator_set.getValidatorExByConsensus(init_consensuses2[index]) == [ + '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 0, 0] + + def test_check_validator_address_failed_with_zero_address(validator_set): assert validator_set.isValidator.call(constants.ADDRESS_ZERO) is False @@ -236,6 +275,7 @@ def test_deposit_to_validator_with_positive_balance(validator_address, deposit_v __balance_check(0 - deposit_value, deposit_value, 0) +# vote def test_vote_addr_and_weights_mismatch_failed(set_candidate): operators, consensuses = set_candidate accounts[1].transfer(validator_set_instance.address, Web3.to_wei(10, 'ether')) @@ -247,17 +287,17 @@ def test_vote_addr_and_weights_mismatch_failed(set_candidate): validator_set_instance.vote([], []) -def test_val_addrs_contains_non_validator(validator_set, set_candidate): +def test_val_addrs_contains_non_validator_failed(validator_set, set_candidate): operators, consensuses = set_candidate turn_round() consensuses.append(accounts[2]) weights = [10, 20, 30, 40] validator_set.vote(consensuses, weights) - assert validator_set.getValidatorByConsensus(consensuses[0])['voteWeight'] == weights[0] - assert validator_set.getValidatorByConsensus(consensuses[1])['voteWeight'] == weights[1] - assert validator_set.getValidatorByConsensus(consensuses[2])['voteWeight'] == weights[2] + assert validator_set.getValidatorExByConsensus(consensuses[0])['voteWeight'] == weights[0] + assert validator_set.getValidatorExByConsensus(consensuses[1])['voteWeight'] == weights[1] + assert validator_set.getValidatorExByConsensus(consensuses[2])['voteWeight'] == weights[2] with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[3]) + validator_set.getValidatorExByConsensus(consensuses[3]) @pytest.mark.xfail @@ -283,8 +323,10 @@ def test_vote_success(validator_set): weights = [10, 20, 30] validator_set.vote(consensuses, weights) for i in range(len(consensuses)): - validator = [operators[i], consensuses[i], operators[i], 1000, 0, vote_address_list[i], weights[i], 0] + validator = [operators[i], consensuses[i], operators[i], 1000, 0] + validator_ex = [vote_address_list[i], weights[i], 0] assert validator_set.getValidatorByConsensus(consensuses[i]) == validator + assert validator_set.getValidatorExByConsensus(consensuses[i]) == validator_ex def test_repeated_vote_counts_accumulate_success(validator_set, set_candidate): @@ -295,11 +337,11 @@ def test_repeated_vote_counts_accumulate_success(validator_set, set_candidate): validator_set.vote(consensuses, weights) validator_set.vote(consensuses, weights) for i in range(len(consensuses)): - assert validator_set.getValidatorByConsensus(consensuses[i])['voteWeight'] == weights[i] * 3 + assert validator_set.getValidatorExByConsensus(consensuses[i])['voteWeight'] == weights[i] * 3 validator_set.vote(consensuses[:-1], weights[:-1]) - assert validator_set.getValidatorByConsensus(consensuses[0])['voteWeight'] == weights[0] * 4 - assert validator_set.getValidatorByConsensus(consensuses[1])['voteWeight'] == weights[1] * 4 + assert validator_set.getValidatorExByConsensus(consensuses[0])['voteWeight'] == weights[0] * 4 + assert validator_set.getValidatorExByConsensus(consensuses[1])['voteWeight'] == weights[1] * 4 def test_vote_for_unelected_validator_failed(validator_set, set_candidate): @@ -332,10 +374,10 @@ def test_vote_address_contains_felony_failed(validator_set, set_candidate, slash assert event_name in tx0.events validator_set.vote([consensuses[0]], [10]) if vote_state: - assert validator_set.getValidatorByConsensus(consensuses[0])['voteWeight'] == 10 + assert validator_set.getValidatorExByConsensus(consensuses[0])['voteWeight'] == 10 else: with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[0]) + validator_set.getValidatorExByConsensus(consensuses[0]) def test_vote_address_contains_unregistered_failed(validator_set, candidate_hub, set_candidate): @@ -346,10 +388,10 @@ def test_vote_address_contains_unregistered_failed(validator_set, candidate_hub, candidate_hub.unregister({'from': operators[0]}) weights = [10, 20, 30] with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[0]) + validator_set.getValidatorExByConsensus(consensuses[0]) validator_set.vote(consensuses, weights) with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[0]) + validator_set.getValidatorExByConsensus(consensuses[0]) def test_vote_array_length_change_success(validator_set, set_candidate): @@ -363,7 +405,7 @@ def test_vote_array_length_change_success(validator_set, set_candidate): validator_set.vote(consensuses[:2], weights1[:2]) actual_validator_fee = [140, 200, 150] for i in range(len(consensuses)): - assert validator_set.getValidatorByConsensus(consensuses[i])['voteWeight'] == actual_validator_fee[i] + assert validator_set.getValidatorExByConsensus(consensuses[i])['voteWeight'] == actual_validator_fee[i] # updateValidatorSet @@ -423,7 +465,8 @@ def test_update_success(): expect_event(tx, "validatorSetUpdated") assert validator_set_instance.getValidators() == [accounts[1]] assert validator_set_instance.getValidatorByConsensus(accounts[1]) == [accounts[0], accounts[1], accounts[2], 1000, - 0, vote_address, 0, 0] + 0] + assert validator_set_instance.getValidatorExByConsensus(accounts[1]) == [vote_address, 0, 0] def test_update_validator_set_default_vote_weight(set_candidate): @@ -434,7 +477,7 @@ def test_update_validator_set_default_vote_weight(set_candidate): validator_set_instance.updateValidatorSet([operators[0]], [accounts[1]], [accounts[2]], [1000], [vote_address], validator_count) assert validator_set_instance.getValidators() == [accounts[1]] - validator = validator_set_instance.getValidatorByConsensus(accounts[1]) + validator = validator_set_instance.getValidatorExByConsensus(accounts[1]) assert validator['voteWeight'] == 0 @@ -454,7 +497,8 @@ def test_update_validator_set_add_new(validator_set, set_candidate): for index, address in enumerate(consensus_addr_list): assert validator_set.getValidatorByConsensus(address) == [operate_addr_list[index], address, fee_addr_list[index], commission_thousandths[index], - 0, vote_addr_list[index], 0, 0] + 0] + assert validator_set.getValidatorExByConsensus(address) == [vote_addr_list[index], 0, 0] def test_update_validator_set_modify_existing(validator_set): @@ -470,9 +514,10 @@ def test_update_validator_set_modify_existing(validator_set): vote_addr_list, validator_count) assert len(validator_set.getValidators()) == 5 for index, address in enumerate(consensus_addr_list): - validator = [operate_addr_list[index], address, fee_addr_list[index], commission_thousandths[index], 0, - vote_addr_list[index], 0, 0] + validator = [operate_addr_list[index], address, fee_addr_list[index], commission_thousandths[index], 0] + validator_ex = [vote_addr_list[index], 0, 0] assert validator_set.getValidatorByConsensus(address) == validator + assert validator_set.getValidatorExByConsensus(address) == validator_ex @pytest.mark.parametrize("legnth", [4, 6, 7]) @@ -507,13 +552,15 @@ def test_validator_count_change_update(validator_set, candidate_hub, set_candida # commission_thousandths is set to 1000 because there is no stake on the validator. commission_thousandths = 1000 assert validator_set.getValidatorByConsensus(consensuses[-1]) == [accounts[20], consensuses[-1], accounts[20], - commission_thousandths, 0, vote_address, 0, 0] + commission_thousandths, 0] + assert validator_set.getValidatorExByConsensus(consensuses[-1]) == [vote_address, 0, 0] candidate_hub.refuseDelegate({'from': operators[0]}) turn_round(consensuses) candidate_hub.unregister({'from': operators[0]}) turn_round(consensuses) assert validator_set.getValidatorByConsensus(consensuses[-1]) == [accounts[20], consensuses[-1], accounts[20], - commission_thousandths, 0, vote_address, 0, 0] + commission_thousandths, 0] + assert validator_set.getValidatorExByConsensus(consensuses[-1]) == [vote_address, 0, 0] with brownie.reverts("no match validator"): validator_set.getValidatorByConsensus(consensuses[0]) @@ -550,6 +597,37 @@ def test_update_validator_set_updates_ranked_validator_list(validator_set): assert validator_set.getRankedValidatorList() == new_consensus_addr_list +def test_update_validator_set_clears_maintenance_and_vote_weight(validator_set, set_candidate_maintenance): + operators, consensuses = set_candidate_maintenance + turn_round() + validator_set.enterMaintenance({'from': operators[0]}) + validator_set.vote(consensuses, [10, 20, 30, 40, 50]) + assert validator_set.getValidatorExByConsensus(consensuses[0])['enterMaintenanceHeight'] != 0 + assert validator_set.getValidatorExByConsensus(consensuses[0])['voteWeight'] == 10 + new_operators = [accounts[10], accounts[11], accounts[12], accounts[13], accounts[14]] + new_consensuses = [accounts[15], accounts[16], accounts[17], accounts[18], accounts[19]] + new_fee_addrs = [accounts[20], accounts[21], accounts[22], accounts[23], accounts[24]] + new_vote_addrs = [random_vote_address() for _ in range(5)] + commission_thousandths = [400 for _ in range(5)] + validator_count = 5 + update_system_contract_address(validator_set, candidate_hub=accounts[0]) + validator_set.updateValidatorSet( + new_operators, + new_consensuses, + new_fee_addrs, + commission_thousandths, + new_vote_addrs, + validator_count + ) + for c in consensuses: + with brownie.reverts("no match validator"): + validator_set.getValidatorExByConsensus(c) + for c in new_consensuses: + ex = validator_set.getValidatorExByConsensus(c) + assert ex['voteWeight'] == 0 + assert ex['enterMaintenanceHeight'] == 0 + + # getValidatorsAndVoteAddresses def test_get_validators_and_vote_address_success(validator_set): operators = [] @@ -832,12 +910,13 @@ def test_distribute_reward_success_with_commissionThousandths_500(): expect_reward = expect_income * commission // 1000 validator = validator_set_instance.currentValidatorSet(0).dict() + vote_address = validator_set_instance.getValidatorExByConsensus(validator['consensusAddress'])['voteAddr'] validator_set_instance.updateValidatorSet( [validator['operateAddress']], [validator['consensusAddress']], [validator['feeAddress']], [commission], - [validator['voteAddr']], + [vote_address], validator_count ) @@ -946,7 +1025,7 @@ def test_distribute_reward_by_weight_proportion_success(validator_set, system_re validator_set.vote(consensuses, weights1, {"from": accounts[99]}) vote_weight = [85, 80, 130] for index, consensus in enumerate(consensuses): - assert validator_set.getValidatorByConsensus(consensus).dict()['voteWeight'] == vote_weight[index] + assert validator_set.getValidatorExByConsensus(consensus).dict()['voteWeight'] == vote_weight[index] __fake_validator_set() trackers = get_trackers(operators) income = 30000 @@ -1090,7 +1169,7 @@ def test_vote_weight_clears_next_round(validator_set, candidate_hub, set_candida tx = candidate_hub.turnRound() assert 'voteRewardTransfer' not in tx.events for consensus in consensuses: - validator = validator_set.getValidatorByConsensus(consensus) + validator = validator_set.getValidatorExByConsensus(consensus) assert validator['voteWeight'] == 0 @@ -1425,9 +1504,9 @@ def test_felony_success_with_validator_set_which_has_income(candidate_hub): def test_subsidy_reduce(): validator_set_instance.updateBlockReward(BLOCK_REWARD) validator_set_instance.updateSubsidyReduceInterval(3) - reduce_interval = validator_set_instance.SUBSIDY_REDUCE_INTERVAL() + reduce_interval = validator_set_instance.subsidyReduceInterval() chain.mine(reduce_interval - chain.height % reduce_interval - 1) - validator_set_instance.deposit(ZERO_ADDRESS, {'value': 1}) + validator_set_instance.depositMock(ZERO_ADDRESS, {'value': 1}) assert validator_set_instance.blockReward() == validator_set_instance.BLOCK_REWARD() * validator_set_instance.REDUCE_FACTOR() // 10000 @@ -1438,12 +1517,12 @@ def test_subsidy_reduce_for_81_times(): validator_set_instance.updateBlockReward(BLOCK_REWARD) validator_set_instance.updateSubsidyReduceInterval(3) - reduce_interval = validator_set_instance.SUBSIDY_REDUCE_INTERVAL() + reduce_interval = validator_set_instance.subsidyReduceInterval() chain.mine(reduce_interval - chain.height % reduce_interval - 1) - validator_set_instance.deposit(ZERO_ADDRESS, {'value': 1}) + validator_set_instance.depositMock(ZERO_ADDRESS, {'value': 1}) for _ in range(80): chain.mine(reduce_interval - 1) - validator_set_instance.deposit(ZERO_ADDRESS, {'value': 1}) + validator_set_instance.depositMock(ZERO_ADDRESS, {'value': 1}) assert validator_set_instance.blockReward() == block_reward @@ -1472,13 +1551,13 @@ def test_exit_maintenance_turn_round_no_maintenance_success(validator_set, candi turn_round() for i in range(len(operators)): - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] == 0 turn_round() for i in range(len(operators)): - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] == 0 @@ -1488,13 +1567,13 @@ def test_exit_maintenance_turn_round_with_maintenance_success(validator_set, can turn_round() validator_set.enterMaintenance({'from': operators[0]}) validator_set.enterMaintenance({'from': operators[1]}) - validator0 = validator_set.getValidatorByConsensus(consensuses[0]) - validator1 = validator_set.getValidatorByConsensus(consensuses[1]) + validator0 = validator_set.getValidatorExByConsensus(consensuses[0]) + validator1 = validator_set.getValidatorExByConsensus(consensuses[1]) assert validator0['enterMaintenanceHeight'] != 0 assert validator1['enterMaintenanceHeight'] != 0 turn_round(chain_get_validator_consensus()) for i in range(len(operators)): - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] == 0 @@ -1514,11 +1593,11 @@ def test_exit_maintenance_turn_round_with_maintenance_slash(validator_set, candi assert 'validatorFelony' in tx.events for i in range(len(operators[:2])): if block_count == 20: - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] == 0 else: with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[i]) + validator_set.getValidatorExByConsensus(consensuses[i]) def test_exit_maintenance_turn_round_slash_then_exit(validator_set, slash_indicator, candidate_hub, @@ -1563,8 +1642,8 @@ def test_slash_during_maintenance(validator_set, slash_indicator, set_candidate_ assert 'validatorMisdemeanor' not in tx.events assert 'validatorFelony' not in tx.events with brownie.reverts("no match validator"): - validator = validator_set.getValidatorByConsensus(consensuses[0]) - validator = validator_set.getValidatorByConsensus(consensuses[1]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[1]) assert validator['enterMaintenanceHeight'] == 0 turn_round(consensuses) @@ -1575,17 +1654,17 @@ def test_validator_apply_maintenance_success(validator_set, set_candidate_mainte operators, consensuses = set_candidate_maintenance turn_round() tx1 = validator_set.enterMaintenance({'from': operators[operator_index]}) - validator_last = validator_set.getValidatorByConsensus(consensuses[operator_index]) + validator_last = validator_set.getValidatorExByConsensus(consensuses[operator_index]) assert validator_last['enterMaintenanceHeight'] != 0 assert 'validatorEnterMaintenance' in tx1.events tx2 = validator_set.enterMaintenance({'from': operators[0]}) - validator_first = validator_set.getValidatorByConsensus(consensuses[0]) + validator_first = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator_first['enterMaintenanceHeight'] != 0 assert 'validatorEnterMaintenance' in tx2.events chain.mine(300) tx = turn_round(consensuses) with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[operator_index]) + validator_set.getValidatorExByConsensus(consensuses[operator_index]) @pytest.mark.parametrize("maintain_index", [ @@ -1614,7 +1693,7 @@ def test_multiple_validators_multiple_candidates_exit_maintenance(validator_set, turn_round() for i in maintain_index: tx = validator_set.enterMaintenance({'from': operators[i]}) - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] != 0 assert 'validatorEnterMaintenance' in tx.events chain.mine(300) @@ -1623,11 +1702,12 @@ def test_multiple_validators_multiple_candidates_exit_maintenance(validator_set, for i in maintain_index: maintain_address.append(consensuses[i]) with brownie.reverts("no match validator"): - validator_set.getValidatorByConsensus(consensuses[i]) + validator_set.getValidatorExByConsensus(consensuses[i]) for consensus in consensuses: if consensus not in maintain_address: validator = validator_set.getValidatorByConsensus(consensus) - assert validator['enterMaintenanceHeight'] == 0 + validator_ex = validator_set.getValidatorExByConsensus(consensus) + assert validator_ex['enterMaintenanceHeight'] == 0 assert validator['consensusAddress'] == consensus turn_round(consensuses) @@ -1716,7 +1796,7 @@ def test_enter_maintenance_success(validator_set, candidate_hub, set_candidate): turn_round() assert len(validator_set.getCurrentValidatorSet()) == 3 tx = validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 expect_event(tx, "validatorEnterMaintenance", { 'validator': consensuses[0] @@ -1732,7 +1812,7 @@ def test_enter_maintenance_already_in_maintenance(validator_set, candidate_hub, turn_round() assert len(validator_set.getCurrentValidatorSet()) == 5 validator_set.enterMaintenance({'from': operators[0]}) - assert validator_set.currentValidatorSet(0)['enterMaintenanceHeight'] != 0 + assert validator_set.getValidatorExByConsensus(consensuses[0])['enterMaintenanceHeight'] != 0 with brownie.reverts("can not enter Temporary Maintenance"): validator_set.enterMaintenance({'from': operators[0]}) @@ -1746,7 +1826,7 @@ def test_enter_maintenance_multiple_validators(validator_set, candidate_hub, set expect_event(tx, "validatorEnterMaintenance", { 'validator': consensuses[i] }) - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] != 0 @@ -1761,7 +1841,7 @@ def test_enter_maintenance_after_exit(validator_set, candidate_hub, set_candidat validator_set.exitMaintenanceTurnRound() tx = validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 expect_event(tx, "validatorEnterMaintenance", { 'validator': consensuses[0] @@ -1789,7 +1869,7 @@ def test_revert_when_notenoughalternates_entermaintenance(validator_set, candida assert len(validator_set.getCurrentValidatorSet()) == 5 for i in range(len(operators[:2])): validator_set.enterMaintenance({'from': operators[i]}) - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] != 0 with brownie.reverts("can not enter Temporary Maintenance"): validator_set.enterMaintenance({'from': operators[2]}) @@ -1827,7 +1907,7 @@ def test_misdemeanor_apply_maintenance(validator_set, candidate_hub, set_candida tx1 = slash_indicator.slash(consensuses[0]) assert 'validatorMisdemeanor' in tx1.events assert 'validatorEnterMaintenance' not in tx1.events - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] == 0 with brownie.reverts("can not enter Temporary Maintenance"): validator_set.enterMaintenance({'from': operators[0]}) @@ -1842,7 +1922,7 @@ def test_alternate_validator_apply_maintenance_success(validator_set, candidate_ validator_set.enterMaintenance({'from': operators[4]}) tx = validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 assert 'validatorEnterMaintenance' in tx.events chain.mine(300) @@ -1882,7 +1962,7 @@ def test_slash_accumulates_during_and_after_maintenance(validator_set, set_candi delegate_coin_success(operators[0], accounts[0], 1e18) turn_round() validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 tx = slash_indicator.slash(consensuses[0]) @@ -1964,7 +2044,7 @@ def test_delegate_and_transfer_during_maintenance(validator_set, candidate_hub, delegate_amount = 100000 btc_amount = 1000 tx = validator_set.enterMaintenance({'from': operator}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 delegate_coin_success(operator, delegator, delegate_amount) txid = delegate_btc_success(operator, btc_delegator, btc_amount, LOCK_SCRIPT, relay=btc_delegator) @@ -1990,7 +2070,7 @@ def test_validator_with_no_delegation_cannot_enter_maintenance(validator_set, ca validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 @@ -2011,17 +2091,17 @@ def test_maintenance_state_check_success(validator_set, set_candidate_maintenanc for operator in operators[:3]: delegate_coin_success(operator, accounts[0], 1e18) turn_round() - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] == 0 validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) maintenance_height = validator['enterMaintenanceHeight'] assert maintenance_height != 0 chain.mine(105) tx = validator_set.exitMaintenance({'from': operators[0]}) with brownie.reverts('no match validator'): - validator_set.getValidatorByConsensus(consensuses[0]) + validator_set.getValidatorExByConsensus(consensuses[0]) expect_event(tx, "validatorExitMaintenance", { 'validator': consensuses[0] }) @@ -2037,11 +2117,11 @@ def test_exitmaintenance_success(validator_set, set_candidate_maintenance): turn_round() validator_set.deposit(consensuses[0], {"value": 100, "from": accounts[99]}) validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 chain.mine(40) tx = validator_set.exitMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] == 0 expect_event(tx, "validatorExitMaintenance", { 'validator': consensuses[0] @@ -2090,7 +2170,7 @@ def test_success_multiple_validators_exitmaintenance(validator_set, candidate_hu for i in range(2): tx = validator_set.exitMaintenance({'from': operators[i]}) - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] == 0 expect_event(tx, "validatorExitMaintenance", { 'validator': consensuses[i] @@ -2398,12 +2478,12 @@ def test_validator_already_in_maintenance_success(validator_set, set_candidate_m turn_round() validator_set.enterMaintenance({'from': operators[0]}) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 tx = validator_set.enterMaintenance(consensuses[0]) - validator_after = validator_set.getValidatorByConsensus(consensuses[0]) + validator_after = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator_after['enterMaintenanceHeight'] == validator['enterMaintenanceHeight'] assert 'validatorEnterMaintenance' not in tx.events @@ -2419,7 +2499,7 @@ def test_only_one_working_validator_success(validator_set): tx = validator_set.enterMaintenance(consensus) - validator = validator_set.getValidatorByConsensus(consensus) + validator = validator_set.getValidatorExByConsensus(consensus) assert validator['enterMaintenanceHeight'] == 0 assert 'validatorEnterMaintenance' not in tx.events @@ -2431,7 +2511,7 @@ def test_working_count_equals_validator_count_success(validator_set, candidate_h __update_slash_address() assert validator_set.getWorkingCount() == len(operators) tx = validator_set.enterMaintenance(consensuses[0]) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] == 0 assert 'validatorEnterMaintenance' not in tx.events @@ -2444,7 +2524,7 @@ def test_validator_can_enter_maintenance_success(validator_set, set_candidate_ma assert validator_set.getWorkingCount() == 4 assert validator_set.validatorCount() == 3 tx = validator_set.enterMaintenance(consensuses[0]) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 expect_event(tx, "validatorEnterMaintenance", { 'validator': consensuses[0] @@ -2458,7 +2538,7 @@ def test_multiple_validators_enter_maintenance_success(validator_set, candidate_ turn_round() for i in range(2): tx = validator_set.enterMaintenance(consensuses[i]) - validator = validator_set.getValidatorByConsensus(consensuses[i]) + validator = validator_set.getValidatorExByConsensus(consensuses[i]) assert validator['enterMaintenanceHeight'] != 0 expect_event(tx, "validatorEnterMaintenance", { 'validator': consensuses[i] @@ -2471,15 +2551,15 @@ def test_validator_after_exit_maintenance_success(validator_set, set_candidate_m turn_round() validator_set.enterMaintenance(consensuses[0]) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 validator_set.exitMaintenance({'from': operators[0]}) - validator_after_exit = validator_set.getValidatorByConsensus(consensuses[0]) + validator_after_exit = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator_after_exit['enterMaintenanceHeight'] == 0 tx = validator_set.enterMaintenance(consensuses[0]) - validator_after_enter = validator_set.getValidatorByConsensus(consensuses[0]) + validator_after_enter = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator_after_enter['enterMaintenanceHeight'] != 0 expect_event(tx, "validatorEnterMaintenance", { 'validator': consensuses[0] @@ -2508,7 +2588,7 @@ def test_validator_has_income_success(validator_set, set_candidate_maintenance): tx = validator_set.enterMaintenance(consensuses[0]) - validator = validator_set.getValidatorByConsensus(consensuses[0]) + validator = validator_set.getValidatorExByConsensus(consensuses[0]) assert validator['enterMaintenanceHeight'] != 0 assert validator_set.getIncoming(consensuses[0]) == deposit_value expect_event(tx, "validatorEnterMaintenance", { @@ -3007,13 +3087,14 @@ def test_get_working_validators_consistency(validator_set, set_candidate_mainten working_validators = validator_set.getWorkingValidators() assert working_count == len(working_validators) + # getTurnLength def test_get_turn_length_success(validator_set): assert validator_set.getTurnLength() == 1 + def test_get_turn_length_after_update(validator_set): __update_gov_address() hex_value = padding_left(Web3.to_hex(2), 64) tx = validator_set.updateParam('turnLength', hex_value) assert validator_set.turnLength() == 2 - \ No newline at end of file From 576e42ecdf312fb36d58be3999dc4dff629a8806 Mon Sep 17 00:00:00 2001 From: kevin9936 Date: Fri, 7 Nov 2025 16:58:16 +0800 Subject: [PATCH 27/27] fix gas errors in tests --- tests/test_btc_light_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_btc_light_client.py b/tests/test_btc_light_client.py index 5f2d2ae7b..974e35bef 100644 --- a/tests/test_btc_light_client.py +++ b/tests/test_btc_light_client.py @@ -9,7 +9,7 @@ def teardown_module(): - gas_price(False) + gas_price(0.1) @pytest.fixture(scope="module", autouse=True)