diff --git a/README.md b/README.md index 83bb08fa..de838a84 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,9 @@ Please make sure your dependency version is as follows: Node: v12.18.3 -Truffle: v5.1.31 -Solc: 0.6.4+commit.1dca32f3 - -Tips: You can manage multi version of Solc and Node: +Tips: You can manage multi version of Node: ```Shell -## Install solc-select and solc -pip3 install solc-select -solc-select install 0.6.4 && solc-select use 0.6.4 - ## Install nvm and node curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash nvm install 12.18.3 && nvm use 12.18.3 @@ -53,7 +46,7 @@ forge test ## Flatten all system contracts ```shell script -npm run flatten +bash flatten.sh ``` All system contracts will be flattened and output into `${workspace}/contracts/flattened/`. @@ -62,19 +55,18 @@ All system contracts will be flattened and output into `${workspace}/contracts/f 1. Edit `init_holders.js` file to alloc the initial BNB holder. 2. Edit `validators.js` file to alloc the initial validator set. -3. Edit `generate-validatorset.js` file to change `fromChainId` and `toChainId`, -4. Edit `generate-tokenhub.js` file to change `refundRelayReward`, `minimumRelayFee` and `maxGasForCallingBEP20`. -5. Edit `generate-tendermintlightclient.js` file to change `chainID` and `initConsensusStateBytes`. -6. run ` node generate-genesis.js` will generate genesis.json +3. Run `bash scripts/generate-*.sh` to change system contracts setting. +4. Run `node scripts/generate-genesis.js` will generate genesis.json ## How to generate mainnet/testnet/QA genesis file ```shell -npm run generate-mainnet -npm run generate-testnet -npm run generate-QA +bash scripts/generate.sh mainnet +bash scripts/generate.sh testnet +bash scripts/generate.sh QA +bash scripts/generate.sh local ``` -Check the `genesis.json` file and you can get the exact compiled bytecode for different network. +Check the `genesis.json` file, and you can get the exact compiled bytecode for different network. ## How to update contract interface for test @@ -83,7 +75,7 @@ Check the `genesis.json` file and you can get the exact compiled bytecode for di forge build // generate interface -cast interface ${workspace}/out/{contract_name}.sol/${contract_name}.json -p ^0.8.10 -n ${contract_name} > ${workspace}/lib/interface/I${contract_name}.sol +cast interface ${workspace}/out/{contract_name}.sol/${contract_name}.json -p ^0.8.0 -n ${contract_name} > ${workspace}/test/utils/interface/I${contract_name}.sol ``` ## BEP-171 unlock bot diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 9f0ceae7..c3482ec6 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -32,7 +32,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // the precision of cross chain value transfer. uint256 public constant PRECISION = 1e10; uint256 public constant EXPIRE_TIME_SECOND_GAP = 1000; - uint256 public constant MAX_NUM_OF_VALIDATORS = 41; + uint256 public constant MAX_NUM_OF_VALIDATORS = 100; bytes public constant INIT_VALIDATORSET_BYTES = hex"f87680f873f871949fb29aac15b9a4b7f17c3385939b007540f4d791949fb29aac15b9a4b7f17c3385939b007540f4d791949fb29aac15b9a4b7f17c3385939b007540f4d79164b085e6972fc98cd3c81d64d40e325acfed44365b97a7567a27939c14dbc7512ddcf54cb1284eb637cfa308ae4e00cb5588"; @@ -56,9 +56,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 public constant BURN_RATIO_SCALE = 10000; address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; - uint256 public constant INIT_BURN_RATIO = 0; + uint256 public constant INIT_BURN_RATIO = 1000; uint256 public burnRatio; - bool public burnRatioInitialized; + bool public burnRatioInitialized; // deprecated // BEP-127 Temporary Maintenance uint256 public constant INIT_MAX_NUM_OF_MAINTAINING = 3; @@ -77,14 +77,16 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 public maxNumOfWorkingCandidates; // BEP-126 Fast Finality - uint256 public constant INIT_FINALITY_REWARD_RATIO = 50; + uint256 public constant INIT_SYSTEM_REWARD_RATIO = 625; // 625/10000 is 1/16 + uint256 public constant SYSTEM_REWARD_RATIO_SCALE = 10000; uint256 public constant MAX_SYSTEM_REWARD_BALANCE = 100 ether; - uint256 public finalityRewardRatio; + uint256 public systemRewardRatio; uint256 public previousHeight; - uint256 public previousBalanceOfSystemReward; + uint256 public previousBalanceOfSystemReward; // deprecated bytes[] public previousVoteAddrFullSet; bytes[] public currentVoteAddrFullSet; + bool public isSystemRewardIncluded; struct Validator { address consensusAddress; @@ -221,17 +223,24 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica * * @param valAddr The validator address who produced the current block */ - function deposit(address valAddr) external payable onlyCoinbase onlyInit noEmptyDeposit{ + function deposit(address valAddr) external payable onlyCoinbase onlyInit noEmptyDeposit onlyZeroGasPrice { uint256 value = msg.value; uint256 index = currentValidatorSetMap[valAddr]; - uint256 curBurnRatio = INIT_BURN_RATIO; - if (burnRatioInitialized) { - curBurnRatio = burnRatio; + if (isSystemRewardIncluded == false){ + systemRewardRatio = INIT_SYSTEM_REWARD_RATIO; + burnRatio = INIT_BURN_RATIO; + isSystemRewardIncluded = true; } - if (value > 0 && curBurnRatio > 0) { - uint256 toBurn = value.mul(curBurnRatio).div(BURN_RATIO_SCALE); + uint256 toSystemReward = value.mul(systemRewardRatio).div(SYSTEM_REWARD_RATIO_SCALE); + if (toSystemReward > 0) { + address(uint160(SYSTEM_REWARD_ADDR)).transfer(toSystemReward); + emit systemTransfer(toSystemReward); + } + + if (value > 0 && burnRatio > 0) { + uint256 toBurn = value.mul(burnRatio).div(BURN_RATIO_SCALE); if (toBurn > 0) { address(uint160(BURN_ADDRESS)).transfer(toBurn); emit feeBurned(toBurn); @@ -240,6 +249,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } } + value = value.sub(toSystemReward); if (index>0) { Validator storage validator = currentValidatorSet[index-1]; if (validator.jailed) { @@ -540,28 +550,18 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return isWorkingValidator(index); } - function distributeFinalityReward(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase oncePerBlock onlyInit { - // first time to call this function - if (finalityRewardRatio == 0) { - finalityRewardRatio = INIT_FINALITY_REWARD_RATIO; - previousBalanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; - return; - } - + function distributeFinalityReward(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase oncePerBlock onlyZeroGasPrice onlyInit { uint256 totalValue; uint256 balanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; if (balanceOfSystemReward > MAX_SYSTEM_REWARD_BALANCE) { - totalValue = balanceOfSystemReward.div(100); - } else if (balanceOfSystemReward > previousBalanceOfSystemReward) { // when a slash happens, theres will no rewards in some epoches, // it's tolerated because slash happens rarely - totalValue = (balanceOfSystemReward.sub(previousBalanceOfSystemReward)).mul(finalityRewardRatio).div(100); + totalValue = balanceOfSystemReward.sub(MAX_SYSTEM_REWARD_BALANCE); } else { return; } - totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(address(this)), totalValue); - previousBalanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; + totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewardsforFinality(payable(address(this)), totalValue); if (totalValue == 0) { return; } @@ -704,7 +704,6 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 newBurnRatio = BytesToTypes.bytesToUint256(32, value); require(newBurnRatio <= BURN_RATIO_SCALE, "the burnRatio must be no greater than 10000"); burnRatio = newBurnRatio; - burnRatioInitialized = true; } else if (Memory.compareStrings(key, "maxNumOfMaintaining")) { require(value.length == 32, "length of maxNumOfMaintaining mismatch"); uint256 newMaxNumOfMaintaining = BytesToTypes.bytesToUint256(32, value); @@ -737,11 +736,11 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica require(newNumOfCabinets > 0, "the numOfCabinets must be greater than 0"); require(newNumOfCabinets <= MAX_NUM_OF_VALIDATORS, "the numOfCabinets must be less than MAX_NUM_OF_VALIDATORS"); numOfCabinets = newNumOfCabinets; - } else if (Memory.compareStrings(key, "finalityRewardRatio")) { - require(value.length == 32, "length of finalityRewardRatio mismatch"); - uint256 newFinalityRewardRatio = BytesToTypes.bytesToUint256(32, value); - require(newFinalityRewardRatio >= 1 && newFinalityRewardRatio <= 100, "the finalityRewardRatio is out of range"); - finalityRewardRatio = newFinalityRewardRatio; + } else if (Memory.compareStrings(key, "systemRewardRatio")) { + require(value.length == 32, "length of systemRewardRatio mismatch"); + uint256 newSystemRewardRatio = BytesToTypes.bytesToUint256(32, value); + require(newSystemRewardRatio >= 1 && newSystemRewardRatio <= SYSTEM_REWARD_RATIO_SCALE, "the systemRewardRatio must be no greater than 10000"); + systemRewardRatio = newSystemRewardRatio; } else { require(false, "unknown param"); } diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template deleted file mode 100644 index ca3fd503..00000000 --- a/contracts/BSCValidatorSet.template +++ /dev/null @@ -1,1150 +0,0 @@ -pragma solidity 0.6.4; -pragma experimental ABIEncoderV2; - -import "./System.sol"; -import "./lib/BytesLib.sol"; -import "./lib/BytesToTypes.sol"; -import "./lib/Memory.sol"; -import "./interface/ILightClient.sol"; -import "./interface/ISlashIndicator.sol"; -import "./interface/ITokenHub.sol"; -import "./interface/IRelayerHub.sol"; -import "./interface/IParamSubscriber.sol"; -import "./interface/IBSCValidatorSet.sol"; -import "./interface/IApplication.sol"; -import "./lib/SafeMath.sol"; -import "./lib/RLPDecode.sol"; -import "./lib/CmnPkg.sol"; - - -contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplication { - - using SafeMath for uint256; - - using RLPDecode for *; - - // will not transfer value less than 0.1 BNB for validators - uint256 constant public DUSTY_INCOMING = 1e17; - - uint8 public constant JAIL_MESSAGE_TYPE = 1; - uint8 public constant VALIDATORS_UPDATE_MESSAGE_TYPE = 0; - - // the precision of cross chain value transfer. - uint256 public constant PRECISION = 1e10; - uint256 public constant EXPIRE_TIME_SECOND_GAP = 1000; - uint256 public constant MAX_NUM_OF_VALIDATORS = 41; - - bytes public constant INIT_VALIDATORSET_BYTES = hex"{{initValidatorSetBytes}}"; - - uint32 public constant ERROR_UNKNOWN_PACKAGE_TYPE = 101; - uint32 public constant ERROR_FAIL_CHECK_VALIDATORS = 102; - uint32 public constant ERROR_LEN_OF_VAL_MISMATCH = 103; - uint32 public constant ERROR_RELAYFEE_TOO_LARGE = 104; - - uint256 public constant INIT_NUM_OF_CABINETS = 21; - uint256 public constant EPOCH = 200; - - /*********************** state of the contract **************************/ - Validator[] public currentValidatorSet; - uint256 public expireTimeSecondGap; - uint256 public totalInComing; - - // key is the `consensusAddress` of `Validator`, - // value is the index of the element in `currentValidatorSet`. - mapping(address =>uint256) public currentValidatorSetMap; - uint256 public numOfJailed; - - uint256 public constant BURN_RATIO_SCALE = 10000; - address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; - uint256 public constant INIT_BURN_RATIO = {{initBurnRatio}}; - uint256 public burnRatio; - bool public burnRatioInitialized; - - // BEP-127 Temporary Maintenance - uint256 public constant INIT_MAX_NUM_OF_MAINTAINING = 3; - uint256 public constant INIT_MAINTAIN_SLASH_SCALE = 2; - - uint256 public maxNumOfMaintaining; - uint256 public numOfMaintaining; - uint256 public maintainSlashScale; - - // Corresponds strictly to currentValidatorSet - // validatorExtraSet[index] = the `ValidatorExtra` info of currentValidatorSet[index] - ValidatorExtra[] public validatorExtraSet; - // BEP-131 candidate validator - uint256 public numOfCabinets; - uint256 public maxNumOfCandidates; - uint256 public maxNumOfWorkingCandidates; - - // BEP-126 Fast Finality - uint256 public constant INIT_FINALITY_REWARD_RATIO = 50; - uint256 public constant MAX_SYSTEM_REWARD_BALANCE = 100 ether; - - uint256 public finalityRewardRatio; - uint256 public previousHeight; - uint256 public previousBalanceOfSystemReward; - bytes[] public previousVoteAddrFullSet; - bytes[] public currentVoteAddrFullSet; - - struct Validator { - address consensusAddress; - address payable feeAddress; - address BBCFeeAddress; - uint64 votingPower; - - // only in state - bool jailed; - uint256 incoming; - } - - struct ValidatorExtra { - // BEP-127 Temporary Maintenance - uint256 enterMaintenanceHeight; // the height from where the validator enters Maintenance - bool isMaintaining; - - // BEP-126 Fast Finality - bytes voteAddress; - - // reserve for future use - uint256[19] slots; - } - - /*********************** cross chain package **************************/ - struct IbcValidatorSetPackage { - uint8 packageType; - Validator[] validatorSet; - bytes[] voteAddrs; - } - - /*********************** modifiers **************************/ - modifier noEmptyDeposit() { - require(msg.value > 0, "deposit value is zero"); - _; - } - - modifier initValidatorExtraSet() { - if (validatorExtraSet.length == 0) { - ValidatorExtra memory validatorExtra; - // init validatorExtraSet - uint256 validatorsNum = currentValidatorSet.length; - for (uint i; i previousHeight, "can not do this twice in one block"); - _; - previousHeight = block.number; - } - - /*********************** events **************************/ - event validatorSetUpdated(); - event validatorJailed(address indexed validator); - event validatorEmptyJailed(address indexed validator); - event batchTransfer(uint256 amount); - event batchTransferFailed(uint256 indexed amount, string reason); - event batchTransferLowerFailed(uint256 indexed amount, bytes reason); - event systemTransfer(uint256 amount); - event directTransfer(address payable indexed validator, uint256 amount); - event directTransferFail(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); - event validatorFelony(address indexed validator, uint256 amount); - event failReasonWithStr(string message); - event unexpectedPackage(uint8 channelId, bytes msgBytes); - event paramChange(string key, bytes value); - event feeBurned(uint256 amount); - event validatorEnterMaintenance(address indexed validator); - event validatorExitMaintenance(address indexed validator); - event finalityRewardDeposit(address indexed validator, uint256 amount); - event deprecatedFinalityRewardDeposit(address indexed validator, uint256 amount); - - /*********************** init **************************/ - function init() external onlyNotInit{ - (IbcValidatorSetPackage memory validatorSetPkg, bool valid)= decodeValidatorSetSynPackage(INIT_VALIDATORSET_BYTES); - require(valid, "failed to parse init validatorSet"); - {% if network == 'local' %} - ValidatorExtra memory validatorExtra; - {% endif %} - for (uint i; i 0 && curBurnRatio > 0) { - uint256 toBurn = value.mul(curBurnRatio).div(BURN_RATIO_SCALE); - if (toBurn > 0) { - address(uint160(BURN_ADDRESS)).transfer(toBurn); - emit feeBurned(toBurn); - - value = value.sub(toBurn); - } - } - - if (index>0) { - Validator storage validator = currentValidatorSet[index-1]; - if (validator.jailed) { - emit deprecatedDeposit(valAddr,value); - } else { - totalInComing = totalInComing.add(value); - validator.incoming = validator.incoming.add(value); - emit validatorDeposit(valAddr,value); - } - } else { - // get incoming from deprecated validator; - emit deprecatedDeposit(valAddr,value); - } - } - - function jailValidator(Validator memory v) internal returns (uint32) { - uint256 index = currentValidatorSetMap[v.consensusAddress]; - if (index==0 || currentValidatorSet[index-1].jailed) { - emit validatorEmptyJailed(v.consensusAddress); - return CODE_OK; - } - uint n = currentValidatorSet.length; - bool shouldKeep = (numOfJailed >= n-1); - // will not jail if it is the last valid validator - if (shouldKeep) { - emit validatorEmptyJailed(v.consensusAddress); - return CODE_OK; - } - ++numOfJailed; - currentValidatorSet[index-1].jailed = true; - emit validatorJailed(v.consensusAddress); - return CODE_OK; - } - - function updateValidatorSet(Validator[] memory validatorSet, bytes[] memory voteAddrs) internal returns (uint32) { - { - // do verify. - if (validatorSet.length > MAX_NUM_OF_VALIDATORS) { - emit failReasonWithStr("the number of validators exceed the limit"); - return ERROR_FAIL_CHECK_VALIDATORS; - } - for (uint i; i= DUSTY_INCOMING) { - ++crossSize; - } else if (currentValidatorSet[i].incoming > 0) { - ++directSize; - } - } - - //cross transfer - address[] memory crossAddrs = new address[](crossSize); - uint256[] memory crossAmounts = new uint256[](crossSize); - uint256[] memory crossIndexes = new uint256[](crossSize); - address[] memory crossRefundAddrs = new address[](crossSize); - uint256 crossTotal; - // direct transfer - address payable[] memory directAddrs = new address payable[](directSize); - uint256[] memory directAmounts = new uint256[](directSize); - crossSize = 0; - directSize = 0; - uint256 relayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); - if (relayFee > DUSTY_INCOMING) { - emit failReasonWithStr("fee is larger than DUSTY_INCOMING"); - return ERROR_RELAYFEE_TOO_LARGE; - } - for (uint i; i= DUSTY_INCOMING) { - crossAddrs[crossSize] = currentValidatorSet[i].BBCFeeAddress; - uint256 value = currentValidatorSet[i].incoming - currentValidatorSet[i].incoming % PRECISION; - crossAmounts[crossSize] = value.sub(relayFee); - crossRefundAddrs[crossSize] = currentValidatorSet[i].feeAddress; - crossIndexes[crossSize] = i; - crossTotal = crossTotal.add(value); - ++crossSize; - } else if (currentValidatorSet[i].incoming > 0) { - directAddrs[directSize] = currentValidatorSet[i].feeAddress; - directAmounts[directSize] = currentValidatorSet[i].incoming; - ++directSize; - } - } - - //step 2: do cross chain transfer - bool failCross = false; - if (crossTotal > 0) { - try ITokenHub(TOKEN_HUB_ADDR).batchTransferOutBNB{value : crossTotal}(crossAddrs, crossAmounts, crossRefundAddrs, uint64(block.timestamp + expireTimeSecondGap)) returns (bool success) { - if (success) { - emit batchTransfer(crossTotal); - } else { - emit batchTransferFailed(crossTotal, "batch transfer return false"); - } - }catch Error(string memory reason) { - failCross = true; - emit batchTransferFailed(crossTotal, reason); - }catch (bytes memory lowLevelData) { - failCross = true; - emit batchTransferLowerFailed(crossTotal, lowLevelData); - } - } - - if (failCross) { - for (uint i; i0) { - for (uint i; i0) { - emit systemTransfer(address(this).balance); - address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); - } - // step 5: do update validator set state - totalInComing = 0; - numOfJailed = 0; - if (validatorSetTemp.length>0) { - doUpdateState(validatorSetTemp, voteAddrsTemp); - } - - // step 6: clean slash contract - ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); - emit validatorSetUpdated(); - return CODE_OK; - } - - /** - * @dev With each epoch, there will be a partial rotation between cabinets and candidates. Rotation is determined by this function - * - */ - function shuffle(address[] memory validators, bytes[] memory voteAddrs, uint256 epochNumber, uint startIdx, uint offset, uint limit, uint modNumber) internal pure { - for (uint i; i 0) { - uint256 epochNumber = block.number / EPOCH; - shuffle(validators, voteAddrs, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); - shuffle(validators, voteAddrs, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, - _maxNumOfWorkingCandidates, validators.length - _numOfCabinets+_maxNumOfWorkingCandidates); - } - address[] memory miningValidators = new address[](_numOfCabinets); - bytes[] memory miningVoteAddrs = new bytes[](_numOfCabinets); - for (uint i; i<_numOfCabinets; ++i) { - miningValidators[i] = validators[i]; - miningVoteAddrs[i] = voteAddrs[i]; - } - return (miningValidators, miningVoteAddrs); - } - - /** - * @dev Get all validators, including all of the cabinets and all of the candidates - * - */ - function getValidators() public view returns(address[] memory) { - uint n = currentValidatorSet.length; - uint valid = 0; - for (uint i; i= currentValidatorSet.length) { - return false; - } - - // validatorExtraSet[index] should not be used before it has been init. - if (index >= validatorExtraSet.length) { - return !currentValidatorSet[index].jailed; - } - - return !currentValidatorSet[index].jailed && !validatorExtraSet[index].isMaintaining; - } - - function getIncoming(address validator)external view returns(uint256) { - uint256 index = currentValidatorSetMap[validator]; - if (index<=0) { - return 0; - } - return currentValidatorSet[index-1].incoming; - } - - function isCurrentValidator(address validator) external view override returns (bool) { - uint256 index = currentValidatorSetMap[validator]; - if (index <= 0) { - return false; - } - - // the actual index - index = index - 1; - return isWorkingValidator(index); - } - - function distributeFinalityReward(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase oncePerBlock onlyInit { - // first time to call this function - if (finalityRewardRatio == 0) { - finalityRewardRatio = INIT_FINALITY_REWARD_RATIO; - previousBalanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; - return; - } - - uint256 totalValue; - uint256 balanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; - if (balanceOfSystemReward > MAX_SYSTEM_REWARD_BALANCE) { - totalValue = balanceOfSystemReward.div(100); - } else if (balanceOfSystemReward > previousBalanceOfSystemReward) { - // when a slash happens, theres will no rewards in some epoches, - // it's tolerated because slash happens rarely - totalValue = (balanceOfSystemReward.sub(previousBalanceOfSystemReward)).mul(finalityRewardRatio).div(100); - } else { - return; - } - - totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(address(this)), totalValue); - previousBalanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; - if (totalValue == 0) { - return; - } - - uint256 totalWeight; - for (uint256 i; i 0) { - Validator storage validator = currentValidatorSet[index - 1]; - if (validator.jailed) { - emit deprecatedFinalityRewardDeposit(valAddr, value); - } else { - totalInComing = totalInComing.add(value); - validator.incoming = validator.incoming.add(value); - emit finalityRewardDeposit(valAddr, value); - } - } else { - // get incoming from deprecated validator; - emit deprecatedFinalityRewardDeposit(valAddr, value); - } - } - - } - - function getWorkingValidatorCount() public view returns(uint256 workingValidatorCount) { - workingValidatorCount = getValidators().length; - uint256 _numOfCabinets = numOfCabinets > 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; - if (workingValidatorCount > _numOfCabinets) { - workingValidatorCount = _numOfCabinets; - } - if (workingValidatorCount == 0) { - workingValidatorCount = 1; - } - } - /*********************** For slash **************************/ - function misdemeanor(address validator) external onlySlash initValidatorExtraSet override { - uint256 validatorIndex = _misdemeanor(validator); - if (canEnterMaintenance(validatorIndex)) { - _enterMaintenance(validator, validatorIndex); - } - } - - function felony(address validator)external onlySlash initValidatorExtraSet override{ - uint256 index = currentValidatorSetMap[validator]; - if (index <= 0) { - return; - } - // the actual index - index = index - 1; - - bool isMaintaining = validatorExtraSet[index].isMaintaining; - if (_felony(validator, index) && isMaintaining) { - --numOfMaintaining; - } - } - - /*********************** For Temporary Maintenance **************************/ - function getCurrentValidatorIndex(address _validator) public view returns (uint256) { - uint256 index = currentValidatorSetMap[_validator]; - require(index > 0, "only current validators"); - - // the actual index - return index - 1; - } - - function canEnterMaintenance(uint256 index) public view returns (bool) { - if (index >= currentValidatorSet.length) { - return false; - } - - if ( - currentValidatorSet[index].consensusAddress == address(0) // - 0. check if empty validator - || (maxNumOfMaintaining == 0 || maintainSlashScale == 0) // - 1. check if not start - || numOfMaintaining >= maxNumOfMaintaining // - 2. check if reached upper limit - || !isWorkingValidator(index) // - 3. check if not working(not jailed and not maintaining) - || validatorExtraSet[index].enterMaintenanceHeight > 0 // - 5. check if has Maintained during current 24-hour period - // current validators are selected every 24 hours(from 00:00:00 UTC to 23:59:59 UTC) - || getValidators().length <= 1 // - 6. check num of remaining working validators - ) { - return false; - } - - return true; - } - - - /** - * @dev Enter maintenance for current validators. refer to https://github.com/bnb-chain/BEPs/blob/master/BEP127.md - * - */ - function enterMaintenance() external initValidatorExtraSet { - // check maintain config - if (maxNumOfMaintaining == 0) { - maxNumOfMaintaining = INIT_MAX_NUM_OF_MAINTAINING; - } - if (maintainSlashScale == 0) { - maintainSlashScale = INIT_MAINTAIN_SLASH_SCALE; - } - - uint256 index = getCurrentValidatorIndex(msg.sender); - require(canEnterMaintenance(index), "can not enter Temporary Maintenance"); - _enterMaintenance(msg.sender, index); - } - - /** - * @dev Exit maintenance for current validators. refer to https://github.com/bnb-chain/BEPs/blob/master/BEP127.md - * - */ - function exitMaintenance() external { - uint256 index = getCurrentValidatorIndex(msg.sender); - - // jailed validators are allowed to exit maintenance - require(validatorExtraSet[index].isMaintaining, "not in maintenance"); - uint256 workingValidatorCount = getWorkingValidatorCount(); - _exitMaintenance(msg.sender, index, workingValidatorCount); - } - - /*********************** Param update ********************************/ - function updateParam(string calldata key, bytes calldata value) override external onlyInit onlyGov{ - if (Memory.compareStrings(key, "expireTimeSecondGap")) { - require(value.length == 32, "length of expireTimeSecondGap mismatch"); - uint256 newExpireTimeSecondGap = BytesToTypes.bytesToUint256(32, value); - require(newExpireTimeSecondGap >=100 && newExpireTimeSecondGap <= 1e5, "the expireTimeSecondGap is out of range"); - expireTimeSecondGap = newExpireTimeSecondGap; - } else if (Memory.compareStrings(key, "burnRatio")) { - require(value.length == 32, "length of burnRatio mismatch"); - uint256 newBurnRatio = BytesToTypes.bytesToUint256(32, value); - require(newBurnRatio <= BURN_RATIO_SCALE, "the burnRatio must be no greater than 10000"); - burnRatio = newBurnRatio; - burnRatioInitialized = true; - } else if (Memory.compareStrings(key, "maxNumOfMaintaining")) { - require(value.length == 32, "length of maxNumOfMaintaining mismatch"); - uint256 newMaxNumOfMaintaining = BytesToTypes.bytesToUint256(32, value); - uint256 _numOfCabinets = numOfCabinets; - if (_numOfCabinets == 0) { - _numOfCabinets = INIT_NUM_OF_CABINETS; - } - require(newMaxNumOfMaintaining < _numOfCabinets, "the maxNumOfMaintaining must be less than numOfCabinets"); - maxNumOfMaintaining = newMaxNumOfMaintaining; - } else if (Memory.compareStrings(key, "maintainSlashScale")) { - require(value.length == 32, "length of maintainSlashScale mismatch"); - uint256 newMaintainSlashScale = BytesToTypes.bytesToUint256(32, value); - require(newMaintainSlashScale > 0 && newMaintainSlashScale < 10, "the maintainSlashScale must be greater than 0 and less than 10"); - maintainSlashScale = newMaintainSlashScale; - } else if (Memory.compareStrings(key, "maxNumOfWorkingCandidates")) { - require(value.length == 32, "length of maxNumOfWorkingCandidates mismatch"); - uint256 newMaxNumOfWorkingCandidates = BytesToTypes.bytesToUint256(32, value); - require(newMaxNumOfWorkingCandidates <= maxNumOfCandidates, "the maxNumOfWorkingCandidates must be not greater than maxNumOfCandidates"); - maxNumOfWorkingCandidates = newMaxNumOfWorkingCandidates; - } else if (Memory.compareStrings(key, "maxNumOfCandidates")) { - require(value.length == 32, "length of maxNumOfCandidates mismatch"); - uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); - maxNumOfCandidates = newMaxNumOfCandidates; - if (maxNumOfWorkingCandidates > maxNumOfCandidates) { - maxNumOfWorkingCandidates = maxNumOfCandidates; - } - } else if (Memory.compareStrings(key, "numOfCabinets")) { - require(value.length == 32, "length of numOfCabinets mismatch"); - uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); - require(newNumOfCabinets > 0, "the numOfCabinets must be greater than 0"); - require(newNumOfCabinets <= MAX_NUM_OF_VALIDATORS, "the numOfCabinets must be less than MAX_NUM_OF_VALIDATORS"); - numOfCabinets = newNumOfCabinets; - } else if (Memory.compareStrings(key, "finalityRewardRatio")) { - require(value.length == 32, "length of finalityRewardRatio mismatch"); - uint256 newFinalityRewardRatio = BytesToTypes.bytesToUint256(32, value); - require(newFinalityRewardRatio >= 1 && newFinalityRewardRatio <= 100, "the finalityRewardRatio is out of range"); - finalityRewardRatio = newFinalityRewardRatio; - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } - - /*********************** Internal Functions **************************/ - function doUpdateState(Validator[] memory newValidatorSet, bytes[] memory newVoteAddrs) private { - uint n = currentValidatorSet.length; - uint m = newValidatorSet.length; - - // delete stale validators - for (uint i; im) { - for (uint i=m; in) { - ValidatorExtra memory _validatorExtra; - for (uint i=n; i < m; ++i) { - _validatorExtra.voteAddress = newVoteAddrs[i]; - currentValidatorSet.push(newValidatorSet[i]); - validatorExtraSet.push(_validatorExtra); - currentValidatorSetMap[newValidatorSet[i].consensusAddress] = i+1; - } - } - - // update vote addr full set - setPreviousVoteAddrFullSet(); - setCurrentVoteAddrFullSet(); - - // make sure all new validators are cleared maintainInfo - // should not happen, still protect - numOfMaintaining = 0; - n = currentValidatorSet.length; - for (uint i; im) { - for (uint i=m; in) { - for (uint i=n; i < m; ++i) { - previousVoteAddrFullSet.push(currentVoteAddrFullSet[i]); - } - } - } - - function setCurrentVoteAddrFullSet() private { - uint n = currentVoteAddrFullSet.length; - uint m = validatorExtraSet.length; - - if (n>m) { - for (uint i=m; in) { - for (uint i=n; i < m; ++i) { - currentVoteAddrFullSet.push(validatorExtraSet[i].voteAddress); - } - } - } - - function isMonitoredForMaliciousVote(bytes calldata voteAddr) external override view returns (bool) { - uint m = currentVoteAddrFullSet.length; - for (uint i; i 0; --index) { - i = index - 1; // the actual index - if (!validatorExtraSet[i].isMaintaining) { - continue; - } - - // only maintaining validators - validator = currentValidatorSet[i].consensusAddress; - - // exit maintenance - isFelony = _exitMaintenance(validator, i, workingValidatorCount); - if (!isFelony || numOfFelony >= _validatorSet.length - 1) { - continue; - } - - // record the jailed validator in validatorSet - for (uint k; k<_validatorSet.length; ++k) { - if (_validatorSet[k].consensusAddress == validator) { - _validatorSet[k].jailed = true; - ++numOfFelony; - break; - } - } - } - - // 2. get unjailed validators from validatorSet - unjailedValidatorSet = new Validator[](_validatorSet.length - numOfFelony); - unjailedVoteAddrs = new bytes[](_validatorSet.length - numOfFelony); - i = 0; - for (uint index; index<_validatorSet.length; ++index) { - if (!_validatorSet[index].jailed) { - unjailedValidatorSet[i] = _validatorSet[index]; - unjailedVoteAddrs[i] = _voteAddrs[index]; - ++i; - } - } - - return (unjailedValidatorSet, unjailedVoteAddrs); - } - - function _enterMaintenance(address validator, uint256 index) private { - ++numOfMaintaining; - validatorExtraSet[index].isMaintaining = true; - validatorExtraSet[index].enterMaintenanceHeight = block.number; - emit validatorEnterMaintenance(validator); - } - - function _exitMaintenance(address validator, uint index, uint256 workingValidatorCount) private returns (bool isFelony){ - if (maintainSlashScale == 0 || workingValidatorCount == 0 || numOfMaintaining == 0) { - // should not happen, still protect - return false; - } - - // step 0: modify numOfMaintaining - --numOfMaintaining; - - // step 1: calculate slashCount - uint256 slashCount = - block.number - .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(workingValidatorCount) - .div(maintainSlashScale); - - // step 2: clear maintaining info of the validator - validatorExtraSet[index].isMaintaining = false; - - // step3: slash the validator - (uint256 misdemeanorThreshold, uint256 felonyThreshold) = ISlashIndicator(SLASH_CONTRACT_ADDR).getSlashThresholds(); - isFelony = false; - if (slashCount >= felonyThreshold) { - _felony(validator, index); - ISlashIndicator(SLASH_CONTRACT_ADDR).sendFelonyPackage(validator); - isFelony = true; - } else if (slashCount >= misdemeanorThreshold) { - _misdemeanor(validator); - } - - emit validatorExitMaintenance(validator); - } - - //rlp encode & decode function - function decodeValidatorSetSynPackage(bytes memory msgBytes) internal pure returns(IbcValidatorSetPackage memory, bool) { - IbcValidatorSetPackage memory validatorSetPkg; - - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) { - validatorSetPkg.packageType = uint8(iter.next().toUint()); - } else if (idx == 1) { - RLPDecode.RLPItem[] memory items = iter.next().toList(); - validatorSetPkg.validatorSet = new Validator[](items.length); - validatorSetPkg.voteAddrs = new bytes[](items.length); - for (uint j; j address) public channelHandlerContractMap; - mapping(address => mapping(uint8 => bool))public registeredContractChannelMap; - mapping(uint8 => uint64) public channelSendSequenceMap; - mapping(uint8 => uint64) public channelReceiveSequenceMap; - mapping(uint8 => bool) public isRelayRewardFromSystemReward; - - // to prevent the utilization of ancient block header - mapping(uint8 => uint64) public channelSyncedHeaderMap; - - - // BEP-171: Security Enhancement for Cross-Chain Module - // 0xebbda044f67428d7e9b472f9124983082bcda4f84f5148ca0a9ccbe06350f196 - bytes32 public constant SUSPEND_PROPOSAL = keccak256("SUSPEND_PROPOSAL"); - // 0xcf82004e82990eca84a75e16ba08aa620238e076e0bc7fc4c641df44bbf5b55a - bytes32 public constant REOPEN_PROPOSAL = keccak256("REOPEN_PROPOSAL"); - // 0x605b57daa79220f76a5cdc8f5ee40e59093f21a4e1cec30b9b99c555e94c75b9 - bytes32 public constant CANCEL_TRANSFER_PROPOSAL = keccak256("CANCEL_TRANSFER_PROPOSAL"); - // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 - bytes32 public constant EMPTY_CONTENT_HASH = keccak256(""); - uint16 public constant INIT_SUSPEND_QUORUM = 1; - uint16 public constant INIT_REOPEN_QUORUM = 2; - uint16 public constant INIT_CANCEL_TRANSFER_QUORUM = 2; - uint256 public constant EMERGENCY_PROPOSAL_EXPIRE_PERIOD = 1 hours; - - bool public isSuspended; - // proposal type hash => latest emergency proposal - mapping(bytes32 => EmergencyProposal) public emergencyProposals; - // proposal type hash => the threshold of proposal approved - mapping(bytes32 => uint16) public quorumMap; - // IAVL key hash => is challenged - mapping(bytes32 => bool) public challenged; - - // struct - // BEP-171: Security Enhancement for Cross-Chain Module - struct EmergencyProposal { - uint16 quorum; - uint128 expiredAt; - bytes32 contentHash; - - address[] approvers; - } - - // event - event crossChainPackage(uint16 chainId, uint64 indexed oracleSequence, uint64 indexed packageSequence, uint8 indexed channelId, bytes payload); - event receivedPackage(uint8 packageType, uint64 indexed packageSequence, uint8 indexed channelId); - event unsupportedPackage(uint64 indexed packageSequence, uint8 indexed channelId, bytes payload); - event unexpectedRevertInPackageHandler(address indexed contractAddr, string reason); - event unexpectedFailureAssertionInPackageHandler(address indexed contractAddr, bytes lowLevelData); - event paramChange(string key, bytes value); - event enableOrDisableChannel(uint8 indexed channelId, bool isEnable); - event addChannel(uint8 indexed channelId, address indexed contractAddr); - - // BEP-171: Security Enhancement for Cross-Chain Module - event ProposalSubmitted( - bytes32 indexed proposalTypeHash, - address indexed proposer, - uint128 quorum, - uint128 expiredAt, - bytes32 contentHash - ); - event Suspended(address indexed executor); - event Reopened(address indexed executor); - event SuccessChallenge( - address indexed challenger, - uint64 packageSequence, - uint8 channelId - ); - - modifier sequenceInOrder(uint64 _sequence, uint8 _channelID) { - uint64 expectedSequence = channelReceiveSequenceMap[_channelID]; - require(_sequence == expectedSequence, "sequence not in order"); - - channelReceiveSequenceMap[_channelID]=expectedSequence+1; - _; - } - - modifier blockSynced(uint64 _height) { - require(ILightClient(LIGHT_CLIENT_ADDR).isHeaderSynced(_height), "light client not sync the block yet"); - _; - } - - modifier channelSupported(uint8 _channelID) { - require(channelHandlerContractMap[_channelID]!=address(0x0), "channel is not supported"); - _; - } - - modifier onlyRegisteredContractChannel(uint8 channleId) { - require(registeredContractChannelMap[msg.sender][channleId], "the contract and channel have not been registered"); - _; - } - - modifier headerInOrder(uint64 height, uint8 channelId) { - require(height >= channelSyncedHeaderMap[channelId], "too old header"); - if (height != channelSyncedHeaderMap[channelId]) { - channelSyncedHeaderMap[channelId] = height; - } - _; - } - - // BEP-171: Security Enhancement for Cross-Chain Module - modifier onlyCabinet() { - uint256 indexPlus = IBSCValidatorSetV2(VALIDATOR_CONTRACT_ADDR).currentValidatorSetMap(msg.sender); - uint256 numOfCabinets = IBSCValidatorSetV2(VALIDATOR_CONTRACT_ADDR).numOfCabinets(); - if (numOfCabinets == 0) { - numOfCabinets = 21; - } - - require(indexPlus > 0 && indexPlus <= numOfCabinets, "not cabinet"); - _; - } - - modifier whenNotSuspended() { - require(!isSuspended, "suspended"); - _; - } - - modifier whenSuspended() { - require(isSuspended, "not suspended"); - _; - } - - // | length | prefix | sourceChainID| destinationChainID | channelID | sequence | - // | 32 bytes | 1 byte | 2 bytes | 2 bytes | 1 bytes | 8 bytes | - function generateKey(uint64 _sequence, uint8 _channelID) internal pure returns(bytes memory) { - uint256 fullCROSS_CHAIN_KEY_PREFIX = CROSS_CHAIN_KEY_PREFIX | _channelID; - bytes memory key = new bytes(14); - - uint256 ptr; - assembly { - ptr := add(key, 14) - } - assembly { - mstore(ptr, _sequence) - } - ptr -= 8; - assembly { - mstore(ptr, fullCROSS_CHAIN_KEY_PREFIX) - } - ptr -= 6; - assembly { - mstore(ptr, 14) - } - return key; - } - - function init() external onlyNotInit { - channelHandlerContractMap[BIND_CHANNELID] = TOKEN_MANAGER_ADDR; - isRelayRewardFromSystemReward[BIND_CHANNELID] = false; - registeredContractChannelMap[TOKEN_MANAGER_ADDR][BIND_CHANNELID] = true; - - channelHandlerContractMap[TRANSFER_IN_CHANNELID] = TOKEN_HUB_ADDR; - isRelayRewardFromSystemReward[TRANSFER_IN_CHANNELID] = false; - registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_IN_CHANNELID] = true; - - channelHandlerContractMap[TRANSFER_OUT_CHANNELID] = TOKEN_HUB_ADDR; - isRelayRewardFromSystemReward[TRANSFER_OUT_CHANNELID] = false; - registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_OUT_CHANNELID] = true; - - - channelHandlerContractMap[STAKING_CHANNELID] = VALIDATOR_CONTRACT_ADDR; - isRelayRewardFromSystemReward[STAKING_CHANNELID] = true; - registeredContractChannelMap[VALIDATOR_CONTRACT_ADDR][STAKING_CHANNELID] = true; - - channelHandlerContractMap[GOV_CHANNELID] = GOV_HUB_ADDR; - isRelayRewardFromSystemReward[GOV_CHANNELID] = true; - registeredContractChannelMap[GOV_HUB_ADDR][GOV_CHANNELID] = true; - - channelHandlerContractMap[SLASH_CHANNELID] = SLASH_CONTRACT_ADDR; - isRelayRewardFromSystemReward[SLASH_CHANNELID] = true; - registeredContractChannelMap[SLASH_CONTRACT_ADDR][SLASH_CHANNELID] = true; - - batchSizeForOracle = INIT_BATCH_SIZE; - - oracleSequence = -1; - previousTxHeight = 0; - txCounter = 0; - - alreadyInit=true; - } - - function encodePayload(uint8 packageType, uint256 relayFee, bytes memory msgBytes) public pure returns(bytes memory) { - uint256 payloadLength = msgBytes.length + 33; - bytes memory payload = new bytes(payloadLength); - uint256 ptr; - assembly { - ptr := payload - } - ptr+=33; - - assembly { - mstore(ptr, relayFee) - } - - ptr-=32; - assembly { - mstore(ptr, packageType) - } - - ptr-=1; - assembly { - mstore(ptr, payloadLength) - } - - ptr+=65; - (uint256 src,) = Memory.fromBytes(msgBytes); - Memory.copy(src, ptr, msgBytes.length); - - return payload; - } - - // | type | relayFee |package | - // | 1 byte | 32 bytes | bytes | - function decodePayloadHeader(bytes memory payload) internal pure returns(bool, uint8, uint256, bytes memory) { - if (payload.length < 33) { - return (false, 0, 0, new bytes(0)); - } - - uint256 ptr; - assembly { - ptr := payload - } - - uint8 packageType; - ptr+=1; - assembly { - packageType := mload(ptr) - } - - uint256 relayFee; - ptr+=32; - assembly { - relayFee := mload(ptr) - } - - ptr+=32; - bytes memory msgBytes = new bytes(payload.length-33); - (uint256 dst, ) = Memory.fromBytes(msgBytes); - Memory.copy(ptr, dst, payload.length-33); - - return (true, packageType, relayFee, msgBytes); - } - - function handlePackage(bytes calldata payload, bytes calldata proof, uint64 height, uint64 packageSequence, uint8 channelId) - onlyInit - onlyRelayer - sequenceInOrder(packageSequence, channelId) - blockSynced(height) - channelSupported(channelId) - headerInOrder(height, channelId) - whenNotSuspended - external { - bytes memory payloadLocal = payload; // fix error: stack too deep, try removing local variables - bytes memory proofLocal = proof; // fix error: stack too deep, try removing local variables - require(MerkleProof.validateMerkleProof(ILightClient(LIGHT_CLIENT_ADDR).getAppHash(height), STORE_NAME, generateKey(packageSequence, channelId), payloadLocal, proofLocal), "invalid merkle proof"); - - address payable headerRelayer = ILightClient(LIGHT_CLIENT_ADDR).getSubmitter(height); - - uint64 sequenceLocal = packageSequence; // fix error: stack too deep, try removing local variables - uint8 channelIdLocal = channelId; // fix error: stack too deep, try removing local variables - (bool success, uint8 packageType, uint256 relayFee, bytes memory msgBytes) = decodePayloadHeader(payloadLocal); - if (!success) { - emit unsupportedPackage(sequenceLocal, channelIdLocal, payloadLocal); - return; - } - emit receivedPackage(packageType, sequenceLocal, channelIdLocal); - if (packageType == SYN_PACKAGE) { - address handlerContract = channelHandlerContractMap[channelIdLocal]; - try IApplication(handlerContract).handleSynPackage(channelIdLocal, msgBytes) returns (bytes memory responsePayload) { - if (responsePayload.length!=0) { - sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(ACK_PACKAGE, 0, responsePayload)); - channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1; - } - } catch Error(string memory reason) { - sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(FAIL_ACK_PACKAGE, 0, msgBytes)); - channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1; - emit unexpectedRevertInPackageHandler(handlerContract, reason); - } catch (bytes memory lowLevelData) { - sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(FAIL_ACK_PACKAGE, 0, msgBytes)); - channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1; - emit unexpectedFailureAssertionInPackageHandler(handlerContract, lowLevelData); - } - } else if (packageType == ACK_PACKAGE) { - address handlerContract = channelHandlerContractMap[channelIdLocal]; - try IApplication(handlerContract).handleAckPackage(channelIdLocal, msgBytes) { - } catch Error(string memory reason) { - emit unexpectedRevertInPackageHandler(handlerContract, reason); - } catch (bytes memory lowLevelData) { - emit unexpectedFailureAssertionInPackageHandler(handlerContract, lowLevelData); - } - } else if (packageType == FAIL_ACK_PACKAGE) { - address handlerContract = channelHandlerContractMap[channelIdLocal]; - try IApplication(handlerContract).handleFailAckPackage(channelIdLocal, msgBytes) { - } catch Error(string memory reason) { - emit unexpectedRevertInPackageHandler(handlerContract, reason); - } catch (bytes memory lowLevelData) { - emit unexpectedFailureAssertionInPackageHandler(handlerContract, lowLevelData); - } - } - IRelayerIncentivize(INCENTIVIZE_ADDR).addReward(headerRelayer, msg.sender, relayFee, isRelayRewardFromSystemReward[channelIdLocal] || packageType != SYN_PACKAGE); - } - - function sendPackage(uint64 packageSequence, uint8 channelId, bytes memory payload) internal whenNotSuspended { - if (block.number > previousTxHeight) { - ++oracleSequence; - txCounter = 1; - previousTxHeight=block.number; - } else { - ++txCounter; - if (txCounter>batchSizeForOracle) { - ++oracleSequence; - txCounter = 1; - } - } - emit crossChainPackage(bscChainID, uint64(oracleSequence), packageSequence, channelId, payload); - } - - function sendSynPackage(uint8 channelId, bytes calldata msgBytes, uint256 relayFee) - onlyInit - onlyRegisteredContractChannel(channelId) - external override { - uint64 sendSequence = channelSendSequenceMap[channelId]; - sendPackage(sendSequence, channelId, encodePayload(SYN_PACKAGE, relayFee, msgBytes)); - ++sendSequence; - channelSendSequenceMap[channelId] = sendSequence; - } - - function updateParam(string calldata key, bytes calldata value) - onlyGov - whenNotSuspended - external override { - if (Memory.compareStrings(key, "batchSizeForOracle")) { - uint256 newBatchSizeForOracle = BytesToTypes.bytesToUint256(32, value); - require(newBatchSizeForOracle <= 10000 && newBatchSizeForOracle >= 10, "the newBatchSizeForOracle should be in [10, 10000]"); - batchSizeForOracle = newBatchSizeForOracle; - } else if (Memory.compareStrings(key, "addOrUpdateChannel")) { - bytes memory valueLocal = value; - require(valueLocal.length == 22, "length of value for addOrUpdateChannel should be 22, channelId:isFromSystem:handlerAddress"); - uint8 channelId; - assembly { - channelId := mload(add(valueLocal, 1)) - } - - uint8 rewardConfig; - assembly { - rewardConfig := mload(add(valueLocal, 2)) - } - bool isRewardFromSystem = (rewardConfig == 0x0); - - address handlerContract; - assembly { - handlerContract := mload(add(valueLocal, 22)) - } - - require(isContract(handlerContract), "address is not a contract"); - channelHandlerContractMap[channelId]=handlerContract; - registeredContractChannelMap[handlerContract][channelId] = true; - isRelayRewardFromSystemReward[channelId] = isRewardFromSystem; - emit addChannel(channelId, handlerContract); - } else if (Memory.compareStrings(key, "enableOrDisableChannel")) { - bytes memory valueLocal = value; - require(valueLocal.length == 2, "length of value for enableOrDisableChannel should be 2, channelId:isEnable"); - - uint8 channelId; - assembly { - channelId := mload(add(valueLocal, 1)) - } - uint8 status; - assembly { - status := mload(add(valueLocal, 2)) - } - bool isEnable = (status == 1); - - address handlerContract = channelHandlerContractMap[channelId]; - if (handlerContract != address(0x00)) { //channel existing - registeredContractChannelMap[handlerContract][channelId] = isEnable; - emit enableOrDisableChannel(channelId, isEnable); - } - } else if (Memory.compareStrings(key, "suspendQuorum")) { - require(value.length == 2, "length of value for suspendQuorum should be 2"); - uint16 suspendQuorum = BytesToTypes.bytesToUint16(2, value); - require(suspendQuorum > 0 && suspendQuorum < 100, "invalid suspend quorum"); - quorumMap[SUSPEND_PROPOSAL] = suspendQuorum; - } else if (Memory.compareStrings(key, "reopenQuorum")) { - require(value.length == 2, "length of value for reopenQuorum should be 2"); - uint16 reopenQuorum = BytesToTypes.bytesToUint16(2, value); - require(reopenQuorum > 0 && reopenQuorum < 100, "invalid reopen quorum"); - quorumMap[REOPEN_PROPOSAL] = reopenQuorum; - } else if (Memory.compareStrings(key, "cancelTransferQuorum")) { - require(value.length == 2, "length of value for cancelTransferQuorum should be 2"); - uint16 cancelTransferQuorum = BytesToTypes.bytesToUint16(2, value); - require(cancelTransferQuorum > 0 && cancelTransferQuorum < 100, "invalid cancel transfer quorum"); - quorumMap[CANCEL_TRANSFER_PROPOSAL] = cancelTransferQuorum; - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } - - // BEP-171: Security Enhancement for Cross-Chain Module - function challenge( - // to avoid stack too deep error, using `uint64[4] calldata params` - // instead of `uint64 height0, uint64 height1, uint64 packageSequence, uint8 channelId` - uint64[4] calldata params, // 0-height0, 1-height1, 2-packageSequence, 3-channelId, - bytes calldata payload0, - bytes calldata payload1, - bytes calldata proof0, - bytes calldata proof1 - ) - onlyInit - blockSynced(params[0]) - blockSynced(params[1]) - channelSupported(uint8(params[3])) - whenNotSuspended - external { - // the same key with different values (payloads) - require(keccak256(payload0) != keccak256(payload1), "same payload"); - - bytes memory _key; - uint64 _packageSequence; - uint8 _channelId; - { - _packageSequence = params[2]; - _channelId = uint8(params[3]); - _key = generateKey(_packageSequence, _channelId); - bytes32 _keyHash = keccak256(_key); - require(!challenged[_keyHash], "already challenged"); - - // if succeeding in challenge - challenged[_keyHash] = true; - } - - // verify payload0 + proof0 - { - uint64 _height0 = params[0]; - bytes memory _payload0 = payload0; - bytes memory _proof0 = proof0; - bytes32 _appHash0 = ILightClient(LIGHT_CLIENT_ADDR).getAppHash(_height0); - require(MerkleProof.validateMerkleProof(_appHash0, STORE_NAME, _key, _payload0, _proof0), "invalid merkle proof0"); - } - - // verify payload1 + proof1 - { - uint64 _height1 = params[1]; - bytes memory _payload1 = payload1; - bytes memory _proof1 = proof1; - bytes32 _appHash1 = ILightClient(LIGHT_CLIENT_ADDR).getAppHash(_height1); - require(MerkleProof.validateMerkleProof(_appHash1, STORE_NAME, _key, _payload1, _proof1), "invalid merkle proof1"); - } - - _suspend(); - emit SuccessChallenge(msg.sender, _packageSequence, _channelId); - } - - function suspend() onlyInit onlyCabinet whenNotSuspended external { - bool isExecutable = _approveProposal(SUSPEND_PROPOSAL, EMPTY_CONTENT_HASH); - if (isExecutable) { - _suspend(); - } - } - - function reopen() onlyInit onlyCabinet whenSuspended external { - bool isExecutable = _approveProposal(REOPEN_PROPOSAL, EMPTY_CONTENT_HASH); - if (isExecutable) { - isSuspended = false; - emit Reopened(msg.sender); - } - } - - function cancelTransfer(address tokenAddr, address attacker) onlyInit onlyCabinet external { - bytes32 _contentHash = keccak256(abi.encode(tokenAddr, attacker)); - bool isExecutable = _approveProposal(CANCEL_TRANSFER_PROPOSAL, _contentHash); - if (isExecutable) { - ITokenHub(TOKEN_HUB_ADDR).cancelTransferIn(tokenAddr, attacker); - } - } - - function _approveProposal(bytes32 proposalTypeHash, bytes32 _contentHash) internal returns (bool isExecutable) { - if (quorumMap[proposalTypeHash] == 0) { - quorumMap[SUSPEND_PROPOSAL] = INIT_SUSPEND_QUORUM; - quorumMap[REOPEN_PROPOSAL] = INIT_REOPEN_QUORUM; - quorumMap[CANCEL_TRANSFER_PROPOSAL] = INIT_CANCEL_TRANSFER_QUORUM; - } - - EmergencyProposal storage p = emergencyProposals[proposalTypeHash]; - - // It is ok if there is an evil validator always cancel the previous vote, - // the credible validator could use private transaction service to send a batch tx including 2 approve transactions - if (block.timestamp >= p.expiredAt || p.contentHash != _contentHash) { - // current proposal expired / not exist or not same with the new, create a new EmergencyProposal - p.quorum = quorumMap[proposalTypeHash]; - p.expiredAt = uint128(block.timestamp + EMERGENCY_PROPOSAL_EXPIRE_PERIOD); - p.contentHash = _contentHash; - p.approvers = [msg.sender]; - - emit ProposalSubmitted(proposalTypeHash, msg.sender, p.quorum, p.expiredAt, _contentHash); - } else { - // current proposal exists - for (uint256 i = 0; i < p.approvers.length; ++i) { - require(p.approvers[i] != msg.sender, "already approved"); - } - p.approvers.push(msg.sender); - } - - if (p.approvers.length >= p.quorum) { - // 1. remove current proposal - delete emergencyProposals[proposalTypeHash]; - - // 2. exec this proposal - return true; - } - - return false; - } - - function _suspend() whenNotSuspended internal { - isSuspended = true; - emit Suspended(msg.sender); - } -} diff --git a/contracts/RelayerHub.template b/contracts/RelayerHub.template deleted file mode 100644 index e711e2bb..00000000 --- a/contracts/RelayerHub.template +++ /dev/null @@ -1,211 +0,0 @@ -pragma solidity 0.6.4; - -import "./lib/BytesToTypes.sol"; -import "./lib/Memory.sol"; -import "./interface/IRelayerHub.sol"; -import "./interface/IParamSubscriber.sol"; -import "./System.sol"; -import "./lib/SafeMath.sol"; - - -contract RelayerHub is IRelayerHub, System, IParamSubscriber { - using SafeMath for uint256; - - uint256 public constant INIT_REQUIRED_DEPOSIT = 1e20; - uint256 public constant INIT_DUES = 1e17; - - {% if network == 'local' %} - address public constant WHITELIST_1 = {{whitelist1Address}}; - address public constant WHITELIST_2 = {{whitelist2Address}}; - {% elif network == 'QA' %} - address public constant WHITELIST_1 = 0x88cb4D8F77742c24d647BEf8049D3f3C56067cDD; - address public constant WHITELIST_2 = 0x42D596440775C90db8d9187b47650986E1063493; - {% elif network == 'testnet' %} - address public constant WHITELIST_1 = 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791; - address public constant WHITELIST_2 = 0x37B8516a0F88E65D677229b402ec6C1e0E333004; - {% else %} - address public constant WHITELIST_1 = 0xb005741528b86F5952469d80A8614591E3c5B632; - address public constant WHITELIST_2 = 0x446AA6E0DC65690403dF3F127750da1322941F3e; - {% endif %} - - uint256 internal requiredDeposit; // have to keep it to not break the storage layout - uint256 internal dues; - - mapping(address => relayer) deprecatedRelayers; // old map holding the relayers which are to be allowed safe exit - mapping(address => bool) relayersExistMap; - - struct relayer { - uint256 deposit; - uint256 dues; - } - - mapping(address => bool) relayManagersExistMap; - mapping(address => address) managerToRelayer; - mapping(address => bool) currentRelayers; - mapping(address => bool) provisionalRelayers; - mapping(address => address) managerToProvisionalRelayer; - - bool public whitelistInitDone; - - modifier onlyManager(){ - require(relayManagersExistMap[msg.sender], "manager does not exist"); - _; - } - - modifier exist() { - require(relayersExistMap[msg.sender], "relayer do not exist"); - _; - } - - modifier onlyProvisionalRelayer() { - require(provisionalRelayers[msg.sender], "relayer is not a provisional relayer"); - _; - } - - event relayerUnRegister(address _relayer); - event paramChange(string key, bytes value); - - event managerRemoved(address _removedManager); - event managerAdded(address _addedManager); - event relayerUpdated(address _from, address _to); - event relayerAddedProvisionally(address _relayer); - - function init() external onlyNotInit { - requiredDeposit = INIT_REQUIRED_DEPOSIT; - dues = INIT_DUES; - {% if network == 'local' %} - whitelistInit(); - {% endif %} - alreadyInit = true; - } - - function unregister() external exist onlyInit { - relayer memory r = deprecatedRelayers[msg.sender]; - msg.sender.transfer(r.deposit.sub(r.dues)); - address payable systemPayable = address(uint160(SYSTEM_REWARD_ADDR)); - systemPayable.transfer(r.dues); - delete relayersExistMap[msg.sender]; - delete deprecatedRelayers[msg.sender]; - emit relayerUnRegister(msg.sender); - } - - function whitelistInit() {% if network == 'local' %} public {% else %} external {% endif %} { - require(!whitelistInitDone, "the whitelists already updated"); - addInitRelayer(WHITELIST_1); - addInitRelayer(WHITELIST_2); - whitelistInitDone = true; - } - - function addInitRelayer(address addr) internal { - relayManagersExistMap[addr] = true; - managerToRelayer[addr] = addr; // for the current whitelisted relayers we are keeping manager and relayer address the same - currentRelayers[addr] = true; - emit managerAdded(addr); - emit relayerUpdated(address(0), addr); - } - - /*********************** Param update ********************************/ - function updateParam(string calldata key, bytes calldata value) external override onlyInit onlyGov { - if (Memory.compareStrings(key, "addManager")) { - require(value.length == 20, "length of manager address mismatch"); - address newManager = BytesToTypes.bytesToAddress(20, value); - addManagerByGov(newManager); - } else if (Memory.compareStrings(key, "removeManager")) { - require(value.length == 20, "length of manager address mismatch"); - address managerAddress = BytesToTypes.bytesToAddress(20, value); - removeManager(managerAddress); - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } - - function removeManagerByHimself() external { - // here the manager removes himself - removeManager(msg.sender); - } - - function removeManager(address managerAddress) internal { - // check if the manager address already exists - require(relayManagersExistMap[managerAddress], "manager doesn't exist"); - - address relayerAddress = managerToRelayer[managerAddress]; - - delete (relayManagersExistMap[managerAddress]); - delete (managerToRelayer[managerAddress]); - - delete (provisionalRelayers[managerToProvisionalRelayer[managerAddress]]); - delete (managerToProvisionalRelayer[managerAddress]); - - // emit success event - emit managerRemoved(managerAddress); - if (relayerAddress != address(0)) { - delete (currentRelayers[relayerAddress]); - emit relayerUpdated(relayerAddress, address(0)); - } - } - - function addManagerByGov(address managerToBeAdded) internal { - require(!relayManagersExistMap[managerToBeAdded], "manager already exists"); - - relayManagersExistMap[managerToBeAdded] = true; - - emit managerAdded(managerToBeAdded); - } - - // updateRelayer() can be used to add relayer for the first time, update it in future and remove it - // in case of removal, we set relayerToBeAdded to be address(0) - function updateRelayer(address relayerToBeAdded) external onlyManager { - require(!isContract(relayerToBeAdded), "contract is not allowed to be a relayer"); - - if (relayerToBeAdded != address(0)) { - require(!currentRelayers[relayerToBeAdded], "relayer already exists"); - provisionalRelayers[relayerToBeAdded] = true; - managerToProvisionalRelayer[msg.sender] = relayerToBeAdded; - } else { - address oldRelayer = managerToRelayer[msg.sender]; - address oldProvisionalRelayer = managerToProvisionalRelayer[msg.sender]; - delete managerToRelayer[msg.sender]; - delete currentRelayers[oldRelayer]; - delete provisionalRelayers[oldProvisionalRelayer]; - delete managerToProvisionalRelayer[msg.sender]; - emit relayerUpdated(oldRelayer, relayerToBeAdded); - return; - } - - emit relayerAddedProvisionally(relayerToBeAdded); - } - - // acceptBeingRelayer needs to be called by the relayer after being added provisionally. - // This 2 step process of relayer updating is required to avoid having a contract as a relayer. - function acceptBeingRelayer(address manager) external onlyProvisionalRelayer { - - // ensure msg.sender is not contract and it is not a proxy - require(!isContract(msg.sender), "provisional relayer is a contract"); - require(tx.origin == msg.sender, "provisional relayer is a proxy"); - require(managerToProvisionalRelayer[manager] == msg.sender, "provisional is not set for this manager"); - - address oldRelayer = managerToRelayer[manager]; - - currentRelayers[msg.sender] = true; - managerToRelayer[manager] = msg.sender; - - delete provisionalRelayers[msg.sender]; - delete managerToProvisionalRelayer[manager]; - delete currentRelayers[oldRelayer]; - emit relayerUpdated(oldRelayer, msg.sender); - - } - - function isRelayer(address relayerAddress) external override view returns (bool){ - return currentRelayers[relayerAddress]; - } - - function isProvisionalRelayer(address relayerAddress) external view returns (bool){ - return provisionalRelayers[relayerAddress]; - } - - function isManager(address managerAddress) external view returns (bool){ - return relayManagersExistMap[managerAddress]; - } -} diff --git a/contracts/RelayerIncentivize.template b/contracts/RelayerIncentivize.template deleted file mode 100644 index e52c3740..00000000 --- a/contracts/RelayerIncentivize.template +++ /dev/null @@ -1,232 +0,0 @@ -pragma solidity 0.6.4; - -import "./interface/IRelayerIncentivize.sol"; -import "./System.sol"; -import "./lib/SafeMath.sol"; -import "./lib/Memory.sol"; -import "./lib/BytesToTypes.sol"; -import "./interface/IParamSubscriber.sol"; -import "./interface/ISystemReward.sol"; - -contract RelayerIncentivize is IRelayerIncentivize, System, IParamSubscriber { - - using SafeMath for uint256; - - uint256 public constant ROUND_SIZE={{roundSize}}; - uint256 public constant MAXIMUM_WEIGHT={{maximumWeight}}; - - uint256 public constant HEADER_RELAYER_REWARD_RATE_MOLECULE = {{headerRelayerRewardRateMolecule}}; - uint256 public constant HEADER_RELAYER_REWARD_RATE_DENOMINATOR = {{headerRelayerRewardRateDenominator}}; - uint256 public constant CALLER_COMPENSATION_MOLECULE = {{callerCompensationMolecule}}; - uint256 public constant CALLER_COMPENSATION_DENOMINATOR = {{callerCompensationDenominator}}; - - uint256 public headerRelayerRewardRateMolecule; - uint256 public headerRelayerRewardRateDenominator; - uint256 public callerCompensationMolecule; - uint256 public callerCompensationDenominator; - - mapping(address => uint256) public headerRelayersSubmitCount; - address payable[] public headerRelayerAddressRecord; - - mapping(address => uint256) public packageRelayersSubmitCount; - address payable[] public packageRelayerAddressRecord; - - uint256 public collectedRewardForHeaderRelayer=0; - uint256 public collectedRewardForTransferRelayer=0; - - uint256 public roundSequence=0; - uint256 public countInRound=0; - - mapping(address => uint256) public relayerRewardVault; - - uint256 public dynamicExtraIncentiveAmount; - - event distributeCollectedReward(uint256 sequence, uint256 roundRewardForHeaderRelayer, uint256 roundRewardForTransferRelayer); - event paramChange(string key, bytes value); - event rewardToRelayer(address relayer, uint256 amount); - - function init() onlyNotInit external { - require(!alreadyInit, "already initialized"); - headerRelayerRewardRateMolecule=HEADER_RELAYER_REWARD_RATE_MOLECULE; - headerRelayerRewardRateDenominator=HEADER_RELAYER_REWARD_RATE_DENOMINATOR; - callerCompensationMolecule=CALLER_COMPENSATION_MOLECULE; - callerCompensationDenominator=CALLER_COMPENSATION_DENOMINATOR; - alreadyInit = true; - } - - receive() external payable{} - - function addReward(address payable headerRelayerAddr, address payable packageRelayer, uint256 amount, bool fromSystemReward) onlyInit onlyCrossChainContract external override returns (bool) { - uint256 actualAmount; - if (fromSystemReward) { - actualAmount = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(address(uint160(INCENTIVIZE_ADDR)), amount.add(dynamicExtraIncentiveAmount)); - } else { - actualAmount = ISystemReward(TOKEN_HUB_ADDR).claimRewards(address(uint160(INCENTIVIZE_ADDR)), amount); - if (dynamicExtraIncentiveAmount > 0) { - actualAmount = actualAmount.add(ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(address(uint160(INCENTIVIZE_ADDR)), dynamicExtraIncentiveAmount)); - } - } - - ++countInRound; - - uint256 reward = calculateRewardForHeaderRelayer(actualAmount); - collectedRewardForHeaderRelayer = collectedRewardForHeaderRelayer.add(reward); - collectedRewardForTransferRelayer = collectedRewardForTransferRelayer.add(actualAmount).sub(reward); - - if (headerRelayersSubmitCount[headerRelayerAddr]==0) { - headerRelayerAddressRecord.push(headerRelayerAddr); - } - ++headerRelayersSubmitCount[headerRelayerAddr]; - - if (packageRelayersSubmitCount[packageRelayer]==0) { - packageRelayerAddressRecord.push(packageRelayer); - } - ++packageRelayersSubmitCount[packageRelayer]; - - if (countInRound>=ROUND_SIZE) { - emit distributeCollectedReward(roundSequence, collectedRewardForHeaderRelayer, collectedRewardForTransferRelayer); - - uint256 callerHeaderReward = distributeHeaderRelayerReward(); - uint256 callerPackageReward = distributePackageRelayerReward(); - - relayerRewardVault[packageRelayer] = relayerRewardVault[packageRelayer].add(callerHeaderReward).add(callerPackageReward); - - ++roundSequence; - countInRound = 0; - } - return true; - } - - function claimRelayerReward(address relayerAddr) external { - uint256 reward = relayerRewardVault[relayerAddr]; - require(reward > 0, "no relayer reward"); - relayerRewardVault[relayerAddr] = 0; - address payable recipient = address(uint160(relayerAddr)); - if (!recipient.send(reward)) { - address payable systemPayable = address(uint160(SYSTEM_REWARD_ADDR)); - systemPayable.transfer(reward); - emit rewardToRelayer(SYSTEM_REWARD_ADDR, reward); - return; - } - emit rewardToRelayer(relayerAddr, reward); - } - - function calculateRewardForHeaderRelayer(uint256 reward) internal view returns (uint256) { - return reward.mul(headerRelayerRewardRateMolecule).div(headerRelayerRewardRateDenominator); - } - - function distributeHeaderRelayerReward() internal returns (uint256) { - uint256 totalReward = collectedRewardForHeaderRelayer; - - uint256 totalWeight=0; - address payable[] memory relayers = headerRelayerAddressRecord; - uint256[] memory relayerWeight = new uint256[](relayers.length); - for (uint256 index = 0; index < relayers.length; ++index) { - address relayer = relayers[index]; - uint256 weight = calculateHeaderRelayerWeight(headerRelayersSubmitCount[relayer]); - relayerWeight[index] = weight; - totalWeight = totalWeight.add(weight); - } - - uint256 callerReward = totalReward.mul(callerCompensationMolecule).div(callerCompensationDenominator); - totalReward = totalReward.sub(callerReward); - uint256 remainReward = totalReward; - for (uint256 index = 1; index < relayers.length; ++index) { - uint256 reward = relayerWeight[index].mul(totalReward).div(totalWeight); - relayerRewardVault[relayers[index]] = relayerRewardVault[relayers[index]].add(reward); - remainReward = remainReward.sub(reward); - } - relayerRewardVault[relayers[0]] = relayerRewardVault[relayers[0]].add(remainReward); - - collectedRewardForHeaderRelayer = 0; - for (uint256 index = 0; index < relayers.length; ++index) { - delete headerRelayersSubmitCount[relayers[index]]; - } - delete headerRelayerAddressRecord; - return callerReward; - } - - function distributePackageRelayerReward() internal returns (uint256) { - uint256 totalReward = collectedRewardForTransferRelayer; - - uint256 totalWeight=0; - address payable[] memory relayers = packageRelayerAddressRecord; - uint256[] memory relayerWeight = new uint256[](relayers.length); - for (uint256 index = 0; index < relayers.length; ++index) { - address relayer = relayers[index]; - uint256 weight = calculateTransferRelayerWeight(packageRelayersSubmitCount[relayer]); - relayerWeight[index] = weight; - totalWeight = totalWeight + weight; - } - - uint256 callerReward = totalReward.mul(callerCompensationMolecule).div(callerCompensationDenominator); - totalReward = totalReward.sub(callerReward); - uint256 remainReward = totalReward; - for (uint256 index = 1; index < relayers.length; ++index) { - uint256 reward = relayerWeight[index].mul(totalReward).div(totalWeight); - relayerRewardVault[relayers[index]] = relayerRewardVault[relayers[index]].add(reward); - remainReward = remainReward.sub(reward); - } - relayerRewardVault[relayers[0]] = relayerRewardVault[relayers[0]].add(remainReward); - - collectedRewardForTransferRelayer = 0; - for (uint256 index = 0; index < relayers.length; ++index) { - delete packageRelayersSubmitCount[relayers[index]]; - } - delete packageRelayerAddressRecord; - return callerReward; - } - - function calculateTransferRelayerWeight(uint256 count) public pure returns(uint256) { - if (count <= MAXIMUM_WEIGHT) { - return count; - } else if (MAXIMUM_WEIGHT < count && count <= 2*MAXIMUM_WEIGHT) { - return MAXIMUM_WEIGHT; - } else if (2*MAXIMUM_WEIGHT < count && count <= (2*MAXIMUM_WEIGHT + 3*MAXIMUM_WEIGHT/4)) { - return 3*MAXIMUM_WEIGHT - count; - } else { - return count/4; - } - } - - function calculateHeaderRelayerWeight(uint256 count) public pure returns(uint256) { - if (count <= MAXIMUM_WEIGHT) { - return count; - } else { - return MAXIMUM_WEIGHT; - } - } - - function updateParam(string calldata key, bytes calldata value) override external onlyGov{ - require(alreadyInit, "contract has not been initialized"); - if (Memory.compareStrings(key,"headerRelayerRewardRateMolecule")) { - require(value.length == 32, "length of headerRelayerRewardRateMolecule mismatch"); - uint256 newHeaderRelayerRewardRateMolecule = BytesToTypes.bytesToUint256(32, value); - require(newHeaderRelayerRewardRateMolecule <= headerRelayerRewardRateDenominator, "new headerRelayerRewardRateMolecule shouldn't be greater than headerRelayerRewardRateDenominator"); - headerRelayerRewardRateMolecule = newHeaderRelayerRewardRateMolecule; - } else if (Memory.compareStrings(key,"headerRelayerRewardRateDenominator")) { - require(value.length == 32, "length of rewardForValidatorSetChange mismatch"); - uint256 newHeaderRelayerRewardRateDenominator = BytesToTypes.bytesToUint256(32, value); - require(newHeaderRelayerRewardRateDenominator != 0 && newHeaderRelayerRewardRateDenominator >= headerRelayerRewardRateMolecule, "the new headerRelayerRewardRateDenominator must not be zero and no less than headerRelayerRewardRateMolecule"); - headerRelayerRewardRateDenominator = newHeaderRelayerRewardRateDenominator; - } else if (Memory.compareStrings(key,"callerCompensationMolecule")) { - require(value.length == 32, "length of rewardForValidatorSetChange mismatch"); - uint256 newCallerCompensationMolecule = BytesToTypes.bytesToUint256(32, value); - require(newCallerCompensationMolecule <= callerCompensationDenominator, "new callerCompensationMolecule shouldn't be greater than callerCompensationDenominator"); - callerCompensationMolecule = newCallerCompensationMolecule; - } else if (Memory.compareStrings(key,"callerCompensationDenominator")) { - require(value.length == 32, "length of rewardForValidatorSetChange mismatch"); - uint256 newCallerCompensationDenominator = BytesToTypes.bytesToUint256(32, value); - require(newCallerCompensationDenominator != 0 && newCallerCompensationDenominator >= callerCompensationMolecule, "the newCallerCompensationDenominator must not be zero and no less than callerCompensationMolecule"); - callerCompensationDenominator = newCallerCompensationDenominator; - } else if (Memory.compareStrings(key,"dynamicExtraIncentiveAmount")) { - require(value.length == 32, "length of dynamicExtraIncentiveAmount mismatch"); - uint256 newDynamicExtraIncentiveAmount = BytesToTypes.bytesToUint256(32, value); - require(newDynamicExtraIncentiveAmount >= 0 , "the newDynamicExtraIncentiveAmount must be no less than zero"); - dynamicExtraIncentiveAmount = newDynamicExtraIncentiveAmount; - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } -} diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index ca4aad7d..4d4b78c3 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -39,6 +39,10 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication uint256 public finalitySlashRewardRatio; bool public enableMaliciousVoteSlash; + uint256 public constant INIT_MALICIOUS_VOTE_SLASH_SCOPE = 86400; // 3 days + + uint256 public maliciousVoteSlashScope; + event validatorSlashed(address indexed validator); event maliciousVoteSlashed(bytes32 indexed voteAddrSlice); event indicatorCleaned(); @@ -77,13 +81,6 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication _; previousHeight = block.number; } - - modifier onlyZeroGasPrice() { - - require(tx.gasprice == 0 , "gasprice is not zero"); - - _; - } function init() external onlyNotInit{ misdemeanorThreshold = MISDEMEANOR_THRESHOLD; @@ -202,10 +199,13 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication if (finalitySlashRewardRatio == 0) { finalitySlashRewardRatio = INIT_FINALITY_SLASH_REWARD_RATIO; } + if (maliciousVoteSlashScope == 0) { + maliciousVoteSlashScope = INIT_MALICIOUS_VOTE_SLASH_SCOPE; + } // Basic check - require(_evidence.voteA.tarNum+256 > block.number && - _evidence.voteB.tarNum+256 > block.number, "target block too old"); + require(_evidence.voteA.tarNum+maliciousVoteSlashScope > block.number && + _evidence.voteB.tarNum+maliciousVoteSlashScope > block.number, "target block too old"); require(!(_evidence.voteA.srcHash == _evidence.voteB.srcHash && _evidence.voteA.tarHash == _evidence.voteB.tarHash), "two identical votes"); require(_evidence.voteA.srcNum < _evidence.voteA.tarNum && @@ -228,7 +228,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication for (uint i; i < voteAddrs.length; ++i) { if (BytesLib.equal(voteAddrs[i], _evidence.voteAddr)) { uint256 amount = (address(SYSTEM_REWARD_ADDR).balance * finalitySlashRewardRatio) / 100; - ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, amount); + ISystemReward(SYSTEM_REWARD_ADDR).claimRewardsforFinality(msg.sender, amount); IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony( vals[i]); break; } @@ -311,6 +311,11 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication } else if (Memory.compareStrings(key, "enableMaliciousVoteSlash")) { require(value.length == 32, "length of enableMaliciousVoteSlash mismatch"); enableMaliciousVoteSlash = BytesToTypes.bytesToBool(32, value); + } else if (Memory.compareStrings(key, "maliciousVoteSlashScope")) { + require(value.length == 32, "length of maliciousVoteSlashScope mismatch"); + uint256 newMaliciousVoteSlashScope = BytesToTypes.bytesToUint256(32, value); + require(newMaliciousVoteSlashScope >= 28800*1 && newMaliciousVoteSlashScope < 28800*30, "the malicious vote slash scope out of range"); + maliciousVoteSlashScope = newMaliciousVoteSlashScope; } else { require(false, "unknown param"); } diff --git a/contracts/SlashIndicator.template b/contracts/SlashIndicator.template deleted file mode 100644 index cdbcd38f..00000000 --- a/contracts/SlashIndicator.template +++ /dev/null @@ -1,355 +0,0 @@ -pragma solidity 0.6.4; -pragma experimental ABIEncoderV2; - -import "./System.sol"; -import "./lib/BytesToTypes.sol"; -import "./lib/TypesToBytes.sol"; -import "./lib/BytesLib.sol"; -import "./lib/Memory.sol"; -import "./interface/ISlashIndicator.sol"; -import "./interface/IApplication.sol"; -import "./interface/IBSCValidatorSet.sol"; -import "./interface/IParamSubscriber.sol"; -import "./interface/ICrossChain.sol"; -import "./interface/ISystemReward.sol"; -import "./lib/CmnPkg.sol"; -import "./lib/RLPEncode.sol"; - -contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication{ - using RLPEncode for *; - - uint256 public constant MISDEMEANOR_THRESHOLD = 50; - uint256 public constant FELONY_THRESHOLD = 150; - uint256 public constant BSC_RELAYER_REWARD = 1e16; - {% if mock %}uint256 public constant DECREASE_RATE = 35;{% else %}uint256 public constant DECREASE_RATE = 4;{% endif %} - - // State of the contract - address[] public validators; - mapping(address => Indicator) public indicators; - uint256 public previousHeight; - - // The BSC validators assign proper values for `misdemeanorThreshold` and `felonyThreshold` through governance. - // The proper values depends on BSC network's tolerance for continuous missing blocks. - uint256 public misdemeanorThreshold; - uint256 public felonyThreshold; - - // BEP-126 Fast Finality - uint256 public constant INIT_FINALITY_SLASH_REWARD_RATIO = 20; - - uint256 public finalitySlashRewardRatio; - bool public enableMaliciousVoteSlash; - - event validatorSlashed(address indexed validator); - event maliciousVoteSlashed(bytes32 indexed voteAddrSlice); - event indicatorCleaned(); - event paramChange(string key, bytes value); - - event knownResponse(uint32 code); - event unKnownResponse(uint32 code); - event crashResponse(); - - event failedFelony(address indexed validator, uint256 slashCount, bytes failReason); - event failedMaliciousVoteSlash(bytes32 indexed voteAddrSlice, bytes failReason); - - 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"); - _; - previousHeight = block.number; - } - - modifier onlyZeroGasPrice() { - {% if mock %} - require(true, "gasprice is not zero"); - {% else %} - require(tx.gasprice == 0 , "gasprice is not zero"); - {% endif %} - _; - } - {% if mock %} - function getSlashValidators() external view returns (address[] memory) { - return validators; - }{% endif %} - function init() external onlyNotInit{ - misdemeanorThreshold = MISDEMEANOR_THRESHOLD; - felonyThreshold = FELONY_THRESHOLD; - alreadyInit = true; - {% if network == 'local' %} - enableMaliciousVoteSlash = true; - {% endif %} - } - - /*********************** Implement cross chain app ********************************/ - function handleSynPackage(uint8, bytes calldata) external onlyCrossChainContract onlyInit override returns(bytes memory) { - require(false, "receive unexpected syn package"); - } - - function handleAckPackage(uint8, bytes calldata msgBytes) external onlyCrossChainContract onlyInit override { - (CmnPkg.CommonAckPackage memory response, bool ok) = CmnPkg.decodeCommonAckPackage(msgBytes); - if (ok) { - emit knownResponse(response.code); - } else { - emit unKnownResponse(response.code); - } - return; - } - - function handleFailAckPackage(uint8, bytes calldata) external onlyCrossChainContract onlyInit override { - emit crashResponse(); - return; - } - - /*********************** External func ********************************/ - /** - * @dev Slash the validator who should have produced the current block - * - * @param validator The validator who should have produced the current block - */ - function slash(address validator) external onlyCoinbase onlyInit oncePerBlock onlyZeroGasPrice{ - if (!IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).isCurrentValidator(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; - if (indicator.count % felonyThreshold == 0) { - indicator.count = 0; - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator); - try ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeSlashPackage(validator), 0) {} catch (bytes memory reason) { - emit failedFelony(validator, indicator.count, reason); - } - } else if (indicator.count % misdemeanorThreshold == 0) { - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).misdemeanor(validator); - } - indicators[validator] = indicator; - emit validatorSlashed(validator); - } - - // To prevent validator misbehaving and leaving, do not clean slash record to zero, but decrease by felonyThreshold/DECREASE_RATE . - // Clean is an effective implement to reorganize "validators" and "indicators". - function clean() external override(ISlashIndicator) onlyValidatorContract onlyInit{ - if (validators.length == 0) { - return; - } - uint i; - uint j = validators.length-1; - for ( ; i<=j; ) { - bool findLeft = false; - bool findRight = false; - for( ; i felonyThreshold/DECREASE_RATE){ - leftIndicator.count = leftIndicator.count - felonyThreshold/DECREASE_RATE; - indicators[validators[i]] = leftIndicator; - }else{ - findLeft = true; - break; - } - } - for( ; i<=j; --j){ - Indicator memory rightIndicator = indicators[validators[j]]; - if(rightIndicator.count > felonyThreshold/DECREASE_RATE){ - rightIndicator.count = rightIndicator.count - felonyThreshold/DECREASE_RATE; - indicators[validators[j]] = rightIndicator; - findRight = true; - break; - }else{ - delete indicators[validators[j]]; - validators.pop(); - } - // avoid underflow - if(j==0){ - break; - } - } - // swap element in array - if (findLeft && findRight){ - delete indicators[validators[i]]; - validators[i] = validators[j]; - validators.pop(); - } - // avoid underflow - if(j==0){ - break; - } - // move to next - ++i; - --j; - } - emit indicatorCleaned(); - } - - function submitFinalityViolationEvidence(FinalityEvidence memory _evidence) public onlyInit onlyRelayer { - require(enableMaliciousVoteSlash, "malicious vote slash not enabled"); - if (finalitySlashRewardRatio == 0) { - finalitySlashRewardRatio = INIT_FINALITY_SLASH_REWARD_RATIO; - } - - // Basic check - require(_evidence.voteA.tarNum+256 > block.number && - _evidence.voteB.tarNum+256 > block.number, "target block too old"); - 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"); - - // check voteAddr to protect validators from being slashed for old voteAddr - require(IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).isMonitoredForMaliciousVote(_evidence.voteAddr),"voteAddr is not found"); - - // BLS verification - require(verifyBLSSignature(_evidence.voteA, _evidence.voteAddr) && - verifyBLSSignature(_evidence.voteB, _evidence.voteAddr), "verify signature failed"); - - // reward sender and felony validator if validator found - (address[] memory vals, bytes[] memory voteAddrs) = IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).getLivingValidators(); - for (uint i; i < voteAddrs.length; ++i) { - if (BytesLib.equal(voteAddrs[i], _evidence.voteAddr)) { - uint256 amount = (address(SYSTEM_REWARD_ADDR).balance * finalitySlashRewardRatio) / 100; - ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, amount); - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony( vals[i]); - break; - } - } - - // send slash msg to bc - bytes32 voteAddrSlice = BytesLib.toBytes32(_evidence.voteAddr,0); - try ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeVoteSlashPackage(_evidence.voteAddr), 0) { - emit maliciousVoteSlashed(voteAddrSlice); - } catch (bytes memory reason) { - emit failedMaliciousVoteSlash(voteAddrSlice, reason); - } - } - - /** - * @dev Send a felony cross-chain package to jail a validator - * - * @param validator Who will be jailed - */ - function sendFelonyPackage(address validator) external override(ISlashIndicator) onlyValidatorContract onlyInit { - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeSlashPackage(validator), 0); - } - - 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(); - TypesToBytes.bytes32ToBytes(32, vote.srcHash, _bytes); - elements[1] = _bytes.encodeBytes(); - elements[2] = vote.tarNum.encodeUint(); - TypesToBytes.bytes32ToBytes(32, vote.tarHash, _bytes); - elements[3] = _bytes.encodeBytes(); - - TypesToBytes.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= 1 && newMisdemeanorThreshold < felonyThreshold, "the misdemeanorThreshold out of range"); - misdemeanorThreshold = newMisdemeanorThreshold; - } else if (Memory.compareStrings(key,"felonyThreshold")) { - require(value.length == 32, "length of felonyThreshold mismatch"); - uint256 newFelonyThreshold = BytesToTypes.bytesToUint256(32, value); - require(newFelonyThreshold <= 1000 && newFelonyThreshold > misdemeanorThreshold, "the felonyThreshold out of range"); - felonyThreshold = newFelonyThreshold; - } else if (Memory.compareStrings(key, "finalitySlashRewardRatio")) { - require(value.length == 32, "length of finalitySlashRewardRatio mismatch"); - uint256 newFinalitySlashRewardRatio = BytesToTypes.bytesToUint256(32, value); - require(newFinalitySlashRewardRatio >= 10 && newFinalitySlashRewardRatio < 100, "the finality slash reward ratio out of range"); - finalitySlashRewardRatio = newFinalitySlashRewardRatio; - } else if (Memory.compareStrings(key, "enableMaliciousVoteSlash")) { - require(value.length == 32, "length of enableMaliciousVoteSlash mismatch"); - enableMaliciousVoteSlash = BytesToTypes.bytesToBool(32, value); - } else { - require(false, "unknown param"); - } - emit paramChange(key,value); - } - - /*********************** query api ********************************/ - function getSlashIndicator(address validator) external view returns (uint256,uint256) { - Indicator memory indicator = indicators[validator]; - return (indicator.height, indicator.count); - } - - function encodeSlashPackage(address valAddr) internal view returns (bytes memory) { - bytes[] memory elements = new bytes[](4); - elements[0] = valAddr.encodeAddress(); - elements[1] = uint256(block.number).encodeUint(); - elements[2] = uint256(bscChainID).encodeUint(); - elements[3] = uint256(block.timestamp).encodeUint(); - return elements.encodeList(); - } - - function encodeVoteSlashPackage(bytes memory voteAddr) internal view returns (bytes memory) { - bytes[] memory elements = new bytes[](4); - elements[0] = voteAddr.encodeBytes(); - elements[1] = uint256(block.number).encodeUint(); - elements[2] = uint256(bscChainID).encodeUint(); - elements[3] = uint256(block.timestamp).encodeUint(); - return elements.encodeList(); - } - - function getSlashThresholds() override(ISlashIndicator) external view returns (uint256, uint256) { - return (misdemeanorThreshold, felonyThreshold); - } -} diff --git a/contracts/Staking.template b/contracts/Staking.template deleted file mode 100644 index dff2c910..00000000 --- a/contracts/Staking.template +++ /dev/null @@ -1,707 +0,0 @@ -pragma solidity 0.6.4; - -import "./System.sol"; -import "./interface/IApplication.sol"; -import "./interface/ICrossChain.sol"; -import "./interface/IParamSubscriber.sol"; -import "./interface/IStaking.sol"; -import "./interface/ITokenHub.sol"; -import "./lib/BytesToTypes.sol"; -import "./lib/BytesLib.sol"; -import "./lib/CmnPkg.sol"; -import "./lib/Memory.sol"; -import "./lib/RLPEncode.sol"; -import "./lib/RLPDecode.sol"; -import "./lib/SafeMath.sol"; - - -contract Staking is IStaking, System, IParamSubscriber, IApplication { - using SafeMath for uint256; - using RLPEncode for *; - using RLPDecode for *; - - // Cross Stake Event type - uint8 public constant EVENT_DELEGATE = 0x01; - uint8 public constant EVENT_UNDELEGATE = 0x02; - uint8 public constant EVENT_REDELEGATE = 0x03; - uint8 public constant EVENT_DISTRIBUTE_REWARD = 0x04; - uint8 public constant EVENT_DISTRIBUTE_UNDELEGATED = 0x05; - - // ack package status code - uint8 public constant CODE_FAILED = 0; - uint8 public constant CODE_SUCCESS = 1; - - // Error code - uint32 public constant ERROR_WITHDRAW_BNB = 101; - - uint256 public constant TEN_DECIMALS = 1e10; - uint256 public constant LOCK_TIME = 8 days; // 8*24*3600 second - - uint256 public constant INIT_RELAYER_FEE = 16 * 1e15; - uint256 public constant INIT_BSC_RELAYER_FEE = 1 * 1e16; - uint256 public constant INIT_MIN_DELEGATION = 100 * 1e18; - uint256 public constant INIT_TRANSFER_GAS = 2300; - - uint256 public relayerFee; - uint256 public bSCRelayerFee; - uint256 public minDelegation; - - mapping(address => uint256) delegated; // delegator => totalAmount - mapping(address => mapping(address => uint256)) delegatedOfValidator; // delegator => validator => amount - mapping(address => uint256) distributedReward; // delegator => reward - mapping(address => mapping(address => uint256)) pendingUndelegateTime; // delegator => validator => minTime - mapping(address => uint256) undelegated; // delegator => totalUndelegated - mapping(address => mapping(address => mapping(address => uint256))) pendingRedelegateTime; // delegator => srcValidator => dstValidator => minTime - - mapping(uint256 => bytes32) packageQueue; // index => package's hash - mapping(address => uint256) delegateInFly; // delegator => delegate request in fly - mapping(address => uint256) undelegateInFly; // delegator => undelegate request in fly - mapping(address => uint256) redelegateInFly; // delegator => redelegate request in fly - - uint256 internal leftIndex; - uint256 internal rightIndex; - uint8 internal locked; - - uint256 public transferGas; // this param is newly added after the hardfork on testnet. It need to be initialed by governed - - modifier noReentrant() { - require(locked != 2, "No re-entrancy"); - locked = 2; - _; - locked = 1; - } - - modifier tenDecimalPrecision(uint256 amount) { - require(msg.value%TEN_DECIMALS==0 && amount%TEN_DECIMALS==0, "precision loss in conversion"); - _; - } - - modifier initParams() { - if (!alreadyInit) { - relayerFee = INIT_RELAYER_FEE; - bSCRelayerFee = INIT_BSC_RELAYER_FEE; - minDelegation = INIT_MIN_DELEGATION; - transferGas = INIT_TRANSFER_GAS; - alreadyInit = true; - } - _; - } - - /*********************************** Events **********************************/ - event delegateSubmitted(address indexed delegator, address indexed validator, uint256 amount, uint256 relayerFee); - event undelegateSubmitted(address indexed delegator, address indexed validator, uint256 amount, uint256 relayerFee); - event redelegateSubmitted(address indexed delegator, address indexed validatorSrc, address indexed validatorDst, uint256 amount, uint256 relayerFee); - event rewardReceived(address indexed delegator, uint256 amount); - event rewardClaimed(address indexed delegator, uint256 amount); - event undelegatedReceived(address indexed delegator, address indexed validator, uint256 amount); - event undelegatedClaimed(address indexed delegator, uint256 amount); - event delegateSuccess(address indexed delegator, address indexed validator, uint256 amount); - event undelegateSuccess(address indexed delegator, address indexed validator, uint256 amount); - event redelegateSuccess(address indexed delegator, address indexed valSrc, address indexed valDst, uint256 amount); - event delegateFailed(address indexed delegator, address indexed validator, uint256 amount, uint8 errCode); - event undelegateFailed(address indexed delegator, address indexed validator, uint256 amount, uint8 errCode); - event redelegateFailed(address indexed delegator, address indexed valSrc, address indexed valDst, uint256 amount, uint8 errCode); - event paramChange(string key, bytes value); - event failedSynPackage(uint8 indexed eventType, uint256 errCode); - event crashResponse(uint8 indexed eventType); - - receive() external payable {} - - /************************* Implement cross chain app *************************/ - function handleSynPackage(uint8, bytes calldata msgBytes) external onlyCrossChainContract initParams override returns(bytes memory) { - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - uint8 eventType = uint8(iter.next().toUint()); - uint32 resCode; - bytes memory ackPackage; - if (eventType == EVENT_DISTRIBUTE_REWARD) { - (resCode, ackPackage) = _handleDistributeRewardSynPackage(iter); - } else if (eventType == EVENT_DISTRIBUTE_UNDELEGATED) { - (resCode, ackPackage) = _handleDistributeUndelegatedSynPackage(iter); - } else { - revert("unknown event type"); - } - - if (resCode != CODE_OK) { - emit failedSynPackage(eventType, resCode); - } - return ackPackage; - } - - function handleAckPackage(uint8, bytes calldata msgBytes) external onlyCrossChainContract initParams override { - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - - uint8 status; - uint8 errCode; - bytes memory packBytes; - bool success; - uint256 idx; - while (iter.hasNext()) { - if (idx == 0) { - status = uint8(iter.next().toUint()); - } else if (idx == 1) { - errCode = uint8(iter.next().toUint()); - } else if (idx == 2) { - packBytes = iter.next().toBytes(); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - require(_checkPackHash(packBytes), "wrong pack hash"); - iter = packBytes.toRLPItem().iterator(); - uint8 eventType = uint8(iter.next().toUint()); - RLPDecode.Iterator memory paramIter; - if (iter.hasNext()) { - paramIter = iter.next().toBytes().toRLPItem().iterator(); - } else { - revert("empty ack package"); - } - if (eventType == EVENT_DELEGATE) { - _handleDelegateAckPackage(paramIter, status, errCode); - } else if (eventType == EVENT_UNDELEGATE) { - _handleUndelegateAckPackage(paramIter, status, errCode); - } else if (eventType == EVENT_REDELEGATE) { - _handleRedelegateAckPackage(paramIter, status, errCode); - } else { - revert("unknown event type"); - } - } - - function handleFailAckPackage(uint8, bytes calldata msgBytes) external onlyCrossChainContract initParams override { - require(_checkPackHash(msgBytes), "wrong pack hash"); - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - uint8 eventType = uint8(iter.next().toUint()); - RLPDecode.Iterator memory paramIter; - if (iter.hasNext()) { - paramIter = iter.next().toBytes().toRLPItem().iterator(); - } else { - revert("empty fail ack package"); - } - if (eventType == EVENT_DELEGATE) { - _handleDelegateFailAckPackage(paramIter); - } else if (eventType == EVENT_UNDELEGATE) { - _handleUndelegateFailAckPackage(paramIter); - } else if (eventType == EVENT_REDELEGATE) { - _handleRedelegateFailAckPackage(paramIter); - } else { - revert("unknown event type"); - } - return; - } - - /***************************** External functions *****************************/ - /** - * @dev Delegate BNB from BSC to BC - * - * @param validator BC validator address encoded to evm address - * @param amount Amount user delegate to BC validator - */ - function delegate(address validator, uint256 amount) override external payable noReentrant tenDecimalPrecision(amount) initParams { - require(amount >= minDelegation, "invalid delegate amount"); - require(msg.value >= amount.add(relayerFee), "not enough msg value"); - (bool success,) = msg.sender.call{gas: transferGas}(""); - require(success, "invalid delegator"); // the msg sender must be payable - - uint256 convertedAmount = amount.div(TEN_DECIMALS); // native bnb decimals is 8 on BBC, while the native bnb decimals on BSC is 18 - uint256 _relayerFee = (msg.value).sub(amount); - uint256 oracleRelayerFee = _relayerFee.sub(bSCRelayerFee); - - bytes[] memory elements = new bytes[](3); - elements[0] = msg.sender.encodeAddress(); - elements[1] = validator.encodeAddress(); - elements[2] = convertedAmount.encodeUint(); - bytes memory msgBytes = _RLPEncode(EVENT_DELEGATE, elements.encodeList()); - packageQueue[rightIndex] = keccak256(msgBytes); - ++rightIndex; - delegateInFly[msg.sender] += 1; - - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(CROSS_STAKE_CHANNELID, msgBytes, oracleRelayerFee.div(TEN_DECIMALS)); - payable(TOKEN_HUB_ADDR).transfer(amount.add(oracleRelayerFee)); - payable(SYSTEM_REWARD_ADDR).transfer(bSCRelayerFee); - - emit delegateSubmitted(msg.sender, validator, amount, oracleRelayerFee); - } - - /** - * @dev Undelegate BNB from BC to BSC - * - * @param validator BC validator encoded address the user delegated - * @param amount BNB amount the user undelegates - */ - function undelegate(address validator, uint256 amount) override external payable noReentrant tenDecimalPrecision(amount) initParams { - require(msg.value >= relayerFee, "not enough relay fee"); - if (amount < minDelegation) { - require(amount == delegatedOfValidator[msg.sender][validator], "invalid amount"); - require(amount > bSCRelayerFee, "not enough funds"); - } - require(block.timestamp >= pendingUndelegateTime[msg.sender][validator], "pending undelegation exist"); - uint256 remainBalance = delegatedOfValidator[msg.sender][validator].sub(amount, "not enough funds"); - if (remainBalance != 0) { - require(remainBalance > bSCRelayerFee, "insufficient balance after undelegate"); - } - - uint256 convertedAmount = amount.div(TEN_DECIMALS); // native bnb decimals is 8 on BBC, while the native bnb decimals on BSC is 18 - uint256 _relayerFee = msg.value; - uint256 oracleRelayerFee = _relayerFee.sub(bSCRelayerFee); - - bytes[] memory elements = new bytes[](3); - elements[0] = msg.sender.encodeAddress(); - elements[1] = validator.encodeAddress(); - elements[2] = convertedAmount.encodeUint(); - bytes memory msgBytes = _RLPEncode(EVENT_UNDELEGATE, elements.encodeList()); - packageQueue[rightIndex] = keccak256(msgBytes); - ++rightIndex; - undelegateInFly[msg.sender] += 1; - - pendingUndelegateTime[msg.sender][validator] = block.timestamp.add(LOCK_TIME); - - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(CROSS_STAKE_CHANNELID, msgBytes, oracleRelayerFee.div(TEN_DECIMALS)); - payable(TOKEN_HUB_ADDR).transfer(oracleRelayerFee); - payable(SYSTEM_REWARD_ADDR).transfer(bSCRelayerFee); - - emit undelegateSubmitted(msg.sender, validator, amount, oracleRelayerFee); - } - - /** - * @dev Redelegate from validatorSrc to validatorDst on BC - * - * @param amount Amount that the user redelegates - */ - function redelegate(address validatorSrc, address validatorDst, uint256 amount) override external noReentrant payable tenDecimalPrecision(amount) initParams { - require(validatorSrc != validatorDst, "invalid redelegation"); - require(msg.value >= relayerFee, "not enough relay fee"); - require(amount >= minDelegation, "invalid amount"); - require(block.timestamp >= pendingRedelegateTime[msg.sender][validatorSrc][validatorDst] && - block.timestamp >= pendingRedelegateTime[msg.sender][validatorDst][validatorSrc], "pending redelegation exist"); - uint256 remainBalance = delegatedOfValidator[msg.sender][validatorSrc].sub(amount, "not enough funds"); - if (remainBalance != 0) { - require(remainBalance > bSCRelayerFee, "insufficient balance after redelegate"); - } - - uint256 convertedAmount = amount.div(TEN_DECIMALS);// native bnb decimals is 8 on BBC, while the native bnb decimals on BSC is 18 - uint256 _relayerFee = msg.value; - uint256 oracleRelayerFee = _relayerFee.sub(bSCRelayerFee); - - bytes[] memory elements = new bytes[](4); - elements[0] = msg.sender.encodeAddress(); - elements[1] = validatorSrc.encodeAddress(); - elements[2] = validatorDst.encodeAddress(); - elements[3] = convertedAmount.encodeUint(); - bytes memory msgBytes = _RLPEncode(EVENT_REDELEGATE, elements.encodeList()); - packageQueue[rightIndex] = keccak256(msgBytes); - ++rightIndex; - redelegateInFly[msg.sender] += 1; - - pendingRedelegateTime[msg.sender][validatorDst][validatorSrc] = block.timestamp.add(LOCK_TIME); - pendingRedelegateTime[msg.sender][validatorSrc][validatorDst] = block.timestamp.add(LOCK_TIME); - - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(CROSS_STAKE_CHANNELID, msgBytes, oracleRelayerFee.div(TEN_DECIMALS)); - payable(TOKEN_HUB_ADDR).transfer(oracleRelayerFee); - payable(SYSTEM_REWARD_ADDR).transfer(bSCRelayerFee); - - emit redelegateSubmitted(msg.sender, validatorSrc, validatorDst, amount, oracleRelayerFee); - } - - /** - * @dev claim delegated reward from BC staking - * - */ - function claimReward() override external noReentrant returns(uint256 amount) { - amount = distributedReward[msg.sender]; - require(amount > 0, "no pending reward"); - - distributedReward[msg.sender] = 0; - (bool success,) = msg.sender.call{gas: transferGas, value: amount}(""); - require(success, "transfer failed"); - emit rewardClaimed(msg.sender, amount); - } - - /** - * @dev claim undelegated BNB from BC staking - * - */ - function claimUndelegated() override external noReentrant returns(uint256 amount) { - amount = undelegated[msg.sender]; - require(amount > 0, "no undelegated funds"); - - undelegated[msg.sender] = 0; - (bool success,) = msg.sender.call{gas: transferGas, value: amount}(""); - require(success, "transfer failed"); - emit undelegatedClaimed(msg.sender, amount); - } - - function getDelegated(address delegator, address validator) override external view returns(uint256) { - return delegatedOfValidator[delegator][validator]; - } - - function getTotalDelegated(address delegator) override external view returns(uint256) { - return delegated[delegator]; - } - - function getDistributedReward(address delegator) override external view returns(uint256) { - return distributedReward[delegator]; - } - - function getPendingRedelegateTime(address delegator, address valSrc, address valDst) override external view returns(uint256) { - return pendingRedelegateTime[delegator][valSrc][valDst]; - } - - function getUndelegated(address delegator) override external view returns(uint256) { - return undelegated[delegator]; - } - - function getPendingUndelegateTime(address delegator, address validator) override external view returns(uint256) { - return pendingUndelegateTime[delegator][validator]; - } - - function getRelayerFee() override external view returns(uint256) { - return relayerFee; - } - - function getMinDelegation() override external view returns(uint256) { - return minDelegation; - } - - function getRequestInFly(address delegator) override external view returns(uint256[3] memory) { - uint256[3] memory request; - request[0] = delegateInFly[delegator]; - request[1] = undelegateInFly[delegator]; - request[2] = redelegateInFly[delegator]; - return request; - } - - /***************************** Internal functions *****************************/ - function _RLPEncode(uint8 eventType, bytes memory msgBytes) internal pure returns(bytes memory output) { - bytes[] memory elements = new bytes[](2); - elements[0] = eventType.encodeUint(); - elements[1] = msgBytes.encodeBytes(); - output = elements.encodeList(); - } - - function _encodeRefundPackage(uint8 eventType, address recipient, uint256 amount, uint32 errorCode) internal pure returns(uint32, bytes memory) { - amount = amount.div(TEN_DECIMALS); - bytes[] memory elements = new bytes[](4); - elements[0] = eventType.encodeUint(); - elements[1] = recipient.encodeAddress(); - elements[2] = amount.encodeUint(); - elements[3] = errorCode.encodeUint(); - bytes memory packageBytes = elements.encodeList(); - return (errorCode, packageBytes); - } - - function _checkPackHash(bytes memory packBytes) internal returns(bool){ - bytes32 revHash = keccak256(packBytes); - bytes32 expHash = packageQueue[leftIndex]; - if (revHash != expHash) { - return false; - } - delete packageQueue[leftIndex]; - ++leftIndex; - return true; - } - - /******************************** Param update ********************************/ - function updateParam(string calldata key, bytes calldata value) override external onlyInit onlyGov { - if (Memory.compareStrings(key, "relayerFee")) { - require(value.length == 32, "length of relayerFee mismatch"); - uint256 newRelayerFee = BytesToTypes.bytesToUint256(32, value); - require(newRelayerFee < minDelegation, "the relayerFee must be less than minDelegation"); - require(newRelayerFee > bSCRelayerFee, "the relayerFee must be more than BSCRelayerFee"); - require(newRelayerFee%TEN_DECIMALS==0, "the relayerFee mod ten decimals must be zero"); - relayerFee = newRelayerFee; - } else if (Memory.compareStrings(key, "bSCRelayerFee")) { - require(value.length == 32, "length of bSCRelayerFee mismatch"); - uint256 newBSCRelayerFee = BytesToTypes.bytesToUint256(32, value); - require(newBSCRelayerFee != 0, "the BSCRelayerFee must not be zero"); - require(newBSCRelayerFee < relayerFee, "the BSCRelayerFee must be less than relayerFee"); - require(newBSCRelayerFee%TEN_DECIMALS==0, "the BSCRelayerFee mod ten decimals must be zero"); - bSCRelayerFee = newBSCRelayerFee; - } else if (Memory.compareStrings(key, "minDelegation")) { - require(value.length == 32, "length of minDelegation mismatch"); - uint256 newMinDelegation = BytesToTypes.bytesToUint256(32, value); - require(newMinDelegation > relayerFee, "the minDelegation must be greater than relayerFee"); - minDelegation = newMinDelegation; - } else if (Memory.compareStrings(key, "transferGas")) { - require(value.length == 32, "length of transferGas mismatch"); - uint256 newTransferGas = BytesToTypes.bytesToUint256(32, value); - require(newTransferGas > 0, "the transferGas cannot be zero"); - transferGas = newTransferGas; - } else { - revert("unknown param"); - } - emit paramChange(key, value); - } - - /************************* Handle cross-chain package *************************/ - function _handleDelegateAckPackage(RLPDecode.Iterator memory paramIter, uint8 status, uint8 errCode) internal { - bool success; - uint256 idx; - address delegator; - address validator; - uint256 bcAmount; - while (paramIter.hasNext()) { - if (idx == 0) { - delegator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 1) { - validator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 2) { - bcAmount = uint256(paramIter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - uint256 amount = bcAmount.mul(TEN_DECIMALS); - delegateInFly[delegator] -= 1; - if (status == CODE_SUCCESS) { - require(errCode == 0, "wrong status"); - delegated[delegator] = delegated[delegator].add(amount); - delegatedOfValidator[delegator][validator] = delegatedOfValidator[delegator][validator].add(amount); - - emit delegateSuccess(delegator, validator, amount); - } else if (status == CODE_FAILED) { - undelegated[delegator] = undelegated[delegator].add(amount); - require(ITokenHub(TOKEN_HUB_ADDR).withdrawStakingBNB(amount), "withdraw bnb failed"); - - emit delegateFailed(delegator, validator, amount, errCode); - } else { - revert("wrong status"); - } - } - - function _handleDelegateFailAckPackage(RLPDecode.Iterator memory paramIter) internal { - bool success; - uint256 idx; - address delegator; - address validator; - uint256 bcAmount; - while (paramIter.hasNext()) { - if (idx == 0) { - delegator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 1) { - validator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 2) { - bcAmount = uint256(paramIter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - uint256 amount = bcAmount.mul(TEN_DECIMALS); - delegateInFly[delegator] -= 1; - undelegated[delegator] = undelegated[delegator].add(amount); - require(ITokenHub(TOKEN_HUB_ADDR).withdrawStakingBNB(amount), "withdraw bnb failed"); - - emit crashResponse(EVENT_DELEGATE); - } - - function _handleUndelegateAckPackage(RLPDecode.Iterator memory paramIter, uint8 status, uint8 errCode) internal { - bool success; - uint256 idx; - address delegator; - address validator; - uint256 bcAmount; - while (paramIter.hasNext()) { - if (idx == 0) { - delegator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 1) { - validator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 2) { - bcAmount = uint256(paramIter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - uint256 amount = bcAmount.mul(TEN_DECIMALS); - undelegateInFly[delegator] -= 1; - if (status == CODE_SUCCESS) { - require(errCode == 0, "wrong status"); - delegated[delegator] = delegated[delegator].sub(amount); - delegatedOfValidator[delegator][validator] = delegatedOfValidator[delegator][validator].sub(amount); - pendingUndelegateTime[delegator][validator] = block.timestamp.add(LOCK_TIME); - - emit undelegateSuccess(delegator, validator, amount); - } else if (status == CODE_FAILED) { - pendingUndelegateTime[delegator][validator] = 0; - emit undelegateFailed(delegator, validator, amount, errCode); - } else { - revert("wrong status"); - } - } - - function _handleUndelegateFailAckPackage(RLPDecode.Iterator memory paramIter) internal { - bool success; - uint256 idx; - address delegator; - address validator; - uint256 bcAmount; - while (paramIter.hasNext()) { - if (idx == 0) { - delegator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 1) { - validator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 2) { - bcAmount = uint256(paramIter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - undelegateInFly[delegator] -= 1; - pendingUndelegateTime[delegator][validator] = 0; - - emit crashResponse(EVENT_UNDELEGATE); - } - - function _handleRedelegateAckPackage(RLPDecode.Iterator memory paramIter, uint8 status, uint8 errCode) internal { - bool success; - uint256 idx; - address delegator; - address valSrc; - address valDst; - uint256 bcAmount; - while (paramIter.hasNext()) { - if (idx == 0) { - delegator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 1) { - valSrc = address(uint160(paramIter.next().toAddress())); - } else if (idx == 2) { - valDst = address(uint160(paramIter.next().toAddress())); - } else if (idx == 3) { - bcAmount = uint256(paramIter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - uint256 amount = bcAmount.mul(TEN_DECIMALS); - redelegateInFly[delegator] -= 1; - if (status == CODE_SUCCESS) { - require(errCode == 0, "wrong status"); - delegatedOfValidator[delegator][valSrc] = delegatedOfValidator[delegator][valSrc].sub(amount); - delegatedOfValidator[delegator][valDst] = delegatedOfValidator[delegator][valDst].add(amount); - pendingRedelegateTime[delegator][valSrc][valDst] = block.timestamp.add(LOCK_TIME); - pendingRedelegateTime[delegator][valDst][valSrc] = block.timestamp.add(LOCK_TIME); - - emit redelegateSuccess(delegator, valSrc, valDst, amount); - } else if (status == CODE_FAILED) { - pendingRedelegateTime[delegator][valSrc][valDst] = 0; - pendingRedelegateTime[delegator][valDst][valSrc] = 0; - emit redelegateFailed(delegator, valSrc, valDst, amount, errCode); - } else { - revert("wrong status"); - } - } - - function _handleRedelegateFailAckPackage(RLPDecode.Iterator memory paramIter) internal { - bool success; - uint256 idx; - address delegator; - address valSrc; - address valDst; - uint256 bcAmount; - while (paramIter.hasNext()) { - if (idx == 0) { - delegator = address(uint160(paramIter.next().toAddress())); - } else if (idx == 1) { - valSrc = address(uint160(paramIter.next().toAddress())); - } else if (idx == 2) { - valDst = address(uint160(paramIter.next().toAddress())); - } else if (idx == 3) { - bcAmount = uint256(paramIter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - redelegateInFly[delegator] -= 1; - pendingRedelegateTime[delegator][valSrc][valDst] = 0; - pendingRedelegateTime[delegator][valDst][valSrc] = 0; - - emit crashResponse(EVENT_REDELEGATE); - } - - function _handleDistributeRewardSynPackage(RLPDecode.Iterator memory iter) internal returns(uint32, bytes memory) { - bool success; - uint256 idx; - address recipient; - uint256 amount; - while (iter.hasNext()) { - if (idx == 0) { - recipient = address(uint160(iter.next().toAddress())); - } else if (idx == 1) { - amount = uint256(iter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - bool ok = ITokenHub(TOKEN_HUB_ADDR).withdrawStakingBNB(amount); - if (!ok) { - return _encodeRefundPackage(EVENT_DISTRIBUTE_REWARD, recipient, amount, ERROR_WITHDRAW_BNB); - } - - distributedReward[recipient] = distributedReward[recipient].add(amount); - - emit rewardReceived(recipient, amount); - return (CODE_OK, new bytes(0)); - } - - function _handleDistributeUndelegatedSynPackage(RLPDecode.Iterator memory iter) internal returns(uint32, bytes memory) { - bool success; - uint256 idx; - address recipient; - address validator; - uint256 amount; - while (iter.hasNext()) { - if (idx == 0) { - recipient = address(uint160(iter.next().toAddress())); - } else if (idx == 1) { - validator = address(uint160(iter.next().toAddress())); - } else if (idx == 2) { - amount = uint256(iter.next().toUint()); - success = true; - } else { - break; - } - ++idx; - } - require(success, "rlp decode failed"); - - bool ok = ITokenHub(TOKEN_HUB_ADDR).withdrawStakingBNB(amount); - if (!ok) { - return _encodeRefundPackage(EVENT_DISTRIBUTE_UNDELEGATED, recipient, amount, ERROR_WITHDRAW_BNB); - } - - pendingUndelegateTime[recipient][validator] = 0; - undelegated[recipient] = undelegated[recipient].add(amount); - - emit undelegatedReceived(recipient, validator, amount); - return (CODE_OK, new bytes(0)); - } -} diff --git a/contracts/System.sol b/contracts/System.sol index bbfea7cd..ad6d285d 100644 --- a/contracts/System.sol +++ b/contracts/System.sol @@ -18,7 +18,7 @@ contract System { uint8 constant public GOV_CHANNELID = 0x09; uint8 constant public SLASH_CHANNELID = 0x0b; uint8 constant public CROSS_STAKE_CHANNELID = 0x10; - uint16 constant public bscChainID = 0x0060; + uint16 constant public bscChainID = 0x02ca; address public constant VALIDATOR_CONTRACT_ADDR = 0x0000000000000000000000000000000000001000; address public constant SLASH_CONTRACT_ADDR = 0x0000000000000000000000000000000000001001; @@ -37,6 +37,11 @@ contract System { _; } + modifier onlyZeroGasPrice() { + require(tx.gasprice == 0 , "gasprice is not zero"); + _; + } + modifier onlyNotInit() { require(!alreadyInit, "the contract already init"); _; diff --git a/contracts/System.template b/contracts/System.template deleted file mode 100644 index efd9abbc..00000000 --- a/contracts/System.template +++ /dev/null @@ -1,96 +0,0 @@ -pragma solidity 0.6.4; - -import "./interface/ISystemReward.sol"; -import "./interface/IRelayerHub.sol"; -import "./interface/ILightClient.sol"; - -contract System { - - bool public alreadyInit; - - uint32 public constant CODE_OK = 0; - uint32 public constant ERROR_FAIL_DECODE = 100; - - uint8 constant public BIND_CHANNELID = 0x01; - uint8 constant public TRANSFER_IN_CHANNELID = 0x02; - uint8 constant public TRANSFER_OUT_CHANNELID = 0x03; - uint8 constant public STAKING_CHANNELID = 0x08; - uint8 constant public GOV_CHANNELID = 0x09; - uint8 constant public SLASH_CHANNELID = 0x0b; - uint8 constant public CROSS_STAKE_CHANNELID = 0x10; - uint16 constant public bscChainID = 0x{{bscChainId}}; - - address public constant VALIDATOR_CONTRACT_ADDR = 0x0000000000000000000000000000000000001000; - address public constant SLASH_CONTRACT_ADDR = 0x0000000000000000000000000000000000001001; - address public constant SYSTEM_REWARD_ADDR = 0x0000000000000000000000000000000000001002; - address public constant LIGHT_CLIENT_ADDR = 0x0000000000000000000000000000000000001003; - address public constant TOKEN_HUB_ADDR = 0x0000000000000000000000000000000000001004; - address public constant INCENTIVIZE_ADDR=0x0000000000000000000000000000000000001005; - address public constant RELAYERHUB_CONTRACT_ADDR = 0x0000000000000000000000000000000000001006; - address public constant GOV_HUB_ADDR = 0x0000000000000000000000000000000000001007; - address public constant TOKEN_MANAGER_ADDR = 0x0000000000000000000000000000000000001008; - address public constant CROSS_CHAIN_CONTRACT_ADDR = 0x0000000000000000000000000000000000002000; - address public constant STAKING_CONTRACT_ADDR = 0x0000000000000000000000000000000000002001; - - modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "the message sender must be the block producer"); - _; - } - - modifier onlyNotInit() { - require(!alreadyInit, "the contract already init"); - _; - } - - modifier onlyInit() { - require(alreadyInit, "the contract not init yet"); - _; - } - - modifier onlySlash() { - require(msg.sender == SLASH_CONTRACT_ADDR, "the message sender must be slash contract"); - _; - } - - modifier onlyTokenHub() { - require(msg.sender == TOKEN_HUB_ADDR, "the message sender must be token hub contract"); - _; - } - - modifier onlyGov() { - require(msg.sender == GOV_HUB_ADDR, "the message sender must be governance contract"); - _; - } - - modifier onlyValidatorContract() { - require(msg.sender == VALIDATOR_CONTRACT_ADDR, "the message sender must be validatorSet contract"); - _; - } - - modifier onlyCrossChainContract() { - require(msg.sender == CROSS_CHAIN_CONTRACT_ADDR, "the message sender must be cross chain contract"); - _; - } - - modifier onlyRelayerIncentivize() { - require(msg.sender == INCENTIVIZE_ADDR, "the message sender must be incentivize contract"); - _; - } - - modifier onlyRelayer() { - require(IRelayerHub(RELAYERHUB_CONTRACT_ADDR).isRelayer(msg.sender), "the msg sender is not a relayer"); - _; - } - - modifier onlyTokenManager() { - require(msg.sender == TOKEN_MANAGER_ADDR, "the msg sender must be tokenManager"); - _; - } - - // Not reliable, do not use when need strong verify - function isContract(address addr) internal view returns (bool) { - uint size; - assembly { size := extcodesize(addr) } - return size > 0; - } -} diff --git a/contracts/SystemReward.sol b/contracts/SystemReward.sol index 7669d23f..26f29eca 100644 --- a/contracts/SystemReward.sol +++ b/contracts/SystemReward.sol @@ -6,7 +6,8 @@ import "./interface/IParamSubscriber.sol"; import "./interface/ISystemReward.sol"; contract SystemReward is System, IParamSubscriber, ISystemReward { - uint256 public constant MAX_REWARDS = 1e18; + uint256 public constant MAX_REWARDS/*_FOR_RELAYER*/ = 1e18; + uint256 public constant MAX_REWARDS_FOR_FINALITY = 5e18; uint public numOperator; mapping(address => bool) operators; @@ -53,6 +54,20 @@ contract SystemReward is System, IParamSubscriber, ISystemReward { return actualAmount; } + function claimRewardsforFinality(address payable to, uint256 amount) external override(ISystemReward) doInit onlyOperator returns (uint256) { + uint256 actualAmount = amount < address(this).balance ? amount : address(this).balance; + if (actualAmount > MAX_REWARDS_FOR_FINALITY) { + actualAmount = MAX_REWARDS_FOR_FINALITY; + } + if (actualAmount != 0) { + to.transfer(actualAmount); + emit rewardTo(to, actualAmount); + } else { + emit rewardEmpty(); + } + return actualAmount; + } + function isOperator(address addr) external view returns (bool) { return operators[addr]; } diff --git a/contracts/SystemReward.template b/contracts/SystemReward.template deleted file mode 100644 index ae9cb84d..00000000 --- a/contracts/SystemReward.template +++ /dev/null @@ -1,90 +0,0 @@ -pragma solidity 0.6.4; - -import "./System.sol"; -import "./lib/Memory.sol"; -import "./interface/IParamSubscriber.sol"; -import "./interface/ISystemReward.sol"; - -contract SystemReward is System, IParamSubscriber, ISystemReward { - uint256 public constant MAX_REWARDS = 1e18; - - uint public numOperator; - mapping(address => bool) operators; - - modifier doInit() { - if (!alreadyInit) { - operators[LIGHT_CLIENT_ADDR] = true; - operators[INCENTIVIZE_ADDR] = true; - {% if network == 'local' %} - operators[VALIDATOR_CONTRACT_ADDR] = true; - operators[SLASH_CONTRACT_ADDR] = true; - numOperator = 4; - {% else %} - numOperator = 2; - {% endif %} - alreadyInit = true; - } - _; - } - - modifier onlyOperator() { - require(operators[msg.sender],"only operator is allowed to call the method"); - _; - } - - event rewardTo(address indexed to, uint256 amount); - event rewardEmpty(); - event receiveDeposit(address indexed from, uint256 amount); - event addOperator(address indexed operator); - event deleteOperator(address indexed operator); - event paramChange(string key, bytes value); - - receive() external payable{ - if (msg.value>0) { - emit receiveDeposit(msg.sender, msg.value); - } - } - - function claimRewards(address payable to, uint256 amount) external override(ISystemReward) doInit onlyOperator returns (uint256) { - uint256 actualAmount = amount < address(this).balance ? amount : address(this).balance; - if (actualAmount > MAX_REWARDS) { - actualAmount = MAX_REWARDS; - } - if (actualAmount != 0) { - to.transfer(actualAmount); - emit rewardTo(to, actualAmount); - } else { - emit rewardEmpty(); - } - return actualAmount; - } - - function isOperator(address addr) external view returns (bool) { - return operators[addr]; - } - - function updateParam(string calldata key, bytes calldata value) onlyGov external override { - if (Memory.compareStrings(key, "addOperator")) { - bytes memory valueLocal = value; - require(valueLocal.length == 20, "length of value for addOperator should be 20"); - address operatorAddr; - assembly { - operatorAddr := mload(add(valueLocal, 20)) - } - operators[operatorAddr] = true; - emit addOperator(operatorAddr); - } else if (Memory.compareStrings(key, "deleteOperator")) { - bytes memory valueLocal = value; - require(valueLocal.length == 20, "length of value for deleteOperator should be 20"); - address operatorAddr; - assembly { - operatorAddr := mload(add(valueLocal, 20)) - } - delete operators[operatorAddr]; - emit deleteOperator(operatorAddr); - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } -} diff --git a/contracts/TendermintLightClient.template b/contracts/TendermintLightClient.template deleted file mode 100644 index 48e9f04c..00000000 --- a/contracts/TendermintLightClient.template +++ /dev/null @@ -1,268 +0,0 @@ -pragma solidity 0.6.4; - -import "./lib/Memory.sol"; -import "./lib/BytesToTypes.sol"; -import "./interface/ILightClient.sol"; -import "./interface/ISystemReward.sol"; -import "./interface/IParamSubscriber.sol"; -import "./System.sol"; - -contract TendermintLightClient is ILightClient, System, IParamSubscriber{ - - struct ConsensusState { - uint64 preValidatorSetChangeHeight; - bytes32 appHash; - bytes32 curValidatorSetHash; - bytes nextValidatorSet; - } - - mapping(uint64 => ConsensusState) public lightClientConsensusStates; - mapping(uint64 => address payable) public submitters; - uint64 public initialHeight; - uint64 public latestHeight; - bytes32 public chainID; - - bytes constant public INIT_CONSENSUS_STATE_BYTES = hex"{{initConsensusStateBytes}}"; - uint256 constant public INIT_REWARD_FOR_VALIDATOR_SER_CHANGE = {{rewardForValidatorSetChange}}; - uint256 public rewardForValidatorSetChange; - - event initConsensusState(uint64 initHeight, bytes32 appHash); - event syncConsensusState(uint64 height, uint64 preValidatorSetChangeHeight, bytes32 appHash, bool validatorChanged); - event paramChange(string key, bytes value); - - /* solium-disable-next-line */ - constructor() public {} - - function init() external onlyNotInit { - uint256 pointer; - uint256 length; - (pointer, length) = Memory.fromBytes(INIT_CONSENSUS_STATE_BYTES); - - /* solium-disable-next-line */ - assembly { - sstore(chainID_slot, mload(pointer)) - } - - ConsensusState memory cs; - uint64 height; - (cs, height) = decodeConsensusState(pointer, length, false); - cs.preValidatorSetChangeHeight = 0; - lightClientConsensusStates[height] = cs; - - initialHeight = height; - latestHeight = height; - alreadyInit = true; - rewardForValidatorSetChange = INIT_REWARD_FOR_VALIDATOR_SER_CHANGE; - - emit initConsensusState(initialHeight, cs.appHash); - } - - function syncTendermintHeader(bytes calldata header, uint64 height) external onlyRelayer returns (bool) { - require(submitters[height] == address(0x0), "can't sync duplicated header"); - require(height > initialHeight, "can't sync header before initialHeight"); - - uint64 preValidatorSetChangeHeight = latestHeight; - ConsensusState memory cs = lightClientConsensusStates[preValidatorSetChangeHeight]; - for (; preValidatorSetChangeHeight >= height && preValidatorSetChangeHeight >= initialHeight;) { - preValidatorSetChangeHeight = cs.preValidatorSetChangeHeight; - cs = lightClientConsensusStates[preValidatorSetChangeHeight]; - } - if (cs.nextValidatorSet.length == 0) { - preValidatorSetChangeHeight = cs.preValidatorSetChangeHeight; - cs.nextValidatorSet = lightClientConsensusStates[preValidatorSetChangeHeight].nextValidatorSet; - require(cs.nextValidatorSet.length != 0, "failed to load validator set data"); - } - - //32 + 32 + 8 + 32 + 32 + cs.nextValidatorSet.length; - uint256 length = 136 + cs.nextValidatorSet.length; - bytes memory input = new bytes(length+header.length); - uint256 ptr = Memory.dataPtr(input); - require(encodeConsensusState(cs, preValidatorSetChangeHeight, ptr, length), "failed to serialize consensus state"); - - // write header to input - uint256 src; - ptr = ptr+length; - (src, length) = Memory.fromBytes(header); - Memory.copy(src, ptr, length); - - length = input.length+32; - // Maximum validator quantity is 99 - bytes32[128] memory result; - /* solium-disable-next-line */ - assembly { - // call validateTendermintHeader precompile contract - // Contract address: 0x64 - if iszero(staticcall(not(0), 0x64, input, length, result, 4096)) { - revert(0, 0) - } - } - - //Judge if the validator set is changed - /* solium-disable-next-line */ - assembly { - length := mload(add(result, 0)) - } - bool validatorChanged = false; - if ((length&(0x01<<248))!=0x00) { - validatorChanged = true; - ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, rewardForValidatorSetChange); - } - length = length&0xffffffffffffffff; - - /* solium-disable-next-line */ - assembly { - ptr := add(result, 32) - } - - uint64 actualHeaderHeight; - (cs, actualHeaderHeight) = decodeConsensusState(ptr, length, !validatorChanged); - require(actualHeaderHeight == height, "header height doesn't equal to the specified height"); - - submitters[height] = msg.sender; - cs.preValidatorSetChangeHeight = preValidatorSetChangeHeight; - lightClientConsensusStates[height] = cs; - if (height > latestHeight) { - latestHeight = height; - } - - emit syncConsensusState(height, preValidatorSetChangeHeight, cs.appHash, validatorChanged); - - return true; - } - - function isHeaderSynced(uint64 height) external override view returns (bool) { - return submitters[height] != address(0x0) || height == initialHeight; - } - - function getAppHash(uint64 height) external override view returns (bytes32) { - return lightClientConsensusStates[height].appHash; - } - - function getSubmitter(uint64 height) external override view returns (address payable) { - return submitters[height]; - } - - function getChainID() external view returns (string memory) { - bytes memory chainIDBytes = new bytes(32); - assembly { - mstore(add(chainIDBytes,32), sload(chainID_slot)) - } - - uint8 chainIDLength = 0; - for (uint8 j = 0; j < 32; ++j) { - if (chainIDBytes[j] != 0) { - ++chainIDLength; - } else { - break; - } - } - - bytes memory chainIDStr = new bytes(chainIDLength); - for (uint8 j = 0; j < chainIDLength; ++j) { - chainIDStr[j] = chainIDBytes[j]; - } - - return string(chainIDStr); - } - - // | length | chainID | height | appHash | curValidatorSetHash | [{validator pubkey, voting power}] | - // | 32 bytes | 32 bytes | 8 bytes | 32 bytes | 32 bytes | [{32 bytes, 8 bytes}] | - /* solium-disable-next-line */ - function encodeConsensusState(ConsensusState memory cs, uint64 height, uint256 outputPtr, uint256 size) internal view returns (bool) { - outputPtr = outputPtr + size - cs.nextValidatorSet.length; - - uint256 src; - uint256 length; - (src, length) = Memory.fromBytes(cs.nextValidatorSet); - Memory.copy(src, outputPtr, length); - outputPtr = outputPtr-32; - - bytes32 hash = cs.curValidatorSetHash; - /* solium-disable-next-line */ - assembly { - mstore(outputPtr, hash) - } - outputPtr = outputPtr-32; - - hash = cs.appHash; - /* solium-disable-next-line */ - assembly { - mstore(outputPtr, hash) - } - outputPtr = outputPtr-32; - - /* solium-disable-next-line */ - assembly { - mstore(outputPtr, height) - } - outputPtr = outputPtr-8; - - /* solium-disable-next-line */ - assembly { - mstore(outputPtr, sload(chainID_slot)) - } - outputPtr = outputPtr-32; - - // size doesn't contain length - size = size-32; - /* solium-disable-next-line */ - assembly { - mstore(outputPtr, size) - } - - return true; - } - - // | chainID | height | appHash | curValidatorSetHash | [{validator pubkey, voting power}] | - // | 32 bytes | 8 bytes | 32 bytes | 32 bytes | [{32 bytes, 8 bytes}] | - /* solium-disable-next-line */ - function decodeConsensusState(uint256 ptr, uint256 size, bool leaveOutValidatorSet) internal pure returns(ConsensusState memory, uint64) { - ptr = ptr+8; - uint64 height; - /* solium-disable-next-line */ - assembly { - height := mload(ptr) - } - - ptr = ptr+32; - bytes32 appHash; - /* solium-disable-next-line */ - assembly { - appHash := mload(ptr) - } - - ptr = ptr+32; - bytes32 curValidatorSetHash; - /* solium-disable-next-line */ - assembly { - curValidatorSetHash := mload(ptr) - } - - ConsensusState memory cs; - cs.appHash = appHash; - cs.curValidatorSetHash = curValidatorSetHash; - - if (!leaveOutValidatorSet) { - uint256 dest; - uint256 length; - cs.nextValidatorSet = new bytes(size-104); - (dest,length) = Memory.fromBytes(cs.nextValidatorSet); - - Memory.copy(ptr+32, dest, length); - } - - return (cs, height); - } - - function updateParam(string calldata key, bytes calldata value) external override onlyInit onlyGov{ - if (Memory.compareStrings(key,"rewardForValidatorSetChange")) { - require(value.length == 32, "length of rewardForValidatorSetChange mismatch"); - uint256 newRewardForValidatorSetChange = BytesToTypes.bytesToUint256(32, value); - require(newRewardForValidatorSetChange > 0 && newRewardForValidatorSetChange <= 1e18, "the newRewardForValidatorSetChange out of range"); - rewardForValidatorSetChange = newRewardForValidatorSetChange; - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } -} \ No newline at end of file diff --git a/contracts/TokenHub.sol b/contracts/TokenHub.sol index cc4904b1..0bb22eb2 100644 --- a/contracts/TokenHub.sol +++ b/contracts/TokenHub.sol @@ -161,6 +161,10 @@ contract TokenHub is ITokenHub, System, IParamSubscriber, IApplication, ISystemR return actualAmount; } + function claimRewardsforFinality(address payable, uint256) onlyInit onlyRelayerIncentivize external override returns(uint256) { + revert("CLAIM_REWARDS_FOR_FINALITY_NOT_ALLOWED"); + } + function getMiniRelayFee() external view override returns(uint256) { return relayFee; } diff --git a/contracts/TokenHub.template b/contracts/TokenHub.template deleted file mode 100644 index b30dbd8d..00000000 --- a/contracts/TokenHub.template +++ /dev/null @@ -1,738 +0,0 @@ -pragma solidity 0.6.4; - -import "./interface/IBEP20.sol"; -import "./interface/ITokenHub.sol"; -import "./interface/IParamSubscriber.sol"; -import "./interface/IApplication.sol"; -import "./interface/ICrossChain.sol"; -import "./interface/ISystemReward.sol"; -import "./lib/SafeMath.sol"; -import "./lib/RLPEncode.sol"; -import "./lib/RLPDecode.sol"; -import "./lib/BytesToTypes.sol"; -import "./lib/Memory.sol"; -import "./System.sol"; - -contract TokenHub is ITokenHub, System, IParamSubscriber, IApplication, ISystemReward { - - using SafeMath for uint256; - - using RLPEncode for *; - using RLPDecode for *; - - using RLPDecode for RLPDecode.RLPItem; - using RLPDecode for RLPDecode.Iterator; - - // BSC to BC - struct TransferOutSynPackage { - bytes32 bep2TokenSymbol; - address contractAddr; - uint256[] amounts; - address[] recipients; - address[] refundAddrs; - uint64 expireTime; - } - - // BC to BSC - struct TransferOutAckPackage { - address contractAddr; - uint256[] refundAmounts; - address[] refundAddrs; - uint32 status; - } - - // BC to BSC - struct TransferInSynPackage { - bytes32 bep2TokenSymbol; - address contractAddr; - uint256 amount; - address recipient; - address refundAddr; - uint64 expireTime; - } - - // BSC to BC - struct TransferInRefundPackage { - bytes32 bep2TokenSymbol; - uint256 refundAmount; - address refundAddr; - uint32 status; - } - - // BEP-171: Security Enhancement for Cross-Chain Module - struct LockInfo { - uint256 amount; - uint256 unlockAt; - } - - // transfer in channel - uint8 constant public TRANSFER_IN_SUCCESS = 0; - uint8 constant public TRANSFER_IN_FAILURE_TIMEOUT = 1; - uint8 constant public TRANSFER_IN_FAILURE_UNBOUND_TOKEN = 2; - uint8 constant public TRANSFER_IN_FAILURE_INSUFFICIENT_BALANCE = 3; - uint8 constant public TRANSFER_IN_FAILURE_NON_PAYABLE_RECIPIENT = 4; - uint8 constant public TRANSFER_IN_FAILURE_UNKNOWN = 5; - - uint256 constant public MAX_BEP2_TOTAL_SUPPLY = 9000000000000000000; - uint8 constant public MINIMUM_BEP20_SYMBOL_LEN = 2; - uint8 constant public MAXIMUM_BEP20_SYMBOL_LEN = 8; - uint8 constant public BEP2_TOKEN_DECIMALS = 8; - bytes32 constant public BEP2_TOKEN_SYMBOL_FOR_BNB = 0x424E420000000000000000000000000000000000000000000000000000000000; // "BNB" - uint256 constant public MAX_GAS_FOR_CALLING_BEP20={{maxGasForCallingBEP20}}; - uint256 constant public MAX_GAS_FOR_TRANSFER_BNB={{maxGasForTransferringBNB}}; - - uint256 constant public INIT_MINIMUM_RELAY_FEE ={{initRelayFee}}; - uint256 constant public REWARD_UPPER_LIMIT ={{rewardUpperLimit}}; - uint256 constant public TEN_DECIMALS = 1e10; - - uint256 public relayFee; - - mapping(address => uint256) public bep20ContractDecimals; - mapping(address => bytes32) private contractAddrToBEP2Symbol; - mapping(bytes32 => address) private bep2SymbolToContractAddr; - - // BEP-171: Security Enhancement for Cross-Chain Module - uint256 constant public INIT_BNB_LARGE_TRANSFER_LIMIT = 10000 ether; - uint256 constant public INIT_LOCK_PERIOD = 12 hours; - // the lock period for large cross-chain transfer - uint256 public lockPeriod; - // token address => largeTransferLimit amount, address(0) means BNB - mapping(address => uint256) public largeTransferLimitMap; - // token address => recipient address => lockedAmount + unlockAt, address(0) means BNB - mapping(address => mapping(address => LockInfo)) public lockInfoMap; - uint8 internal reentryLock; - - event transferInSuccess(address bep20Addr, address refundAddr, uint256 amount); - event transferOutSuccess(address bep20Addr, address senderAddr, uint256 amount, uint256 relayFee); - event refundSuccess(address bep20Addr, address refundAddr, uint256 amount, uint32 status); - event refundFailure(address bep20Addr, address refundAddr, uint256 amount, uint32 status); - event rewardTo(address to, uint256 amount); - event receiveDeposit(address from, uint256 amount); - event unexpectedPackage(uint8 channelId, bytes msgBytes); - event paramChange(string key, bytes value); - - // BEP-171: Security Enhancement for Cross-Chain Module - event LargeTransferLocked(address indexed tokenAddr, address indexed recipient, uint256 amount, uint256 unlockAt); - event WithdrawUnlockedToken(address indexed tokenAddr, address indexed recipient, uint256 amount); - event CancelTransfer(address indexed tokenAddr, address indexed attacker, uint256 amount); - event LargeTransferLimitSet(address indexed tokenAddr, address indexed owner, uint256 largeTransferLimit); - - // BEP-171: Security Enhancement for Cross-Chain Module - modifier onlyTokenOwner(address bep20Token) { - require(msg.sender == IBEP20(bep20Token).getOwner(), "not owner of BEP20 token"); - _; - } - - modifier noReentrant() { - require(reentryLock != 2, "No re-entrancy"); - reentryLock = 2; - _; - reentryLock = 1; - } - - function init() onlyNotInit external { - relayFee = INIT_MINIMUM_RELAY_FEE; - bep20ContractDecimals[address(0x0)] = 18; // BNB decimals is 18 - alreadyInit=true; - } - - receive() external payable{ - if (msg.value>0) { - emit receiveDeposit(msg.sender, msg.value); - } - } - - - /** - * @dev Claim relayer reward to target account - * - * @param to Whose relay reward will be claimed. - * @param amount Reward amount - */ - function claimRewards(address payable to, uint256 amount) onlyInit onlyRelayerIncentivize external override returns(uint256) { - uint256 actualAmount = amount < address(this).balance ? amount : address(this).balance; - if (actualAmount > REWARD_UPPER_LIMIT) { - return 0; - } - if (actualAmount>0) { - to.transfer(actualAmount); - emit rewardTo(to, actualAmount); - } - return actualAmount; - } - - function getMiniRelayFee() external view override returns(uint256) { - return relayFee; - } - - /** - * @dev handle sync cross-chain package from BC - * - * @param channelId The channel for cross-chain communication - * @param msgBytes The rlp encoded message bytes sent from BC - */ - function handleSynPackage(uint8 channelId, bytes calldata msgBytes) onlyInit onlyCrossChainContract external override returns(bytes memory) { - if (channelId == TRANSFER_IN_CHANNELID) { - return handleTransferInSynPackage(msgBytes); - } else { - // should not happen - require(false, "unrecognized syn package"); - return new bytes(0); - } - } - - /** - * @dev handle ack cross-chain package from BC,it means cross-chain transfer successfully to BC - * and will refund the remaining token caused by different decimals between BSC and BC. - * - * @param channelId The channel for cross-chain communication - * @param msgBytes The rlp encoded message bytes sent from BC - */ - function handleAckPackage(uint8 channelId, bytes calldata msgBytes) onlyInit onlyCrossChainContract external override { - if (channelId == TRANSFER_OUT_CHANNELID) { - handleTransferOutAckPackage(msgBytes); - } else { - emit unexpectedPackage(channelId, msgBytes); - } - } - - /** - * @dev handle failed ack cross-chain package from BC, it means failed to cross-chain transfer to BC and will refund the token. - * - * @param channelId The channel for cross-chain communication - * @param msgBytes The rlp encoded message bytes sent from BC - */ - function handleFailAckPackage(uint8 channelId, bytes calldata msgBytes) onlyInit onlyCrossChainContract external override { - if (channelId == TRANSFER_OUT_CHANNELID) { - handleTransferOutFailAckPackage(msgBytes); - } else { - emit unexpectedPackage(channelId, msgBytes); - } - } - - function decodeTransferInSynPackage(bytes memory msgBytes) internal pure returns (TransferInSynPackage memory, bool) { - TransferInSynPackage memory transInSynPkg; - - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) transInSynPkg.bep2TokenSymbol = bytes32(iter.next().toUint()); - else if (idx == 1) transInSynPkg.contractAddr = iter.next().toAddress(); - else if (idx == 2) transInSynPkg.amount = iter.next().toUint(); - else if (idx == 3) transInSynPkg.recipient = ((iter.next().toAddress())); - else if (idx == 4) transInSynPkg.refundAddr = iter.next().toAddress(); - else if (idx == 5) { - transInSynPkg.expireTime = uint64(iter.next().toUint()); - success = true; - } - else break; - ++idx; - } - return (transInSynPkg, success); - } - - function encodeTransferInRefundPackage(TransferInRefundPackage memory transInAckPkg) internal pure returns (bytes memory) { - bytes[] memory elements = new bytes[](4); - elements[0] = uint256(transInAckPkg.bep2TokenSymbol).encodeUint(); - elements[1] = transInAckPkg.refundAmount.encodeUint(); - elements[2] = transInAckPkg.refundAddr.encodeAddress(); - elements[3] = uint256(transInAckPkg.status).encodeUint(); - return elements.encodeList(); - } - - function handleTransferInSynPackage(bytes memory msgBytes) internal returns(bytes memory) { - (TransferInSynPackage memory transInSynPkg, bool success) = decodeTransferInSynPackage(msgBytes); - require(success, "unrecognized transferIn package"); - uint32 resCode = doTransferIn(transInSynPkg); - if (resCode != TRANSFER_IN_SUCCESS) { - uint256 bep2Amount = convertToBep2Amount(transInSynPkg.amount, bep20ContractDecimals[transInSynPkg.contractAddr]); - TransferInRefundPackage memory transInAckPkg = TransferInRefundPackage({ - bep2TokenSymbol: transInSynPkg.bep2TokenSymbol, - refundAmount: bep2Amount, - refundAddr: transInSynPkg.refundAddr, - status: resCode - }); - return encodeTransferInRefundPackage(transInAckPkg); - } else { - return new bytes(0); - } - } - - function doTransferIn(TransferInSynPackage memory transInSynPkg) internal returns (uint32) { - if (transInSynPkg.contractAddr==address(0x0)) { - if (block.timestamp > transInSynPkg.expireTime) { - return TRANSFER_IN_FAILURE_TIMEOUT; - } - if (address(this).balance < transInSynPkg.amount) { - return TRANSFER_IN_FAILURE_INSUFFICIENT_BALANCE; - } - - // BEP-171: Security Enhancement for Cross-Chain Module - if (!_checkAndLockTransferIn(transInSynPkg)) { - // directly transfer to the recipient - (bool success, ) = transInSynPkg.recipient.call{gas: MAX_GAS_FOR_TRANSFER_BNB, value: transInSynPkg.amount}(""); - if (!success) { - return TRANSFER_IN_FAILURE_NON_PAYABLE_RECIPIENT; - } - } - - emit transferInSuccess(transInSynPkg.contractAddr, transInSynPkg.recipient, transInSynPkg.amount); - return TRANSFER_IN_SUCCESS; - } else { - if (block.timestamp > transInSynPkg.expireTime) { - return TRANSFER_IN_FAILURE_TIMEOUT; - } - if (contractAddrToBEP2Symbol[transInSynPkg.contractAddr]!= transInSynPkg.bep2TokenSymbol) { - return TRANSFER_IN_FAILURE_UNBOUND_TOKEN; - } - uint256 actualBalance = IBEP20(transInSynPkg.contractAddr).balanceOf{gas: MAX_GAS_FOR_CALLING_BEP20}(address(this)); - if (actualBalance < transInSynPkg.amount) { - return TRANSFER_IN_FAILURE_INSUFFICIENT_BALANCE; - } - - // BEP-171: Security Enhancement for Cross-Chain Module - if (!_checkAndLockTransferIn(transInSynPkg)) { - bool success = IBEP20(transInSynPkg.contractAddr).transfer{gas: MAX_GAS_FOR_CALLING_BEP20}(transInSynPkg.recipient, transInSynPkg.amount); - if (!success) { - return TRANSFER_IN_FAILURE_UNKNOWN; - } - } - - emit transferInSuccess(transInSynPkg.contractAddr, transInSynPkg.recipient, transInSynPkg.amount); - return TRANSFER_IN_SUCCESS; - } - } - - // BEP-171: Security Enhancement for Cross-Chain Module - function setLargeTransferLimit(address bep20Token, uint256 largeTransferLimit) external onlyTokenOwner(bep20Token) { - require(largeTransferLimit > 0, "zero limit not allowed"); - require(contractAddrToBEP2Symbol[bep20Token] != bytes32(0x00), "not bound"); - largeTransferLimitMap[bep20Token] = largeTransferLimit; - - emit LargeTransferLimitSet(bep20Token, msg.sender, largeTransferLimit); - } - - // BEP-171: Security Enhancement for Cross-Chain Module - function withdrawUnlockedToken(address tokenAddress, address recipient) external noReentrant { - LockInfo storage lockInfo = lockInfoMap[tokenAddress][recipient]; - require(lockInfo.amount > 0, "no locked amount"); - require(block.timestamp >= lockInfo.unlockAt, "still on locking period"); - - uint256 _amount = lockInfo.amount; - lockInfo.amount = 0; - - bool _success; - if (tokenAddress == address(0x0)) { - (_success, ) = recipient.call{gas: MAX_GAS_FOR_TRANSFER_BNB, value: _amount}(""); - } else { - _success = IBEP20(tokenAddress).transfer{gas: MAX_GAS_FOR_CALLING_BEP20}(recipient, _amount); - } - require(_success, "withdraw unlocked token failed"); - - emit WithdrawUnlockedToken(tokenAddress, recipient, _amount); - } - - // BEP-171: Security Enhancement for Cross-Chain Module - function cancelTransferIn(address tokenAddress, address attacker) override external onlyCrossChainContract { - LockInfo storage lockInfo = lockInfoMap[tokenAddress][attacker]; - require(lockInfo.amount > 0, "no locked amount"); - - uint256 _amount = lockInfo.amount; - lockInfo.amount = 0; - - emit CancelTransfer(tokenAddress, attacker, _amount); - } - - // BEP-171: Security Enhancement for Cross-Chain Module - function _checkAndLockTransferIn(TransferInSynPackage memory transInSynPkg) internal returns (bool isLocked) { - // check if BEP-171 params init - if (largeTransferLimitMap[address(0x0)] == 0 && lockPeriod == 0) { - largeTransferLimitMap[address(0x0)] = INIT_BNB_LARGE_TRANSFER_LIMIT; - lockPeriod = INIT_LOCK_PERIOD; - } - - // check if it is over large transfer limit - uint256 _limit = largeTransferLimitMap[transInSynPkg.contractAddr]; - if (_limit == 0 || transInSynPkg.amount < _limit) { - return false; - } - - // it is over the large transfer limit - // add time lock to recipient - LockInfo storage lockInfo = lockInfoMap[transInSynPkg.contractAddr][transInSynPkg.recipient]; - lockInfo.amount = lockInfo.amount.add(transInSynPkg.amount); - lockInfo.unlockAt = block.timestamp + lockPeriod; - - emit LargeTransferLocked( - transInSynPkg.contractAddr, - transInSynPkg.recipient, - transInSynPkg.amount, - lockInfo.unlockAt - ); - return true; - } - - function decodeTransferOutAckPackage(bytes memory msgBytes) internal pure returns(TransferOutAckPackage memory, bool) { - TransferOutAckPackage memory transOutAckPkg; - - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) { - transOutAckPkg.contractAddr = iter.next().toAddress(); - } - else if (idx == 1) { - RLPDecode.RLPItem[] memory list = iter.next().toList(); - transOutAckPkg.refundAmounts = new uint256[](list.length); - for (uint256 index=0; index=block.timestamp + 120, "expireTime must be two minutes later"); - require(msg.value%TEN_DECIMALS==0, "invalid received BNB amount: precision loss in amount conversion"); - bytes32 bep2TokenSymbol; - uint256 convertedAmount; - uint256 rewardForRelayer; - if (contractAddr==address(0x0)) { - require(msg.value>=amount.add(relayFee), "received BNB amount should be no less than the sum of transferOut BNB amount and minimum relayFee"); - require(amount%TEN_DECIMALS==0, "invalid transfer amount: precision loss in amount conversion"); - rewardForRelayer=msg.value.sub(amount); - convertedAmount = amount.div(TEN_DECIMALS); // native bnb decimals is 8 on BBC, while the native bnb decimals on BSC is 18 - bep2TokenSymbol=BEP2_TOKEN_SYMBOL_FOR_BNB; - } else { - bep2TokenSymbol = contractAddrToBEP2Symbol[contractAddr]; - require(bep2TokenSymbol!=bytes32(0x00), "the contract has not been bound to any bep2 token"); - require(msg.value>=relayFee, "received BNB amount should be no less than the minimum relayFee"); - rewardForRelayer=msg.value; - uint256 bep20TokenDecimals=bep20ContractDecimals[contractAddr]; - require(bep20TokenDecimals<=BEP2_TOKEN_DECIMALS || (bep20TokenDecimals>BEP2_TOKEN_DECIMALS && amount.mod(10**(bep20TokenDecimals-BEP2_TOKEN_DECIMALS))==0), "invalid transfer amount: precision loss in amount conversion"); - convertedAmount = convertToBep2Amount(amount, bep20TokenDecimals);// convert to bep2 amount - if (isMiniBEP2Token(bep2TokenSymbol)) { - require(convertedAmount >= 1e8 , "For miniToken, the transfer amount must not be less than 1"); - } - require(bep20TokenDecimals>=BEP2_TOKEN_DECIMALS || (bep20TokenDecimalsamount), "amount is too large, uint256 overflow"); - require(convertedAmount<=MAX_BEP2_TOTAL_SUPPLY, "amount is too large, exceed maximum bep2 token amount"); - require(IBEP20(contractAddr).transferFrom(msg.sender, address(this), amount)); - } - TransferOutSynPackage memory transOutSynPkg = TransferOutSynPackage({ - bep2TokenSymbol: bep2TokenSymbol, - contractAddr: contractAddr, - amounts: new uint256[](1), - recipients: new address[](1), - refundAddrs: new address[](1), - expireTime: expireTime - }); - transOutSynPkg.amounts[0]=convertedAmount; - transOutSynPkg.recipients[0]=recipient; - transOutSynPkg.refundAddrs[0]=msg.sender; - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(TRANSFER_OUT_CHANNELID, encodeTransferOutSynPackage(transOutSynPkg), rewardForRelayer.div(TEN_DECIMALS)); - emit transferOutSuccess(contractAddr, msg.sender, amount, rewardForRelayer); - return true; - } - - /** - * @dev request a batch cross-chain BNB transfers from BSC to BC - * - * @param recipientAddrs The destination address of the cross-chain transfer on BC. - * @param amounts The amounts to transfer - * @param refundAddrs The refund addresses that receive the refund funds while failed to cross-chain transfer - * @param expireTime The expire time for these cross-chain transfers - */ - function batchTransferOutBNB(address[] calldata recipientAddrs, uint256[] calldata amounts, address[] calldata refundAddrs, uint64 expireTime) external override onlyInit payable returns (bool) { - require(recipientAddrs.length == amounts.length, "Length of recipientAddrs doesn't equal to length of amounts"); - require(recipientAddrs.length == refundAddrs.length, "Length of recipientAddrs doesn't equal to length of refundAddrs"); - require(expireTime>=block.timestamp + 120, "expireTime must be two minutes later"); - require(msg.value%TEN_DECIMALS==0, "invalid received BNB amount: precision loss in amount conversion"); - uint256 batchLength = amounts.length; - uint256 totalAmount = 0; - uint256 rewardForRelayer; - uint256[] memory convertedAmounts = new uint256[](batchLength); - for (uint i = 0; i < batchLength; ++i) { - require(amounts[i]%TEN_DECIMALS==0, "invalid transfer amount: precision loss in amount conversion"); - totalAmount = totalAmount.add(amounts[i]); - convertedAmounts[i] = amounts[i].div(TEN_DECIMALS); - } - require(msg.value>=totalAmount.add(relayFee.mul(batchLength)), "received BNB amount should be no less than the sum of transfer BNB amount and relayFee"); - rewardForRelayer = msg.value.sub(totalAmount); - - TransferOutSynPackage memory transOutSynPkg = TransferOutSynPackage({ - bep2TokenSymbol: BEP2_TOKEN_SYMBOL_FOR_BNB, - contractAddr: address(0x00), - amounts: convertedAmounts, - recipients: recipientAddrs, - refundAddrs: refundAddrs, - expireTime: expireTime - }); - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(TRANSFER_OUT_CHANNELID, encodeTransferOutSynPackage(transOutSynPkg), rewardForRelayer.div(TEN_DECIMALS)); - emit transferOutSuccess(address(0x0), msg.sender, totalAmount, rewardForRelayer); - return true; - } - - function updateParam(string calldata key, bytes calldata value) override external onlyGov{ - require(value.length == 32, "expected value length is 32"); - string memory localKey = key; - bytes memory localValue = value; - bytes32 bytes32Key; - assembly { - bytes32Key := mload(add(localKey, 32)) - } - if (bytes32Key == bytes32(0x72656c6179466565000000000000000000000000000000000000000000000000)) { // relayFee - uint256 newRelayFee; - assembly { - newRelayFee := mload(add(localValue, 32)) - } - require(newRelayFee <= 1e18 && newRelayFee%(TEN_DECIMALS)==0, "the relayFee out of range"); - relayFee = newRelayFee; - } else if (Memory.compareStrings(key, "largeTransferLockPeriod")) { - uint256 newLockPeriod = BytesToTypes.bytesToUint256(32, value); - require(newLockPeriod <= 1 weeks, "lock period too long"); - lockPeriod = newLockPeriod; - } else if (Memory.compareStrings(key, "bnbLargeTransferLimit")) { - uint256 newBNBLargeTransferLimit = BytesToTypes.bytesToUint256(32, value); - require(newBNBLargeTransferLimit >= 100 ether, "bnb large transfer limit too small"); - largeTransferLimitMap[address(0x0)] = newBNBLargeTransferLimit; - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } - - function getContractAddrByBEP2Symbol(bytes32 bep2Symbol) external view override returns(address) { - return bep2SymbolToContractAddr[bep2Symbol]; - } - - function getBep2SymbolByContractAddr(address contractAddr) external view override returns(bytes32) { - return contractAddrToBEP2Symbol[contractAddr]; - } - - function bindToken(bytes32 bep2Symbol, address contractAddr, uint256 decimals) external override onlyTokenManager { - bep2SymbolToContractAddr[bep2Symbol] = contractAddr; - contractAddrToBEP2Symbol[contractAddr] = bep2Symbol; - bep20ContractDecimals[contractAddr] = decimals; - } - - function unbindToken(bytes32 bep2Symbol, address contractAddr) external override onlyTokenManager { - delete bep2SymbolToContractAddr[bep2Symbol]; - delete contractAddrToBEP2Symbol[contractAddr]; - delete bep20ContractDecimals[contractAddr]; - } - - function isMiniBEP2Token(bytes32 symbol) internal pure returns(bool) { - bytes memory symbolBytes = new bytes(32); - assembly { - mstore(add(symbolBytes, 32), symbol) - } - uint8 symbolLength = 0; - for (uint8 j = 0; j < 32; ++j) { - if (symbolBytes[j] != 0) { - ++symbolLength; - } else { - break; - } - } - if (symbolLength < MINIMUM_BEP20_SYMBOL_LEN + 5) { - return false; - } - if (symbolBytes[symbolLength-5] != 0x2d) { // '-' - return false; - } - if (symbolBytes[symbolLength-1] != 'M') { // ABC-XXXM - return false; - } - return true; - } - - function convertToBep2Amount(uint256 amount, uint256 bep20TokenDecimals) internal pure returns (uint256) { - if (bep20TokenDecimals > BEP2_TOKEN_DECIMALS) { - return amount.div(10**(bep20TokenDecimals-BEP2_TOKEN_DECIMALS)); - } - return amount.mul(10**(BEP2_TOKEN_DECIMALS-bep20TokenDecimals)); - } - - function convertFromBep2Amount(uint256 amount, uint256 bep20TokenDecimals) internal pure returns (uint256) { - if (bep20TokenDecimals > BEP2_TOKEN_DECIMALS) { - return amount.mul(10**(bep20TokenDecimals-BEP2_TOKEN_DECIMALS)); - } - return amount.div(10**(BEP2_TOKEN_DECIMALS-bep20TokenDecimals)); - } - - function getBoundContract(string memory bep2Symbol) public view returns (address) { - bytes32 bep2TokenSymbol; - assembly { - bep2TokenSymbol := mload(add(bep2Symbol, 32)) - } - return bep2SymbolToContractAddr[bep2TokenSymbol]; - } - - function getBoundBep2Symbol(address contractAddr) public view returns (string memory) { - bytes32 bep2SymbolBytes32 = contractAddrToBEP2Symbol[contractAddr]; - bytes memory bep2SymbolBytes = new bytes(32); - assembly { - mstore(add(bep2SymbolBytes,32), bep2SymbolBytes32) - } - uint8 bep2SymbolLength = 0; - for (uint8 j = 0; j < 32; ++j) { - if (bep2SymbolBytes[j] != 0) { - ++bep2SymbolLength; - } else { - break; - } - } - bytes memory bep2Symbol = new bytes(bep2SymbolLength); - for (uint8 j = 0; j < bep2SymbolLength; ++j) { - bep2Symbol[j] = bep2SymbolBytes[j]; - } - return string(bep2Symbol); - } - - function withdrawStakingBNB(uint256 amount) external override returns(bool) { - require(msg.sender == STAKING_CONTRACT_ADDR, "only staking system contract can call this function"); - if (amount != 0) { - payable(STAKING_CONTRACT_ADDR).transfer(amount); - } - return true; - } -} diff --git a/contracts/interface/ISystemReward.sol b/contracts/interface/ISystemReward.sol index 67a71f58..4e127df6 100644 --- a/contracts/interface/ISystemReward.sol +++ b/contracts/interface/ISystemReward.sol @@ -2,4 +2,5 @@ pragma solidity 0.6.4; interface ISystemReward { function claimRewards(address payable to, uint256 amount) external returns(uint256 actualAmount); + function claimRewardsforFinality(address payable to, uint256 amount) external returns(uint256 actualAmount); } \ No newline at end of file diff --git a/flatten.sh b/flatten.sh new file mode 100644 index 00000000..c1bbebcd --- /dev/null +++ b/flatten.sh @@ -0,0 +1,11 @@ +forge flatten contracts/BSCValidatorSet.sol > contracts/flattened/BSCValidatorSet.sol +forge flatten contracts/GovHub.sol > contracts/flattened/GovHub.sol +forge flatten contracts/RelayerHub.sol > contracts/flattened/RelayerHub.sol +forge flatten contracts/RelayerIncentivize.sol > contracts/flattened/RelayerIncentivize.sol +forge flatten contracts/SlashIndicator.sol > contracts/flattened/SlashIndicator.sol +forge flatten contracts/SystemReward.sol > contracts/flattened/SystemReward.sol +forge flatten contracts/TendermintLightClient.sol > contracts/flattened/TendermintLightClient.sol +forge flatten contracts/TokenHub.sol > contracts/flattened/TokenHub.sol +forge flatten contracts/CrossChain.sol > contracts/flattened/CrossChain.sol +forge flatten contracts/TokenManager.sol > contracts/flattened/TokenManager.sol +forge flatten contracts/Staking.sol > contracts/flattened/Staking.sol \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index ae076aaf..c088dcf3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -19,6 +19,7 @@ verbosity = 3 ignored_error_codes = [1878, 5574, 5667] ffi = true initial_balance = '0xffffffffffffffffffffffff' +bytecode_hash = 'none' [profile.default.rpc_storage_caching] chains = 'all' @@ -28,8 +29,8 @@ endpoints = 'all' runs = 1024 [fmt] -line_length = 240 -tab_width = 2 +line_length = 120 +tab_width = 4 bracket_spacing = false int_types = 'long' func_attrs_with_params_multiline = true diff --git a/generate-crosschain.js b/generate-crosschain.js deleted file mode 100644 index 9031c7c5..00000000 --- a/generate-crosschain.js +++ /dev/null @@ -1,40 +0,0 @@ -const program = require("commander"); -const fs = require("fs"); -const nunjucks = require("nunjucks"); -const formatChainID = require("./utils"); - - -program.version("0.0.1"); -program.option( - "-t, --template