From af4c1475814e90e3ee3cb70bdf07e54108fa2e79 Mon Sep 17 00:00:00 2001 From: Roshan <48975233+Pythonberg1997@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:44:28 +0800 Subject: [PATCH] chore: lint contract and fix workflow (#537) --- .github/workflows/unit-test.yml | 15 +- contracts/BSCValidatorSet.sol | 2501 ++++++++++--------- contracts/CrossChain.sol | 1013 ++++---- contracts/GovHub.sol | 146 +- contracts/MerkleProof.sol | 113 +- contracts/RelayerHub.sol | 21 +- contracts/RelayerIncentivize.sol | 412 +-- contracts/SlashIndicator.sol | 770 +++--- contracts/Staking.sol | 1314 +++++----- contracts/System.sol | 231 +- contracts/SystemReward.sol | 129 +- contracts/TendermintLightClient.sol | 480 ++-- contracts/TokenHub.sol | 1602 ++++++------ contracts/TokenManager.sol | 1284 +++++----- contracts/interface/IApplication.sol | 7 +- contracts/interface/IBEP20.sol | 2 +- contracts/interface/IBSCValidatorSet.sol | 12 +- contracts/interface/IBSCValidatorSetV2.sol | 10 +- contracts/interface/ILightClient.sol | 10 +- contracts/interface/IParamSubscriber.sol | 2 +- contracts/interface/IRelayerHub.sol | 4 +- contracts/interface/IRelayerIncentivize.sol | 9 +- contracts/interface/ISlashIndicator.sol | 8 +- contracts/interface/IStaking.sol | 33 +- contracts/interface/ISystemReward.sol | 4 +- contracts/interface/ITokenHub.sol | 38 +- contracts/lib/BytesLib.sol | 352 ++- contracts/lib/BytesToTypes.sol | 342 +-- contracts/lib/CmnPkg.sol | 6 +- contracts/lib/Memory.sol | 41 +- contracts/lib/RLPDecode.sol | 128 +- contracts/lib/RLPEncode.sol | 107 +- contracts/lib/SizeOf.sol | 99 +- contracts/lib/TypesToBytes.sol | 39 +- contracts/mock/MockTokenHub.sol | 91 +- contracts/tool/BSCValidatorSetTool.sol | 181 +- scripts/generate.py | 12 +- test/utils/Deployer.sol | 3 +- 38 files changed, 5931 insertions(+), 5640 deletions(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 3b90170b..a706a78f 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -4,12 +4,10 @@ on: branches: - master - develop - - bc-fusion pull_request: branches: - master - develop - - bc-fusion permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. @@ -31,7 +29,7 @@ jobs: ${{ runner.os }}-yarn- - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@v1.2.0 - name: Install Project Dependencies run: | @@ -46,14 +44,9 @@ jobs: run: | forge build - - name: Start Local Chain - run: | - npm install pm2 -g - pm2 start --name local-chain "anvil -f https://bsc-dataseed1.ninicoin.io" - sleep 5 - env: - PORT: 8545 + - name: Set RPC Env + run: echo "RPC_BSC=https://bsc-dataseed.bnbchain.org" >> $GITHUB_ENV - name: Unit Test run: | - forge test --rpc-url http://127.0.0.1:8545 + forge test diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 2ce27a60..91cf59ba 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -18,1330 +18,1409 @@ import "./lib/RLPDecode.sol"; import "./lib/CmnPkg.sol"; interface ICrossChain { - function registeredContractChannelMap(address, uint8) external view returns (bool); + function registeredContractChannelMap(address, uint8) external view returns (bool); } 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 public constant 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 = 100; + + bytes public constant INIT_VALIDATORSET_BYTES = + hex"f905ec80f905e8f846942a7cdd959bfe8d9487b2a43b33565295a698f7e294b6a7edd747c0554875d3fc531d19ba1497992c5e941ff80f3f7f110ffd8920a3ac38fdef318fe94a3f86048c27395000f846946488aa4d1955ee33403f8ccb1d4de5fb97c7ade294220f003d8bdfaadf52aa1e55ae4cc485e6794875941a87e90e440a39c99aa9cb5cea0ad6a3f0b2407b86048c27395000f846949ef9f4360c606c7ab4db26b016007d3ad0ab86a0946103af86a874b705854033438383c82575f25bc29418e2db06cbff3e3c5f856410a1838649e760175786048c27395000f84694ee01c3b1283aa067c58eab4709f85e99d46de5fe94ee4b9bfb1871c64e2bcabb1dc382dc8b7c4218a29415904ab26ab0e99d70b51c220ccdcccabee6e29786048c27395000f84694685b1ded8013785d6623cc18d214320b6bb6475994a20ef4e5e4e7e36258dbf51f4d905114cb1b34bc9413e39085dc88704f4394d35209a02b1a9520320c86048c27395000f8469478f3adfc719c99674c072166708589033e2d9afe9448a30d5eaa7b64492a160f139e2da2800ec3834e94055838358c29edf4dcc1ba1985ad58aedbb6be2b86048c27395000f84694c2be4ec20253b8642161bc3f444f53679c1f3d479466f50c616d737e60d7ca6311ff0d9c434197898a94d1d678a2506eeaa365056fe565df8bc8659f28b086048c27395000f846942f7be8361c80a4c1e7e9aaf001d0877f1cfde218945f93992ac37f3e61db2ef8a587a436a161fd210b94ecbc4fb1a97861344dad0867ca3cba2b860411f086048c27395000f84694ce2fd7544e0b2cc94692d4a704debef7bcb613289444abc67b4b2fba283c582387f54c9cba7c34bafa948acc2ab395ded08bb75ce85bf0f95ad2abc51ad586048c27395000f84694b8f7166496996a7da21cf1f1b04d9b3e26a3d077946770572763289aac606e4f327c2f6cc1aa3b3e3b94882d745ed97d4422ca8da1c22ec49d880c4c097286048c27395000f846942d4c407bbe49438ed859fe965b140dcf1aab71a9943ad0939e120f33518fbba04631afe7a3ed6327b194b2bbb170ca4e499a2b0f3cc85ebfa6e8c4dfcbea86048c27395000f846946bbad7cf34b5fa511d8e963dbba288b1960e75d694853b0f6c324d1f4e76c8266942337ac1b0af1a229442498946a51ca5924552ead6fc2af08b94fcba648601d1a94a2000f846944430b3230294d12c6ab2aac5c2cd68e80b16b581947b107f4976a252a6939b771202c28e64e03f52d694795811a7f214084116949fc4f53cedbf189eeab28601d1a94a2000f84694ea0a6e3c511bbd10f4519ece37dc24887e11b55d946811ca77acfb221a49393c193f3a22db829fcc8e9464feb7c04830dd9ace164fc5c52b3f5a29e5018a8601d1a94a2000f846947ae2f5b9e386cd1b50a4550696d957cb4900f03a94e83bcc5077e6b873995c24bac871b5ad856047e19464e48d4057a90b233e026c1041e6012ada897fe88601d1a94a2000f8469482012708dafc9e1b880fd083b32182b869be8e09948e5adc73a2d233a1b496ed3115464dd6c7b887509428b383d324bc9a37f4e276190796ba5a8947f5ed8601d1a94a2000f8469422b81f8e175ffde54d797fe11eb03f9e3bf75f1d94a1c3ef7ca38d8ba80cce3bfc53ebd2903ed21658942767f7447f7b9b70313d4147b795414aecea54718601d1a94a2000f8469468bf0b8b6fb4e317a0f9d6f03eaf8ce6675bc60d94675cfe570b7902623f47e7f59c9664b5f5065dcf94d84f0d2e50bcf00f2fc476e1c57f5ca2d57f625b8601d1a94a2000f846948c4d90829ce8f72d0163c1d5cf348a862d5506309485c42a7b34309bee2ed6a235f86d16f059deec5894cc2cedc53f0fa6d376336efb67e43d167169f3b78601d1a94a2000f8469435e7a025f4da968de7e4d7e4004197917f4070f194b1182abaeeb3b4d8eba7e6a4162eac7ace23d57394c4fd0d870da52e73de2dd8ded19fe3d26f43a1138601d1a94a2000f84694d6caa02bbebaebb5d7e581e4b66559e635f805ff94c07335cf083c1c46a487f0325769d88e163b653694efaff03b42e41f953a925fc43720e45fb61a19938601d1a94a2000"; + + 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 BLOCK_FEES_RATIO_SCALE = 10000; + address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; + uint256 public constant INIT_BURN_RATIO = 1000; + uint256 public burnRatio; + bool public burnRatioInitialized; // deprecated - 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 = 100; - - bytes public constant INIT_VALIDATORSET_BYTES = hex"f905ec80f905e8f846942a7cdd959bfe8d9487b2a43b33565295a698f7e294b6a7edd747c0554875d3fc531d19ba1497992c5e941ff80f3f7f110ffd8920a3ac38fdef318fe94a3f86048c27395000f846946488aa4d1955ee33403f8ccb1d4de5fb97c7ade294220f003d8bdfaadf52aa1e55ae4cc485e6794875941a87e90e440a39c99aa9cb5cea0ad6a3f0b2407b86048c27395000f846949ef9f4360c606c7ab4db26b016007d3ad0ab86a0946103af86a874b705854033438383c82575f25bc29418e2db06cbff3e3c5f856410a1838649e760175786048c27395000f84694ee01c3b1283aa067c58eab4709f85e99d46de5fe94ee4b9bfb1871c64e2bcabb1dc382dc8b7c4218a29415904ab26ab0e99d70b51c220ccdcccabee6e29786048c27395000f84694685b1ded8013785d6623cc18d214320b6bb6475994a20ef4e5e4e7e36258dbf51f4d905114cb1b34bc9413e39085dc88704f4394d35209a02b1a9520320c86048c27395000f8469478f3adfc719c99674c072166708589033e2d9afe9448a30d5eaa7b64492a160f139e2da2800ec3834e94055838358c29edf4dcc1ba1985ad58aedbb6be2b86048c27395000f84694c2be4ec20253b8642161bc3f444f53679c1f3d479466f50c616d737e60d7ca6311ff0d9c434197898a94d1d678a2506eeaa365056fe565df8bc8659f28b086048c27395000f846942f7be8361c80a4c1e7e9aaf001d0877f1cfde218945f93992ac37f3e61db2ef8a587a436a161fd210b94ecbc4fb1a97861344dad0867ca3cba2b860411f086048c27395000f84694ce2fd7544e0b2cc94692d4a704debef7bcb613289444abc67b4b2fba283c582387f54c9cba7c34bafa948acc2ab395ded08bb75ce85bf0f95ad2abc51ad586048c27395000f84694b8f7166496996a7da21cf1f1b04d9b3e26a3d077946770572763289aac606e4f327c2f6cc1aa3b3e3b94882d745ed97d4422ca8da1c22ec49d880c4c097286048c27395000f846942d4c407bbe49438ed859fe965b140dcf1aab71a9943ad0939e120f33518fbba04631afe7a3ed6327b194b2bbb170ca4e499a2b0f3cc85ebfa6e8c4dfcbea86048c27395000f846946bbad7cf34b5fa511d8e963dbba288b1960e75d694853b0f6c324d1f4e76c8266942337ac1b0af1a229442498946a51ca5924552ead6fc2af08b94fcba648601d1a94a2000f846944430b3230294d12c6ab2aac5c2cd68e80b16b581947b107f4976a252a6939b771202c28e64e03f52d694795811a7f214084116949fc4f53cedbf189eeab28601d1a94a2000f84694ea0a6e3c511bbd10f4519ece37dc24887e11b55d946811ca77acfb221a49393c193f3a22db829fcc8e9464feb7c04830dd9ace164fc5c52b3f5a29e5018a8601d1a94a2000f846947ae2f5b9e386cd1b50a4550696d957cb4900f03a94e83bcc5077e6b873995c24bac871b5ad856047e19464e48d4057a90b233e026c1041e6012ada897fe88601d1a94a2000f8469482012708dafc9e1b880fd083b32182b869be8e09948e5adc73a2d233a1b496ed3115464dd6c7b887509428b383d324bc9a37f4e276190796ba5a8947f5ed8601d1a94a2000f8469422b81f8e175ffde54d797fe11eb03f9e3bf75f1d94a1c3ef7ca38d8ba80cce3bfc53ebd2903ed21658942767f7447f7b9b70313d4147b795414aecea54718601d1a94a2000f8469468bf0b8b6fb4e317a0f9d6f03eaf8ce6675bc60d94675cfe570b7902623f47e7f59c9664b5f5065dcf94d84f0d2e50bcf00f2fc476e1c57f5ca2d57f625b8601d1a94a2000f846948c4d90829ce8f72d0163c1d5cf348a862d5506309485c42a7b34309bee2ed6a235f86d16f059deec5894cc2cedc53f0fa6d376336efb67e43d167169f3b78601d1a94a2000f8469435e7a025f4da968de7e4d7e4004197917f4070f194b1182abaeeb3b4d8eba7e6a4162eac7ace23d57394c4fd0d870da52e73de2dd8ded19fe3d26f43a1138601d1a94a2000f84694d6caa02bbebaebb5d7e581e4b66559e635f805ff94c07335cf083c1c46a487f0325769d88e163b653694efaff03b42e41f953a925fc43720e45fb61a19938601d1a94a2000"; - - 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 BLOCK_FEES_RATIO_SCALE = 10000; - address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; - uint256 public constant INIT_BURN_RATIO = 1000; - uint256 public burnRatio; - bool public burnRatioInitialized; // deprecated - - // 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_SYSTEM_REWARD_RATIO = 625; // 625/10000 is 1/16 - uint256 public constant MAX_SYSTEM_REWARD_BALANCE = 100 ether; - - uint256 public systemRewardRatio; - uint256 public previousHeight; - uint256 public previousBalanceOfSystemReward; // deprecated - bytes[] public previousVoteAddrFullSet; - bytes[] public currentVoteAddrFullSet; - bool public isSystemRewardIncluded; - - // BEP-294 BC-fusion - Validator[] private _tmpMigratedValidatorSet; - bytes[] private _tmpMigratedVoteAddrs; - - 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; + 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 - 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 0, "deposit value is zero"); + _; + } + + modifier initValidatorExtraSet() { + if (validatorExtraSet.length == 0) { + ValidatorExtra memory validatorExtra; + // init validatorExtraSet + uint256 validatorsNum = currentValidatorSet.length; + for (uint256 i; i < validatorsNum; ++i) { + validatorExtraSet.push(validatorExtra); + } + } - _; - } - - modifier oncePerBlock() { - require(block.number > 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); - event tmpValidatorSetUpdated(uint256 validatorsNum); - - /*********************** init **************************/ - function init() external onlyNotInit{ - (IbcValidatorSetPackage memory validatorSetPkg, bool valid)= decodeValidatorSetSynPackage(INIT_VALIDATORSET_BYTES); - require(valid, "failed to parse init validatorSet"); - 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); + event tmpValidatorSetUpdated(uint256 validatorsNum); + + /*----------------- init -----------------*/ + function init() external onlyNotInit { + (IbcValidatorSetPackage memory validatorSetPkg, bool valid) = + decodeValidatorSetSynPackage(INIT_VALIDATORSET_BYTES); + require(valid, "failed to parse init validatorSet"); + for (uint256 i; i < validatorSetPkg.validatorSet.length; ++i) { + currentValidatorSet.push(validatorSetPkg.validatorSet[i]); + currentValidatorSetMap[validatorSetPkg.validatorSet[i].consensusAddress] = i + 1; + } + expireTimeSecondGap = EXPIRE_TIME_SECOND_GAP; + alreadyInit = true; } - expireTimeSecondGap = EXPIRE_TIME_SECOND_GAP; - alreadyInit = true; - } - receive() external payable {} + receive() external payable { } - /*********************** Cross Chain App Implement **************************/ - function handleSynPackage(uint8, bytes calldata msgBytes) onlyInit onlyCrossChainContract initValidatorExtraSet external override returns(bytes memory responsePayload) { - (IbcValidatorSetPackage memory validatorSetPackage, bool ok) = decodeValidatorSetSynPackage(msgBytes); - if (!ok) { - return CmnPkg.encodeCommonAckPackage(ERROR_FAIL_DECODE); - } - uint32 resCode; - if (validatorSetPackage.packageType == VALIDATORS_UPDATE_MESSAGE_TYPE) { - resCode = updateValidatorSet(validatorSetPackage.validatorSet, validatorSetPackage.voteAddrs); - } else if (validatorSetPackage.packageType == JAIL_MESSAGE_TYPE) { - if (validatorSetPackage.validatorSet.length != 1) { - emit failReasonWithStr("length of jail validators must be one"); - resCode = ERROR_LEN_OF_VAL_MISMATCH; - } else { - address validator = validatorSetPackage.validatorSet[0].consensusAddress; - uint256 index = currentValidatorSetMap[validator]; - if (index == 0 || currentValidatorSet[index-1].jailed) { - emit validatorEmptyJailed(validator); + /*----------------- Cross Chain App Implement -----------------*/ + function handleSynPackage( + uint8, + bytes calldata msgBytes + ) external override onlyInit onlyCrossChainContract initValidatorExtraSet returns (bytes memory responsePayload) { + (IbcValidatorSetPackage memory validatorSetPackage, bool ok) = decodeValidatorSetSynPackage(msgBytes); + if (!ok) { + return CmnPkg.encodeCommonAckPackage(ERROR_FAIL_DECODE); + } + uint32 resCode; + if (validatorSetPackage.packageType == VALIDATORS_UPDATE_MESSAGE_TYPE) { + resCode = updateValidatorSet(validatorSetPackage.validatorSet, validatorSetPackage.voteAddrs); + } else if (validatorSetPackage.packageType == JAIL_MESSAGE_TYPE) { + if (validatorSetPackage.validatorSet.length != 1) { + emit failReasonWithStr("length of jail validators must be one"); + resCode = ERROR_LEN_OF_VAL_MISMATCH; + } else { + address validator = validatorSetPackage.validatorSet[0].consensusAddress; + uint256 index = currentValidatorSetMap[validator]; + if (index == 0 || currentValidatorSet[index - 1].jailed) { + emit validatorEmptyJailed(validator); + } else { + // felony will failed if the validator is the only one in the validator set + bool success = _felony(validator, index - 1); + if (!success) { + emit validatorEmptyJailed(validator); + } + } + resCode = CODE_OK; + } } else { - // felony will failed if the validator is the only one in the validator set - bool success = _felony(validator, index-1); - if (!success) { - emit validatorEmptyJailed(validator); - } - } - resCode = CODE_OK; - } - } else { - resCode = ERROR_UNKNOWN_PACKAGE_TYPE; - } - if (resCode == CODE_OK) { - return new bytes(0); - } else { - return CmnPkg.encodeCommonAckPackage(resCode); - } - } - - function handleAckPackage(uint8 channelId, bytes calldata msgBytes) external onlyCrossChainContract override { - // should not happen - emit unexpectedPackage(channelId, msgBytes); - } - - function handleFailAckPackage(uint8 channelId, bytes calldata msgBytes) external onlyCrossChainContract override { - // should not happen - emit unexpectedPackage(channelId, msgBytes); - } - - /*********************** External Functions **************************/ - /** - * @dev Update validator set method after fusion fork. - */ - function updateValidatorSetV2( - address[] memory _consensusAddrs, - uint64[] memory _votingPowers, - bytes[] memory _voteAddrs - ) public onlyCoinbase onlyZeroGasPrice { - uint256 _length = _consensusAddrs.length; - Validator[] memory _validatorSet = new Validator[](_length); - for (uint256 i; i < _length; ++i) { - _validatorSet[i] = Validator({ - consensusAddress: _consensusAddrs[i], - feeAddress: payable(address(0)), - BBCFeeAddress: address(0), - votingPower: _votingPowers[i], - jailed: false, - incoming: 0 - }); + resCode = ERROR_UNKNOWN_PACKAGE_TYPE; + } + if (resCode == CODE_OK) { + return new bytes(0); + } else { + return CmnPkg.encodeCommonAckPackage(resCode); + } } - // if staking channel is not closed, store the migrated validator set and return - if (ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).registeredContractChannelMap(VALIDATOR_CONTRACT_ADDR, STAKING_CHANNELID)) { - uint256 newLength = _validatorSet.length; - uint256 oldLength = _tmpMigratedValidatorSet.length; - if (oldLength > newLength) { - for (uint256 i = newLength; i < oldLength; ++i) { - _tmpMigratedValidatorSet.pop(); - _tmpMigratedVoteAddrs.pop(); - } - } - - for (uint256 i; i < newLength; ++i) { - if (i >= oldLength) { - _tmpMigratedValidatorSet.push(_validatorSet[i]); - _tmpMigratedVoteAddrs.push(_voteAddrs[i]); - } else { - _tmpMigratedValidatorSet[i] = _validatorSet[i]; - _tmpMigratedVoteAddrs[i] = _voteAddrs[i]; + function handleAckPackage(uint8 channelId, bytes calldata msgBytes) external override onlyCrossChainContract { + // should not happen + emit unexpectedPackage(channelId, msgBytes); + } + + function handleFailAckPackage(uint8 channelId, bytes calldata msgBytes) external override onlyCrossChainContract { + // should not happen + emit unexpectedPackage(channelId, msgBytes); + } + + /*----------------- External Functions -----------------*/ + /** + * @dev Update validator set method after fusion fork. + */ + function updateValidatorSetV2( + address[] memory _consensusAddrs, + uint64[] memory _votingPowers, + bytes[] memory _voteAddrs + ) public onlyCoinbase onlyZeroGasPrice { + uint256 _length = _consensusAddrs.length; + Validator[] memory _validatorSet = new Validator[](_length); + for (uint256 i; i < _length; ++i) { + _validatorSet[i] = Validator({ + consensusAddress: _consensusAddrs[i], + feeAddress: payable(address(0)), + BBCFeeAddress: address(0), + votingPower: _votingPowers[i], + jailed: false, + incoming: 0 + }); } - } - emit tmpValidatorSetUpdated(newLength); - return; - } + // if staking channel is not closed, store the migrated validator set and return + if ( + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).registeredContractChannelMap( + VALIDATOR_CONTRACT_ADDR, STAKING_CHANNELID + ) + ) { + uint256 newLength = _validatorSet.length; + uint256 oldLength = _tmpMigratedValidatorSet.length; + if (oldLength > newLength) { + for (uint256 i = newLength; i < oldLength; ++i) { + _tmpMigratedValidatorSet.pop(); + _tmpMigratedVoteAddrs.pop(); + } + } - // step 0: force all maintaining validators to exit `Temporary Maintenance` - // - 1. validators exit maintenance - // - 2. clear all maintainInfo - // - 3. get unjailed validators from validatorSet - (Validator[] memory validatorSetTemp, bytes[] memory voteAddrsTemp) = _forceMaintainingValidatorsExit(_validatorSet, _voteAddrs); - - // step 1: distribute incoming - for (uint i; i < currentValidatorSet.length; ++i) { - uint256 incoming = currentValidatorSet[i].incoming; - if (incoming != 0) { - currentValidatorSet[i].incoming = 0; - IStakeHub(STAKE_HUB_ADDR).distributeReward{value : incoming}(currentValidatorSet[i].consensusAddress); - } - } + for (uint256 i; i < newLength; ++i) { + if (i >= oldLength) { + _tmpMigratedValidatorSet.push(_validatorSet[i]); + _tmpMigratedVoteAddrs.push(_voteAddrs[i]); + } else { + _tmpMigratedValidatorSet[i] = _validatorSet[i]; + _tmpMigratedVoteAddrs[i] = _voteAddrs[i]; + } + } - // step 2: do dusk transfer - if (address(this).balance>0) { - emit systemTransfer(address(this).balance); - address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); - } + emit tmpValidatorSetUpdated(newLength); + return; + } - // step 3: do update validator set state - totalInComing = 0; - numOfJailed = 0; - if (validatorSetTemp.length != 0) { - doUpdateState(validatorSetTemp, voteAddrsTemp); - } + // step 0: force all maintaining validators to exit `Temporary Maintenance` + // - 1. validators exit maintenance + // - 2. clear all maintainInfo + // - 3. get unjailed validators from validatorSet + (Validator[] memory validatorSetTemp, bytes[] memory voteAddrsTemp) = + _forceMaintainingValidatorsExit(_validatorSet, _voteAddrs); + + // step 1: distribute incoming + for (uint256 i; i < currentValidatorSet.length; ++i) { + uint256 incoming = currentValidatorSet[i].incoming; + if (incoming != 0) { + currentValidatorSet[i].incoming = 0; + IStakeHub(STAKE_HUB_ADDR).distributeReward{ value: incoming }(currentValidatorSet[i].consensusAddress); + } + } - // step 3: clean slash contract - ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); - emit validatorSetUpdated(); - } - - /** - * @dev Collect all fee of transactions from the current block and deposit it to the contract - * - * @param valAddr The validator address who produced the current block - */ - function deposit(address valAddr) external payable onlyCoinbase onlyInit noEmptyDeposit onlyZeroGasPrice { - uint256 value = msg.value; - uint256 index = currentValidatorSetMap[valAddr]; - - if (isSystemRewardIncluded == false){ - systemRewardRatio = INIT_SYSTEM_REWARD_RATIO; - burnRatio = INIT_BURN_RATIO; - isSystemRewardIncluded = true; - } + // step 2: do dusk transfer + if (address(this).balance > 0) { + emit systemTransfer(address(this).balance); + address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); + } - if (value > 0 && systemRewardRatio > 0) { - uint256 toSystemReward = msg.value.mul(systemRewardRatio).div(BLOCK_FEES_RATIO_SCALE); - if (toSystemReward > 0) { - address(uint160(SYSTEM_REWARD_ADDR)).transfer(toSystemReward); - emit systemTransfer(toSystemReward); + // step 3: do update validator set state + totalInComing = 0; + numOfJailed = 0; + if (validatorSetTemp.length != 0) { + doUpdateState(validatorSetTemp, voteAddrsTemp); + } - value = value.sub(toSystemReward); - } + // step 3: clean slash contract + ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); + emit validatorSetUpdated(); } - if (value > 0 && burnRatio > 0) { - uint256 toBurn = msg.value.mul(burnRatio).div(BLOCK_FEES_RATIO_SCALE); - if (toBurn > 0) { - address(uint160(BURN_ADDRESS)).transfer(toBurn); - emit feeBurned(toBurn); + /** + * @dev Collect all fee of transactions from the current block and deposit it to the contract + * + * @param valAddr The validator address who produced the current block + */ + function deposit(address valAddr) external payable onlyCoinbase onlyInit noEmptyDeposit onlyZeroGasPrice { + uint256 value = msg.value; + uint256 index = currentValidatorSetMap[valAddr]; - value = value.sub(toBurn); - } - } + if (isSystemRewardIncluded == false) { + systemRewardRatio = INIT_SYSTEM_REWARD_RATIO; + burnRatio = INIT_BURN_RATIO; + isSystemRewardIncluded = true; + } - 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); - } - } + if (value > 0 && systemRewardRatio > 0) { + uint256 toSystemReward = msg.value.mul(systemRewardRatio).div(BLOCK_FEES_RATIO_SCALE); + if (toSystemReward > 0) { + address(uint160(SYSTEM_REWARD_ADDR)).transfer(toSystemReward); + emit systemTransfer(toSystemReward); - 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 0 && burnRatio > 0) { + uint256 toBurn = msg.value.mul(burnRatio).div(BLOCK_FEES_RATIO_SCALE); + if (toBurn > 0) { + address(uint160(BURN_ADDRESS)).transfer(toBurn); + emit feeBurned(toBurn); - { - //step 1: do calculate distribution, do not make it as an internal function for saving gas. - uint crossSize; - uint directSize; - uint validatorsNum = currentValidatorSet.length; - uint8[] memory isMigrated = new uint8[](validatorsNum); - 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 < validatorsNum; ++i) { - if (isMigrated[i] == 1) { - if (currentValidatorSet[i].incoming != 0) { - directAddrs[directSize] = payable(currentValidatorSet[i].consensusAddress); - directAmounts[directSize] = currentValidatorSet[i].incoming; - isMigrated[directSize] = 1; // directSize must be less than i. so we can use directSize as index - ++directSize; - } - } else if (currentValidatorSet[i].incoming >= 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; - isMigrated[directSize] = 0; - ++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; i 0) { - for (uint i; i < directAddrs.length; ++i) { - if (isMigrated[i] == 1) { - IStakeHub(STAKE_HUB_ADDR).distributeReward{value : directAmounts[i]}(directAddrs[i]); - } else { - bool success = directAddrs[i].send(directAmounts[i]); - if (success) { - emit directTransfer(directAddrs[i], directAmounts[i]); + value = value.sub(toBurn); + } + } + + if (index > 0) { + Validator storage validator = currentValidatorSet[index - 1]; + if (validator.jailed) { + emit deprecatedDeposit(valAddr, value); } else { - emit directTransferFail(directAddrs[i], directAmounts[i]); + totalInComing = totalInComing.add(value); + validator.incoming = validator.incoming.add(value); + emit validatorDeposit(valAddr, value); } - } + } else { + // get incoming from deprecated validator; + emit deprecatedDeposit(valAddr, value); } - } } - for (uint i; i < currentValidatorSet.length; ++i) { - if (currentValidatorSet[i].incoming != 0) { - currentValidatorSet[i].incoming = 0; - } - } + 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) { + // when a slash happens, theres will no rewards in some epochs, + // it's tolerated because slash happens rarely + totalValue = balanceOfSystemReward.sub(MAX_SYSTEM_REWARD_BALANCE); + } else { + return; + } - // step 4: do dusk transfer - if (address(this).balance>0) { - emit systemTransfer(address(this).balance); - address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); - } + totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(address(this)), totalValue); + if (totalValue == 0) { + return; + } - // step 5: do update validator set state - totalInComing = 0; - numOfJailed = 0; - if (validatorSetTemp.length > 0) { - doUpdateState(validatorSetTemp, voteAddrsTemp); - } + uint256 totalWeight; + for (uint256 i; i < weights.length; ++i) { + totalWeight += weights[i]; + } + if (totalWeight == 0) { + return; + } - // 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 ? numOfCabinets : INIT_NUM_OF_CABINETS; - - address[] memory validators = getValidators(); - bytes[] memory voteAddrs = getVoteAddresses(validators); - if (validators.length <= _numOfCabinets) { - return (validators, voteAddrs); + uint256 value; + address valAddr; + uint256 index; + + for (uint256 i; i < valAddrs.length; ++i) { + value = (totalValue * weights[i]) / totalWeight; + valAddr = valAddrs[i]; + index = currentValidatorSetMap[valAddr]; + if (index > 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); + } + } } - if ((validators.length - _numOfCabinets) < _maxNumOfWorkingCandidates){ - _maxNumOfWorkingCandidates = validators.length - _numOfCabinets; - } - if (_maxNumOfWorkingCandidates > 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); - } - - /** - * @notice Return the consensus address of the validators in `currentValidatorSet` that are not jailed and not maintaining - */ - function getValidators() public view returns(address[] memory) { - uint n = currentValidatorSet.length; - uint valid = 0; - for (uint i; i 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; + + address[] memory validators = getValidators(); + bytes[] memory voteAddrs = getVoteAddresses(validators); + if (validators.length <= _numOfCabinets) { + return (validators, voteAddrs); + } + + if ((validators.length - _numOfCabinets) < _maxNumOfWorkingCandidates) { + _maxNumOfWorkingCandidates = validators.length - _numOfCabinets; + } + if (_maxNumOfWorkingCandidates > 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 (uint256 i; i < _numOfCabinets; ++i) { + miningValidators[i] = validators[i]; + miningVoteAddrs[i] = voteAddrs[i]; + } + return (miningValidators, miningVoteAddrs); + } + + /** + * @notice Return the consensus address of the validators in `currentValidatorSet` that are not jailed and not maintaining + */ + function getValidators() public view returns (address[] memory) { + uint256 n = currentValidatorSet.length; + uint256 valid = 0; + for (uint256 i; i < n; ++i) { + if (isWorkingValidator(i)) { + ++valid; + } + } + address[] memory consensusAddrs = new address[](valid); + valid = 0; + for (uint256 i; i < n; ++i) { + if (isWorkingValidator(i)) { + consensusAddrs[valid] = currentValidatorSet[i].consensusAddress; + ++valid; + } + } + return consensusAddrs; } - address[] memory consensusAddrs = new address[](valid); - valid = 0; - for (uint i; i= currentValidatorSet.length) { - return false; + + /** + * @notice Return whether the validator is a working validator(not jailed or maintaining) by index + * + * @param index The index of the validator in `currentValidatorSet`(from 0 to `currentValidatorSet.length-1`) + */ + function isWorkingValidator(uint256 index) public view returns (bool) { + if (index >= 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; } - // validatorExtraSet[index] should not be used before it has been init. - if (index >= validatorExtraSet.length) { - return !currentValidatorSet[index].jailed; + /** + * @notice Return whether the validator is a working validator(not jailed or maintaining) by consensus address + * Will return false if the validator is not in `currentValidatorSet` + */ + 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); } - return !currentValidatorSet[index].jailed && !validatorExtraSet[index].isMaintaining; - } + /** + * @notice Return the index of the validator in `currentValidatorSet`(from 0 to `currentValidatorSet.length-1`) + */ + function getCurrentValidatorIndex(address validator) public view returns (uint256) { + uint256 index = currentValidatorSetMap[validator]; + require(index > 0, "only current validators"); - /** - * @notice Return the current incoming of the validator - */ - function getIncoming(address validator)external view returns(uint256) { - uint256 index = currentValidatorSetMap[validator]; - if (index<=0) { - return 0; + // the actual index + return index - 1; } - return currentValidatorSet[index-1].incoming; - } - - /** - * @notice Return whether the validator is a working validator(not jailed or maintaining) by consensus address - * Will return false if the validator is not in `currentValidatorSet` - */ - function isCurrentValidator(address validator) external view override returns (bool) { - uint256 index = currentValidatorSetMap[validator]; - if (index <= 0) { - return false; + + /** + * @notice Return the number of mining validators. + * The function name is misleading, it should be `getMiningValidatorCount`. But it's kept for compatibility. + */ + 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; + } } - // the actual index - index = index - 1; - return isWorkingValidator(index); - } - - 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) { - // when a slash happens, theres will no rewards in some epochs, - // it's tolerated because slash happens rarely - totalValue = balanceOfSystemReward.sub(MAX_SYSTEM_REWARD_BALANCE); - } else { - return; + /*----------------- For slash -----------------*/ + function misdemeanor(address validator) external override onlySlash initValidatorExtraSet { + uint256 validatorIndex = _misdemeanor(validator); + if (canEnterMaintenance(validatorIndex)) { + _enterMaintenance(validator, validatorIndex); + } } - totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(address(this)), totalValue); - if (totalValue == 0) { - return; + function felony(address validator) external override initValidatorExtraSet { + require(msg.sender == SLASH_CONTRACT_ADDR || msg.sender == STAKE_HUB_ADDR, "only slash or stakeHub contract"); + + 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; + } } - uint256 totalWeight; - for (uint256 i; i= 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; } - uint256 value; - address valAddr; - uint256 index; - - for (uint256 i; i 0) { - Validator storage validator = currentValidatorSet[index - 1]; - if (validator.jailed) { - emit deprecatedFinalityRewardDeposit(valAddr, value); + /** + * @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 miningValidatorCount = getWorkingValidatorCount(); + _exitMaintenance(msg.sender, index, miningValidatorCount); + } + + /*----------------- Param update -----------------*/ + function updateParam(string calldata key, bytes calldata value) external override 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.add(systemRewardRatio) <= BLOCK_FEES_RATIO_SCALE, + "the burnRatio plus systemRewardRatio must be no greater than 10000" + ); + burnRatio = newBurnRatio; + } 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, "systemRewardRatio")) { + require(value.length == 32, "length of systemRewardRatio mismatch"); + uint256 newSystemRewardRatio = BytesToTypes.bytesToUint256(32, value); + require( + newSystemRewardRatio.add(burnRatio) <= BLOCK_FEES_RATIO_SCALE, + "the systemRewardRatio plus burnRatio must be no greater than 10000" + ); + systemRewardRatio = newSystemRewardRatio; } 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); - } + require(false, "unknown param"); + } + emit paramChange(key, value); } - } + /*----------------- Internal Functions -----------------*/ + 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 (uint256 i; i < validatorSet.length; ++i) { + for (uint256 j; j < i; ++j) { + if (validatorSet[i].consensusAddress == validatorSet[j].consensusAddress) { + emit failReasonWithStr("duplicate consensus address of validatorSet"); + return ERROR_FAIL_CHECK_VALIDATORS; + } + } + } + } - 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; - } - } + // step 0: force all maintaining validators to exit `Temporary Maintenance` + // - 1. validators exit maintenance + // - 2. clear all maintainInfo + // - 3. get unjailed validators from validatorSet + Validator[] memory validatorSetTemp; + bytes[] memory voteAddrsTemp; + { + // get migrated validators + Validator[] memory bscValidatorSet = _tmpMigratedValidatorSet; + bytes[] memory bscVoteAddrs = _tmpMigratedVoteAddrs; + for (uint256 i; i < bscValidatorSet.length; ++i) { + bscValidatorSet[i].votingPower = bscValidatorSet[i].votingPower * 3; // amplify the voting power for BSC validators + } + (Validator[] memory mergedValidators, bytes[] memory mergedVoteAddrs) = + _mergeValidatorSet(validatorSet, voteAddrs, bscValidatorSet, bscVoteAddrs); - /*********************** For slash **************************/ - function misdemeanor(address validator) external onlySlash initValidatorExtraSet override { - uint256 validatorIndex = _misdemeanor(validator); - if (canEnterMaintenance(validatorIndex)) { - _enterMaintenance(validator, validatorIndex); - } - } + (validatorSetTemp, voteAddrsTemp) = _forceMaintainingValidatorsExit(mergedValidators, mergedVoteAddrs); + } - function felony(address validator) external initValidatorExtraSet override { - require(msg.sender == SLASH_CONTRACT_ADDR || msg.sender == STAKE_HUB_ADDR, "only slash or stakeHub contract"); + { + //step 1: do calculate distribution, do not make it as an internal function for saving gas. + uint256 crossSize; + uint256 directSize; + uint256 validatorsNum = currentValidatorSet.length; + uint8[] memory isMigrated = new uint8[](validatorsNum); + for (uint256 i; i < validatorsNum; ++i) { + if ( + IStakeHub(STAKE_HUB_ADDR).consensusToOperator(currentValidatorSet[i].consensusAddress) != address(0) + ) { + isMigrated[i] = 1; + if (currentValidatorSet[i].incoming != 0) { + ++directSize; + } + } else if (currentValidatorSet[i].incoming >= DUSTY_INCOMING) { + ++crossSize; + } else if (currentValidatorSet[i].incoming != 0) { + ++directSize; + } + } - uint256 index = currentValidatorSetMap[validator]; - if (index <= 0) { - return; - } - // the actual index - index = index - 1; + //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 (uint256 i; i < validatorsNum; ++i) { + if (isMigrated[i] == 1) { + if (currentValidatorSet[i].incoming != 0) { + directAddrs[directSize] = payable(currentValidatorSet[i].consensusAddress); + directAmounts[directSize] = currentValidatorSet[i].incoming; + isMigrated[directSize] = 1; // directSize must be less than i. so we can use directSize as index + ++directSize; + } + } else if (currentValidatorSet[i].incoming >= 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; + isMigrated[directSize] = 0; + ++directSize; + } + } - bool isMaintaining = validatorExtraSet[index].isMaintaining; - if (_felony(validator, index) && isMaintaining) { - --numOfMaintaining; - } - } - - function removeTmpMigratedValidator(address validator) external onlyStakeHub { - for (uint256 i; i < _tmpMigratedValidatorSet.length; ++i) { - if (_tmpMigratedValidatorSet[i].consensusAddress == validator) { - _tmpMigratedValidatorSet[i].jailed = true; - break; - } - } - } - - /*********************** For Temporary Maintenance **************************/ - /** - * @notice Return the index of the validator in `currentValidatorSet`(from 0 to `currentValidatorSet.length-1`) - */ - function getCurrentValidatorIndex(address validator) public view returns (uint256) { - uint256 index = currentValidatorSetMap[validator]; - require(index > 0, "only current validators"); - - // the actual index - return index - 1; - } - - /** - * @notice Return whether the validator at index could enter maintenance - */ - function canEnterMaintenance(uint256 index) public view returns (bool) { - if (index >= currentValidatorSet.length) { - return false; - } + //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 ( - 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; - } + if (failCross) { + for (uint256 i; i < crossIndexes.length; ++i) { + uint256 idx = crossIndexes[i]; + bool success = currentValidatorSet[idx].feeAddress.send(currentValidatorSet[idx].incoming); + if (success) { + emit directTransfer(currentValidatorSet[idx].feeAddress, currentValidatorSet[idx].incoming); + } else { + emit directTransferFail(currentValidatorSet[idx].feeAddress, currentValidatorSet[idx].incoming); + } + } + } - return true; - } + // step 3: direct transfer + if (directAddrs.length > 0) { + for (uint256 i; i < directAddrs.length; ++i) { + if (isMigrated[i] == 1) { + IStakeHub(STAKE_HUB_ADDR).distributeReward{ value: directAmounts[i] }(directAddrs[i]); + } else { + bool success = directAddrs[i].send(directAmounts[i]); + if (success) { + emit directTransfer(directAddrs[i], directAmounts[i]); + } else { + emit directTransferFail(directAddrs[i], directAmounts[i]); + } + } + } + } + } - /** - * @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; - } + for (uint256 i; i < currentValidatorSet.length; ++i) { + if (currentValidatorSet[i].incoming != 0) { + currentValidatorSet[i].incoming = 0; + } + } - 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.add(systemRewardRatio) <= BLOCK_FEES_RATIO_SCALE, "the burnRatio plus systemRewardRatio must be no greater than 10000"); - burnRatio = newBurnRatio; - } 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, "systemRewardRatio")) { - require(value.length == 32, "length of systemRewardRatio mismatch"); - uint256 newSystemRewardRatio = BytesToTypes.bytesToUint256(32, value); - require(newSystemRewardRatio.add(burnRatio) <= BLOCK_FEES_RATIO_SCALE, "the systemRewardRatio plus burnRatio must be no greater than 10000"); - systemRewardRatio = newSystemRewardRatio; - } 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; i 0) { + emit systemTransfer(address(this).balance); + address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); + } - // if old validator set is larger than new validator set, pop the extra validators - if (n>m) { - for (uint i=m; i 0) { + doUpdateState(validatorSetTemp, voteAddrsTemp); + } - uint k = n < m ? n:m; - for (uint i; 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; - } - } + // if old validator set is larger than new validator set, pop the extra validators + if (n > m) { + for (uint256 i = m; i < n; ++i) { + currentValidatorSet.pop(); + validatorExtraSet.pop(); + } + } - // 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; i n) { + ValidatorExtra memory _validatorExtra; + for (uint256 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 (uint256 i; i < n; ++i) { + validatorExtraSet[i].isMaintaining = false; + validatorExtraSet[i].enterMaintenanceHeight = 0; + } } - } - - /** - * @dev Check if two validators are the same - * - * Vote address is not considered - */ - function isSameValidator(Validator memory v1, Validator memory v2) private pure returns(bool) { - return v1.consensusAddress == v2.consensusAddress && v1.feeAddress == v2.feeAddress && v1.BBCFeeAddress == v2.BBCFeeAddress; - } - - function getVoteAddresses(address[] memory validators) internal view returns(bytes[] memory) { - uint n = currentValidatorSet.length; - uint length = validators.length; - bytes[] memory voteAddrs = new bytes[](length); - - // check if validatorExtraSet has been initialized - if (validatorExtraSet.length != n) { - return voteAddrs; + + /** + * @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, + uint256 startIdx, + uint256 offset, + uint256 limit, + uint256 modNumber + ) internal pure { + for (uint256 i; i < limit; ++i) { + uint256 random = uint256(keccak256(abi.encodePacked(epochNumber, startIdx + i))) % modNumber; + if ((startIdx + i) != (offset + random)) { + address tmpAddr = validators[startIdx + i]; + bytes memory tmpBLS = voteAddrs[startIdx + i]; + validators[startIdx + i] = validators[offset + random]; + validators[offset + random] = tmpAddr; + voteAddrs[startIdx + i] = voteAddrs[offset + random]; + voteAddrs[offset + random] = tmpBLS; + } + } } - for (uint i; im) { - for (uint i=m; in) { - for (uint i=n; i < m; ++i) { - previousVoteAddrFullSet.push(currentVoteAddrFullSet[i]); - } - } - } + function setPreviousVoteAddrFullSet() private { + uint256 n = previousVoteAddrFullSet.length; + uint256 m = currentVoteAddrFullSet.length; - function setCurrentVoteAddrFullSet() private { - uint n = currentVoteAddrFullSet.length; - uint m = validatorExtraSet.length; + if (n > m) { + for (uint256 i = m; i < n; ++i) { + previousVoteAddrFullSet.pop(); + } + } - if (n>m) { - for (uint i=m; i n) { + for (uint256 i = n; i < m; ++i) { + previousVoteAddrFullSet.push(currentVoteAddrFullSet[i]); + } + } } - uint k = n < m ? n:m; - for (uint i; i m) { + for (uint256 i = m; i < n; ++i) { + currentVoteAddrFullSet.pop(); + } + } + + uint256 k = n < m ? n : m; + for (uint256 i; i < k; ++i) { + if (!BytesLib.equal(currentVoteAddrFullSet[i], validatorExtraSet[i].voteAddress)) { + currentVoteAddrFullSet[i] = validatorExtraSet[i].voteAddress; + } + } + + if (m > n) { + for (uint256 i = n; i < m; ++i) { + currentVoteAddrFullSet.push(validatorExtraSet[i].voteAddress); + } + } } - if (m>n) { - for (uint i=n; i < m; ++i) { - currentVoteAddrFullSet.push(validatorExtraSet[i].voteAddress); - } + function isMonitoredForMaliciousVote(bytes calldata voteAddr) external view override returns (bool) { + uint256 m = currentVoteAddrFullSet.length; + for (uint256 i; i < m; ++i) { + if (BytesLib.equal(voteAddr, currentVoteAddrFullSet[i])) { + return true; + } + } + + uint256 n = previousVoteAddrFullSet.length; + for (uint256 i; i < n; ++i) { + if (BytesLib.equal(voteAddr, previousVoteAddrFullSet[i])) { + return true; + } + } + + return false; } - } - 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; + } - function _misdemeanor(address validator) private returns (uint256) { - uint256 index = currentValidatorSetMap[validator]; - if (index <= 0) { - return ~uint256(0); - } - // the actually index - index = index - 1; - - uint256 income = currentValidatorSet[index].incoming; - currentValidatorSet[index].incoming = 0; - uint256 rest = currentValidatorSet.length - 1; - emit validatorMisdemeanor(validator, income); - if (rest == 0) { - // should not happen, but still protect - return index; - } + // only maintaining validators + validator = currentValidatorSet[i].consensusAddress; - // averageDistribute*rest may less than income, but it is ok, the dust income will go to system reward eventually. - uint256 averageDistribute = income / rest; - if (averageDistribute != 0) { - 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) { - continue; - } - - // get the latest consensus address - address latestConsensusAddress; - address operatorAddress = IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator); - if (operatorAddress != address(0)) { - latestConsensusAddress = IStakeHub(STAKE_HUB_ADDR).getValidatorConsensusAddress(operatorAddress); - } - - // record the jailed validator in validatorSet - for (uint j; j<_validatorSet.length; ++j) { - if (_validatorSet[j].consensusAddress == validator || _validatorSet[j].consensusAddress == latestConsensusAddress) { - _validatorSet[j].jailed = true; - break; - } - } - } + // record the jailed validator in validatorSet + for (uint256 j; j < _validatorSet.length; ++j) { + if ( + _validatorSet[j].consensusAddress == validator + || _validatorSet[j].consensusAddress == latestConsensusAddress + ) { + _validatorSet[j].jailed = true; + break; + } + } + } + + // count the number of felony validators + for (uint256 k; k < _validatorSet.length; ++k) { + if (_validatorSet[k].jailed || _validatorSet[k].consensusAddress == address(0)) { + ++numOfFelony; + } + } + + // 2. get unjailed validators from validatorSet + if (numOfFelony >= _validatorSet.length) { + // make sure there is at least one validator + unjailedValidatorSet = new Validator[](1); + unjailedVoteAddrs = new bytes[](1); + unjailedValidatorSet[0] = _validatorSet[0]; + unjailedVoteAddrs[0] = _voteAddrs[0]; + unjailedValidatorSet[0].jailed = false; + } else { + unjailedValidatorSet = new Validator[](_validatorSet.length - numOfFelony); + unjailedVoteAddrs = new bytes[](_validatorSet.length - numOfFelony); + i = 0; + for (uint256 index; index < _validatorSet.length; ++index) { + if (!_validatorSet[index].jailed && _validatorSet[index].consensusAddress != address(0)) { + unjailedValidatorSet[i] = _validatorSet[index]; + unjailedVoteAddrs[i] = _voteAddrs[index]; + ++i; + } + } + } - // count the number of felony validators - for (uint k; k<_validatorSet.length; ++k) { - if (_validatorSet[k].jailed || _validatorSet[k].consensusAddress == address(0)) { - ++numOfFelony; - } + return (unjailedValidatorSet, unjailedVoteAddrs); } - // 2. get unjailed validators from validatorSet - if (numOfFelony >= _validatorSet.length) { - // make sure there is at least one validator - unjailedValidatorSet = new Validator[](1); - unjailedVoteAddrs = new bytes[](1); - unjailedValidatorSet[0] = _validatorSet[0]; - unjailedVoteAddrs[0] = _voteAddrs[0]; - unjailedValidatorSet[0].jailed = false; - } else { - 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 && _validatorSet[index].consensusAddress != address(0)) { - unjailedValidatorSet[i] = _validatorSet[index]; - unjailedVoteAddrs[i] = _voteAddrs[index]; - ++i; - } - } + function _enterMaintenance(address validator, uint256 index) private { + ++numOfMaintaining; + validatorExtraSet[index].isMaintaining = true; + validatorExtraSet[index].enterMaintenanceHeight = block.number; + emit validatorEnterMaintenance(validator); } - return (unjailedValidatorSet, unjailedVoteAddrs); - } + function _exitMaintenance( + address validator, + uint256 index, + uint256 miningValidatorCount + ) private returns (bool isFelony) { + if (maintainSlashScale == 0 || miningValidatorCount == 0 || numOfMaintaining == 0) { + // should not happen, still protect + return false; + } - function _enterMaintenance(address validator, uint256 index) private { - ++numOfMaintaining; - validatorExtraSet[index].isMaintaining = true; - validatorExtraSet[index].enterMaintenanceHeight = block.number; - emit validatorEnterMaintenance(validator); - } + // step 1: calculate slashCount + uint256 slashCount = block.number.sub(validatorExtraSet[index].enterMaintenanceHeight).div(miningValidatorCount) + .div(maintainSlashScale); + + // step2: slash the validator + (uint256 misdemeanorThreshold, uint256 felonyThreshold) = + ISlashIndicator(SLASH_CONTRACT_ADDR).getSlashThresholds(); + isFelony = false; + if (slashCount >= felonyThreshold) { + _felony(validator, index); + if (IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator) != address(0)) { + ISlashIndicator(SLASH_CONTRACT_ADDR).downtimeSlash(validator, slashCount); + } else { + ISlashIndicator(SLASH_CONTRACT_ADDR).sendFelonyPackage(validator); + } + isFelony = true; + } else if (slashCount >= misdemeanorThreshold) { + _misdemeanor(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 3: modify global storage + --numOfMaintaining; + validatorExtraSet[index].isMaintaining = false; - // step 1: calculate slashCount - uint256 slashCount = - block.number - .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(workingValidatorCount) - .div(maintainSlashScale); - - // step2: slash the validator - (uint256 misdemeanorThreshold, uint256 felonyThreshold) = ISlashIndicator(SLASH_CONTRACT_ADDR).getSlashThresholds(); - isFelony = false; - if (slashCount >= felonyThreshold) { - _felony(validator, index); - if (IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator) != address(0)) { - ISlashIndicator(SLASH_CONTRACT_ADDR).downtimeSlash(validator, slashCount); - } else { - ISlashIndicator(SLASH_CONTRACT_ADDR).sendFelonyPackage(validator); - } - isFelony = true; - } else if (slashCount >= misdemeanorThreshold) { - _misdemeanor(validator); + emit validatorExitMaintenance(validator); } - // step 3: modify global storage - --numOfMaintaining; - validatorExtraSet[index].isMaintaining = false; + function _mergeValidatorSet( + Validator[] memory validatorSet1, + bytes[] memory voteAddrSet1, + Validator[] memory validatorSet2, + bytes[] memory voteAddrSet2 + ) internal view returns (Validator[] memory, bytes[] memory) { + uint256 _length = IStakeHub(STAKE_HUB_ADDR).maxElectedValidators(); + if (validatorSet1.length + validatorSet2.length < _length) { + _length = validatorSet1.length + validatorSet2.length; + } + Validator[] memory mergedValidatorSet = new Validator[](_length); + bytes[] memory mergedVoteAddrSet = new bytes[](_length); + + uint256 i; + uint256 j; + uint256 k; + while ((i < validatorSet1.length || j < validatorSet2.length) && k < _length) { + if (i == validatorSet1.length) { + mergedValidatorSet[k] = validatorSet2[j]; + mergedVoteAddrSet[k] = voteAddrSet2[j]; + ++j; + ++k; + continue; + } - emit validatorExitMaintenance(validator); - } + if (j == validatorSet2.length) { + mergedValidatorSet[k] = validatorSet1[i]; + mergedVoteAddrSet[k] = voteAddrSet1[i]; + ++i; + ++k; + continue; + } - function _mergeValidatorSet(Validator[] memory validatorSet1, bytes[] memory voteAddrSet1, Validator[] memory validatorSet2, bytes[] memory voteAddrSet2) internal view returns (Validator[] memory, bytes[] memory) { - uint256 _length = IStakeHub(STAKE_HUB_ADDR).maxElectedValidators(); - if (validatorSet1.length + validatorSet2.length < _length) { - _length = validatorSet1.length + validatorSet2.length; - } - Validator[] memory mergedValidatorSet = new Validator[](_length); - bytes[] memory mergedVoteAddrSet = new bytes[](_length); - - uint256 i; - uint256 j; - uint256 k; - while ((i < validatorSet1.length || j < validatorSet2.length) && k < _length) { - if (i == validatorSet1.length) { - mergedValidatorSet[k] = validatorSet2[j]; - mergedVoteAddrSet[k] = voteAddrSet2[j]; - ++j; - ++k; - continue; - } - - if (j == validatorSet2.length) { - mergedValidatorSet[k] = validatorSet1[i]; - mergedVoteAddrSet[k] = voteAddrSet1[i]; - ++i; - ++k; - continue; - } - - if (validatorSet1[i].votingPower > validatorSet2[j].votingPower) { - mergedValidatorSet[k] = validatorSet1[i]; - mergedVoteAddrSet[k] = voteAddrSet1[i]; - ++i; - } else if (validatorSet1[i].votingPower < validatorSet2[j].votingPower) { - mergedValidatorSet[k] = validatorSet2[j]; - mergedVoteAddrSet[k] = voteAddrSet2[j]; - ++j; - } else { - if (validatorSet1[i].consensusAddress < validatorSet2[j].consensusAddress) { - mergedValidatorSet[k] = validatorSet1[i]; - mergedVoteAddrSet[k] = voteAddrSet1[i]; - ++i; - } else { - mergedValidatorSet[k] = validatorSet2[j]; - mergedVoteAddrSet[k] = voteAddrSet2[j]; - ++j; + if (validatorSet1[i].votingPower > validatorSet2[j].votingPower) { + mergedValidatorSet[k] = validatorSet1[i]; + mergedVoteAddrSet[k] = voteAddrSet1[i]; + ++i; + } else if (validatorSet1[i].votingPower < validatorSet2[j].votingPower) { + mergedValidatorSet[k] = validatorSet2[j]; + mergedVoteAddrSet[k] = voteAddrSet2[j]; + ++j; + } else { + if (validatorSet1[i].consensusAddress < validatorSet2[j].consensusAddress) { + mergedValidatorSet[k] = validatorSet1[i]; + mergedVoteAddrSet[k] = voteAddrSet1[i]; + ++i; + } else { + mergedValidatorSet[k] = validatorSet2[j]; + mergedVoteAddrSet[k] = voteAddrSet2[j]; + ++j; + } + } + ++k; } - } - ++k; - } - return (mergedValidatorSet, mergedVoteAddrSet); - } - - //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; +contract CrossChain is System, ICrossChain, IParamSubscriber { + // constant variables + string public constant STORE_NAME = "ibc"; + uint256 public constant CROSS_CHAIN_KEY_PREFIX = 0x01003800; // last 6 bytes + uint8 public constant SYN_PACKAGE = 0x00; + uint8 public constant ACK_PACKAGE = 0x01; + uint8 public constant FAIL_ACK_PACKAGE = 0x02; + uint256 public constant INIT_BATCH_SIZE = 50; + + // governable parameters + uint256 public batchSizeForOracle; + + //state variables + uint256 public previousTxHeight; + uint256 public txCounter; + int64 public oracleSequence; + mapping(uint8 => 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; } - _; - } - - // 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; + + // 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; + _; } - 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) + modifier blockSynced(uint64 _height) { + require(ILightClient(LIGHT_CLIENT_ADDR).isHeaderSynced(_height), "light client not sync the block yet"); + _; } - assembly { - mstore(ptr, _sequence) + + modifier channelSupported(uint8 _channelID) { + require(channelHandlerContractMap[_channelID] != address(0x0), "channel is not supported"); + _; } - ptr -= 8; - assembly { - mstore(ptr, fullCROSS_CHAIN_KEY_PREFIX) + + modifier onlyRegisteredContractChannel(uint8 channleId) { + require( + registeredContractChannelMap[msg.sender][channleId], "the contract and channel have not been registered" + ); + _; } - ptr -= 6; - assembly { - mstore(ptr, 14) + + modifier headerInOrder(uint64 height, uint8 channelId) { + require(height >= channelSyncedHeaderMap[channelId], "too old header"); + if (height != channelSyncedHeaderMap[channelId]) { + channelSyncedHeaderMap[channelId] = height; + } + _; } - return key; - } - function init() external onlyNotInit { - channelHandlerContractMap[BIND_CHANNELID] = TOKEN_MANAGER_ADDR; - isRelayRewardFromSystemReward[BIND_CHANNELID] = false; - registeredContractChannelMap[TOKEN_MANAGER_ADDR][BIND_CHANNELID] = true; + // 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; + } - channelHandlerContractMap[TRANSFER_IN_CHANNELID] = TOKEN_HUB_ADDR; - isRelayRewardFromSystemReward[TRANSFER_IN_CHANNELID] = false; - registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_IN_CHANNELID] = true; + require(indexPlus > 0 && indexPlus <= numOfCabinets, "not cabinet"); + _; + } - channelHandlerContractMap[TRANSFER_OUT_CHANNELID] = TOKEN_HUB_ADDR; - isRelayRewardFromSystemReward[TRANSFER_OUT_CHANNELID] = false; - registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_OUT_CHANNELID] = true; + modifier whenNotSuspended() { + require(!isSuspended, "suspended"); + _; + } + modifier whenSuspended() { + require(isSuspended, "not suspended"); + _; + } - channelHandlerContractMap[STAKING_CHANNELID] = VALIDATOR_CONTRACT_ADDR; - isRelayRewardFromSystemReward[STAKING_CHANNELID] = true; - registeredContractChannelMap[VALIDATOR_CONTRACT_ADDR][STAKING_CHANNELID] = true; + // | 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); - channelHandlerContractMap[GOV_CHANNELID] = GOV_HUB_ADDR; - isRelayRewardFromSystemReward[GOV_CHANNELID] = true; - registeredContractChannelMap[GOV_HUB_ADDR][GOV_CHANNELID] = true; + 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; + } - channelHandlerContractMap[SLASH_CHANNELID] = SLASH_CONTRACT_ADDR; - isRelayRewardFromSystemReward[SLASH_CHANNELID] = true; - registeredContractChannelMap[SLASH_CONTRACT_ADDR][SLASH_CHANNELID] = true; + function init() external onlyNotInit { + channelHandlerContractMap[BIND_CHANNELID] = TOKEN_MANAGER_ADDR; + isRelayRewardFromSystemReward[BIND_CHANNELID] = false; + registeredContractChannelMap[TOKEN_MANAGER_ADDR][BIND_CHANNELID] = true; - batchSizeForOracle = INIT_BATCH_SIZE; + channelHandlerContractMap[TRANSFER_IN_CHANNELID] = TOKEN_HUB_ADDR; + isRelayRewardFromSystemReward[TRANSFER_IN_CHANNELID] = false; + registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_IN_CHANNELID] = true; - oracleSequence = -1; - previousTxHeight = 0; - txCounter = 0; + channelHandlerContractMap[TRANSFER_OUT_CHANNELID] = TOKEN_HUB_ADDR; + isRelayRewardFromSystemReward[TRANSFER_OUT_CHANNELID] = false; + registeredContractChannelMap[TOKEN_HUB_ADDR][TRANSFER_OUT_CHANNELID] = true; - alreadyInit=true; - } + channelHandlerContractMap[STAKING_CHANNELID] = VALIDATOR_CONTRACT_ADDR; + isRelayRewardFromSystemReward[STAKING_CHANNELID] = true; + registeredContractChannelMap[VALIDATOR_CONTRACT_ADDR][STAKING_CHANNELID] = 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; + channelHandlerContractMap[GOV_CHANNELID] = GOV_HUB_ADDR; + isRelayRewardFromSystemReward[GOV_CHANNELID] = true; + registeredContractChannelMap[GOV_HUB_ADDR][GOV_CHANNELID] = true; - assembly { - mstore(ptr, relayFee) - } + channelHandlerContractMap[SLASH_CHANNELID] = SLASH_CONTRACT_ADDR; + isRelayRewardFromSystemReward[SLASH_CHANNELID] = true; + registeredContractChannelMap[SLASH_CONTRACT_ADDR][SLASH_CHANNELID] = true; - ptr-=32; - assembly { - mstore(ptr, packageType) - } + batchSizeForOracle = INIT_BATCH_SIZE; - ptr-=1; - assembly { - mstore(ptr, payloadLength) + oracleSequence = -1; + previousTxHeight = 0; + txCounter = 0; + + alreadyInit = true; } - ptr+=65; - (uint256 src,) = Memory.fromBytes(msgBytes); - Memory.copy(src, ptr, msgBytes.length); + 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; - return payload; - } + assembly { + mstore(ptr, relayFee) + } - // | 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)); - } + ptr -= 32; + assembly { + mstore(ptr, packageType) + } - uint256 ptr; - assembly { - ptr := payload - } + ptr -= 1; + assembly { + mstore(ptr, payloadLength) + } - uint8 packageType; - ptr+=1; - assembly { - packageType := mload(ptr) - } + ptr += 65; + (uint256 src,) = Memory.fromBytes(msgBytes); + Memory.copy(src, ptr, msgBytes.length); - uint256 relayFee; - ptr+=32; - assembly { - relayFee := mload(ptr) + return payload; } - 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; + // | 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) } - } 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); - } + + 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); } - 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; - } + + function handlePackage( + bytes calldata payload, + bytes calldata proof, + uint64 height, + uint64 packageSequence, + uint8 channelId + ) + external + onlyInit + onlyRelayer + sequenceInOrder(packageSequence, channelId) + blockSynced(height) + channelSupported(channelId) + headerInOrder(height, channelId) + whenNotSuspended + { + 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 + ); } - 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"); + + 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); } - 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; + + function sendSynPackage( + uint8 channelId, + bytes calldata msgBytes, + uint256 relayFee + ) external override onlyInit onlyRegisteredContractChannel(channelId) { + uint64 sendSequence = channelSendSequenceMap[channelId]; + sendPackage(sendSequence, channelId, encodePayload(SYN_PACKAGE, relayFee, msgBytes)); + ++sendSequence; + channelSendSequenceMap[channelId] = sendSequence; } - // 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"); + function updateParam(string calldata key, bytes calldata value) external override onlyGov whenNotSuspended { + 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); } - // verify payload1 + proof1 + // 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 + ) + external + onlyInit + blockSynced(params[0]) + blockSynced(params[1]) + channelSupported(uint8(params[3])) + whenNotSuspended { - 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"); - } + // 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; + } - _suspend(); - emit SuccessChallenge(msg.sender, _packageSequence, _channelId); - } + // 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" + ); + } - function suspend() onlyInit onlyCabinet whenNotSuspended external { - bool isExecutable = _approveProposal(SUSPEND_PROPOSAL, EMPTY_CONTENT_HASH); - if (isExecutable) { - _suspend(); - } - } + // 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" + ); + } - function reopen() onlyInit onlyCabinet whenSuspended external { - bool isExecutable = _approveProposal(REOPEN_PROPOSAL, EMPTY_CONTENT_HASH); - if (isExecutable) { - isSuspended = false; - emit Reopened(msg.sender); + _suspend(); + emit SuccessChallenge(msg.sender, _packageSequence, _channelId); } - } - 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 suspend() external onlyInit onlyCabinet whenNotSuspended { + bool isExecutable = _approveProposal(SUSPEND_PROPOSAL, EMPTY_CONTENT_HASH); + if (isExecutable) { + _suspend(); + } } - } - 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; + function reopen() external onlyInit onlyCabinet whenSuspended { + bool isExecutable = _approveProposal(REOPEN_PROPOSAL, EMPTY_CONTENT_HASH); + if (isExecutable) { + isSuspended = false; + emit Reopened(msg.sender); + } } - 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); + function cancelTransfer(address tokenAddr, address attacker) external onlyInit onlyCabinet { + bytes32 _contentHash = keccak256(abi.encode(tokenAddr, attacker)); + bool isExecutable = _approveProposal(CANCEL_TRANSFER_PROPOSAL, _contentHash); + if (isExecutable) { + ITokenHub(TOKEN_HUB_ADDR).cancelTransferIn(tokenAddr, attacker); + } } - if (p.approvers.length >= p.quorum) { - // 1. remove current proposal - delete emergencyProposals[proposalTypeHash]; + 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; + } - // 2. exec this proposal - return true; - } + 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); + } - return false; - } + if (p.approvers.length >= p.quorum) { + // 1. remove current proposal + delete emergencyProposals[proposalTypeHash]; + + // 2. exec this proposal + return true; + } - function _suspend() whenNotSuspended internal { - isSuspended = true; - emit Suspended(msg.sender); - } + return false; + } + + function _suspend() internal whenNotSuspended { + isSuspended = true; + emit Suspended(msg.sender); + } } diff --git a/contracts/GovHub.sol b/contracts/GovHub.sol index d5781620..5223e552 100644 --- a/contracts/GovHub.sol +++ b/contracts/GovHub.sol @@ -1,4 +1,5 @@ pragma solidity 0.6.4; + import "./System.sol"; import "./lib/BytesToTypes.sol"; import "./lib/Memory.sol"; @@ -8,90 +9,91 @@ import "./interface/IApplication.sol"; import "./lib/RLPDecode.sol"; import "./lib/CmnPkg.sol"; +contract GovHub is System, IApplication { + using RLPDecode for *; -contract GovHub is System, IApplication{ - using RLPDecode for *; - - uint8 public constant PARAM_UPDATE_MESSAGE_TYPE = 0; - - uint32 public constant ERROR_TARGET_NOT_CONTRACT = 101; - uint32 public constant ERROR_TARGET_CONTRACT_FAIL = 102; + uint8 public constant PARAM_UPDATE_MESSAGE_TYPE = 0; - event failReasonWithStr(string message); - event failReasonWithBytes(bytes message); - event paramChange(string key, bytes value); + uint32 public constant ERROR_TARGET_NOT_CONTRACT = 101; + uint32 public constant ERROR_TARGET_CONTRACT_FAIL = 102; - struct ParamChangePackage { - string key; - bytes value; - address target; - } + event failReasonWithStr(string message); + event failReasonWithBytes(bytes message); + event paramChange(string key, bytes value); - function handleSynPackage(uint8, bytes calldata msgBytes) external onlyCrossChainContract override returns(bytes memory responsePayload) { - (ParamChangePackage memory proposal, bool success) = decodeSynPackage(msgBytes); - if (!success) { - return CmnPkg.encodeCommonAckPackage(ERROR_FAIL_DECODE); + struct ParamChangePackage { + string key; + bytes value; + address target; } - uint32 resCode = notifyUpdates(proposal); - if (resCode == CODE_OK) { - return new bytes(0); - } else { - return CmnPkg.encodeCommonAckPackage(resCode); - } - } - - // should not happen - function handleAckPackage(uint8, bytes calldata) external onlyCrossChainContract override { - require(false, "receive unexpected ack package"); - } - // should not happen - function handleFailAckPackage(uint8, bytes calldata) external onlyCrossChainContract override { - require(false, "receive unexpected fail ack package"); - } + function handleSynPackage( + uint8, + bytes calldata msgBytes + ) external override onlyCrossChainContract returns (bytes memory responsePayload) { + (ParamChangePackage memory proposal, bool success) = decodeSynPackage(msgBytes); + if (!success) { + return CmnPkg.encodeCommonAckPackage(ERROR_FAIL_DECODE); + } + uint32 resCode = notifyUpdates(proposal); + if (resCode == CODE_OK) { + return new bytes(0); + } else { + return CmnPkg.encodeCommonAckPackage(resCode); + } + } - function updateParam(string calldata key, bytes calldata value, address target) external onlyGovernorTimelock { - ParamChangePackage memory proposal = ParamChangePackage(key, value, target); - notifyUpdates(proposal); - } + // should not happen + function handleAckPackage(uint8, bytes calldata) external override onlyCrossChainContract { + require(false, "receive unexpected ack package"); + } - function notifyUpdates(ParamChangePackage memory proposal) internal returns(uint32) { + // should not happen + function handleFailAckPackage(uint8, bytes calldata) external override onlyCrossChainContract { + require(false, "receive unexpected fail ack package"); + } - if (!isContract(proposal.target)) { - emit failReasonWithStr("the target is not a contract"); - return ERROR_TARGET_NOT_CONTRACT; + function updateParam(string calldata key, bytes calldata value, address target) external onlyGovernorTimelock { + ParamChangePackage memory proposal = ParamChangePackage(key, value, target); + notifyUpdates(proposal); } - try IParamSubscriber(proposal.target).updateParam(proposal.key, proposal.value) { - }catch Error(string memory reason) { - emit failReasonWithStr(reason); - return ERROR_TARGET_CONTRACT_FAIL; - } catch (bytes memory lowLevelData) { - emit failReasonWithBytes(lowLevelData); - return ERROR_TARGET_CONTRACT_FAIL; + + function notifyUpdates(ParamChangePackage memory proposal) internal returns (uint32) { + if (!isContract(proposal.target)) { + emit failReasonWithStr("the target is not a contract"); + return ERROR_TARGET_NOT_CONTRACT; + } + try IParamSubscriber(proposal.target).updateParam(proposal.key, proposal.value) { } + catch Error(string memory reason) { + emit failReasonWithStr(reason); + return ERROR_TARGET_CONTRACT_FAIL; + } catch (bytes memory lowLevelData) { + emit failReasonWithBytes(lowLevelData); + return ERROR_TARGET_CONTRACT_FAIL; + } + return CODE_OK; } - return CODE_OK; - } - //rlp encode & decode function - function decodeSynPackage(bytes memory msgBytes) internal pure returns (ParamChangePackage memory, bool) { - ParamChangePackage memory pkg; + //rlp encode & decode function + function decodeSynPackage(bytes memory msgBytes) internal pure returns (ParamChangePackage memory, bool) { + ParamChangePackage memory pkg; - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) { - pkg.key = string(iter.next().toBytes()); - } else if (idx == 1) { - pkg.value = iter.next().toBytes(); - } else if (idx == 2) { - pkg.target = iter.next().toAddress(); - success = true; - } else { - break; - } - ++idx; + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + pkg.key = string(iter.next().toBytes()); + } else if (idx == 1) { + pkg.value = iter.next().toBytes(); + } else if (idx == 2) { + pkg.target = iter.next().toAddress(); + success = true; + } else { + break; + } + ++idx; + } + return (pkg, success); } - return (pkg, success); - } } diff --git a/contracts/MerkleProof.sol b/contracts/MerkleProof.sol index 1856f250..51bdd209 100644 --- a/contracts/MerkleProof.sol +++ b/contracts/MerkleProof.sol @@ -3,69 +3,74 @@ pragma solidity 0.6.4; import "./lib/Memory.sol"; library MerkleProof { - function validateMerkleProof(bytes32 appHash, string memory storeName, bytes memory key, bytes memory value, bytes memory proof) - internal view returns (bool) { - if (appHash == bytes32(0)) { - return false; - } + function validateMerkleProof( + bytes32 appHash, + string memory storeName, + bytes memory key, + bytes memory value, + bytes memory proof + ) internal view returns (bool) { + if (appHash == bytes32(0)) { + return false; + } - // | storeName | key length | key | value length | value | appHash | proof | - // | 32 bytes | 32 bytes | | 32 bytes | | 32 bytes | - bytes memory input = new bytes(128+key.length+value.length+proof.length); + // | storeName | key length | key | value length | value | appHash | proof | + // | 32 bytes | 32 bytes | | 32 bytes | | 32 bytes | + bytes memory input = new bytes(128 + key.length + value.length + proof.length); - uint256 ptr = Memory.dataPtr(input); + uint256 ptr = Memory.dataPtr(input); - bytes memory storeNameBytes = bytes(storeName); - /* solium-disable-next-line */ - assembly { - mstore(ptr, mload(add(storeNameBytes, 32))) - } + bytes memory storeNameBytes = bytes(storeName); + /* solium-disable-next-line */ + assembly { + mstore(ptr, mload(add(storeNameBytes, 32))) + } - uint256 src; - uint256 length; + uint256 src; + uint256 length; - // write key length and key to input - ptr += 32; - (src, length) = Memory.fromBytes(key); - /* solium-disable-next-line */ - assembly { - mstore(ptr, length) - } - ptr += 32; - Memory.copy(src, ptr, length); + // write key length and key to input + ptr += 32; + (src, length) = Memory.fromBytes(key); + /* solium-disable-next-line */ + assembly { + mstore(ptr, length) + } + ptr += 32; + Memory.copy(src, ptr, length); - // write value length and value to input - ptr += length; - (src, length) = Memory.fromBytes(value); - /* solium-disable-next-line */ - assembly { - mstore(ptr, length) - } - ptr += 32; - Memory.copy(src, ptr, length); + // write value length and value to input + ptr += length; + (src, length) = Memory.fromBytes(value); + /* solium-disable-next-line */ + assembly { + mstore(ptr, length) + } + ptr += 32; + Memory.copy(src, ptr, length); - // write appHash to input - ptr += length; - /* solium-disable-next-line */ - assembly { - mstore(ptr, appHash) - } + // write appHash to input + ptr += length; + /* solium-disable-next-line */ + assembly { + mstore(ptr, appHash) + } - // write proof to input - ptr += 32; - (src,length) = Memory.fromBytes(proof); - Memory.copy(src, ptr, length); + // write proof to input + ptr += 32; + (src, length) = Memory.fromBytes(proof); + Memory.copy(src, ptr, length); - length = input.length+32; + length = input.length + 32; - uint256[1] memory result; - /* solium-disable-next-line */ - assembly { - // call validateMerkleProof precompile contract - // Contract address: 0x65 - if iszero(staticcall(not(0), 0x65, input, length, result, 0x20)) {} - } + uint256[1] memory result; + /* solium-disable-next-line */ + assembly { + // call validateMerkleProof precompile contract + // Contract address: 0x65 + if iszero(staticcall(not(0), 0x65, input, length, result, 0x20)) { } + } - return result[0] == 0x01; - } + return result[0] == 0x01; + } } diff --git a/contracts/RelayerHub.sol b/contracts/RelayerHub.sol index 8e802fd3..b9b1bcfd 100644 --- a/contracts/RelayerHub.sol +++ b/contracts/RelayerHub.sol @@ -7,17 +7,14 @@ 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; - - address public constant WHITELIST_1 = 0xb005741528b86F5952469d80A8614591E3c5B632; - address public constant WHITELIST_2 = 0x446AA6E0DC65690403dF3F127750da1322941F3e; - + address public constant WHITELIST_1 = 0xb005741528b86F5952469d80A8614591E3c5B632; + address public constant WHITELIST_2 = 0x446AA6E0DC65690403dF3F127750da1322941F3e; uint256 internal requiredDeposit; // have to keep it to not break the storage layout uint256 internal dues; @@ -38,7 +35,7 @@ contract RelayerHub is IRelayerHub, System, IParamSubscriber { bool public whitelistInitDone; - modifier onlyManager(){ + modifier onlyManager() { require(relayManagersExistMap[msg.sender], "manager does not exist"); _; } @@ -92,7 +89,7 @@ contract RelayerHub is IRelayerHub, System, IParamSubscriber { emit relayerUpdated(address(0), addr); } - /*********************** Param update ********************************/ + /*----------------- 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"); @@ -135,7 +132,7 @@ contract RelayerHub is IRelayerHub, System, IParamSubscriber { function addManagerByGov(address managerToBeAdded) internal { require(!relayManagersExistMap[managerToBeAdded], "manager already exists"); - + relayManagersExistMap[managerToBeAdded] = true; emit managerAdded(managerToBeAdded); @@ -167,7 +164,6 @@ contract RelayerHub is IRelayerHub, System, IParamSubscriber { // 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"); @@ -182,18 +178,17 @@ contract RelayerHub is IRelayerHub, System, IParamSubscriber { delete managerToProvisionalRelayer[manager]; delete currentRelayers[oldRelayer]; emit relayerUpdated(oldRelayer, msg.sender); - } - function isRelayer(address relayerAddress) external override view returns (bool){ + function isRelayer(address relayerAddress) external view override returns (bool) { return currentRelayers[relayerAddress]; } - function isProvisionalRelayer(address relayerAddress) external view returns (bool){ + function isProvisionalRelayer(address relayerAddress) external view returns (bool) { return provisionalRelayers[relayerAddress]; } - function isManager(address managerAddress) external view returns (bool){ + function isManager(address managerAddress) external view returns (bool) { return relayManagersExistMap[managerAddress]; } } diff --git a/contracts/RelayerIncentivize.sol b/contracts/RelayerIncentivize.sol index 5bad9ef9..16f2e5db 100644 --- a/contracts/RelayerIncentivize.sol +++ b/contracts/RelayerIncentivize.sol @@ -9,224 +9,252 @@ import "./interface/IParamSubscriber.sol"; import "./interface/ISystemReward.sol"; contract RelayerIncentivize is IRelayerIncentivize, System, IParamSubscriber { + using SafeMath for uint256; - using SafeMath for uint256; + uint256 public constant ROUND_SIZE = 100; + uint256 public constant MAXIMUM_WEIGHT = 40; - uint256 public constant ROUND_SIZE=100; - uint256 public constant MAXIMUM_WEIGHT=40; + uint256 public constant HEADER_RELAYER_REWARD_RATE_MOLECULE = 1; + uint256 public constant HEADER_RELAYER_REWARD_RATE_DENOMINATOR = 5; + uint256 public constant CALLER_COMPENSATION_MOLECULE = 1; + uint256 public constant CALLER_COMPENSATION_DENOMINATOR = 80; - uint256 public constant HEADER_RELAYER_REWARD_RATE_MOLECULE = 1; - uint256 public constant HEADER_RELAYER_REWARD_RATE_DENOMINATOR = 5; - uint256 public constant CALLER_COMPENSATION_MOLECULE = 1; - uint256 public constant CALLER_COMPENSATION_DENOMINATOR = 80; + uint256 public headerRelayerRewardRateMolecule; + uint256 public headerRelayerRewardRateDenominator; + uint256 public callerCompensationMolecule; + uint256 public 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 headerRelayersSubmitCount; - address payable[] public headerRelayerAddressRecord; + mapping(address => uint256) public packageRelayersSubmitCount; + address payable[] public packageRelayerAddressRecord; - mapping(address => uint256) public packageRelayersSubmitCount; - address payable[] public packageRelayerAddressRecord; + uint256 public collectedRewardForHeaderRelayer = 0; + uint256 public collectedRewardForTransferRelayer = 0; - uint256 public collectedRewardForHeaderRelayer=0; - uint256 public collectedRewardForTransferRelayer=0; + uint256 public roundSequence = 0; + uint256 public countInRound = 0; - uint256 public roundSequence=0; - uint256 public countInRound=0; + mapping(address => uint256) public relayerRewardVault; - mapping(address => uint256) public relayerRewardVault; + uint256 public dynamicExtraIncentiveAmount; - uint256 public dynamicExtraIncentiveAmount; + event distributeCollectedReward( + uint256 sequence, uint256 roundRewardForHeaderRelayer, uint256 roundRewardForTransferRelayer + ); + event paramChange(string key, bytes value); + event rewardToRelayer(address relayer, uint256 amount); - 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)); - } + function init() external onlyNotInit { + 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; } - ++countInRound; - - uint256 reward = calculateRewardForHeaderRelayer(actualAmount); - collectedRewardForHeaderRelayer = collectedRewardForHeaderRelayer.add(reward); - collectedRewardForTransferRelayer = collectedRewardForTransferRelayer.add(actualAmount).sub(reward); - - if (headerRelayersSubmitCount[headerRelayerAddr]==0) { - headerRelayerAddressRecord.push(headerRelayerAddr); + receive() external payable { } + + function addReward( + address payable headerRelayerAddr, + address payable packageRelayer, + uint256 amount, + bool fromSystemReward + ) external override onlyInit onlyCrossChainContract 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; } - ++headerRelayersSubmitCount[headerRelayerAddr]; - if (packageRelayersSubmitCount[packageRelayer]==0) { - packageRelayerAddressRecord.push(packageRelayer); + 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); } - ++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); + function calculateRewardForHeaderRelayer(uint256 reward) internal view returns (uint256) { + return reward.mul(headerRelayerRewardRateMolecule).div(headerRelayerRewardRateDenominator); } - 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); + 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; } - 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; + 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; } - 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); + 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; + } } - 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 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"); + function updateParam(string calldata key, bytes calldata value) external override 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); } - emit paramChange(key, value); - } } diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index b0943715..0a646768 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -16,404 +16,436 @@ import "./interface/IStakeHub.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 DECREASE_RATE = 4; - - // 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 - // @notice change name from `INIT_FINALITY_SLASH_REWARD_RATIO` to `INIT_FELONY_SLASH_REWARD_RATIO` after BC-fusion - uint256 public constant INIT_FELONY_SLASH_REWARD_RATIO = 20; - - // @notice change name from `finalitySlashRewardRatio` to `felonySlashRewardRatio` after BC-fusion - uint256 public felonySlashRewardRatio; - bool public enableMaliciousVoteSlash; - - uint256 public constant INIT_FELONY_SLASH_SCOPE = 28800; // 1 days(block number) - - uint256 public felonySlashScope; - - 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); - - 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; - } - - function init() external onlyNotInit{ - misdemeanorThreshold = MISDEMEANOR_THRESHOLD; - felonyThreshold = FELONY_THRESHOLD; - alreadyInit = true; - } - - /*********************** 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); +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 DECREASE_RATE = 4; + + // 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 + // @notice change name from `INIT_FINALITY_SLASH_REWARD_RATIO` to `INIT_FELONY_SLASH_REWARD_RATIO` after BC-fusion + uint256 public constant INIT_FELONY_SLASH_REWARD_RATIO = 20; + + // @notice change name from `finalitySlashRewardRatio` to `felonySlashRewardRatio` after BC-fusion + uint256 public felonySlashRewardRatio; + bool public enableMaliciousVoteSlash; + + uint256 public constant INIT_FELONY_SLASH_SCOPE = 28800; // 1 days(block number) + + uint256 public felonySlashScope; + + 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); + + struct Indicator { + uint256 height; + uint256 count; + bool exist; } - 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; + + // Proof that a validator misbehaved in fast finality + struct VoteData { + uint256 srcNum; + bytes32 srcHash; + uint256 tarNum; + bytes32 tarHash; + bytes sig; } - Indicator memory indicator = indicators[validator]; - if (indicator.exist) { - ++indicator.count; - } else { - indicator.exist = true; - indicator.count = 1; - validators.push(validator); + + struct FinalityEvidence { + VoteData voteA; + VoteData voteB; + bytes voteAddr; + } + + modifier oncePerBlock() { + require(block.number > previousHeight, "can not slash twice in one block"); + _; + previousHeight = block.number; } - indicator.height = block.number; - if (indicator.count % felonyThreshold == 0) { - indicator.count = 0; - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator); - if (IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator) != address(0)) { - _downtimeSlash(validator, indicator.count); - } else { - // send slash msg to bc if validator is not migrated - try ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeSlashPackage(validator), 0) {} catch (bytes memory reason) { - emit failedFelony(validator, indicator.count, reason); + + function init() external onlyNotInit { + misdemeanorThreshold = MISDEMEANOR_THRESHOLD; + felonyThreshold = FELONY_THRESHOLD; + alreadyInit = true; + } + + /*----------------- Implement cross chain app -----------------*/ + function handleSynPackage( + uint8, + bytes calldata + ) external override onlyCrossChainContract onlyInit returns (bytes memory) { + require(false, "receive unexpected syn package"); + } + + function handleAckPackage(uint8, bytes calldata msgBytes) external override onlyCrossChainContract onlyInit { + (CmnPkg.CommonAckPackage memory response, bool ok) = CmnPkg.decodeCommonAckPackage(msgBytes); + if (ok) { + emit knownResponse(response.code); + } else { + emit unKnownResponse(response.code); } - } - } else if (indicator.count % misdemeanorThreshold == 0) { - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).misdemeanor(validator); + return; } - 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; + + function handleFailAckPackage(uint8, bytes calldata) external override onlyCrossChainContract onlyInit { + emit crashResponse(); + 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; + + /*----------------- 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; } - } - 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(); + Indicator memory indicator = indicators[validator]; + if (indicator.exist) { + ++indicator.count; + } else { + indicator.exist = true; + indicator.count = 1; + validators.push(validator); } - // avoid underflow - if(j==0){ - break; + indicator.height = block.number; + if (indicator.count % felonyThreshold == 0) { + indicator.count = 0; + IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator); + if (IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator) != address(0)) { + _downtimeSlash(validator, indicator.count); + } else { + // send slash msg to bc if validator is not migrated + 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); } - } - // 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; + indicators[validator] = indicator; + emit validatorSlashed(validator); } - emit indicatorCleaned(); - } - - function downtimeSlash(address validator, uint256 count) external override onlyValidatorContract { - _downtimeSlash(validator, count); - } - - function _downtimeSlash(address validator, uint256 count) internal { - try IStakeHub(STAKE_HUB_ADDR).downtimeSlash(validator) { - } catch Error(string memory reason) { - emit failedFelony(validator, count, bytes(reason)); - } catch (bytes memory lowLevelData) { - emit failedFelony(validator, count, lowLevelData); - } - } - function submitFinalityViolationEvidence(FinalityEvidence memory _evidence) public onlyInit { - require(enableMaliciousVoteSlash, "malicious vote slash not enabled"); - if (felonySlashRewardRatio == 0) { - felonySlashRewardRatio = INIT_FELONY_SLASH_REWARD_RATIO; + // 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; + } + uint256 i; + uint256 j = validators.length - 1; + for (; i <= j;) { + bool findLeft = false; + bool findRight = false; + for (; i < j; ++i) { + Indicator memory leftIndicator = indicators[validators[i]]; + if (leftIndicator.count > 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(); } - if (felonySlashScope == 0) { - felonySlashScope = INIT_FELONY_SLASH_SCOPE; + + function downtimeSlash(address validator, uint256 count) external override onlyValidatorContract { + _downtimeSlash(validator, count); } - // Basic check - require(_evidence.voteA.tarNum+felonySlashScope > block.number && - _evidence.voteB.tarNum+felonySlashScope > 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 - // TODO: after BC-fusion, we don't need to check if validator is living - (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 * felonySlashRewardRatio) / 100; - ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, amount); - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(vals[i]); - break; - } + function _downtimeSlash(address validator, uint256 count) internal { + try IStakeHub(STAKE_HUB_ADDR).downtimeSlash(validator) { } + catch Error(string memory reason) { + emit failedFelony(validator, count, bytes(reason)); + } catch (bytes memory lowLevelData) { + emit failedFelony(validator, count, lowLevelData); + } } - if (IStakeHub(STAKE_HUB_ADDR).voteToOperator(_evidence.voteAddr) != address(0)) { - IStakeHub(STAKE_HUB_ADDR).maliciousVoteSlash(_evidence.voteAddr); - } else { - // send slash msg to bc if the validator not migrated - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeVoteSlashPackage(_evidence.voteAddr), 0); + function submitFinalityViolationEvidence(FinalityEvidence memory _evidence) public onlyInit { + require(enableMaliciousVoteSlash, "malicious vote slash not enabled"); + if (felonySlashRewardRatio == 0) { + felonySlashRewardRatio = INIT_FELONY_SLASH_REWARD_RATIO; + } + if (felonySlashScope == 0) { + felonySlashScope = INIT_FELONY_SLASH_SCOPE; + } + + // Basic check + require( + _evidence.voteA.tarNum + felonySlashScope > block.number + && _evidence.voteB.tarNum + felonySlashScope > 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 + // TODO: after BC-fusion, we don't need to check if validator is living + (address[] memory vals, bytes[] memory voteAddrs) = + IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).getLivingValidators(); + for (uint256 i; i < voteAddrs.length; ++i) { + if (BytesLib.equal(voteAddrs[i], _evidence.voteAddr)) { + uint256 amount = (address(SYSTEM_REWARD_ADDR).balance * felonySlashRewardRatio) / 100; + ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, amount); + IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(vals[i]); + break; + } + } - bytes32 voteAddrSlice = BytesLib.toBytes32(_evidence.voteAddr, 0); - emit maliciousVoteSlashed(voteAddrSlice); + if (IStakeHub(STAKE_HUB_ADDR).voteToOperator(_evidence.voteAddr) != address(0)) { + IStakeHub(STAKE_HUB_ADDR).maliciousVoteSlash(_evidence.voteAddr); + } else { + // send slash msg to bc if the validator not migrated + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage( + SLASH_CHANNELID, encodeVoteSlashPackage(_evidence.voteAddr), 0 + ); + + bytes32 voteAddrSlice = BytesLib.toBytes32(_evidence.voteAddr, 0); + emit maliciousVoteSlashed(voteAddrSlice); + } } - } - function submitDoubleSignEvidence(bytes memory header1, bytes memory header2) public onlyInit { - if (felonySlashRewardRatio == 0) { - felonySlashRewardRatio = INIT_FELONY_SLASH_REWARD_RATIO; + function submitDoubleSignEvidence(bytes memory header1, bytes memory header2) public onlyInit { + if (felonySlashRewardRatio == 0) { + felonySlashRewardRatio = INIT_FELONY_SLASH_REWARD_RATIO; + } + if (felonySlashScope == 0) { + felonySlashScope = INIT_FELONY_SLASH_SCOPE; + } + + require(header1.length != 0 && header2.length != 0, "empty header"); + + bytes[] memory elements = new bytes[](3); + elements[0] = bscChainID.encodeUint(); + elements[1] = header1.encodeBytes(); + elements[2] = header2.encodeBytes(); + + // call precompile contract to verify evidence + bytes memory input = elements.encodeList(); + bytes memory output = new bytes(52); + assembly { + let len := mload(input) + if iszero(staticcall(not(0), 0x68, add(input, 0x20), len, add(output, 0x20), 0x34)) { revert(0, 0) } + } + + address signer; + uint256 evidenceHeight; + assembly { + signer := mload(add(output, 0x14)) + evidenceHeight := mload(add(output, 0x34)) + } + require(IStakeHub(STAKE_HUB_ADDR).consensusToOperator(signer) != address(0), "validator not migrated"); + require(evidenceHeight + felonySlashScope >= block.number, "evidence too old"); + + // reward sender and felony validator + IStakeHub(STAKE_HUB_ADDR).doubleSignSlash(signer); + IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(signer); + + uint256 amount = (address(SYSTEM_REWARD_ADDR).balance * felonySlashRewardRatio) / 100; + ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, amount); } - if (felonySlashScope == 0) { - felonySlashScope = INIT_FELONY_SLASH_SCOPE; + + /** + * @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 { + try ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeSlashPackage(validator), 0) { } + catch (bytes memory reason) { + emit failedFelony(validator, 0, reason); + } } - require(header1.length != 0 && header2.length != 0, "empty header"); - - bytes[] memory elements = new bytes[](3); - elements[0] = bscChainID.encodeUint(); - elements[1] = header1.encodeBytes(); - elements[2] = header2.encodeBytes(); - - // call precompile contract to verify evidence - bytes memory input = elements.encodeList(); - bytes memory output = new bytes(52); - assembly { - let len := mload(input) - if iszero(staticcall(not(0), 0x68, add(input, 0x20), len, add(output, 0x20), 0x34)) { - revert(0, 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; } - address signer; - uint256 evidenceHeight; - assembly { - signer := mload(add(output, 0x14)) - evidenceHeight := mload(add(output, 0x34)) + function _bytesConcat(bytes memory data, bytes memory _bytes, uint256 index, uint256 len) internal pure { + for (uint256 i; i < len; ++i) { + data[index++] = _bytes[i]; + } } - require(IStakeHub(STAKE_HUB_ADDR).consensusToOperator(signer) != address(0), "validator not migrated"); - require(evidenceHeight + felonySlashScope >= block.number, "evidence too old"); - - // reward sender and felony validator - IStakeHub(STAKE_HUB_ADDR).doubleSignSlash(signer); - IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(signer); - - uint256 amount = (address(SYSTEM_REWARD_ADDR).balance * felonySlashRewardRatio) / 100; - ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(msg.sender, amount); - } - - /** - * @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 { - try ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SLASH_CHANNELID, encodeSlashPackage(validator), 0) {} - catch (bytes memory reason) { - emit failedFelony(validator, 0, reason); + + /*----------------- Param update -----------------*/ + function updateParam(string calldata key, bytes calldata value) external override onlyInit onlyGov { + if (Memory.compareStrings(key, "misdemeanorThreshold")) { + require(value.length == 32, "length of misdemeanorThreshold mismatch"); + uint256 newMisdemeanorThreshold = BytesToTypes.bytesToUint256(32, value); + require( + newMisdemeanorThreshold >= 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, "felonySlashRewardRatio")) { + require(value.length == 32, "length of felonySlashRewardRatio mismatch"); + uint256 newFelonySlashRewardRatio = BytesToTypes.bytesToUint256(32, value); + require( + newFelonySlashRewardRatio >= 10 && newFelonySlashRewardRatio < 100, + "the felony slash reward ratio out of range" + ); + felonySlashRewardRatio = newFelonySlashRewardRatio; + } else if (Memory.compareStrings(key, "enableMaliciousVoteSlash")) { + require(value.length == 32, "length of enableMaliciousVoteSlash mismatch"); + enableMaliciousVoteSlash = BytesToTypes.bytesToBool(32, value); + } else if (Memory.compareStrings(key, "felonySlashScope")) { + require(value.length == 32, "length of felonySlashScope mismatch"); + uint256 newMaliciousVoteSlashScope = BytesToTypes.bytesToUint256(32, value); + require( + newMaliciousVoteSlashScope >= 28800 * 1 && newMaliciousVoteSlashScope < 28800 * 30, + "the malicious vote slash scope out of range" + ); + felonySlashScope = newMaliciousVoteSlashScope; + } else { + require(false, "unknown param"); + } + emit paramChange(key, value); } - } - - 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) - } + + /*----------------- query api -----------------*/ + function getSlashIndicator(address validator) external view returns (uint256, uint256) { + Indicator memory indicator = indicators[validator]; + return (indicator.height, indicator.count); } - if (BytesLib.toUint8(output, 0) != uint8(1)) { - return false; + + 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(); } - 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, "felonySlashRewardRatio")) { - require(value.length == 32, "length of felonySlashRewardRatio mismatch"); - uint256 newFelonySlashRewardRatio = BytesToTypes.bytesToUint256(32, value); - require(newFelonySlashRewardRatio >= 10 && newFelonySlashRewardRatio < 100, "the felony slash reward ratio out of range"); - felonySlashRewardRatio = newFelonySlashRewardRatio; - } else if (Memory.compareStrings(key, "enableMaliciousVoteSlash")) { - require(value.length == 32, "length of enableMaliciousVoteSlash mismatch"); - enableMaliciousVoteSlash = BytesToTypes.bytesToBool(32, value); - } else if (Memory.compareStrings(key, "felonySlashScope")) { - require(value.length == 32, "length of felonySlashScope mismatch"); - uint256 newMaliciousVoteSlashScope = BytesToTypes.bytesToUint256(32, value); - require(newMaliciousVoteSlashScope >= 28800*1 && newMaliciousVoteSlashScope < 28800*30, "the malicious vote slash scope out of range"); - felonySlashScope = newMaliciousVoteSlashScope; - } else { - require(false, "unknown param"); + + function getSlashThresholds() external view override(ISlashIndicator) returns (uint256, uint256) { + return (misdemeanorThreshold, felonyThreshold); } - 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.sol b/contracts/Staking.sol index 43077a10..208b7ab3 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -14,647 +14,677 @@ 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 Deprecated after fusion - */ - function delegate(address, uint256) override external payable { - revert("not supported"); - } - - /** - * @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 Deprecated after fusion - */ - function redelegate(address, address, uint256) override external payable { - revert("not supported"); - } - - /** - * @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; - bool isAutoUndelegate; - 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 if (idx == 3) { - isAutoUndelegate = iter.next().toBoolean(); - } 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); - - // this is to address the issue that the contract state will not being updated - // when the Beacon Chain system undelegate all the funds after second sunset upgrade - if (isAutoUndelegate) { - delegated[recipient] = delegated[recipient].sub(amount); - delegatedOfValidator[recipient][validator] = delegatedOfValidator[recipient][validator].sub(amount); - emit undelegateSuccess(recipient, validator, amount); - } - - emit undelegatedReceived(recipient, validator, amount); - return (CODE_OK, new bytes(0)); - } + 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 override onlyCrossChainContract initParams 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 override onlyCrossChainContract initParams { + 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 override onlyCrossChainContract initParams { + 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 Deprecated after fusion + */ + function delegate(address, uint256) external payable override { + revert("not supported"); + } + + /** + * @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 + ) external payable override 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 Deprecated after fusion + */ + function redelegate(address, address, uint256) external payable override { + revert("not supported"); + } + + /** + * @dev claim delegated reward from BC staking + * + */ + function claimReward() external override 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() external override 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) external view override returns (uint256) { + return delegatedOfValidator[delegator][validator]; + } + + function getTotalDelegated(address delegator) external view override returns (uint256) { + return delegated[delegator]; + } + + function getDistributedReward(address delegator) external view override returns (uint256) { + return distributedReward[delegator]; + } + + function getPendingRedelegateTime( + address delegator, + address valSrc, + address valDst + ) external view override returns (uint256) { + return pendingRedelegateTime[delegator][valSrc][valDst]; + } + + function getUndelegated(address delegator) external view override returns (uint256) { + return undelegated[delegator]; + } + + function getPendingUndelegateTime(address delegator, address validator) external view override returns (uint256) { + return pendingUndelegateTime[delegator][validator]; + } + + function getRelayerFee() external view override returns (uint256) { + return relayerFee; + } + + function getMinDelegation() external view override returns (uint256) { + return minDelegation; + } + + function getRequestInFly(address delegator) external view override 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) external override 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; + bool isAutoUndelegate; + 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 if (idx == 3) { + isAutoUndelegate = iter.next().toBoolean(); + } 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); + + // this is to address the issue that the contract state will not being updated + // when the Beacon Chain system undelegate all the funds after second sunset upgrade + if (isAutoUndelegate) { + delegated[recipient] = delegated[recipient].sub(amount); + delegatedOfValidator[recipient][validator] = delegatedOfValidator[recipient][validator].sub(amount); + emit undelegateSuccess(recipient, validator, amount); + } + + emit undelegatedReceived(recipient, validator, amount); + return (CODE_OK, new bytes(0)); + } } diff --git a/contracts/System.sol b/contracts/System.sol index cabdf0ab..45dfc87c 100644 --- a/contracts/System.sol +++ b/contracts/System.sol @@ -5,119 +5,120 @@ 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; - uint8 constant public BC_FUSION_CHANNELID = 0x11; // new channel id for cross-chain redelegate from Beacon Chain to Smart Chain after Feynman upgrade - uint16 constant public bscChainID = 0x0038; - - 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; - address public constant STAKE_HUB_ADDR = 0x0000000000000000000000000000000000002002; - address public constant STAKE_CREDIT_ADDR = 0x0000000000000000000000000000000000002003; - address public constant GOVERNOR_ADDR = 0x0000000000000000000000000000000000002004; - address public constant GOV_TOKEN_ADDR = 0x0000000000000000000000000000000000002005; - address public constant TIMELOCK_ADDR = 0x0000000000000000000000000000000000002006; - address public constant TOKEN_RECOVER_PORTAL_ADDR = 0x0000000000000000000000000000000000003000; - - modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "the message sender must be the block producer"); - _; - } - - modifier onlyZeroGasPrice() { - require(tx.gasprice == 0 , "gasprice is not zero"); - _; - } - - 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"); - _; - } - - modifier onlyStakeHub() { - require(msg.sender == STAKE_HUB_ADDR, "the msg sender must be stakeHub"); - _; - } - - modifier onlyGovernorTimelock() { - require(msg.sender == TIMELOCK_ADDR, "the msg sender must be governor timelock contract"); - _; - } - - modifier onlyTokenRecoverPortal() { - require(msg.sender == TOKEN_RECOVER_PORTAL_ADDR, "the msg sender must be token recover portal"); - _; - } - - // 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; - } + bool public alreadyInit; + + uint32 public constant CODE_OK = 0; + uint32 public constant ERROR_FAIL_DECODE = 100; + + uint8 public constant BIND_CHANNELID = 0x01; + uint8 public constant TRANSFER_IN_CHANNELID = 0x02; + uint8 public constant TRANSFER_OUT_CHANNELID = 0x03; + uint8 public constant STAKING_CHANNELID = 0x08; + uint8 public constant GOV_CHANNELID = 0x09; + uint8 public constant SLASH_CHANNELID = 0x0b; + uint8 public constant CROSS_STAKE_CHANNELID = 0x10; + uint8 public constant BC_FUSION_CHANNELID = 0x11; // new channel id for cross-chain redelegate from Beacon Chain to Smart Chain after Feynman upgrade + uint16 public constant bscChainID = 0x0038; + + 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; + address public constant STAKE_HUB_ADDR = 0x0000000000000000000000000000000000002002; + address public constant STAKE_CREDIT_ADDR = 0x0000000000000000000000000000000000002003; + address public constant GOVERNOR_ADDR = 0x0000000000000000000000000000000000002004; + address public constant GOV_TOKEN_ADDR = 0x0000000000000000000000000000000000002005; + address public constant TIMELOCK_ADDR = 0x0000000000000000000000000000000000002006; + address public constant TOKEN_RECOVER_PORTAL_ADDR = 0x0000000000000000000000000000000000003000; + + modifier onlyCoinbase() { + require(msg.sender == block.coinbase, "the message sender must be the block producer"); + _; + } + + modifier onlyZeroGasPrice() { + require(tx.gasprice == 0, "gasprice is not zero"); + _; + } + + 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"); + _; + } + + modifier onlyStakeHub() { + require(msg.sender == STAKE_HUB_ADDR, "the msg sender must be stakeHub"); + _; + } + + modifier onlyGovernorTimelock() { + require(msg.sender == TIMELOCK_ADDR, "the msg sender must be governor timelock contract"); + _; + } + + modifier onlyTokenRecoverPortal() { + require(msg.sender == TOKEN_RECOVER_PORTAL_ADDR, "the msg sender must be token recover portal"); + _; + } + + // Not reliable, do not use when need strong verify + function isContract(address addr) internal view returns (bool) { + uint256 size; + assembly { + size := extcodesize(addr) + } + return size > 0; + } } diff --git a/contracts/SystemReward.sol b/contracts/SystemReward.sol index a9591bfd..0998b0f4 100644 --- a/contracts/SystemReward.sol +++ b/contracts/SystemReward.sol @@ -6,79 +6,82 @@ import "./interface/IParamSubscriber.sol"; import "./interface/ISystemReward.sol"; contract SystemReward is System, IParamSubscriber, ISystemReward { - uint256 public constant MAX_REWARDS = 5e18; + uint256 public constant MAX_REWARDS = 5e18; - uint public numOperator; - mapping(address => bool) operators; + uint256 public numOperator; + mapping(address => bool) operators; - modifier doInit() { - if (!alreadyInit) { - operators[LIGHT_CLIENT_ADDR] = true; - operators[INCENTIVIZE_ADDR] = true; - numOperator = 2; - alreadyInit = true; + modifier doInit() { + if (!alreadyInit) { + operators[LIGHT_CLIENT_ADDR] = true; + operators[INCENTIVIZE_ADDR] = true; + numOperator = 2; + alreadyInit = true; + } + _; } - _; - } - modifier onlyOperator() { - require(operators[msg.sender],"only operator is allowed to call the method"); - _; - } + 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); + 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); + 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(); + 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; } - return actualAmount; - } - function isOperator(address addr) external view returns (bool) { - return operators[addr]; - } + 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"); + function updateParam(string calldata key, bytes calldata value) external override onlyGov { + 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); } - emit paramChange(key, value); - } } diff --git a/contracts/TendermintLightClient.sol b/contracts/TendermintLightClient.sol index ef5bd3db..e0a1e01e 100644 --- a/contracts/TendermintLightClient.sol +++ b/contracts/TendermintLightClient.sol @@ -7,259 +7,271 @@ 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"42696e616e63652d436861696e2d5469677269730000000000000000000000000000000006915167cedaf7bbf7df47d932fdda630527ee648562cf3e52c5e5f46156a3a971a4ceb443c53a50d8653ef8cf1e5716da68120fb51b636dc6d111ec3277b098ecd42d49d3769d8a1f78b4c17a965f7a30d4181fabbd1f969f46d3c8e83b5ad4845421d8000000e8d4a510002ba4e81542f437b7ae1f8a35ddb233c789a8dc22734377d9b6d63af1ca403b61000000e8d4a51000df8da8c5abfdb38595391308bb71e5a1e0aabdc1d0cf38315d50d6be939b2606000000e8d4a51000b6619edca4143484800281d698b70c935e9152ad57b31d85c05f2f79f64b39f3000000e8d4a510009446d14ad86c8d2d74780b0847110001a1c2e252eedfea4753ebbbfce3a22f52000000e8d4a510000353c639f80cc8015944436dab1032245d44f912edc31ef668ff9f4a45cd0599000000e8d4a51000e81d3797e0544c3a718e1f05f0fb782212e248e784c1a851be87e77ae0db230e000000e8d4a510005e3fcda30bd19d45c4b73688da35e7da1fce7c6859b2c1f20ed5202d24144e3e000000e8d4a51000b06a59a2d75bf5d014fce7c999b5e71e7a960870f725847d4ba3235baeaa08ef000000e8d4a510000c910e2fe650e4e01406b3310b489fb60a84bc3ff5c5bee3a56d5898b6a8af32000000e8d4a5100071f2d7b8ec1c8b99a653429b0118cd201f794f409d0fea4d65b1b662f2b00063000000e8d4a51000"; - uint256 constant public INIT_REWARD_FOR_VALIDATOR_SER_CHANGE = 1e16; - 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); - - 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"); +contract TendermintLightClient is ILightClient, System, IParamSubscriber { + struct ConsensusState { + uint64 preValidatorSetChangeHeight; + bytes32 appHash; + bytes32 curValidatorSetHash; + bytes nextValidatorSet; } - //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) - } + mapping(uint64 => ConsensusState) public lightClientConsensusStates; + mapping(uint64 => address payable) public submitters; + uint64 public initialHeight; + uint64 public latestHeight; + bytes32 public chainID; + + bytes public constant INIT_CONSENSUS_STATE_BYTES = + hex"42696e616e63652d436861696e2d5469677269730000000000000000000000000000000006915167cedaf7bbf7df47d932fdda630527ee648562cf3e52c5e5f46156a3a971a4ceb443c53a50d8653ef8cf1e5716da68120fb51b636dc6d111ec3277b098ecd42d49d3769d8a1f78b4c17a965f7a30d4181fabbd1f969f46d3c8e83b5ad4845421d8000000e8d4a510002ba4e81542f437b7ae1f8a35ddb233c789a8dc22734377d9b6d63af1ca403b61000000e8d4a51000df8da8c5abfdb38595391308bb71e5a1e0aabdc1d0cf38315d50d6be939b2606000000e8d4a51000b6619edca4143484800281d698b70c935e9152ad57b31d85c05f2f79f64b39f3000000e8d4a510009446d14ad86c8d2d74780b0847110001a1c2e252eedfea4753ebbbfce3a22f52000000e8d4a510000353c639f80cc8015944436dab1032245d44f912edc31ef668ff9f4a45cd0599000000e8d4a51000e81d3797e0544c3a718e1f05f0fb782212e248e784c1a851be87e77ae0db230e000000e8d4a510005e3fcda30bd19d45c4b73688da35e7da1fce7c6859b2c1f20ed5202d24144e3e000000e8d4a51000b06a59a2d75bf5d014fce7c999b5e71e7a960870f725847d4ba3235baeaa08ef000000e8d4a510000c910e2fe650e4e01406b3310b489fb60a84bc3ff5c5bee3a56d5898b6a8af32000000e8d4a5100071f2d7b8ec1c8b99a653429b0118cd201f794f409d0fea4d65b1b662f2b00063000000e8d4a51000"; + uint256 public constant INIT_REWARD_FOR_VALIDATOR_SER_CHANGE = 1e16; + 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); + + 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); } - //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) + 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; } - 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; + function isHeaderSynced(uint64 height) external view override returns (bool) { + return submitters[height] != address(0x0) || height == initialHeight; } - 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)) + function getAppHash(uint64 height) external view override returns (bytes32) { + return lightClientConsensusStates[height].appHash; } - uint8 chainIDLength = 0; - for (uint8 j = 0; j < 32; ++j) { - if (chainIDBytes[j] != 0) { - ++chainIDLength; - } else { - break; - } + function getSubmitter(uint64 height) external view override returns (address payable) { + return submitters[height]; } - bytes memory chainIDStr = new bytes(chainIDLength); - for (uint8 j = 0; j < chainIDLength; ++j) { - chainIDStr[j] = chainIDBytes[j]; + 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); } - 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; + // | 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 */ - assembly { - mstore(outputPtr, hash) + 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; } - outputPtr = outputPtr-32; - hash = cs.appHash; + // | chainID | height | appHash | curValidatorSetHash | [{validator pubkey, voting power}] | + // | 32 bytes | 8 bytes | 32 bytes | 32 bytes | [{32 bytes, 8 bytes}] | /* 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); + 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); } - 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"); + 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); } - emit paramChange(key, value); - } } diff --git a/contracts/TokenHub.sol b/contracts/TokenHub.sol index c792c61b..fc2902b9 100644 --- a/contracts/TokenHub.sol +++ b/contracts/TokenHub.sol @@ -14,763 +14,853 @@ 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 = 50000; - uint256 constant public MAX_GAS_FOR_TRANSFER_BNB = 10000; - - uint256 constant public INIT_MINIMUM_RELAY_FEE = 2e15; - uint256 constant public REWARD_UPPER_LIMIT = 1e18; - 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; - // the lock Period for token recover - uint256 constant public LOCK_PERIOD_FOR_TOKEN_RECOVER = 7 days; - // 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-299: Token Migration after BC Fusion - event TokenRecoverLocked(bytes32 indexed tokenSymbol, address indexed tokenAddr, address indexed recipient, uint256 amount, uint256 unlockAt); - event CancelTokenRecoverLock(bytes32 indexed tokenSymbol, address indexed tokenAddr, address indexed attacker, uint256 amount); - event NotBoundToken(bytes32 indexed tokenSymbol, address indexed recipient, uint256 amount); - - // 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 claimMigrationFund(uint256 amount) onlyStakeHub external returns(bool) { - if (address(this).balance >= amount) { - payable(STAKE_HUB_ADDR).transfer(amount); - return true; - } - return false; - } - - 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 + 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 public constant TRANSFER_IN_SUCCESS = 0; + uint8 public constant TRANSFER_IN_FAILURE_TIMEOUT = 1; + uint8 public constant TRANSFER_IN_FAILURE_UNBOUND_TOKEN = 2; + uint8 public constant TRANSFER_IN_FAILURE_INSUFFICIENT_BALANCE = 3; + uint8 public constant TRANSFER_IN_FAILURE_NON_PAYABLE_RECIPIENT = 4; + uint8 public constant TRANSFER_IN_FAILURE_UNKNOWN = 5; + + uint256 public constant MAX_BEP2_TOTAL_SUPPLY = 9000000000000000000; + uint8 public constant MINIMUM_BEP20_SYMBOL_LEN = 2; + uint8 public constant MAXIMUM_BEP20_SYMBOL_LEN = 8; + uint8 public constant BEP2_TOKEN_DECIMALS = 8; + bytes32 public constant BEP2_TOKEN_SYMBOL_FOR_BNB = + 0x424E420000000000000000000000000000000000000000000000000000000000; // "BNB" + uint256 public constant MAX_GAS_FOR_CALLING_BEP20 = 50000; + uint256 public constant MAX_GAS_FOR_TRANSFER_BNB = 10000; + + uint256 public constant INIT_MINIMUM_RELAY_FEE = 2e15; + uint256 public constant REWARD_UPPER_LIMIT = 1e18; + uint256 public constant 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 public constant INIT_BNB_LARGE_TRANSFER_LIMIT = 10000 ether; + uint256 public constant INIT_LOCK_PERIOD = 12 hours; + // the lock period for large cross-chain transfer + uint256 public lockPeriod; + // the lock Period for token recover + uint256 public constant LOCK_PERIOD_FOR_TOKEN_RECOVER = 7 days; + // 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-299: Token Migration after BC Fusion + event TokenRecoverLocked( + bytes32 indexed tokenSymbol, + address indexed tokenAddr, + address indexed recipient, + uint256 amount, + uint256 unlockAt + ); + event CancelTokenRecoverLock( + bytes32 indexed tokenSymbol, address indexed tokenAddr, address indexed attacker, uint256 amount ); - 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 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 + ) external override onlyInit onlyRelayerIncentivize 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 claimMigrationFund(uint256 amount) external onlyStakeHub returns (bool) { + if (address(this).balance >= amount) { + payable(STAKE_HUB_ADDR).transfer(amount); + return true; + } + return false; + } + + 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 + ) external override onlyInit onlyCrossChainContract returns (bytes memory) { + if (channelId == TRANSFER_IN_CHANNELID) { + return handleTransferInSynPackage(msgBytes); } else { - emit refundSuccess(transOutAckPkg.contractAddr, transOutAckPkg.refundAddrs[index], transOutAckPkg.refundAmounts[index], transOutAckPkg.status); - } - } - } else { - for (uint256 index = 0; index= convertedAmount, "insufficient balance"); - _lockRecoverToken(tokenSymbol, contractAddr, convertedAmount, recipient); - }else{ - convertedAmount = amount.mul(TEN_DECIMALS); // native bnb decimals is 8 on BC, while the native bnb decimals on BSC is 18 - require(address(this).balance >= convertedAmount, "insufficient balance"); - address contractAddr = address(0x00); - _lockRecoverToken(tokenSymbol, contractAddr, convertedAmount, recipient); - } - } - - // lock the token for 7 days to the recipient address - function _lockRecoverToken(bytes32 tokenSymbol, address contractAddr, uint256 amount, address recipient) internal { - LockInfo storage lockInfo = lockInfoMap[contractAddr][recipient]; - lockInfo.amount = lockInfo.amount.add(amount); - lockInfo.unlockAt = block.timestamp + LOCK_PERIOD_FOR_TOKEN_RECOVER; - - emit TokenRecoverLocked( - tokenSymbol, - contractAddr, - recipient, - amount, - lockInfo.unlockAt - ); - } - - function cancelTokenRecoverLock(bytes32 tokenSymbol, address attacker) override external onlyTokenRecoverPortal { - address tokenAddress = address(0x00); - if (tokenSymbol != BEP2_TOKEN_SYMBOL_FOR_BNB) { - tokenAddress = bep2SymbolToContractAddr[tokenSymbol]; - require(tokenAddress != address(0x00), "invalid symbol"); - } - LockInfo storage lockInfo = lockInfoMap[tokenAddress][attacker]; - require(lockInfo.amount > 0, "no locked amount"); - - uint256 _amount = lockInfo.amount; - lockInfo.amount = 0; - - emit CancelTokenRecoverLock(tokenSymbol, tokenAddress, attacker, _amount); - } - - /** - * @dev request a cross-chain transfer from BSC to BC - * @notice this function is deprecated after Feynman upgrade - * - * @param contractAddr The token contract which is transferred - * @param recipient The destination address of the cross-chain transfer on BC. - * @param amount The amount to transfer - * @param expireTime The expire time for the cross-chain transfer - */ - function transferOut(address contractAddr, address recipient, uint256 amount, uint64 expireTime) external override onlyInit payable returns (bool) { - revert("deprecated"); - } - - /** - * @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; - } + 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 + ) external override onlyInit onlyCrossChainContract { + 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) external override 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 < list.length; ++index) { + transOutAckPkg.refundAmounts[index] = list[index].toUint(); + } + } else if (idx == 2) { + RLPDecode.RLPItem[] memory list = iter.next().toList(); + transOutAckPkg.refundAddrs = new address[](list.length); + for (uint256 index = 0; index < list.length; ++index) { + transOutAckPkg.refundAddrs[index] = list[index].toAddress(); + } + } else if (idx == 3) { + transOutAckPkg.status = uint32(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (transOutAckPkg, success); + } + + function handleTransferOutAckPackage(bytes memory msgBytes) internal { + (TransferOutAckPackage memory transOutAckPkg, bool decodeSuccess) = decodeTransferOutAckPackage(msgBytes); + require(decodeSuccess, "unrecognized transferOut ack package"); + doRefund(transOutAckPkg); + } + + function doRefund(TransferOutAckPackage memory transOutAckPkg) internal { + if (transOutAckPkg.contractAddr == address(0x0)) { + for (uint256 index = 0; index < transOutAckPkg.refundAmounts.length; ++index) { + (bool success,) = transOutAckPkg.refundAddrs[index].call{ + gas: MAX_GAS_FOR_TRANSFER_BNB, + value: transOutAckPkg.refundAmounts[index] + }(""); + if (!success) { + emit refundFailure( + transOutAckPkg.contractAddr, + transOutAckPkg.refundAddrs[index], + transOutAckPkg.refundAmounts[index], + transOutAckPkg.status + ); + } else { + emit refundSuccess( + transOutAckPkg.contractAddr, + transOutAckPkg.refundAddrs[index], + transOutAckPkg.refundAmounts[index], + transOutAckPkg.status + ); + } + } + } else { + for (uint256 index = 0; index < transOutAckPkg.refundAmounts.length; ++index) { + bool success = IBEP20(transOutAckPkg.contractAddr).transfer{ gas: MAX_GAS_FOR_CALLING_BEP20 }( + transOutAckPkg.refundAddrs[index], transOutAckPkg.refundAmounts[index] + ); + if (success) { + emit refundSuccess( + transOutAckPkg.contractAddr, + transOutAckPkg.refundAddrs[index], + transOutAckPkg.refundAmounts[index], + transOutAckPkg.status + ); + } else { + emit refundFailure( + transOutAckPkg.contractAddr, + transOutAckPkg.refundAddrs[index], + transOutAckPkg.refundAmounts[index], + transOutAckPkg.status + ); + } + } + } + } + + function decodeTransferOutSynPackage(bytes memory msgBytes) + internal + pure + returns (TransferOutSynPackage memory, bool) + { + TransferOutSynPackage memory transOutSynPkg; + + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + transOutSynPkg.bep2TokenSymbol = bytes32(iter.next().toUint()); + } else if (idx == 1) { + transOutSynPkg.contractAddr = iter.next().toAddress(); + } else if (idx == 2) { + RLPDecode.RLPItem[] memory list = iter.next().toList(); + transOutSynPkg.amounts = new uint256[](list.length); + for (uint256 index = 0; index < list.length; ++index) { + transOutSynPkg.amounts[index] = list[index].toUint(); + } + } else if (idx == 3) { + RLPDecode.RLPItem[] memory list = iter.next().toList(); + transOutSynPkg.recipients = new address[](list.length); + for (uint256 index = 0; index < list.length; ++index) { + transOutSynPkg.recipients[index] = list[index].toAddress(); + } + } else if (idx == 4) { + RLPDecode.RLPItem[] memory list = iter.next().toList(); + transOutSynPkg.refundAddrs = new address[](list.length); + for (uint256 index = 0; index < list.length; ++index) { + transOutSynPkg.refundAddrs[index] = list[index].toAddress(); + } + } else if (idx == 5) { + transOutSynPkg.expireTime = uint64(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (transOutSynPkg, success); + } + + function handleTransferOutFailAckPackage(bytes memory msgBytes) internal { + (TransferOutSynPackage memory transOutSynPkg, bool decodeSuccess) = decodeTransferOutSynPackage(msgBytes); + require(decodeSuccess, "unrecognized transferOut syn package"); + TransferOutAckPackage memory transOutAckPkg; + transOutAckPkg.contractAddr = transOutSynPkg.contractAddr; + transOutAckPkg.refundAmounts = transOutSynPkg.amounts; + uint256 bep20TokenDecimals = bep20ContractDecimals[transOutSynPkg.contractAddr]; + for (uint256 idx = 0; idx < transOutSynPkg.amounts.length; ++idx) { + transOutSynPkg.amounts[idx] = convertFromBep2Amount(transOutSynPkg.amounts[idx], bep20TokenDecimals); + } + transOutAckPkg.refundAddrs = transOutSynPkg.refundAddrs; + transOutAckPkg.status = TRANSFER_IN_FAILURE_UNKNOWN; + doRefund(transOutAckPkg); + } + + function encodeTransferOutSynPackage(TransferOutSynPackage memory transOutSynPkg) + internal + pure + returns (bytes memory) + { + bytes[] memory elements = new bytes[](6); + + elements[0] = uint256(transOutSynPkg.bep2TokenSymbol).encodeUint(); + elements[1] = transOutSynPkg.contractAddr.encodeAddress(); + + uint256 batchLength = transOutSynPkg.amounts.length; + + bytes[] memory amountsElements = new bytes[](batchLength); + for (uint256 index = 0; index < batchLength; ++index) { + amountsElements[index] = transOutSynPkg.amounts[index].encodeUint(); + } + elements[2] = amountsElements.encodeList(); + + bytes[] memory recipientsElements = new bytes[](batchLength); + for (uint256 index = 0; index < batchLength; ++index) { + recipientsElements[index] = transOutSynPkg.recipients[index].encodeAddress(); + } + elements[3] = recipientsElements.encodeList(); + + bytes[] memory refundAddrsElements = new bytes[](batchLength); + for (uint256 index = 0; index < batchLength; ++index) { + refundAddrsElements[index] = transOutSynPkg.refundAddrs[index].encodeAddress(); + } + elements[4] = refundAddrsElements.encodeList(); + + elements[5] = uint256(transOutSynPkg.expireTime).encodeUint(); + return elements.encodeList(); + } + + /** + * @dev request a BC token recover from BSC + * + * @param tokenSymbol The token symbol on BSC. + * @param recipient The destination address of the transfer on BSC. + * @param amount The amount to transfer + */ + function recoverBCAsset( + bytes32 tokenSymbol, + address recipient, + uint256 amount + ) external override onlyInit onlyTokenRecoverPortal { + require(amount <= MAX_BEP2_TOTAL_SUPPLY, "amount is too large, exceed maximum bep2 token amount"); + uint256 convertedAmount; + if (tokenSymbol != BEP2_TOKEN_SYMBOL_FOR_BNB) { + address contractAddr = bep2SymbolToContractAddr[tokenSymbol]; + if (contractAddr == address(0x00)) { + // if the token is not bound, just emit an event + // please notify the token owner to handle the token recovery + emit NotBoundToken(tokenSymbol, recipient, amount); + return; + } + + uint256 bep20TokenDecimals = bep20ContractDecimals[contractAddr]; + convertedAmount = convertFromBep2Amount(amount, bep20TokenDecimals); // convert to bep20 amount + require(IBEP20(contractAddr).balanceOf(address(this)) >= convertedAmount, "insufficient balance"); + _lockRecoverToken(tokenSymbol, contractAddr, convertedAmount, recipient); + } else { + convertedAmount = amount.mul(TEN_DECIMALS); // native bnb decimals is 8 on BC, while the native bnb decimals on BSC is 18 + require(address(this).balance >= convertedAmount, "insufficient balance"); + address contractAddr = address(0x00); + _lockRecoverToken(tokenSymbol, contractAddr, convertedAmount, recipient); + } + } + + // lock the token for 7 days to the recipient address + function _lockRecoverToken(bytes32 tokenSymbol, address contractAddr, uint256 amount, address recipient) internal { + LockInfo storage lockInfo = lockInfoMap[contractAddr][recipient]; + lockInfo.amount = lockInfo.amount.add(amount); + lockInfo.unlockAt = block.timestamp + LOCK_PERIOD_FOR_TOKEN_RECOVER; + + emit TokenRecoverLocked(tokenSymbol, contractAddr, recipient, amount, lockInfo.unlockAt); + } + + function cancelTokenRecoverLock(bytes32 tokenSymbol, address attacker) external override onlyTokenRecoverPortal { + address tokenAddress = address(0x00); + if (tokenSymbol != BEP2_TOKEN_SYMBOL_FOR_BNB) { + tokenAddress = bep2SymbolToContractAddr[tokenSymbol]; + require(tokenAddress != address(0x00), "invalid symbol"); + } + LockInfo storage lockInfo = lockInfoMap[tokenAddress][attacker]; + require(lockInfo.amount > 0, "no locked amount"); + + uint256 _amount = lockInfo.amount; + lockInfo.amount = 0; + + emit CancelTokenRecoverLock(tokenSymbol, tokenAddress, attacker, _amount); + } + + /** + * @dev request a cross-chain transfer from BSC to BC + * @notice this function is deprecated after Feynman upgrade + * + * @param contractAddr The token contract which is transferred + * @param recipient The destination address of the cross-chain transfer on BC. + * @param amount The amount to transfer + * @param expireTime The expire time for the cross-chain transfer + */ + function transferOut( + address contractAddr, + address recipient, + uint256 amount, + uint64 expireTime + ) external payable override onlyInit returns (bool) { + revert("deprecated"); + } + + /** + * @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 payable override onlyInit 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 (uint256 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) external override 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/TokenManager.sol b/contracts/TokenManager.sol index fd521244..f5f8ca78 100644 --- a/contracts/TokenManager.sol +++ b/contracts/TokenManager.sol @@ -11,597 +11,697 @@ import "./lib/RLPDecode.sol"; import "./System.sol"; contract TokenManager is System, IApplication, IParamSubscriber { + using SafeMath for uint256; + + using RLPEncode for *; + using RLPDecode for *; + + using RLPDecode for RLPDecode.RLPItem; + using RLPDecode for RLPDecode.Iterator; + + // BC to BSC + struct BindSynPackage { + uint8 packageType; + bytes32 bep2TokenSymbol; + address contractAddr; + uint256 totalSupply; + uint256 peggyAmount; + uint8 bep20Decimals; + uint64 expireTime; + } + + // BSC to BC + struct ReactBindSynPackage { + uint32 status; + bytes32 bep2TokenSymbol; + } + + // BSC to BC + struct MirrorSynPackage { + address mirrorSender; + address bep20Addr; + bytes32 bep20Name; + bytes32 bep20Symbol; + uint256 bep20Supply; + uint8 bep20Decimals; + uint256 mirrorFee; + uint64 expireTime; + } + + // BC to BSC + struct MirrorAckPackage { + address mirrorSender; + address bep20Addr; + uint8 bep20Decimals; + bytes32 bep2Symbol; + uint256 mirrorFee; + uint8 errorCode; + } + + // BSC to BC + struct SyncSynPackage { + address syncSender; + address bep20Addr; + bytes32 bep2Symbol; + uint256 bep20Supply; + uint256 syncFee; + uint64 expireTime; + } + + // BC to BSC + struct SyncAckPackage { + address syncSender; + address bep20Addr; + uint256 syncFee; + uint8 errorCode; + } + + uint8 public constant BIND_PACKAGE = 0; + uint8 public constant UNBIND_PACKAGE = 1; + + // bind status + uint8 public constant BIND_STATUS_TIMEOUT = 1; + uint8 public constant BIND_STATUS_SYMBOL_MISMATCH = 2; + uint8 public constant BIND_STATUS_TOO_MUCH_TOKENHUB_BALANCE = 3; + uint8 public constant BIND_STATUS_TOTAL_SUPPLY_MISMATCH = 4; + uint8 public constant BIND_STATUS_DECIMALS_MISMATCH = 5; + uint8 public constant BIND_STATUS_ALREADY_BOUND_TOKEN = 6; + uint8 public constant BIND_STATUS_REJECTED = 7; + + uint8 public constant MIRROR_CHANNELID = 0x04; + uint8 public constant SYNC_CHANNELID = 0x05; + uint8 public constant BEP2_TOKEN_DECIMALS = 8; + uint256 public constant MAX_GAS_FOR_TRANSFER_BNB = 10000; + uint256 public constant MAX_BEP2_TOTAL_SUPPLY = 9000000000000000000; + uint256 public constant LOG_MAX_UINT256 = 77; + // mirror status + uint8 public constant MIRROR_STATUS_TIMEOUT = 1; + uint8 public constant MIRROR_STATUS_DUPLICATED_BEP2_SYMBOL = 2; + uint8 public constant MIRROR_STATUS_ALREADY_BOUND = 3; + // sync status + uint8 public constant SYNC_STATUS_TIMEOUT = 1; + uint8 public constant SYNC_STATUS_NOT_BOUND_MIRROR = 2; + + uint8 public constant MINIMUM_BEP20_SYMBOL_LEN = 2; + uint8 public constant MAXIMUM_BEP20_SYMBOL_LEN = 8; + + uint256 public constant TEN_DECIMALS = 1e10; + + mapping(bytes32 => BindSynPackage) public bindPackageRecord; + + mapping(address => bool) public mirrorPendingRecord; + mapping(address => bool) public boundByMirror; + uint256 public mirrorFee; + uint256 public syncFee; + + event bindSuccess(address indexed contractAddr, string bep2Symbol, uint256 totalSupply, uint256 peggyAmount); + event bindFailure(address indexed contractAddr, string bep2Symbol, uint32 failedReason); + event unexpectedPackage(uint8 channelId, bytes msgBytes); + event paramChange(string key, bytes value); + event mirrorSuccess(address indexed bep20Addr, bytes32 bep2Symbol); + event mirrorFailure(address indexed bep20Addr, uint8 errCode); + event syncSuccess(address indexed bep20Addr); + event syncFailure(address indexed bep20Addr, uint8 errCode); + + function handleSynPackage( + uint8 channelId, + bytes calldata msgBytes + ) external override onlyCrossChainContract returns (bytes memory) { + if (channelId == BIND_CHANNELID) { + return handleBindSynPackage(msgBytes); + } else { + emit unexpectedPackage(channelId, msgBytes); + return new bytes(0); + } + } + + function handleAckPackage(uint8 channelId, bytes calldata msgBytes) external override onlyCrossChainContract { + if (channelId == MIRROR_CHANNELID) { + handleMirrorAckPackage(msgBytes); + } else if (channelId == SYNC_CHANNELID) { + handleSyncAckPackage(msgBytes); + } else { + emit unexpectedPackage(channelId, msgBytes); + } + } + + function handleFailAckPackage(uint8 channelId, bytes calldata msgBytes) external override onlyCrossChainContract { + if (channelId == MIRROR_CHANNELID) { + handleMirrorFailAckPackage(msgBytes); + } else if (channelId == SYNC_CHANNELID) { + handleSyncFailAckPackage(msgBytes); + } else { + emit unexpectedPackage(channelId, msgBytes); + } + } - using SafeMath for uint256; - - using RLPEncode for *; - using RLPDecode for *; - - using RLPDecode for RLPDecode.RLPItem; - using RLPDecode for RLPDecode.Iterator; - - // BC to BSC - struct BindSynPackage { - uint8 packageType; - bytes32 bep2TokenSymbol; - address contractAddr; - uint256 totalSupply; - uint256 peggyAmount; - uint8 bep20Decimals; - uint64 expireTime; - } - - // BSC to BC - struct ReactBindSynPackage { - uint32 status; - bytes32 bep2TokenSymbol; - } - - // BSC to BC - struct MirrorSynPackage { - address mirrorSender; - address bep20Addr; - bytes32 bep20Name; - bytes32 bep20Symbol; - uint256 bep20Supply; - uint8 bep20Decimals; - uint256 mirrorFee; - uint64 expireTime; - } - - // BC to BSC - struct MirrorAckPackage { - address mirrorSender; - address bep20Addr; - uint8 bep20Decimals; - bytes32 bep2Symbol; - uint256 mirrorFee; - uint8 errorCode; - } - - // BSC to BC - struct SyncSynPackage { - address syncSender; - address bep20Addr; - bytes32 bep2Symbol; - uint256 bep20Supply; - uint256 syncFee; - uint64 expireTime; - } - - // BC to BSC - struct SyncAckPackage { - address syncSender; - address bep20Addr; - uint256 syncFee; - uint8 errorCode; - } - - uint8 constant public BIND_PACKAGE = 0; - uint8 constant public UNBIND_PACKAGE = 1; - - // bind status - uint8 constant public BIND_STATUS_TIMEOUT = 1; - uint8 constant public BIND_STATUS_SYMBOL_MISMATCH = 2; - uint8 constant public BIND_STATUS_TOO_MUCH_TOKENHUB_BALANCE = 3; - uint8 constant public BIND_STATUS_TOTAL_SUPPLY_MISMATCH = 4; - uint8 constant public BIND_STATUS_DECIMALS_MISMATCH = 5; - uint8 constant public BIND_STATUS_ALREADY_BOUND_TOKEN = 6; - uint8 constant public BIND_STATUS_REJECTED = 7; - - uint8 constant public MIRROR_CHANNELID = 0x04; - uint8 constant public SYNC_CHANNELID = 0x05; - uint8 constant public BEP2_TOKEN_DECIMALS = 8; - uint256 constant public MAX_GAS_FOR_TRANSFER_BNB=10000; - uint256 constant public MAX_BEP2_TOTAL_SUPPLY = 9000000000000000000; - uint256 constant public LOG_MAX_UINT256 = 77; - // mirror status - uint8 constant public MIRROR_STATUS_TIMEOUT = 1; - uint8 constant public MIRROR_STATUS_DUPLICATED_BEP2_SYMBOL = 2; - uint8 constant public MIRROR_STATUS_ALREADY_BOUND = 3; - // sync status - uint8 constant public SYNC_STATUS_TIMEOUT = 1; - uint8 constant public SYNC_STATUS_NOT_BOUND_MIRROR = 2; - - uint8 constant public MINIMUM_BEP20_SYMBOL_LEN = 2; - uint8 constant public MAXIMUM_BEP20_SYMBOL_LEN = 8; - - uint256 constant public TEN_DECIMALS = 1e10; - - mapping(bytes32 => BindSynPackage) public bindPackageRecord; - - mapping(address => bool) public mirrorPendingRecord; - mapping(address => bool) public boundByMirror; - uint256 public mirrorFee; - uint256 public syncFee; - - event bindSuccess(address indexed contractAddr, string bep2Symbol, uint256 totalSupply, uint256 peggyAmount); - event bindFailure(address indexed contractAddr, string bep2Symbol, uint32 failedReason); - event unexpectedPackage(uint8 channelId, bytes msgBytes); - event paramChange(string key, bytes value); - event mirrorSuccess(address indexed bep20Addr, bytes32 bep2Symbol); - event mirrorFailure(address indexed bep20Addr, uint8 errCode); - event syncSuccess(address indexed bep20Addr); - event syncFailure(address indexed bep20Addr, uint8 errCode); - - function handleSynPackage(uint8 channelId, bytes calldata msgBytes) onlyCrossChainContract external override returns(bytes memory) { - if (channelId == BIND_CHANNELID) { - return handleBindSynPackage(msgBytes); - } else { - emit unexpectedPackage(channelId, msgBytes); - return new bytes(0); - } - } - - function handleAckPackage(uint8 channelId, bytes calldata msgBytes) onlyCrossChainContract external override { - if (channelId == MIRROR_CHANNELID) { - handleMirrorAckPackage(msgBytes); - } else if (channelId == SYNC_CHANNELID) { - handleSyncAckPackage(msgBytes); - } else { - emit unexpectedPackage(channelId, msgBytes); - } - } - - function handleFailAckPackage(uint8 channelId, bytes calldata msgBytes) onlyCrossChainContract external override { - if (channelId == MIRROR_CHANNELID) { - handleMirrorFailAckPackage(msgBytes); - } else if (channelId == SYNC_CHANNELID) { - handleSyncFailAckPackage(msgBytes); - } else { - emit unexpectedPackage(channelId, msgBytes); - } - } - - function decodeBindSynPackage(bytes memory msgBytes) internal pure returns(BindSynPackage memory, bool) { - BindSynPackage memory bindSynPkg; - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) bindSynPkg.packageType = uint8(iter.next().toUint()); - else if (idx == 1) bindSynPkg.bep2TokenSymbol = bytes32(iter.next().toUint()); - else if (idx == 2) bindSynPkg.contractAddr = iter.next().toAddress(); - else if (idx == 3) bindSynPkg.totalSupply = iter.next().toUint(); - else if (idx == 4) bindSynPkg.peggyAmount = iter.next().toUint(); - else if (idx == 5) bindSynPkg.bep20Decimals = uint8(iter.next().toUint()); - else if (idx == 6) { - bindSynPkg.expireTime = uint64(iter.next().toUint()); - success = true; - } - else break; - ++idx; - } - return (bindSynPkg, success); - } - - function handleBindSynPackage(bytes memory msgBytes) internal returns(bytes memory) { - (BindSynPackage memory bindSynPkg, bool success) = decodeBindSynPackage(msgBytes); - require(success, "unrecognized transferIn package"); - if (bindSynPkg.packageType == BIND_PACKAGE) { - bindPackageRecord[bindSynPkg.bep2TokenSymbol]=bindSynPkg; - } else if (bindSynPkg.packageType == UNBIND_PACKAGE) { - address contractAddr = ITokenHub(TOKEN_HUB_ADDR).getContractAddrByBEP2Symbol(bindSynPkg.bep2TokenSymbol); - if (contractAddr!=address(0x00)) { - ITokenHub(TOKEN_HUB_ADDR).unbindToken(bindSynPkg.bep2TokenSymbol, contractAddr); - } - } else { - require(false, "unrecognized bind package"); - } - return new bytes(0); - } - - function encodeReactBindSynPackage(ReactBindSynPackage memory reactBindSynPackage) internal pure returns (bytes memory) { - bytes[] memory elements = new bytes[](2); - elements[0] = reactBindSynPackage.status.encodeUint(); - elements[1] = uint256(reactBindSynPackage.bep2TokenSymbol).encodeUint(); - return elements.encodeList(); - } - - function approveBind(address contractAddr, string memory bep2Symbol) payable public returns (bool) { - require(!mirrorPendingRecord[contractAddr], "the bep20 token is in mirror pending status"); - bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol); - BindSynPackage memory bindSynPkg = bindPackageRecord[bep2TokenSymbol]; - require(bindSynPkg.bep2TokenSymbol!=bytes32(0x00), "bind request doesn't exist"); - uint256 lockedAmount = bindSynPkg.totalSupply.sub(bindSynPkg.peggyAmount); - require(contractAddr==bindSynPkg.contractAddr, "contact address doesn't equal to the contract address in bind request"); - require(IBEP20(contractAddr).getOwner()==msg.sender, "only bep20 owner can approve this bind request"); - uint256 tokenHubBalance = IBEP20(contractAddr).balanceOf(TOKEN_HUB_ADDR); - require(IBEP20(contractAddr).allowance(msg.sender, address(this)).add(tokenHubBalance)>=lockedAmount, "allowance is not enough"); - uint256 relayFee = msg.value; - uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); - require(relayFee >= miniRelayFee && relayFee%TEN_DECIMALS == 0, "relayFee must be N * 1e10 and greater than miniRelayFee"); - - uint32 verifyCode = verifyBindParameters(bindSynPkg, contractAddr); - if (verifyCode == CODE_OK) { - IBEP20(contractAddr).transferFrom(msg.sender, TOKEN_HUB_ADDR, lockedAmount.sub(tokenHubBalance)); - ITokenHub(TOKEN_HUB_ADDR).bindToken(bindSynPkg.bep2TokenSymbol, bindSynPkg.contractAddr, bindSynPkg.bep20Decimals); - emit bindSuccess(contractAddr, bep2Symbol, bindSynPkg.totalSupply, lockedAmount); - } else { - emit bindFailure(contractAddr, bep2Symbol, verifyCode); - } - delete bindPackageRecord[bep2TokenSymbol]; - ReactBindSynPackage memory reactBindSynPackage = ReactBindSynPackage({ - status: verifyCode, - bep2TokenSymbol: bep2TokenSymbol - }); - address(uint160(TOKEN_HUB_ADDR)).transfer(relayFee); - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(BIND_CHANNELID, encodeReactBindSynPackage(reactBindSynPackage), relayFee.div(TEN_DECIMALS)); - return true; - } - - function rejectBind(address contractAddr, string memory bep2Symbol) payable public returns (bool) { - bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol); - BindSynPackage memory bindSynPkg = bindPackageRecord[bep2TokenSymbol]; - require(bindSynPkg.bep2TokenSymbol!=bytes32(0x00), "bind request doesn't exist"); - require(contractAddr==bindSynPkg.contractAddr, "contact address doesn't equal to the contract address in bind request"); - require(IBEP20(contractAddr).getOwner()==msg.sender, "only bep20 owner can reject"); - uint256 relayFee = msg.value; - uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); - require(relayFee >= miniRelayFee && relayFee%TEN_DECIMALS == 0, "relayFee must be N * 1e10 and greater than miniRelayFee"); - delete bindPackageRecord[bep2TokenSymbol]; - ReactBindSynPackage memory reactBindSynPackage = ReactBindSynPackage({ - status: BIND_STATUS_REJECTED, - bep2TokenSymbol: bep2TokenSymbol - }); - address(uint160(TOKEN_HUB_ADDR)).transfer(relayFee); - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(BIND_CHANNELID, encodeReactBindSynPackage(reactBindSynPackage), relayFee.div(TEN_DECIMALS)); - emit bindFailure(contractAddr, bep2Symbol, BIND_STATUS_REJECTED); - return true; - } - - function expireBind(string memory bep2Symbol) payable public returns (bool) { - bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol); - BindSynPackage memory bindSynPkg = bindPackageRecord[bep2TokenSymbol]; - require(bindSynPkg.bep2TokenSymbol!=bytes32(0x00), "bind request doesn't exist"); - require(bindSynPkg.expireTime= miniRelayFee &&relayFee%TEN_DECIMALS == 0, "relayFee must be N * 1e10 and greater than miniRelayFee"); - delete bindPackageRecord[bep2TokenSymbol]; - ReactBindSynPackage memory reactBindSynPackage = ReactBindSynPackage({ - status: BIND_STATUS_TIMEOUT, - bep2TokenSymbol: bep2TokenSymbol - }); - address(uint160(TOKEN_HUB_ADDR)).transfer(relayFee); - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(BIND_CHANNELID, encodeReactBindSynPackage(reactBindSynPackage), relayFee.div(TEN_DECIMALS)); - emit bindFailure(bindSynPkg.contractAddr, bep2Symbol, BIND_STATUS_TIMEOUT); - return true; - } - - function encodeMirrorSynPackage(MirrorSynPackage memory mirrorSynPackage) internal pure returns (bytes memory) { - bytes[] memory elements = new bytes[](8); - elements[0] = mirrorSynPackage.mirrorSender.encodeAddress(); - elements[1] = mirrorSynPackage.bep20Addr.encodeAddress(); - elements[2] = uint256(mirrorSynPackage.bep20Name).encodeUint(); - elements[3] = uint256(mirrorSynPackage.bep20Symbol).encodeUint(); - elements[4] = mirrorSynPackage.bep20Supply.encodeUint(); - elements[5] = uint256(mirrorSynPackage.bep20Decimals).encodeUint(); - elements[6] = mirrorSynPackage.mirrorFee.encodeUint(); - elements[7] = uint256(mirrorSynPackage.expireTime).encodeUint(); - return elements.encodeList(); - } - - function decodeMirrorSynPackage(bytes memory msgBytes) internal pure returns(MirrorSynPackage memory, bool) { - MirrorSynPackage memory mirrorSynPackage; - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) mirrorSynPackage.mirrorSender = iter.next().toAddress(); - else if (idx == 1) mirrorSynPackage.bep20Addr = iter.next().toAddress(); - else if (idx == 2) mirrorSynPackage.bep20Name = bytes32(iter.next().toUint()); - else if (idx == 3) mirrorSynPackage.bep20Symbol = bytes32(iter.next().toUint()); - else if (idx == 4) mirrorSynPackage.bep20Supply = iter.next().toUint(); - else if (idx == 5) mirrorSynPackage.bep20Decimals = uint8(iter.next().toUint()); - else if (idx == 6) mirrorSynPackage.mirrorFee = iter.next().toUint(); - else if (idx == 7) { - mirrorSynPackage.expireTime = uint64(iter.next().toUint()); - success = true; - } - else break; - ++idx; - } - return (mirrorSynPackage, success); - } - - function decodeMirrorAckPackage(bytes memory msgBytes) internal pure returns(MirrorAckPackage memory, bool) { - MirrorAckPackage memory mirrorAckPackage; - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) mirrorAckPackage.mirrorSender = iter.next().toAddress(); - else if (idx == 1) mirrorAckPackage.bep20Addr = iter.next().toAddress(); - else if (idx == 2) mirrorAckPackage.bep20Decimals = uint8(iter.next().toUint()); - else if (idx == 3) mirrorAckPackage.bep2Symbol = bytes32(iter.next().toUint()); - else if (idx == 4) mirrorAckPackage.mirrorFee = iter.next().toUint(); - else if (idx == 5) { - mirrorAckPackage.errorCode = uint8(iter.next().toUint()); - success = true; - } - else break; - ++idx; - } - return (mirrorAckPackage, success); - } - - function mirror(address bep20Addr, uint64 expireTime) payable public returns (bool) { - require(ITokenHub(TOKEN_HUB_ADDR).getBep2SymbolByContractAddr(bep20Addr) == bytes32(0x00), "already bound"); - require(!mirrorPendingRecord[bep20Addr], "mirror pending"); - uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); - require(msg.value%TEN_DECIMALS == 0 && msg.value>=mirrorFee.add(miniRelayFee), "msg.value must be N * 1e10 and greater than sum of miniRelayFee and mirrorFee"); - require(expireTime>=block.timestamp + 120 && expireTime <= block.timestamp + 86400, "expireTime must be two minutes later and one day earlier"); - uint8 decimals = IBEP20(bep20Addr).decimals(); - uint256 totalSupply = IBEP20(bep20Addr).totalSupply(); - require(convertToBep2Amount(totalSupply, decimals) <= MAX_BEP2_TOTAL_SUPPLY, "too large total supply"); - string memory name = IBEP20(bep20Addr).name(); - bytes memory nameBytes = bytes(name); - require(nameBytes.length>=1 && nameBytes.length<=32, "name length must be in [1,32]"); - string memory symbol = IBEP20(bep20Addr).symbol(); - bytes memory symbolBytes = bytes(symbol); - require(symbolBytes.length>=MINIMUM_BEP20_SYMBOL_LEN && symbolBytes.length<=MAXIMUM_BEP20_SYMBOL_LEN, "symbol length must be in [2,8]"); - for (uint8 i = 0; i < symbolBytes.length; ++i) { - require((symbolBytes[i]>='A' && symbolBytes[i]<='Z') || (symbolBytes[i]>='a' && symbolBytes[i]<='z') || (symbolBytes[i]>='0' && symbolBytes[i]<='9'), "symbol should only contain alphabet and number"); - } - address(uint160(TOKEN_HUB_ADDR)).transfer(msg.value.sub(mirrorFee)); - mirrorPendingRecord[bep20Addr] = true; - bytes32 bytes32Name; - assembly { - bytes32Name := mload(add(name, 32)) - } - bytes32 bytes32Symbol; - assembly { - bytes32Symbol := mload(add(symbol, 32)) - } - MirrorSynPackage memory mirrorSynPackage = MirrorSynPackage({ - mirrorSender: msg.sender, - bep20Addr: bep20Addr, - bep20Name: bytes32Name, - bep20Symbol: bytes32Symbol, - bep20Supply: totalSupply, - bep20Decimals: decimals, - mirrorFee: mirrorFee.div(TEN_DECIMALS), - expireTime: expireTime - }); - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(MIRROR_CHANNELID, encodeMirrorSynPackage(mirrorSynPackage), msg.value.sub(mirrorFee).div(TEN_DECIMALS)); - return true; - } - - function handleMirrorAckPackage(bytes memory msgBytes) internal { - (MirrorAckPackage memory mirrorAckPackage, bool decodeSuccess) = decodeMirrorAckPackage(msgBytes); - require(decodeSuccess, "unrecognized package"); - mirrorPendingRecord[mirrorAckPackage.bep20Addr] = false; - if (mirrorAckPackage.errorCode == CODE_OK ) { - address(uint160(TOKEN_HUB_ADDR)).transfer(mirrorAckPackage.mirrorFee); - ITokenHub(TOKEN_HUB_ADDR).bindToken(mirrorAckPackage.bep2Symbol, mirrorAckPackage.bep20Addr, mirrorAckPackage.bep20Decimals); - boundByMirror[mirrorAckPackage.bep20Addr] = true; - emit mirrorSuccess(mirrorAckPackage.bep20Addr, mirrorAckPackage.bep2Symbol); - return; - } else { - (bool success, ) = mirrorAckPackage.mirrorSender.call{gas: MAX_GAS_FOR_TRANSFER_BNB, value: mirrorAckPackage.mirrorFee}(""); - if (!success) { - address(uint160(SYSTEM_REWARD_ADDR)).transfer(mirrorAckPackage.mirrorFee); - } - emit mirrorFailure(mirrorAckPackage.bep20Addr, mirrorAckPackage.errorCode); - } - } - - function handleMirrorFailAckPackage(bytes memory msgBytes) internal { - (MirrorSynPackage memory mirrorSynPackage, bool decodeSuccess) = decodeMirrorSynPackage(msgBytes); - require(decodeSuccess, "unrecognized package"); - mirrorPendingRecord[mirrorSynPackage.bep20Addr] = false; - (bool success, ) = mirrorSynPackage.mirrorSender.call{gas: MAX_GAS_FOR_TRANSFER_BNB, value: mirrorSynPackage.mirrorFee.mul(TEN_DECIMALS)}(""); - if (!success) { - address(uint160(SYSTEM_REWARD_ADDR)).transfer(mirrorSynPackage.mirrorFee.mul(TEN_DECIMALS)); - } - } - - function encodeSyncSynPackage(SyncSynPackage memory syncSynPackage) internal pure returns (bytes memory) { - bytes[] memory elements = new bytes[](6); - elements[0] = syncSynPackage.syncSender.encodeAddress(); - elements[1] = syncSynPackage.bep20Addr.encodeAddress(); - elements[2] = uint256(syncSynPackage.bep2Symbol).encodeUint(); - elements[3] = syncSynPackage.bep20Supply.encodeUint(); - elements[4] = syncSynPackage.syncFee.encodeUint(); - elements[5] = uint256(syncSynPackage.expireTime).encodeUint(); - return elements.encodeList(); - } - - function decodeSyncSynPackage(bytes memory msgBytes) internal pure returns(SyncSynPackage memory, bool) { - SyncSynPackage memory syncSynPackage; - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) syncSynPackage.syncSender = iter.next().toAddress(); - else if (idx == 1) syncSynPackage.bep20Addr = iter.next().toAddress(); - else if (idx == 2) syncSynPackage.bep2Symbol = bytes32(iter.next().toUint()); - else if (idx == 3) syncSynPackage.bep20Supply = iter.next().toUint(); - else if (idx == 4) syncSynPackage.syncFee = iter.next().toUint(); - else if (idx == 5) { - syncSynPackage.expireTime = uint64(iter.next().toUint()); - success = true; - } - else break; - ++idx; - } - return (syncSynPackage, success); - } - - function decodeSyncAckPackage(bytes memory msgBytes) internal pure returns(SyncAckPackage memory, bool) { - SyncAckPackage memory syncAckPackage; - RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); - bool success = false; - uint256 idx=0; - while (iter.hasNext()) { - if (idx == 0) syncAckPackage.syncSender = iter.next().toAddress(); - else if (idx == 1) syncAckPackage.bep20Addr = iter.next().toAddress(); - else if (idx == 2) syncAckPackage.syncFee = iter.next().toUint(); - else if (idx == 3) { - syncAckPackage.errorCode = uint8(iter.next().toUint()); - success = true; - } - else break; - ++idx; - } - return (syncAckPackage, success); - } - - function sync(address bep20Addr, uint64 expireTime) payable public returns (bool) { - bytes32 bep2Symbol = ITokenHub(TOKEN_HUB_ADDR).getBep2SymbolByContractAddr(bep20Addr); - require(bep2Symbol != bytes32(0x00), "not bound"); - require(boundByMirror[bep20Addr], "not bound by mirror"); - uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); - require(msg.value%TEN_DECIMALS == 0 && msg.value>=syncFee.add(miniRelayFee), "msg.value must be N * 1e10 and no less sum of miniRelayFee and syncFee"); - require(expireTime>=block.timestamp + 120 && expireTime <= block.timestamp + 86400, "expireTime must be two minutes later and one day earlier"); - uint256 totalSupply = IBEP20(bep20Addr).totalSupply(); - uint8 decimals = IBEP20(bep20Addr).decimals(); - require(convertToBep2Amount(totalSupply, decimals) <= MAX_BEP2_TOTAL_SUPPLY, "too large total supply"); - - address(uint160(TOKEN_HUB_ADDR)).transfer(msg.value.sub(syncFee)); - SyncSynPackage memory syncSynPackage = SyncSynPackage({ - syncSender: msg.sender, - bep20Addr: bep20Addr, - bep2Symbol: bep2Symbol, - bep20Supply: totalSupply, - syncFee: syncFee.div(TEN_DECIMALS), - expireTime: expireTime - }); - ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage(SYNC_CHANNELID, encodeSyncSynPackage(syncSynPackage), msg.value.sub(syncFee).div(TEN_DECIMALS)); - return true; - } - - function handleSyncAckPackage(bytes memory msgBytes) internal { - (SyncAckPackage memory syncAckPackage, bool decodeSuccess) = decodeSyncAckPackage(msgBytes); - require(decodeSuccess, "unrecognized package"); - if (syncAckPackage.errorCode == CODE_OK ) { - address(uint160(TOKEN_HUB_ADDR)).transfer(syncAckPackage.syncFee); - emit syncSuccess(syncAckPackage.bep20Addr); - return; - } else { - emit syncFailure(syncAckPackage.bep20Addr, syncAckPackage.errorCode); - } - (bool success, ) = syncAckPackage.syncSender.call{gas: MAX_GAS_FOR_TRANSFER_BNB, value: syncAckPackage.syncFee}(""); - if (!success) { - address(uint160(SYSTEM_REWARD_ADDR)).transfer(syncAckPackage.syncFee); - } - } - - function handleSyncFailAckPackage(bytes memory msgBytes) internal { - (SyncSynPackage memory syncSynPackage, bool decodeSuccess) = decodeSyncSynPackage(msgBytes); - require(decodeSuccess, "unrecognized package"); - (bool success, ) = syncSynPackage.syncSender.call{gas: MAX_GAS_FOR_TRANSFER_BNB, value: syncSynPackage.syncFee.mul(TEN_DECIMALS)}(""); - if (!success) { - address(uint160(SYSTEM_REWARD_ADDR)).transfer(syncSynPackage.syncFee.mul(TEN_DECIMALS)); - } - } - - function updateParam(string calldata key, bytes calldata value) override external onlyGov { - require(value.length == 32, "expected value length 32"); - string memory localKey = key; - bytes memory localValue = value; - bytes32 bytes32Key; - assembly { - bytes32Key := mload(add(localKey, 32)) - } - if (bytes32Key == bytes32(0x6d6972726f724665650000000000000000000000000000000000000000000000)) { // mirrorFee - uint256 newMirrorFee; - assembly { - newMirrorFee := mload(add(localValue, 32)) - } - require(newMirrorFee%(TEN_DECIMALS)==0, "mirrorFee must be N * 1e10"); - mirrorFee = newMirrorFee; - } else if (bytes32Key == bytes32(0x73796e6346656500000000000000000000000000000000000000000000000000)) { // syncFee - uint256 newSyncFee; - assembly { - newSyncFee := mload(add(localValue, 32)) - } - require(newSyncFee%(TEN_DECIMALS)==0, "syncFee must be N * 1e10"); - syncFee = newSyncFee; - } else { - require(false, "unknown param"); - } - emit paramChange(key, value); - } - - function bep2TokenSymbolConvert(string memory symbol) internal pure returns(bytes32) { - bytes32 result; - assembly { - result := mload(add(symbol, 32)) - } - return result; - } - - function queryRequiredLockAmountForBind(string memory symbol) public view returns(uint256) { - bytes32 bep2Symbol; - assembly { - bep2Symbol := mload(add(symbol, 32)) - } - BindSynPackage memory bindRequest = bindPackageRecord[bep2Symbol]; - if (bindRequest.contractAddr==address(0x00)) { - return 0; - } - uint256 tokenHubBalance = IBEP20(bindRequest.contractAddr).balanceOf(TOKEN_HUB_ADDR); - uint256 requiredBalance = bindRequest.totalSupply.sub(bindRequest.peggyAmount); - return requiredBalance.sub(tokenHubBalance); - } - - function verifyBindParameters(BindSynPackage memory bindSynPkg, address contractAddr) internal view returns(uint32) { - uint256 decimals = IBEP20(contractAddr).decimals(); - string memory bep20Symbol = IBEP20(contractAddr).symbol(); - uint256 tokenHubBalance = IBEP20(contractAddr).balanceOf(TOKEN_HUB_ADDR); - uint256 lockedAmount = bindSynPkg.totalSupply.sub(bindSynPkg.peggyAmount); - if (bindSynPkg.expireTime lockedAmount) { - return BIND_STATUS_TOO_MUCH_TOKENHUB_BALANCE; - } - if (IBEP20(bindSynPkg.contractAddr).totalSupply() != bindSynPkg.totalSupply) { - return BIND_STATUS_TOTAL_SUPPLY_MISMATCH; - } - if (decimals!=bindSynPkg.bep20Decimals) { - return BIND_STATUS_DECIMALS_MISMATCH; - } - if (ITokenHub(TOKEN_HUB_ADDR).getContractAddrByBEP2Symbol(bindSynPkg.bep2TokenSymbol)!=address(0x00)|| - ITokenHub(TOKEN_HUB_ADDR).getBep2SymbolByContractAddr(bindSynPkg.contractAddr)!=bytes32(0x00)) { - return BIND_STATUS_ALREADY_BOUND_TOKEN; - } - return CODE_OK; - } - - function checkSymbol(string memory bep20Symbol, bytes32 bep2TokenSymbol) internal pure returns(bool) { - bytes memory bep20SymbolBytes = bytes(bep20Symbol); - if (bep20SymbolBytes.length > MAXIMUM_BEP20_SYMBOL_LEN || bep20SymbolBytes.length < MINIMUM_BEP20_SYMBOL_LEN) { - return false; - } - - bytes memory bep2TokenSymbolBytes = new bytes(32); - assembly { - mstore(add(bep2TokenSymbolBytes, 32), bep2TokenSymbol) - } - if (bep2TokenSymbolBytes[bep20SymbolBytes.length] != 0x2d) { // '-' - return false; - } - bool symbolMatch = true; - for (uint256 index=0; index < bep20SymbolBytes.length; ++index) { - if (bep20SymbolBytes[index] != bep2TokenSymbolBytes[index]) { - symbolMatch = false; - break; - } - } - return symbolMatch; - } - - function convertToBep2Amount(uint256 amount, uint256 bep20TokenDecimals) internal pure returns (uint256) { - if (bep20TokenDecimals > BEP2_TOKEN_DECIMALS) { - require(bep20TokenDecimals-BEP2_TOKEN_DECIMALS <= LOG_MAX_UINT256, "too large decimals"); - return amount.div(10**(bep20TokenDecimals-BEP2_TOKEN_DECIMALS)); - } - return amount.mul(10**(BEP2_TOKEN_DECIMALS-bep20TokenDecimals)); - } + function decodeBindSynPackage(bytes memory msgBytes) internal pure returns (BindSynPackage memory, bool) { + BindSynPackage memory bindSynPkg; + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + bindSynPkg.packageType = uint8(iter.next().toUint()); + } else if (idx == 1) { + bindSynPkg.bep2TokenSymbol = bytes32(iter.next().toUint()); + } else if (idx == 2) { + bindSynPkg.contractAddr = iter.next().toAddress(); + } else if (idx == 3) { + bindSynPkg.totalSupply = iter.next().toUint(); + } else if (idx == 4) { + bindSynPkg.peggyAmount = iter.next().toUint(); + } else if (idx == 5) { + bindSynPkg.bep20Decimals = uint8(iter.next().toUint()); + } else if (idx == 6) { + bindSynPkg.expireTime = uint64(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (bindSynPkg, success); + } + + function handleBindSynPackage(bytes memory msgBytes) internal returns (bytes memory) { + (BindSynPackage memory bindSynPkg, bool success) = decodeBindSynPackage(msgBytes); + require(success, "unrecognized transferIn package"); + if (bindSynPkg.packageType == BIND_PACKAGE) { + bindPackageRecord[bindSynPkg.bep2TokenSymbol] = bindSynPkg; + } else if (bindSynPkg.packageType == UNBIND_PACKAGE) { + address contractAddr = ITokenHub(TOKEN_HUB_ADDR).getContractAddrByBEP2Symbol(bindSynPkg.bep2TokenSymbol); + if (contractAddr != address(0x00)) { + ITokenHub(TOKEN_HUB_ADDR).unbindToken(bindSynPkg.bep2TokenSymbol, contractAddr); + } + } else { + require(false, "unrecognized bind package"); + } + return new bytes(0); + } + + function encodeReactBindSynPackage(ReactBindSynPackage memory reactBindSynPackage) + internal + pure + returns (bytes memory) + { + bytes[] memory elements = new bytes[](2); + elements[0] = reactBindSynPackage.status.encodeUint(); + elements[1] = uint256(reactBindSynPackage.bep2TokenSymbol).encodeUint(); + return elements.encodeList(); + } + + function approveBind(address contractAddr, string memory bep2Symbol) public payable returns (bool) { + require(!mirrorPendingRecord[contractAddr], "the bep20 token is in mirror pending status"); + bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol); + BindSynPackage memory bindSynPkg = bindPackageRecord[bep2TokenSymbol]; + require(bindSynPkg.bep2TokenSymbol != bytes32(0x00), "bind request doesn't exist"); + uint256 lockedAmount = bindSynPkg.totalSupply.sub(bindSynPkg.peggyAmount); + require( + contractAddr == bindSynPkg.contractAddr, + "contact address doesn't equal to the contract address in bind request" + ); + require(IBEP20(contractAddr).getOwner() == msg.sender, "only bep20 owner can approve this bind request"); + uint256 tokenHubBalance = IBEP20(contractAddr).balanceOf(TOKEN_HUB_ADDR); + require( + IBEP20(contractAddr).allowance(msg.sender, address(this)).add(tokenHubBalance) >= lockedAmount, + "allowance is not enough" + ); + uint256 relayFee = msg.value; + uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); + require( + relayFee >= miniRelayFee && relayFee % TEN_DECIMALS == 0, + "relayFee must be N * 1e10 and greater than miniRelayFee" + ); + + uint32 verifyCode = verifyBindParameters(bindSynPkg, contractAddr); + if (verifyCode == CODE_OK) { + IBEP20(contractAddr).transferFrom(msg.sender, TOKEN_HUB_ADDR, lockedAmount.sub(tokenHubBalance)); + ITokenHub(TOKEN_HUB_ADDR).bindToken( + bindSynPkg.bep2TokenSymbol, bindSynPkg.contractAddr, bindSynPkg.bep20Decimals + ); + emit bindSuccess(contractAddr, bep2Symbol, bindSynPkg.totalSupply, lockedAmount); + } else { + emit bindFailure(contractAddr, bep2Symbol, verifyCode); + } + delete bindPackageRecord[bep2TokenSymbol]; + ReactBindSynPackage memory reactBindSynPackage = + ReactBindSynPackage({ status: verifyCode, bep2TokenSymbol: bep2TokenSymbol }); + address(uint160(TOKEN_HUB_ADDR)).transfer(relayFee); + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage( + BIND_CHANNELID, encodeReactBindSynPackage(reactBindSynPackage), relayFee.div(TEN_DECIMALS) + ); + return true; + } + + function rejectBind(address contractAddr, string memory bep2Symbol) public payable returns (bool) { + bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol); + BindSynPackage memory bindSynPkg = bindPackageRecord[bep2TokenSymbol]; + require(bindSynPkg.bep2TokenSymbol != bytes32(0x00), "bind request doesn't exist"); + require( + contractAddr == bindSynPkg.contractAddr, + "contact address doesn't equal to the contract address in bind request" + ); + require(IBEP20(contractAddr).getOwner() == msg.sender, "only bep20 owner can reject"); + uint256 relayFee = msg.value; + uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); + require( + relayFee >= miniRelayFee && relayFee % TEN_DECIMALS == 0, + "relayFee must be N * 1e10 and greater than miniRelayFee" + ); + delete bindPackageRecord[bep2TokenSymbol]; + ReactBindSynPackage memory reactBindSynPackage = + ReactBindSynPackage({ status: BIND_STATUS_REJECTED, bep2TokenSymbol: bep2TokenSymbol }); + address(uint160(TOKEN_HUB_ADDR)).transfer(relayFee); + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage( + BIND_CHANNELID, encodeReactBindSynPackage(reactBindSynPackage), relayFee.div(TEN_DECIMALS) + ); + emit bindFailure(contractAddr, bep2Symbol, BIND_STATUS_REJECTED); + return true; + } + + function expireBind(string memory bep2Symbol) public payable returns (bool) { + bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol); + BindSynPackage memory bindSynPkg = bindPackageRecord[bep2TokenSymbol]; + require(bindSynPkg.bep2TokenSymbol != bytes32(0x00), "bind request doesn't exist"); + require(bindSynPkg.expireTime < block.timestamp, "bind request is not expired"); + uint256 relayFee = msg.value; + uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); + require( + relayFee >= miniRelayFee && relayFee % TEN_DECIMALS == 0, + "relayFee must be N * 1e10 and greater than miniRelayFee" + ); + delete bindPackageRecord[bep2TokenSymbol]; + ReactBindSynPackage memory reactBindSynPackage = + ReactBindSynPackage({ status: BIND_STATUS_TIMEOUT, bep2TokenSymbol: bep2TokenSymbol }); + address(uint160(TOKEN_HUB_ADDR)).transfer(relayFee); + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage( + BIND_CHANNELID, encodeReactBindSynPackage(reactBindSynPackage), relayFee.div(TEN_DECIMALS) + ); + emit bindFailure(bindSynPkg.contractAddr, bep2Symbol, BIND_STATUS_TIMEOUT); + return true; + } + + function encodeMirrorSynPackage(MirrorSynPackage memory mirrorSynPackage) internal pure returns (bytes memory) { + bytes[] memory elements = new bytes[](8); + elements[0] = mirrorSynPackage.mirrorSender.encodeAddress(); + elements[1] = mirrorSynPackage.bep20Addr.encodeAddress(); + elements[2] = uint256(mirrorSynPackage.bep20Name).encodeUint(); + elements[3] = uint256(mirrorSynPackage.bep20Symbol).encodeUint(); + elements[4] = mirrorSynPackage.bep20Supply.encodeUint(); + elements[5] = uint256(mirrorSynPackage.bep20Decimals).encodeUint(); + elements[6] = mirrorSynPackage.mirrorFee.encodeUint(); + elements[7] = uint256(mirrorSynPackage.expireTime).encodeUint(); + return elements.encodeList(); + } + + function decodeMirrorSynPackage(bytes memory msgBytes) internal pure returns (MirrorSynPackage memory, bool) { + MirrorSynPackage memory mirrorSynPackage; + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + mirrorSynPackage.mirrorSender = iter.next().toAddress(); + } else if (idx == 1) { + mirrorSynPackage.bep20Addr = iter.next().toAddress(); + } else if (idx == 2) { + mirrorSynPackage.bep20Name = bytes32(iter.next().toUint()); + } else if (idx == 3) { + mirrorSynPackage.bep20Symbol = bytes32(iter.next().toUint()); + } else if (idx == 4) { + mirrorSynPackage.bep20Supply = iter.next().toUint(); + } else if (idx == 5) { + mirrorSynPackage.bep20Decimals = uint8(iter.next().toUint()); + } else if (idx == 6) { + mirrorSynPackage.mirrorFee = iter.next().toUint(); + } else if (idx == 7) { + mirrorSynPackage.expireTime = uint64(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (mirrorSynPackage, success); + } + + function decodeMirrorAckPackage(bytes memory msgBytes) internal pure returns (MirrorAckPackage memory, bool) { + MirrorAckPackage memory mirrorAckPackage; + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + mirrorAckPackage.mirrorSender = iter.next().toAddress(); + } else if (idx == 1) { + mirrorAckPackage.bep20Addr = iter.next().toAddress(); + } else if (idx == 2) { + mirrorAckPackage.bep20Decimals = uint8(iter.next().toUint()); + } else if (idx == 3) { + mirrorAckPackage.bep2Symbol = bytes32(iter.next().toUint()); + } else if (idx == 4) { + mirrorAckPackage.mirrorFee = iter.next().toUint(); + } else if (idx == 5) { + mirrorAckPackage.errorCode = uint8(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (mirrorAckPackage, success); + } + + function mirror(address bep20Addr, uint64 expireTime) public payable returns (bool) { + require(ITokenHub(TOKEN_HUB_ADDR).getBep2SymbolByContractAddr(bep20Addr) == bytes32(0x00), "already bound"); + require(!mirrorPendingRecord[bep20Addr], "mirror pending"); + uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); + require( + msg.value % TEN_DECIMALS == 0 && msg.value >= mirrorFee.add(miniRelayFee), + "msg.value must be N * 1e10 and greater than sum of miniRelayFee and mirrorFee" + ); + require( + expireTime >= block.timestamp + 120 && expireTime <= block.timestamp + 86400, + "expireTime must be two minutes later and one day earlier" + ); + uint8 decimals = IBEP20(bep20Addr).decimals(); + uint256 totalSupply = IBEP20(bep20Addr).totalSupply(); + require(convertToBep2Amount(totalSupply, decimals) <= MAX_BEP2_TOTAL_SUPPLY, "too large total supply"); + string memory name = IBEP20(bep20Addr).name(); + bytes memory nameBytes = bytes(name); + require(nameBytes.length >= 1 && nameBytes.length <= 32, "name length must be in [1,32]"); + string memory symbol = IBEP20(bep20Addr).symbol(); + bytes memory symbolBytes = bytes(symbol); + require( + symbolBytes.length >= MINIMUM_BEP20_SYMBOL_LEN && symbolBytes.length <= MAXIMUM_BEP20_SYMBOL_LEN, + "symbol length must be in [2,8]" + ); + for (uint8 i = 0; i < symbolBytes.length; ++i) { + require( + (symbolBytes[i] >= "A" && symbolBytes[i] <= "Z") || (symbolBytes[i] >= "a" && symbolBytes[i] <= "z") + || (symbolBytes[i] >= "0" && symbolBytes[i] <= "9"), + "symbol should only contain alphabet and number" + ); + } + address(uint160(TOKEN_HUB_ADDR)).transfer(msg.value.sub(mirrorFee)); + mirrorPendingRecord[bep20Addr] = true; + bytes32 bytes32Name; + assembly { + bytes32Name := mload(add(name, 32)) + } + bytes32 bytes32Symbol; + assembly { + bytes32Symbol := mload(add(symbol, 32)) + } + MirrorSynPackage memory mirrorSynPackage = MirrorSynPackage({ + mirrorSender: msg.sender, + bep20Addr: bep20Addr, + bep20Name: bytes32Name, + bep20Symbol: bytes32Symbol, + bep20Supply: totalSupply, + bep20Decimals: decimals, + mirrorFee: mirrorFee.div(TEN_DECIMALS), + expireTime: expireTime + }); + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage( + MIRROR_CHANNELID, encodeMirrorSynPackage(mirrorSynPackage), msg.value.sub(mirrorFee).div(TEN_DECIMALS) + ); + return true; + } + + function handleMirrorAckPackage(bytes memory msgBytes) internal { + (MirrorAckPackage memory mirrorAckPackage, bool decodeSuccess) = decodeMirrorAckPackage(msgBytes); + require(decodeSuccess, "unrecognized package"); + mirrorPendingRecord[mirrorAckPackage.bep20Addr] = false; + if (mirrorAckPackage.errorCode == CODE_OK) { + address(uint160(TOKEN_HUB_ADDR)).transfer(mirrorAckPackage.mirrorFee); + ITokenHub(TOKEN_HUB_ADDR).bindToken( + mirrorAckPackage.bep2Symbol, mirrorAckPackage.bep20Addr, mirrorAckPackage.bep20Decimals + ); + boundByMirror[mirrorAckPackage.bep20Addr] = true; + emit mirrorSuccess(mirrorAckPackage.bep20Addr, mirrorAckPackage.bep2Symbol); + return; + } else { + (bool success,) = mirrorAckPackage.mirrorSender.call{ + gas: MAX_GAS_FOR_TRANSFER_BNB, + value: mirrorAckPackage.mirrorFee + }(""); + if (!success) { + address(uint160(SYSTEM_REWARD_ADDR)).transfer(mirrorAckPackage.mirrorFee); + } + emit mirrorFailure(mirrorAckPackage.bep20Addr, mirrorAckPackage.errorCode); + } + } + + function handleMirrorFailAckPackage(bytes memory msgBytes) internal { + (MirrorSynPackage memory mirrorSynPackage, bool decodeSuccess) = decodeMirrorSynPackage(msgBytes); + require(decodeSuccess, "unrecognized package"); + mirrorPendingRecord[mirrorSynPackage.bep20Addr] = false; + (bool success,) = mirrorSynPackage.mirrorSender.call{ + gas: MAX_GAS_FOR_TRANSFER_BNB, + value: mirrorSynPackage.mirrorFee.mul(TEN_DECIMALS) + }(""); + if (!success) { + address(uint160(SYSTEM_REWARD_ADDR)).transfer(mirrorSynPackage.mirrorFee.mul(TEN_DECIMALS)); + } + } + + function encodeSyncSynPackage(SyncSynPackage memory syncSynPackage) internal pure returns (bytes memory) { + bytes[] memory elements = new bytes[](6); + elements[0] = syncSynPackage.syncSender.encodeAddress(); + elements[1] = syncSynPackage.bep20Addr.encodeAddress(); + elements[2] = uint256(syncSynPackage.bep2Symbol).encodeUint(); + elements[3] = syncSynPackage.bep20Supply.encodeUint(); + elements[4] = syncSynPackage.syncFee.encodeUint(); + elements[5] = uint256(syncSynPackage.expireTime).encodeUint(); + return elements.encodeList(); + } + + function decodeSyncSynPackage(bytes memory msgBytes) internal pure returns (SyncSynPackage memory, bool) { + SyncSynPackage memory syncSynPackage; + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + syncSynPackage.syncSender = iter.next().toAddress(); + } else if (idx == 1) { + syncSynPackage.bep20Addr = iter.next().toAddress(); + } else if (idx == 2) { + syncSynPackage.bep2Symbol = bytes32(iter.next().toUint()); + } else if (idx == 3) { + syncSynPackage.bep20Supply = iter.next().toUint(); + } else if (idx == 4) { + syncSynPackage.syncFee = iter.next().toUint(); + } else if (idx == 5) { + syncSynPackage.expireTime = uint64(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (syncSynPackage, success); + } + + function decodeSyncAckPackage(bytes memory msgBytes) internal pure returns (SyncAckPackage memory, bool) { + SyncAckPackage memory syncAckPackage; + RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); + bool success = false; + uint256 idx = 0; + while (iter.hasNext()) { + if (idx == 0) { + syncAckPackage.syncSender = iter.next().toAddress(); + } else if (idx == 1) { + syncAckPackage.bep20Addr = iter.next().toAddress(); + } else if (idx == 2) { + syncAckPackage.syncFee = iter.next().toUint(); + } else if (idx == 3) { + syncAckPackage.errorCode = uint8(iter.next().toUint()); + success = true; + } else { + break; + } + ++idx; + } + return (syncAckPackage, success); + } + + function sync(address bep20Addr, uint64 expireTime) public payable returns (bool) { + bytes32 bep2Symbol = ITokenHub(TOKEN_HUB_ADDR).getBep2SymbolByContractAddr(bep20Addr); + require(bep2Symbol != bytes32(0x00), "not bound"); + require(boundByMirror[bep20Addr], "not bound by mirror"); + uint256 miniRelayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); + require( + msg.value % TEN_DECIMALS == 0 && msg.value >= syncFee.add(miniRelayFee), + "msg.value must be N * 1e10 and no less sum of miniRelayFee and syncFee" + ); + require( + expireTime >= block.timestamp + 120 && expireTime <= block.timestamp + 86400, + "expireTime must be two minutes later and one day earlier" + ); + uint256 totalSupply = IBEP20(bep20Addr).totalSupply(); + uint8 decimals = IBEP20(bep20Addr).decimals(); + require(convertToBep2Amount(totalSupply, decimals) <= MAX_BEP2_TOTAL_SUPPLY, "too large total supply"); + + address(uint160(TOKEN_HUB_ADDR)).transfer(msg.value.sub(syncFee)); + SyncSynPackage memory syncSynPackage = SyncSynPackage({ + syncSender: msg.sender, + bep20Addr: bep20Addr, + bep2Symbol: bep2Symbol, + bep20Supply: totalSupply, + syncFee: syncFee.div(TEN_DECIMALS), + expireTime: expireTime + }); + ICrossChain(CROSS_CHAIN_CONTRACT_ADDR).sendSynPackage( + SYNC_CHANNELID, encodeSyncSynPackage(syncSynPackage), msg.value.sub(syncFee).div(TEN_DECIMALS) + ); + return true; + } + + function handleSyncAckPackage(bytes memory msgBytes) internal { + (SyncAckPackage memory syncAckPackage, bool decodeSuccess) = decodeSyncAckPackage(msgBytes); + require(decodeSuccess, "unrecognized package"); + if (syncAckPackage.errorCode == CODE_OK) { + address(uint160(TOKEN_HUB_ADDR)).transfer(syncAckPackage.syncFee); + emit syncSuccess(syncAckPackage.bep20Addr); + return; + } else { + emit syncFailure(syncAckPackage.bep20Addr, syncAckPackage.errorCode); + } + (bool success,) = + syncAckPackage.syncSender.call{ gas: MAX_GAS_FOR_TRANSFER_BNB, value: syncAckPackage.syncFee }(""); + if (!success) { + address(uint160(SYSTEM_REWARD_ADDR)).transfer(syncAckPackage.syncFee); + } + } + + function handleSyncFailAckPackage(bytes memory msgBytes) internal { + (SyncSynPackage memory syncSynPackage, bool decodeSuccess) = decodeSyncSynPackage(msgBytes); + require(decodeSuccess, "unrecognized package"); + (bool success,) = syncSynPackage.syncSender.call{ + gas: MAX_GAS_FOR_TRANSFER_BNB, + value: syncSynPackage.syncFee.mul(TEN_DECIMALS) + }(""); + if (!success) { + address(uint160(SYSTEM_REWARD_ADDR)).transfer(syncSynPackage.syncFee.mul(TEN_DECIMALS)); + } + } + + function updateParam(string calldata key, bytes calldata value) external override onlyGov { + require(value.length == 32, "expected value length 32"); + string memory localKey = key; + bytes memory localValue = value; + bytes32 bytes32Key; + assembly { + bytes32Key := mload(add(localKey, 32)) + } + if (bytes32Key == bytes32(0x6d6972726f724665650000000000000000000000000000000000000000000000)) { + // mirrorFee + uint256 newMirrorFee; + assembly { + newMirrorFee := mload(add(localValue, 32)) + } + require(newMirrorFee % (TEN_DECIMALS) == 0, "mirrorFee must be N * 1e10"); + mirrorFee = newMirrorFee; + } else if (bytes32Key == bytes32(0x73796e6346656500000000000000000000000000000000000000000000000000)) { + // syncFee + uint256 newSyncFee; + assembly { + newSyncFee := mload(add(localValue, 32)) + } + require(newSyncFee % (TEN_DECIMALS) == 0, "syncFee must be N * 1e10"); + syncFee = newSyncFee; + } else { + require(false, "unknown param"); + } + emit paramChange(key, value); + } + + function bep2TokenSymbolConvert(string memory symbol) internal pure returns (bytes32) { + bytes32 result; + assembly { + result := mload(add(symbol, 32)) + } + return result; + } + + function queryRequiredLockAmountForBind(string memory symbol) public view returns (uint256) { + bytes32 bep2Symbol; + assembly { + bep2Symbol := mload(add(symbol, 32)) + } + BindSynPackage memory bindRequest = bindPackageRecord[bep2Symbol]; + if (bindRequest.contractAddr == address(0x00)) { + return 0; + } + uint256 tokenHubBalance = IBEP20(bindRequest.contractAddr).balanceOf(TOKEN_HUB_ADDR); + uint256 requiredBalance = bindRequest.totalSupply.sub(bindRequest.peggyAmount); + return requiredBalance.sub(tokenHubBalance); + } + + function verifyBindParameters( + BindSynPackage memory bindSynPkg, + address contractAddr + ) internal view returns (uint32) { + uint256 decimals = IBEP20(contractAddr).decimals(); + string memory bep20Symbol = IBEP20(contractAddr).symbol(); + uint256 tokenHubBalance = IBEP20(contractAddr).balanceOf(TOKEN_HUB_ADDR); + uint256 lockedAmount = bindSynPkg.totalSupply.sub(bindSynPkg.peggyAmount); + if (bindSynPkg.expireTime < block.timestamp) { + return BIND_STATUS_TIMEOUT; + } + if (!checkSymbol(bep20Symbol, bindSynPkg.bep2TokenSymbol)) { + return BIND_STATUS_SYMBOL_MISMATCH; + } + if (tokenHubBalance > lockedAmount) { + return BIND_STATUS_TOO_MUCH_TOKENHUB_BALANCE; + } + if (IBEP20(bindSynPkg.contractAddr).totalSupply() != bindSynPkg.totalSupply) { + return BIND_STATUS_TOTAL_SUPPLY_MISMATCH; + } + if (decimals != bindSynPkg.bep20Decimals) { + return BIND_STATUS_DECIMALS_MISMATCH; + } + if ( + ITokenHub(TOKEN_HUB_ADDR).getContractAddrByBEP2Symbol(bindSynPkg.bep2TokenSymbol) != address(0x00) + || ITokenHub(TOKEN_HUB_ADDR).getBep2SymbolByContractAddr(bindSynPkg.contractAddr) != bytes32(0x00) + ) { + return BIND_STATUS_ALREADY_BOUND_TOKEN; + } + return CODE_OK; + } + + function checkSymbol(string memory bep20Symbol, bytes32 bep2TokenSymbol) internal pure returns (bool) { + bytes memory bep20SymbolBytes = bytes(bep20Symbol); + if (bep20SymbolBytes.length > MAXIMUM_BEP20_SYMBOL_LEN || bep20SymbolBytes.length < MINIMUM_BEP20_SYMBOL_LEN) { + return false; + } + + bytes memory bep2TokenSymbolBytes = new bytes(32); + assembly { + mstore(add(bep2TokenSymbolBytes, 32), bep2TokenSymbol) + } + if (bep2TokenSymbolBytes[bep20SymbolBytes.length] != 0x2d) { + // '-' + return false; + } + bool symbolMatch = true; + for (uint256 index = 0; index < bep20SymbolBytes.length; ++index) { + if (bep20SymbolBytes[index] != bep2TokenSymbolBytes[index]) { + symbolMatch = false; + break; + } + } + return symbolMatch; + } + + function convertToBep2Amount(uint256 amount, uint256 bep20TokenDecimals) internal pure returns (uint256) { + if (bep20TokenDecimals > BEP2_TOKEN_DECIMALS) { + require(bep20TokenDecimals - BEP2_TOKEN_DECIMALS <= LOG_MAX_UINT256, "too large decimals"); + return amount.div(10 ** (bep20TokenDecimals - BEP2_TOKEN_DECIMALS)); + } + return amount.mul(10 ** (BEP2_TOKEN_DECIMALS - bep20TokenDecimals)); + } } diff --git a/contracts/interface/IApplication.sol b/contracts/interface/IApplication.sol index 49deed20..1504f028 100644 --- a/contracts/interface/IApplication.sol +++ b/contracts/interface/IApplication.sol @@ -4,7 +4,10 @@ interface IApplication { /** * @dev Handle syn package */ - function handleSynPackage(uint8 channelId, bytes calldata msgBytes) external returns(bytes memory responsePayload); + function handleSynPackage( + uint8 channelId, + bytes calldata msgBytes + ) external returns (bytes memory responsePayload); /** * @dev Handle ack package @@ -15,4 +18,4 @@ interface IApplication { * @dev Handle fail ack package */ function handleFailAckPackage(uint8 channelId, bytes calldata msgBytes) external; -} \ No newline at end of file +} diff --git a/contracts/interface/IBEP20.sol b/contracts/interface/IBEP20.sol index 222abef7..e6fd155b 100644 --- a/contracts/interface/IBEP20.sol +++ b/contracts/interface/IBEP20.sol @@ -89,4 +89,4 @@ interface IBEP20 { * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); -} \ No newline at end of file +} diff --git a/contracts/interface/IBSCValidatorSet.sol b/contracts/interface/IBSCValidatorSet.sol index b3a060b9..0f03514e 100644 --- a/contracts/interface/IBSCValidatorSet.sol +++ b/contracts/interface/IBSCValidatorSet.sol @@ -2,10 +2,10 @@ pragma solidity 0.6.4; pragma experimental ABIEncoderV2; interface IBSCValidatorSet { - function misdemeanor(address validator) external; - function felony(address validator) external; - function isCurrentValidator(address validator) external view returns (bool); - function getLivingValidators() external view returns(address[] memory, bytes[] memory); - function getMiningValidators() external view returns(address[] memory, bytes[] memory); - function isMonitoredForMaliciousVote(bytes calldata voteAddr) external view returns (bool); + function misdemeanor(address validator) external; + function felony(address validator) external; + function isCurrentValidator(address validator) external view returns (bool); + function getLivingValidators() external view returns (address[] memory, bytes[] memory); + function getMiningValidators() external view returns (address[] memory, bytes[] memory); + function isMonitoredForMaliciousVote(bytes calldata voteAddr) external view returns (bool); } diff --git a/contracts/interface/IBSCValidatorSetV2.sol b/contracts/interface/IBSCValidatorSetV2.sol index 3ecc6dc2..a8b38927 100644 --- a/contracts/interface/IBSCValidatorSetV2.sol +++ b/contracts/interface/IBSCValidatorSetV2.sol @@ -1,10 +1,10 @@ pragma solidity 0.6.4; interface IBSCValidatorSetV2 { - function misdemeanor(address validator) external; - function felony(address validator)external; - function isCurrentValidator(address validator) external view returns (bool); + function misdemeanor(address validator) external; + function felony(address validator) external; + function isCurrentValidator(address validator) external view returns (bool); - function currentValidatorSetMap(address validator) external view returns(uint256); - function numOfCabinets() external view returns(uint256); + function currentValidatorSetMap(address validator) external view returns (uint256); + function numOfCabinets() external view returns (uint256); } diff --git a/contracts/interface/ILightClient.sol b/contracts/interface/ILightClient.sol index cea9187f..9d67dfca 100644 --- a/contracts/interface/ILightClient.sol +++ b/contracts/interface/ILightClient.sol @@ -1,11 +1,9 @@ pragma solidity 0.6.4; interface ILightClient { + function isHeaderSynced(uint64 height) external view returns (bool); - function isHeaderSynced(uint64 height) external view returns (bool); + function getAppHash(uint64 height) external view returns (bytes32); - function getAppHash(uint64 height) external view returns (bytes32); - - function getSubmitter(uint64 height) external view returns (address payable); - -} \ No newline at end of file + function getSubmitter(uint64 height) external view returns (address payable); +} diff --git a/contracts/interface/IParamSubscriber.sol b/contracts/interface/IParamSubscriber.sol index cde8e26a..c84d5f1e 100644 --- a/contracts/interface/IParamSubscriber.sol +++ b/contracts/interface/IParamSubscriber.sol @@ -2,4 +2,4 @@ pragma solidity 0.6.4; interface IParamSubscriber { function updateParam(string calldata key, bytes calldata value) external; -} \ No newline at end of file +} diff --git a/contracts/interface/IRelayerHub.sol b/contracts/interface/IRelayerHub.sol index de98a6e8..d35123ec 100644 --- a/contracts/interface/IRelayerHub.sol +++ b/contracts/interface/IRelayerHub.sol @@ -1,7 +1,5 @@ pragma solidity 0.6.4; interface IRelayerHub { - function isRelayer(address sender) external view returns (bool); + function isRelayer(address sender) external view returns (bool); } - - diff --git a/contracts/interface/IRelayerIncentivize.sol b/contracts/interface/IRelayerIncentivize.sol index d3c40baa..0b94ebbb 100644 --- a/contracts/interface/IRelayerIncentivize.sol +++ b/contracts/interface/IRelayerIncentivize.sol @@ -1,7 +1,10 @@ pragma solidity 0.6.4; interface IRelayerIncentivize { - - function addReward(address payable headerRelayerAddr, address payable packageRelayer, uint256 amount, bool fromSystemReward) external returns (bool); - + function addReward( + address payable headerRelayerAddr, + address payable packageRelayer, + uint256 amount, + bool fromSystemReward + ) external returns (bool); } diff --git a/contracts/interface/ISlashIndicator.sol b/contracts/interface/ISlashIndicator.sol index 879ad287..af0ade46 100644 --- a/contracts/interface/ISlashIndicator.sol +++ b/contracts/interface/ISlashIndicator.sol @@ -1,8 +1,8 @@ pragma solidity 0.6.4; interface ISlashIndicator { - function clean() external; - function downtimeSlash(address validator, uint256 count) external; - function sendFelonyPackage(address validator) external; - function getSlashThresholds() external view returns (uint256, uint256); + function clean() external; + function downtimeSlash(address validator, uint256 count) external; + function sendFelonyPackage(address validator) external; + function getSlashThresholds() external view returns (uint256, uint256); } diff --git a/contracts/interface/IStaking.sol b/contracts/interface/IStaking.sol index d3358fba..c26a5b22 100644 --- a/contracts/interface/IStaking.sol +++ b/contracts/interface/IStaking.sol @@ -1,32 +1,35 @@ pragma solidity 0.6.4; interface IStaking { + function delegate(address validator, uint256 amount) external payable; - function delegate(address validator, uint256 amount) external payable; + function undelegate(address validator, uint256 amount) external payable; - function undelegate(address validator, uint256 amount) external payable; + function redelegate(address validatorSrc, address validatorDst, uint256 amount) external payable; - function redelegate(address validatorSrc, address validatorDst, uint256 amount) external payable; + function claimReward() external returns (uint256); - function claimReward() external returns(uint256); + function claimUndelegated() external returns (uint256); - function claimUndelegated() external returns(uint256); + function getDelegated(address delegator, address validator) external view returns (uint256); - function getDelegated(address delegator, address validator) external view returns(uint256); + function getTotalDelegated(address delegator) external view returns (uint256); - function getTotalDelegated(address delegator) external view returns(uint256); + function getDistributedReward(address delegator) external view returns (uint256); - function getDistributedReward(address delegator) external view returns(uint256); + function getPendingRedelegateTime( + address delegator, + address valSrc, + address valDst + ) external view returns (uint256); - function getPendingRedelegateTime(address delegator, address valSrc, address valDst) external view returns(uint256); + function getUndelegated(address delegator) external view returns (uint256); - function getUndelegated(address delegator) external view returns(uint256); + function getPendingUndelegateTime(address delegator, address validator) external view returns (uint256); - function getPendingUndelegateTime(address delegator, address validator) external view returns(uint256); + function getRelayerFee() external view returns (uint256); - function getRelayerFee() external view returns(uint256); + function getMinDelegation() external view returns (uint256); - function getMinDelegation() external view returns(uint256); - - function getRequestInFly(address delegator) external view returns(uint256[3] memory); + function getRequestInFly(address delegator) external view returns (uint256[3] memory); } diff --git a/contracts/interface/ISystemReward.sol b/contracts/interface/ISystemReward.sol index 67a71f58..b4bd751e 100644 --- a/contracts/interface/ISystemReward.sol +++ b/contracts/interface/ISystemReward.sol @@ -1,5 +1,5 @@ pragma solidity 0.6.4; interface ISystemReward { - function claimRewards(address payable to, uint256 amount) external returns(uint256 actualAmount); -} \ No newline at end of file + function claimRewards(address payable to, uint256 amount) external returns (uint256 actualAmount); +} diff --git a/contracts/interface/ITokenHub.sol b/contracts/interface/ITokenHub.sol index 89373f04..046b9f9e 100644 --- a/contracts/interface/ITokenHub.sol +++ b/contracts/interface/ITokenHub.sol @@ -1,29 +1,35 @@ pragma solidity 0.6.4; interface ITokenHub { + function getMiniRelayFee() external view returns (uint256); - function getMiniRelayFee() external view returns(uint256); + function getContractAddrByBEP2Symbol(bytes32 bep2Symbol) external view returns (address); - function getContractAddrByBEP2Symbol(bytes32 bep2Symbol) external view returns(address); + function getBep2SymbolByContractAddr(address contractAddr) external view returns (bytes32); - function getBep2SymbolByContractAddr(address contractAddr) external view returns(bytes32); + function bindToken(bytes32 bep2Symbol, address contractAddr, uint256 decimals) external; - function bindToken(bytes32 bep2Symbol, address contractAddr, uint256 decimals) external; + function unbindToken(bytes32 bep2Symbol, address contractAddr) external; - function unbindToken(bytes32 bep2Symbol, address contractAddr) external; + function transferOut( + address contractAddr, + address recipient, + uint256 amount, + uint64 expireTime + ) external payable returns (bool); - function transferOut(address contractAddr, address recipient, uint256 amount, uint64 expireTime) - external payable returns (bool); - - function recoverBCAsset(bytes32 tokenSymbol, address recipient, uint256 amount) - external; - function cancelTokenRecoverLock(bytes32 tokenSymbol, address attacker) external; + function recoverBCAsset(bytes32 tokenSymbol, address recipient, uint256 amount) external; + function cancelTokenRecoverLock(bytes32 tokenSymbol, address attacker) external; - /* solium-disable-next-line */ - function batchTransferOutBNB(address[] calldata recipientAddrs, uint256[] calldata amounts, address[] calldata refundAddrs, - uint64 expireTime) external payable returns (bool); + /* solium-disable-next-line */ + function batchTransferOutBNB( + address[] calldata recipientAddrs, + uint256[] calldata amounts, + address[] calldata refundAddrs, + uint64 expireTime + ) external payable returns (bool); - function withdrawStakingBNB(uint256 amount) external returns(bool); + function withdrawStakingBNB(uint256 amount) external returns (bool); - function cancelTransferIn(address tokenAddress, address attacker) external; + function cancelTransferIn(address tokenAddress, address attacker) external; } diff --git a/contracts/lib/BytesLib.sol b/contracts/lib/BytesLib.sol index 8e254df1..8dc4dc52 100644 --- a/contracts/lib/BytesLib.sol +++ b/contracts/lib/BytesLib.sol @@ -8,81 +8,72 @@ pragma solidity 0.6.4; - library BytesLib { - function concat( - bytes memory _preBytes, - bytes memory _postBytes - ) - internal - pure - returns (bytes memory) - { + function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. tempBytes := mload(0x40) - // Store the length of the first bytes array at the beginning of - // the memory for tempBytes. + // Store the length of the first bytes array at the beginning of + // the memory for tempBytes. let length := mload(_preBytes) mstore(tempBytes, length) - // Maintain a memory counter for the current write location in the - // temp bytes array by adding the 32 bytes for the array length to - // the starting location. + // Maintain a memory counter for the current write location in the + // temp bytes array by adding the 32 bytes for the array length to + // the starting location. let mc := add(tempBytes, 0x20) - // Stop copying when the memory counter reaches the length of the - // first bytes array. + // Stop copying when the memory counter reaches the length of the + // first bytes array. let end := add(mc, length) for { - // Initialize a copy counter to the start of the _preBytes data, - // 32 bytes into its memory. + // Initialize a copy counter to the start of the _preBytes data, + // 32 bytes into its memory. let cc := add(_preBytes, 0x20) } lt(mc, end) { - // Increase both counters by 32 bytes each iteration. + // Increase both counters by 32 bytes each iteration. mc := add(mc, 0x20) cc := add(cc, 0x20) } { - // Write the _preBytes data into the tempBytes memory 32 bytes - // at a time. + // Write the _preBytes data into the tempBytes memory 32 bytes + // at a time. mstore(mc, mload(cc)) } - // Add the length of _postBytes to the current length of tempBytes - // and store it as the new length in the first 32 bytes of the - // tempBytes memory. + // Add the length of _postBytes to the current length of tempBytes + // and store it as the new length in the first 32 bytes of the + // tempBytes memory. length := mload(_postBytes) mstore(tempBytes, add(length, mload(tempBytes))) - // Move the memory counter back from a multiple of 0x20 to the - // actual end of the _preBytes data. + // Move the memory counter back from a multiple of 0x20 to the + // actual end of the _preBytes data. mc := end - // Stop copying when the memory counter reaches the new combined - // length of the arrays. + // Stop copying when the memory counter reaches the new combined + // length of the arrays. end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { + for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - // Update the free-memory pointer by padding our last write location - // to 32 bytes: add 31 bytes to the end of tempBytes to move to the - // next 32 byte block, then round down to the nearest multiple of - // 32. If the sum of the length of the two arrays is zero then add - // one before rounding down to leave a blank 32 bytes (the length block with 0). - mstore(0x40, and( - add(add(end, iszero(add(length, mload(_preBytes)))), 31), - not(31) // Round down to the nearest 32 bytes. - )) + } { mstore(mc, mload(cc)) } + + // Update the free-memory pointer by padding our last write location + // to 32 bytes: add 31 bytes to the end of tempBytes to move to the + // next 32 byte block, then round down to the nearest multiple of + // 32. If the sum of the length of the two arrays is zero then add + // one before rounding down to leave a blank 32 bytes (the length block with 0). + mstore( + 0x40, + and( + add(add(end, iszero(add(length, mload(_preBytes)))), 31), + not(31) // Round down to the nearest 32 bytes. + ) + ) } return tempBytes; @@ -90,73 +81,73 @@ library BytesLib { function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { assembly { - // Read the first 32 bytes of _preBytes storage, which is the length - // of the array. (We don't need to use the offset into the slot - // because arrays use the entire slot.) + // Read the first 32 bytes of _preBytes storage, which is the length + // of the array. (We don't need to use the offset into the slot + // because arrays use the entire slot.) let fslot := sload(_preBytes_slot) - // Arrays of 31 bytes or less have an even value in their slot, - // while longer arrays have an odd value. The actual length is - // the slot divided by two for odd values, and the lowest order - // byte divided by two for even values. - // If the slot is even, bitwise and the slot with 255 and divide by - // two to get the length. If the slot is odd, bitwise and the slot - // with -1 and divide by two. + // Arrays of 31 bytes or less have an even value in their slot, + // while longer arrays have an odd value. The actual length is + // the slot divided by two for odd values, and the lowest order + // byte divided by two for even values. + // If the slot is even, bitwise and the slot with 255 and divide by + // two to get the length. If the slot is odd, bitwise and the slot + // with -1 and divide by two. let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) let newlength := add(slength, mlength) - // slength can contain both the length and contents of the array - // if length < 32 bytes so let's prepare for that - // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage switch add(lt(slength, 32), lt(newlength, 32)) case 2 { - // Since the new array still fits in the slot, we just need to - // update the contents of the slot. - // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length + // Since the new array still fits in the slot, we just need to + // update the contents of the slot. + // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length sstore( - _preBytes_slot, - // all the modifications to the slot are inside this - // next block - add( - // we can just add to the slot contents because the - // bytes we want to change are the LSBs - fslot, - add( - mul( - div( - // load the bytes from memory - mload(add(_postBytes, 0x20)), - // zero all bytes to the right - exp(0x100, sub(32, mlength)) - ), - // and now shift left the number of bytes to - // leave space for the length in the slot - exp(0x100, sub(32, newlength)) - ), - // increase length by the double of the memory - // bytes length - mul(mlength, 2) - ) - ) + _preBytes_slot, + // all the modifications to the slot are inside this + // next block + add( + // we can just add to the slot contents because the + // bytes we want to change are the LSBs + fslot, + add( + mul( + div( + // load the bytes from memory + mload(add(_postBytes, 0x20)), + // zero all bytes to the right + exp(0x100, sub(32, mlength)) + ), + // and now shift left the number of bytes to + // leave space for the length in the slot + exp(0x100, sub(32, newlength)) + ), + // increase length by the double of the memory + // bytes length + mul(mlength, 2) + ) + ) ) } case 1 { - // The stored value fits in the slot, but the combined value - // will exceed it. - // get the keccak hash to get the contents of the array + // The stored value fits in the slot, but the combined value + // will exceed it. + // get the keccak hash to get the contents of the array mstore(0x0, _preBytes_slot) let sc := add(keccak256(0x0, 0x20), div(slength, 32)) - // save new length + // save new length sstore(_preBytes_slot, add(mul(newlength, 2), 1)) - // The contents of the _postBytes array start 32 bytes into - // the structure. Our first read should obtain the `submod` - // bytes that can fit into the unused space in the last word - // of the stored array. To get this, we read 32 bytes starting - // from `submod`, so the data we read overlaps with the array - // contents by `submod` bytes. Masking the lowest-order - // `submod` bytes allows us to add that value directly to the - // stored value. + // The contents of the _postBytes array start 32 bytes into + // the structure. Our first read should obtain the `submod` + // bytes that can fit into the unused space in the last word + // of the stored array. To get this, we read 32 bytes starting + // from `submod`, so the data we read overlaps with the array + // contents by `submod` bytes. Masking the lowest-order + // `submod` bytes allows us to add that value directly to the + // stored value. let submod := sub(32, slength) let mc := add(_postBytes, submod) @@ -164,14 +155,11 @@ library BytesLib { let mask := sub(exp(0x100, submod), 1) sstore( - sc, - add( - and( - fslot, - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 - ), - and(mload(mc), mask) - ) + sc, + add( + and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), + and(mload(mc), mask) + ) ) for { @@ -180,25 +168,23 @@ library BytesLib { } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } + } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } default { - // get the keccak hash to get the contents of the array + // get the keccak hash to get the contents of the array mstore(0x0, _preBytes_slot) - // Start copying to the last used word of the stored array. + // Start copying to the last used word of the stored array. let sc := add(keccak256(0x0, 0x20), div(slength, 32)) - // save new length + // save new length sstore(_preBytes_slot, add(mul(newlength, 2), 1)) - // Copy over the first `submod` bytes of the new data as in - // case 1 above. + // Copy over the first `submod` bytes of the new data as in + // case 1 above. let slengthmod := mod(slength, 32) let mlengthmod := mod(mlength, 32) let submod := sub(32, slengthmod) @@ -214,9 +200,7 @@ library BytesLib { } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } + } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) @@ -225,15 +209,7 @@ library BytesLib { } } - function slice( - bytes memory _bytes, - uint _start, - uint _length - ) - internal - pure - returns (bytes memory) - { + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { require(_bytes.length >= (_start + _length)); bytes memory tempBytes; @@ -241,42 +217,40 @@ library BytesLib { assembly { switch iszero(_length) case 0 { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. tempBytes := mload(0x40) - // The first word of the slice result is potentially a partial - // word read from the original array. To read it, we calculate - // the length of that partial word and start copying that many - // bytes into the array. The first word we copy will start with - // data we don't care about, but the last `lengthmod` bytes will - // land at the beginning of the contents of the new array. When - // we're done copying, we overwrite the full first word with - // the actual length of the slice. + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. let lengthmod := and(_length, 31) - // The multiplication in the next line is necessary - // because when slicing multiples of 32 bytes (lengthmod == 0) - // the following copy loop was copying the origin's length - // and then ending prematurely not copying everything it should. + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { - // The multiplication in the next line has the same exact purpose - // as the one above. + // The multiplication in the next line has the same exact purpose + // as the one above. let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } + } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) - //update free-memory pointer - //allocating the array padded to 32 bytes like the compiler does now + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now mstore(0x40, and(add(mc, 31), not(31))) } //if we want a zero-length slice let's just return a zero-length array @@ -290,7 +264,7 @@ library BytesLib { return tempBytes; } - function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) { + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { require(_bytes.length >= (_start + 20)); address tempAddress; @@ -301,7 +275,7 @@ library BytesLib { return tempAddress; } - function toUint8(bytes memory _bytes, uint _start) internal pure returns (uint8) { + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { require(_bytes.length >= (_start + 1)); uint8 tempUint; @@ -312,7 +286,7 @@ library BytesLib { return tempUint; } - function toUint16(bytes memory _bytes, uint _start) internal pure returns (uint16) { + function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { require(_bytes.length >= (_start + 2)); uint16 tempUint; @@ -323,7 +297,7 @@ library BytesLib { return tempUint; } - function toUint32(bytes memory _bytes, uint _start) internal pure returns (uint32) { + function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { require(_bytes.length >= (_start + 4)); uint32 tempUint; @@ -334,7 +308,7 @@ library BytesLib { return tempUint; } - function toUint64(bytes memory _bytes, uint _start) internal pure returns (uint64) { + function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { require(_bytes.length >= (_start + 8)); uint64 tempUint; @@ -345,7 +319,7 @@ library BytesLib { return tempUint; } - function toUint96(bytes memory _bytes, uint _start) internal pure returns (uint96) { + function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { require(_bytes.length >= (_start + 12)); uint96 tempUint; @@ -356,7 +330,7 @@ library BytesLib { return tempUint; } - function toUint128(bytes memory _bytes, uint _start) internal pure returns (uint128) { + function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { require(_bytes.length >= (_start + 16)); uint128 tempUint; @@ -367,7 +341,7 @@ library BytesLib { return tempUint; } - function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) { + function toUint(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { require(_bytes.length >= (_start + 32)); uint256 tempUint; @@ -378,7 +352,7 @@ library BytesLib { return tempUint; } - function toBytes32(bytes memory _bytes, uint _start) internal pure returns (bytes32) { + function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { require(_bytes.length >= (_start + 32)); bytes32 tempBytes32; @@ -395,36 +369,35 @@ library BytesLib { assembly { let length := mload(_preBytes) - // if lengths don't match the arrays are not equal + // if lengths don't match the arrays are not equal switch eq(length, mload(_postBytes)) case 1 { - // cb is a circuit breaker in the for loop since there's - // no said feature for inline assembly loops - // cb = 1 - don't breaker - // cb = 0 - break + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break let cb := 1 let mc := add(_preBytes, 0x20) let end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) + for { let cc := add(_postBytes, 0x20) } // the next line is the loop condition: // while (uint(mc < end) + cb == 2) - } eq(add(lt(mc, end), cb), 2) { + eq(add(lt(mc, end), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { - // if any of these checks fails then arrays are not equal + // if any of these checks fails then arrays are not equal if iszero(eq(mload(mc), mload(cc))) { - // unsuccess: + // unsuccess: success := 0 cb := 0 } } } default { - // unsuccess: + // unsuccess: success := 0 } } @@ -432,62 +405,55 @@ library BytesLib { return success; } - function equalStorage( - bytes storage _preBytes, - bytes memory _postBytes - ) - internal - view - returns (bool) - { + function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { bool success = true; assembly { - // we know _preBytes_offset is 0 + // we know _preBytes_offset is 0 let fslot := sload(_preBytes_slot) - // Decode the length of the stored array like in concatStorage(). + // Decode the length of the stored array like in concatStorage(). let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) - // if lengths don't match the arrays are not equal + // if lengths don't match the arrays are not equal switch eq(slength, mlength) case 1 { - // slength can contain both the length and contents of the array - // if length < 32 bytes so let's prepare for that - // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage if iszero(iszero(slength)) { switch lt(slength, 32) case 1 { - // blank the last byte which is the length + // blank the last byte which is the length fslot := mul(div(fslot, 0x100), 0x100) if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { - // unsuccess: + // unsuccess: success := 0 } } default { - // cb is a circuit breaker in the for loop since there's - // no said feature for inline assembly loops - // cb = 1 - don't breaker - // cb = 0 - break + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break let cb := 1 - // get the keccak hash to get the contents of the array + // get the keccak hash to get the contents of the array mstore(0x0, _preBytes_slot) let sc := keccak256(0x0, 0x20) let mc := add(_postBytes, 0x20) let end := add(mc, mlength) - // the next line is the loop condition: - // while (uint(mc < end) + cb == 2) - for {} eq(add(lt(mc, end), cb), 2) { + // the next line is the loop condition: + // while (uint(mc < end) + cb == 2) + for { } eq(add(lt(mc, end), cb), 2) { sc := add(sc, 1) mc := add(mc, 0x20) } { if iszero(eq(sload(sc), mload(mc))) { - // unsuccess: + // unsuccess: success := 0 cb := 0 } @@ -496,7 +462,7 @@ library BytesLib { } } default { - // unsuccess: + // unsuccess: success := 0 } } diff --git a/contracts/lib/BytesToTypes.sol b/contracts/lib/BytesToTypes.sol index 13c511ad..67b450b5 100644 --- a/contracts/lib/BytesToTypes.sol +++ b/contracts/lib/BytesToTypes.sol @@ -6,516 +6,440 @@ pragma solidity 0.6.4; * @dev The BytesToTypes contract converts the memory byte arrays to the standard solidity types * @author pouladzade@gmail.com */ - library BytesToTypes { - - - function bytesToAddress(uint _offst, bytes memory _input) internal pure returns (address _output) { - + function bytesToAddress(uint256 _offst, bytes memory _input) internal pure returns (address _output) { assembly { _output := mload(add(_input, _offst)) } - } - - function bytesToBool(uint _offst, bytes memory _input) internal pure returns (bool _output) { - + } + + function bytesToBool(uint256 _offst, bytes memory _input) internal pure returns (bool _output) { uint8 x; assembly { x := mload(add(_input, _offst)) } - x==0 ? _output = false : _output = true; - } - - function getStringSize(uint _offst, bytes memory _input) internal pure returns(uint size) { - - assembly{ - - size := mload(add(_input,_offst)) - let chunk_count := add(div(size,32),1) // chunk_count = size/32 + 1 - - if gt(mod(size,32),0) {// if size%32 > 0 - chunk_count := add(chunk_count,1) - } - - size := mul(chunk_count,32)// first 32 bytes reseves for size in strings - } + x == 0 ? _output = false : _output = true; } - function bytesToString(uint _offst, bytes memory _input, bytes memory _output) internal pure { + function getStringSize(uint256 _offst, bytes memory _input) internal pure returns (uint256 size) { + assembly { + size := mload(add(_input, _offst)) + let chunk_count := add(div(size, 32), 1) // chunk_count = size/32 + 1 - uint size = 32; + if gt(mod(size, 32), 0) { + // if size%32 > 0 + chunk_count := add(chunk_count, 1) + } + + size := mul(chunk_count, 32) // first 32 bytes reseves for size in strings + } + } + + function bytesToString(uint256 _offst, bytes memory _input, bytes memory _output) internal pure { + uint256 size = 32; assembly { - let chunk_count - - size := mload(add(_input,_offst)) - chunk_count := add(div(size,32),1) // chunk_count = size/32 + 1 - - if gt(mod(size,32),0) { - chunk_count := add(chunk_count,1) // chunk_count++ - } - - for { let index:= 0 } lt(index , chunk_count) { index := add(index,1) } { - mstore(add(_output,mul(index,32)),mload(add(_input,_offst))) - _offst := sub(_offst,32) // _offst -= 32 + + size := mload(add(_input, _offst)) + chunk_count := add(div(size, 32), 1) // chunk_count = size/32 + 1 + + if gt(mod(size, 32), 0) { chunk_count := add(chunk_count, 1) } // chunk_count++ + + for { let index := 0 } lt(index, chunk_count) { index := add(index, 1) } { + mstore(add(_output, mul(index, 32)), mload(add(_input, _offst))) + _offst := sub(_offst, 32) // _offst -= 32 } } } - function bytesToBytes32(uint _offst, bytes memory _input, bytes32 _output) internal pure { - + function bytesToBytes32(uint256 _offst, bytes memory _input, bytes32 _output) internal pure { assembly { - mstore(_output , add(_input, _offst)) - mstore(add(_output,32) , add(add(_input, _offst),32)) + mstore(_output, add(_input, _offst)) + mstore(add(_output, 32), add(add(_input, _offst), 32)) } } - - function bytesToInt8(uint _offst, bytes memory _input) internal pure returns (int8 _output) { - + + function bytesToInt8(uint256 _offst, bytes memory _input) internal pure returns (int8 _output) { assembly { _output := mload(add(_input, _offst)) } } - - function bytesToInt16(uint _offst, bytes memory _input) internal pure returns (int16 _output) { - + + function bytesToInt16(uint256 _offst, bytes memory _input) internal pure returns (int16 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt24(uint _offst, bytes memory _input) internal pure returns (int24 _output) { - + function bytesToInt24(uint256 _offst, bytes memory _input) internal pure returns (int24 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt32(uint _offst, bytes memory _input) internal pure returns (int32 _output) { - + function bytesToInt32(uint256 _offst, bytes memory _input) internal pure returns (int32 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt40(uint _offst, bytes memory _input) internal pure returns (int40 _output) { - + function bytesToInt40(uint256 _offst, bytes memory _input) internal pure returns (int40 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt48(uint _offst, bytes memory _input) internal pure returns (int48 _output) { - + function bytesToInt48(uint256 _offst, bytes memory _input) internal pure returns (int48 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt56(uint _offst, bytes memory _input) internal pure returns (int56 _output) { - + function bytesToInt56(uint256 _offst, bytes memory _input) internal pure returns (int56 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt64(uint _offst, bytes memory _input) internal pure returns (int64 _output) { - + function bytesToInt64(uint256 _offst, bytes memory _input) internal pure returns (int64 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt72(uint _offst, bytes memory _input) internal pure returns (int72 _output) { - + function bytesToInt72(uint256 _offst, bytes memory _input) internal pure returns (int72 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt80(uint _offst, bytes memory _input) internal pure returns (int80 _output) { - + function bytesToInt80(uint256 _offst, bytes memory _input) internal pure returns (int80 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt88(uint _offst, bytes memory _input) internal pure returns (int88 _output) { - + function bytesToInt88(uint256 _offst, bytes memory _input) internal pure returns (int88 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt96(uint _offst, bytes memory _input) internal pure returns (int96 _output) { - + function bytesToInt96(uint256 _offst, bytes memory _input) internal pure returns (int96 _output) { assembly { _output := mload(add(_input, _offst)) } } - - function bytesToInt104(uint _offst, bytes memory _input) internal pure returns (int104 _output) { - + + function bytesToInt104(uint256 _offst, bytes memory _input) internal pure returns (int104 _output) { assembly { _output := mload(add(_input, _offst)) } } - - function bytesToInt112(uint _offst, bytes memory _input) internal pure returns (int112 _output) { - + + function bytesToInt112(uint256 _offst, bytes memory _input) internal pure returns (int112 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt120(uint _offst, bytes memory _input) internal pure returns (int120 _output) { - + function bytesToInt120(uint256 _offst, bytes memory _input) internal pure returns (int120 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt128(uint _offst, bytes memory _input) internal pure returns (int128 _output) { - + function bytesToInt128(uint256 _offst, bytes memory _input) internal pure returns (int128 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt136(uint _offst, bytes memory _input) internal pure returns (int136 _output) { - + function bytesToInt136(uint256 _offst, bytes memory _input) internal pure returns (int136 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt144(uint _offst, bytes memory _input) internal pure returns (int144 _output) { - + function bytesToInt144(uint256 _offst, bytes memory _input) internal pure returns (int144 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt152(uint _offst, bytes memory _input) internal pure returns (int152 _output) { - + function bytesToInt152(uint256 _offst, bytes memory _input) internal pure returns (int152 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt160(uint _offst, bytes memory _input) internal pure returns (int160 _output) { - + function bytesToInt160(uint256 _offst, bytes memory _input) internal pure returns (int160 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt168(uint _offst, bytes memory _input) internal pure returns (int168 _output) { - + function bytesToInt168(uint256 _offst, bytes memory _input) internal pure returns (int168 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt176(uint _offst, bytes memory _input) internal pure returns (int176 _output) { - + function bytesToInt176(uint256 _offst, bytes memory _input) internal pure returns (int176 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt184(uint _offst, bytes memory _input) internal pure returns (int184 _output) { - + function bytesToInt184(uint256 _offst, bytes memory _input) internal pure returns (int184 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt192(uint _offst, bytes memory _input) internal pure returns (int192 _output) { - + function bytesToInt192(uint256 _offst, bytes memory _input) internal pure returns (int192 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt200(uint _offst, bytes memory _input) internal pure returns (int200 _output) { - + function bytesToInt200(uint256 _offst, bytes memory _input) internal pure returns (int200 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt208(uint _offst, bytes memory _input) internal pure returns (int208 _output) { - + function bytesToInt208(uint256 _offst, bytes memory _input) internal pure returns (int208 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt216(uint _offst, bytes memory _input) internal pure returns (int216 _output) { - + function bytesToInt216(uint256 _offst, bytes memory _input) internal pure returns (int216 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt224(uint _offst, bytes memory _input) internal pure returns (int224 _output) { - + function bytesToInt224(uint256 _offst, bytes memory _input) internal pure returns (int224 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt232(uint _offst, bytes memory _input) internal pure returns (int232 _output) { - + function bytesToInt232(uint256 _offst, bytes memory _input) internal pure returns (int232 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt240(uint _offst, bytes memory _input) internal pure returns (int240 _output) { - + function bytesToInt240(uint256 _offst, bytes memory _input) internal pure returns (int240 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt248(uint _offst, bytes memory _input) internal pure returns (int248 _output) { - + function bytesToInt248(uint256 _offst, bytes memory _input) internal pure returns (int248 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToInt256(uint _offst, bytes memory _input) internal pure returns (int256 _output) { - + function bytesToInt256(uint256 _offst, bytes memory _input) internal pure returns (int256 _output) { assembly { _output := mload(add(_input, _offst)) } } - function bytesToUint8(uint _offst, bytes memory _input) internal pure returns (uint8 _output) { - + function bytesToUint8(uint256 _offst, bytes memory _input) internal pure returns (uint8 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint16(uint _offst, bytes memory _input) internal pure returns (uint16 _output) { - + function bytesToUint16(uint256 _offst, bytes memory _input) internal pure returns (uint16 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint24(uint _offst, bytes memory _input) internal pure returns (uint24 _output) { - + function bytesToUint24(uint256 _offst, bytes memory _input) internal pure returns (uint24 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint32(uint _offst, bytes memory _input) internal pure returns (uint32 _output) { - + function bytesToUint32(uint256 _offst, bytes memory _input) internal pure returns (uint32 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint40(uint _offst, bytes memory _input) internal pure returns (uint40 _output) { - + function bytesToUint40(uint256 _offst, bytes memory _input) internal pure returns (uint40 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint48(uint _offst, bytes memory _input) internal pure returns (uint48 _output) { - + function bytesToUint48(uint256 _offst, bytes memory _input) internal pure returns (uint48 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint56(uint _offst, bytes memory _input) internal pure returns (uint56 _output) { - + function bytesToUint56(uint256 _offst, bytes memory _input) internal pure returns (uint56 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint64(uint _offst, bytes memory _input) internal pure returns (uint64 _output) { - + function bytesToUint64(uint256 _offst, bytes memory _input) internal pure returns (uint64 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint72(uint _offst, bytes memory _input) internal pure returns (uint72 _output) { - + function bytesToUint72(uint256 _offst, bytes memory _input) internal pure returns (uint72 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint80(uint _offst, bytes memory _input) internal pure returns (uint80 _output) { - + function bytesToUint80(uint256 _offst, bytes memory _input) internal pure returns (uint80 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint88(uint _offst, bytes memory _input) internal pure returns (uint88 _output) { - + function bytesToUint88(uint256 _offst, bytes memory _input) internal pure returns (uint88 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint96(uint _offst, bytes memory _input) internal pure returns (uint96 _output) { - + function bytesToUint96(uint256 _offst, bytes memory _input) internal pure returns (uint96 _output) { assembly { _output := mload(add(_input, _offst)) } - } - - function bytesToUint104(uint _offst, bytes memory _input) internal pure returns (uint104 _output) { - + } + + function bytesToUint104(uint256 _offst, bytes memory _input) internal pure returns (uint104 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint112(uint _offst, bytes memory _input) internal pure returns (uint112 _output) { - + function bytesToUint112(uint256 _offst, bytes memory _input) internal pure returns (uint112 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint120(uint _offst, bytes memory _input) internal pure returns (uint120 _output) { - + function bytesToUint120(uint256 _offst, bytes memory _input) internal pure returns (uint120 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint128(uint _offst, bytes memory _input) internal pure returns (uint128 _output) { - + function bytesToUint128(uint256 _offst, bytes memory _input) internal pure returns (uint128 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint136(uint _offst, bytes memory _input) internal pure returns (uint136 _output) { - + function bytesToUint136(uint256 _offst, bytes memory _input) internal pure returns (uint136 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint144(uint _offst, bytes memory _input) internal pure returns (uint144 _output) { - + function bytesToUint144(uint256 _offst, bytes memory _input) internal pure returns (uint144 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint152(uint _offst, bytes memory _input) internal pure returns (uint152 _output) { - + function bytesToUint152(uint256 _offst, bytes memory _input) internal pure returns (uint152 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint160(uint _offst, bytes memory _input) internal pure returns (uint160 _output) { - + function bytesToUint160(uint256 _offst, bytes memory _input) internal pure returns (uint160 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint168(uint _offst, bytes memory _input) internal pure returns (uint168 _output) { - + function bytesToUint168(uint256 _offst, bytes memory _input) internal pure returns (uint168 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint176(uint _offst, bytes memory _input) internal pure returns (uint176 _output) { - + function bytesToUint176(uint256 _offst, bytes memory _input) internal pure returns (uint176 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint184(uint _offst, bytes memory _input) internal pure returns (uint184 _output) { - + function bytesToUint184(uint256 _offst, bytes memory _input) internal pure returns (uint184 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint192(uint _offst, bytes memory _input) internal pure returns (uint192 _output) { - + function bytesToUint192(uint256 _offst, bytes memory _input) internal pure returns (uint192 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint200(uint _offst, bytes memory _input) internal pure returns (uint200 _output) { - + function bytesToUint200(uint256 _offst, bytes memory _input) internal pure returns (uint200 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint208(uint _offst, bytes memory _input) internal pure returns (uint208 _output) { - + function bytesToUint208(uint256 _offst, bytes memory _input) internal pure returns (uint208 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint216(uint _offst, bytes memory _input) internal pure returns (uint216 _output) { - + function bytesToUint216(uint256 _offst, bytes memory _input) internal pure returns (uint216 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint224(uint _offst, bytes memory _input) internal pure returns (uint224 _output) { - + function bytesToUint224(uint256 _offst, bytes memory _input) internal pure returns (uint224 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint232(uint _offst, bytes memory _input) internal pure returns (uint232 _output) { - + function bytesToUint232(uint256 _offst, bytes memory _input) internal pure returns (uint232 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint240(uint _offst, bytes memory _input) internal pure returns (uint240 _output) { - + function bytesToUint240(uint256 _offst, bytes memory _input) internal pure returns (uint240 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint248(uint _offst, bytes memory _input) internal pure returns (uint248 _output) { - + function bytesToUint248(uint256 _offst, bytes memory _input) internal pure returns (uint248 _output) { assembly { _output := mload(add(_input, _offst)) } - } + } - function bytesToUint256(uint _offst, bytes memory _input) internal pure returns (uint256 _output) { - + function bytesToUint256(uint256 _offst, bytes memory _input) internal pure returns (uint256 _output) { assembly { _output := mload(add(_input, _offst)) } - } - + } } diff --git a/contracts/lib/CmnPkg.sol b/contracts/lib/CmnPkg.sol index 04fbc05e..96b0aca1 100644 --- a/contracts/lib/CmnPkg.sol +++ b/contracts/lib/CmnPkg.sol @@ -4,11 +4,9 @@ import "./RLPEncode.sol"; import "./RLPDecode.sol"; library CmnPkg { - using RLPEncode for *; using RLPDecode for *; - struct CommonAckPackage { uint32 code; } @@ -24,7 +22,7 @@ library CmnPkg { RLPDecode.Iterator memory iter = msgBytes.toRLPItem().iterator(); bool success = false; - uint256 idx=0; + uint256 idx = 0; while (iter.hasNext()) { if (idx == 0) { ackPkg.code = uint32(iter.next().toUint()); @@ -36,4 +34,4 @@ library CmnPkg { } return (ackPkg, success); } -} \ No newline at end of file +} diff --git a/contracts/lib/Memory.sol b/contracts/lib/Memory.sol index e4349a14..0b28d556 100644 --- a/contracts/lib/Memory.sol +++ b/contracts/lib/Memory.sol @@ -1,18 +1,17 @@ pragma solidity 0.6.4; library Memory { - // Size of a word, in bytes. - uint internal constant WORD_SIZE = 32; + uint256 internal constant WORD_SIZE = 32; // Size of the header of a 'bytes' array. - uint internal constant BYTES_HEADER_SIZE = 32; + uint256 internal constant BYTES_HEADER_SIZE = 32; // Address of the free memory pointer. - uint internal constant FREE_MEM_PTR = 0x40; + uint256 internal constant FREE_MEM_PTR = 0x40; // Compares the 'len' bytes starting at address 'addr' in memory with the 'len' // bytes starting at 'addr2'. // Returns 'true' if the bytes are the same, otherwise 'false'. - function equals(uint addr, uint addr2, uint len) internal pure returns (bool equal) { + function equals(uint256 addr, uint256 addr2, uint256 len) internal pure returns (bool equal) { assembly { equal := eq(keccak256(addr, len), keccak256(addr2, len)) } @@ -22,11 +21,11 @@ library Memory { // 'bts'. It is allowed to set 'len' to a lower value then 'bts.length', in which case only // the first 'len' bytes will be compared. // Requires that 'bts.length >= len' - function equals(uint addr, uint len, bytes memory bts) internal pure returns (bool equal) { + function equals(uint256 addr, uint256 len, bytes memory bts) internal pure returns (bool equal) { require(bts.length >= len); - uint addr2; + uint256 addr2; assembly { - addr2 := add(bts, /*BYTES_HEADER_SIZE*/32) + addr2 := add(bts, /*BYTES_HEADER_SIZE*/ 32) } return equals(addr, addr2, len); } @@ -38,7 +37,7 @@ library Memory { // Copy 'len' bytes from memory address 'src', to address 'dest'. // This function does not check the or destination, it only copies // the bytes. - function copy(uint src, uint dest, uint len) internal pure { + function copy(uint256 src, uint256 dest, uint256 len) internal pure { // Copy word-length chunks while possible for (; len >= WORD_SIZE; len -= WORD_SIZE) { assembly { @@ -49,7 +48,7 @@ library Memory { } // Copy remaining bytes - uint mask = 256 ** (WORD_SIZE - len) - 1; + uint256 mask = 256 ** (WORD_SIZE - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) let destpart := and(mload(dest), mask) @@ -58,51 +57,51 @@ library Memory { } // Returns a memory pointer to the provided bytes array. - function ptr(bytes memory bts) internal pure returns (uint addr) { + function ptr(bytes memory bts) internal pure returns (uint256 addr) { assembly { addr := bts } } // Returns a memory pointer to the data portion of the provided bytes array. - function dataPtr(bytes memory bts) internal pure returns (uint addr) { + function dataPtr(bytes memory bts) internal pure returns (uint256 addr) { assembly { - addr := add(bts, /*BYTES_HEADER_SIZE*/32) + addr := add(bts, /*BYTES_HEADER_SIZE*/ 32) } } // This function does the same as 'dataPtr(bytes memory)', but will also return the // length of the provided bytes array. - function fromBytes(bytes memory bts) internal pure returns (uint addr, uint len) { + function fromBytes(bytes memory bts) internal pure returns (uint256 addr, uint256 len) { len = bts.length; assembly { - addr := add(bts, /*BYTES_HEADER_SIZE*/32) + addr := add(bts, /*BYTES_HEADER_SIZE*/ 32) } } // Creates a 'bytes memory' variable from the memory address 'addr', with the // length 'len'. The function will allocate new memory for the bytes array, and // the 'len bytes starting at 'addr' will be copied into that new memory. - function toBytes(uint addr, uint len) internal pure returns (bytes memory bts) { + function toBytes(uint256 addr, uint256 len) internal pure returns (bytes memory bts) { bts = new bytes(len); - uint btsptr; + uint256 btsptr; assembly { - btsptr := add(bts, /*BYTES_HEADER_SIZE*/32) + btsptr := add(bts, /*BYTES_HEADER_SIZE*/ 32) } copy(addr, btsptr, len); } // Get the word stored at memory address 'addr' as a 'uint'. - function toUint(uint addr) internal pure returns (uint n) { + function toUint(uint256 addr) internal pure returns (uint256 n) { assembly { n := mload(addr) } } // Get the word stored at memory address 'addr' as a 'bytes32'. - function toBytes32(uint addr) internal pure returns (bytes32 bts) { + function toBytes32(uint256 addr) internal pure returns (bytes32 bts) { assembly { bts := mload(addr) } } -} \ No newline at end of file +} diff --git a/contracts/lib/RLPDecode.sol b/contracts/lib/RLPDecode.sol index 3c694dbc..acbf519b 100644 --- a/contracts/lib/RLPDecode.sol +++ b/contracts/lib/RLPDecode.sol @@ -2,27 +2,27 @@ pragma solidity 0.6.4; library RLPDecode { uint8 constant STRING_SHORT_START = 0x80; - uint8 constant STRING_LONG_START = 0xb8; - uint8 constant LIST_SHORT_START = 0xc0; - uint8 constant LIST_LONG_START = 0xf8; + uint8 constant STRING_LONG_START = 0xb8; + uint8 constant LIST_SHORT_START = 0xc0; + uint8 constant LIST_LONG_START = 0xf8; uint8 constant WORD_SIZE = 32; struct RLPItem { - uint len; - uint memPtr; + uint256 len; + uint256 memPtr; } struct Iterator { - RLPItem item; // Item that's being iterated over. - uint nextPtr; // Position of the next item in the list. + RLPItem item; // Item that's being iterated over. + uint256 nextPtr; // Position of the next item in the list. } function next(Iterator memory self) internal pure returns (RLPItem memory) { require(hasNext(self)); - uint ptr = self.nextPtr; - uint itemLength = _itemLength(ptr); + uint256 ptr = self.nextPtr; + uint256 itemLength = _itemLength(ptr); self.nextPtr = ptr + itemLength; return RLPItem(itemLength, ptr); @@ -34,7 +34,7 @@ library RLPDecode { } function toRLPItem(bytes memory self) internal pure returns (RLPItem memory) { - uint memPtr; + uint256 memPtr; assembly { memPtr := add(self, 0x20) } @@ -45,27 +45,27 @@ library RLPDecode { function iterator(RLPItem memory self) internal pure returns (Iterator memory) { require(isList(self)); - uint ptr = self.memPtr + _payloadOffset(self.memPtr); + uint256 ptr = self.memPtr + _payloadOffset(self.memPtr); return Iterator(self, ptr); } - function rlpLen(RLPItem memory item) internal pure returns (uint) { + function rlpLen(RLPItem memory item) internal pure returns (uint256) { return item.len; } - function payloadLen(RLPItem memory item) internal pure returns (uint) { + function payloadLen(RLPItem memory item) internal pure returns (uint256) { return item.len - _payloadOffset(item.memPtr); } function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) { require(isList(item)); - uint items = numItems(item); + uint256 items = numItems(item); RLPItem[] memory result = new RLPItem[](items); - uint memPtr = item.memPtr + _payloadOffset(item.memPtr); - uint dataLen; - for (uint i = 0; i < items; ++i) { + uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 dataLen; + for (uint256 i = 0; i < items; ++i) { dataLen = _itemLength(memPtr); result[i] = RLPItem(dataLen, memPtr); memPtr = memPtr + dataLen; @@ -78,13 +78,14 @@ library RLPDecode { if (item.len == 0) return false; uint8 byte0; - uint memPtr = item.memPtr; + uint256 memPtr = item.memPtr; assembly { byte0 := byte(0, mload(memPtr)) } - if (byte0 < LIST_SHORT_START) + if (byte0 < LIST_SHORT_START) { return false; + } return true; } @@ -92,7 +93,7 @@ library RLPDecode { bytes memory result = new bytes(item.len); if (result.length == 0) return result; - uint ptr; + uint256 ptr; assembly { ptr := add(0x20, result) } @@ -103,8 +104,8 @@ library RLPDecode { function toBoolean(RLPItem memory item) internal pure returns (bool) { require(item.len == 1); - uint result; - uint memPtr = item.memPtr; + uint256 result; + uint256 memPtr = item.memPtr; assembly { result := byte(0, mload(memPtr)) } @@ -123,34 +124,32 @@ library RLPDecode { return address(toUint(item)); } - function toUint(RLPItem memory item) internal pure returns (uint) { + function toUint(RLPItem memory item) internal pure returns (uint256) { require(item.len > 0 && item.len <= 33); - uint offset = _payloadOffset(item.memPtr); + uint256 offset = _payloadOffset(item.memPtr); require(item.len >= offset, "length is less than offset"); - uint len = item.len - offset; + uint256 len = item.len - offset; - uint result; - uint memPtr = item.memPtr + offset; + uint256 result; + uint256 memPtr = item.memPtr + offset; assembly { result := mload(memPtr) - // shfit to the correct location if neccesary - if lt(len, 32) { - result := div(result, exp(256, sub(32, len))) - } + // shfit to the correct location if neccesary + if lt(len, 32) { result := div(result, exp(256, sub(32, len))) } } return result; } // enforces 32 byte length - function toUintStrict(RLPItem memory item) internal pure returns (uint) { + function toUintStrict(RLPItem memory item) internal pure returns (uint256) { // one byte prefix require(item.len == 33); - uint result; - uint memPtr = item.memPtr + 1; + uint256 result; + uint256 memPtr = item.memPtr + 1; assembly { result := mload(memPtr) } @@ -161,11 +160,11 @@ library RLPDecode { function toBytes(RLPItem memory item) internal pure returns (bytes memory) { require(item.len > 0); - uint offset = _payloadOffset(item.memPtr); - uint len = item.len - offset; // data length + uint256 offset = _payloadOffset(item.memPtr); + uint256 len = item.len - offset; // data length bytes memory result = new bytes(len); - uint destPtr; + uint256 destPtr; assembly { destPtr := add(0x20, result) } @@ -174,12 +173,12 @@ library RLPDecode { return result; } - function numItems(RLPItem memory item) private pure returns (uint) { + function numItems(RLPItem memory item) private pure returns (uint256) { if (item.len == 0) return 0; - uint count = 0; - uint currPtr = item.memPtr + _payloadOffset(item.memPtr); - uint endPtr = item.memPtr + item.len; + uint256 count = 0; + uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 endPtr = item.memPtr + item.len; while (currPtr < endPtr) { currPtr = currPtr + _itemLength(currPtr); // skip over an item ++count; @@ -188,21 +187,19 @@ library RLPDecode { return count; } - function _itemLength(uint memPtr) private pure returns (uint) { - uint itemLen; - uint byte0; + function _itemLength(uint256 memPtr) private pure returns (uint256) { + uint256 itemLen; + uint256 byte0; assembly { byte0 := byte(0, mload(memPtr)) } - if (byte0 < STRING_SHORT_START) + if (byte0 < STRING_SHORT_START) { itemLen = 1; - - else if (byte0 < STRING_LONG_START) + } else if (byte0 < STRING_LONG_START) { itemLen = byte0 - STRING_SHORT_START + 1; - - else if (byte0 < LIST_SHORT_START) { - uint dataLen; + } else if (byte0 < LIST_SHORT_START) { + uint256 dataLen; assembly { let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is memPtr := add(memPtr, 1) // skip over the first byte @@ -212,14 +209,10 @@ library RLPDecode { itemLen := add(dataLen, add(byteLen, 1)) } require(itemLen >= dataLen, "addition overflow"); - } - - else if (byte0 < LIST_LONG_START) { + } else if (byte0 < LIST_LONG_START) { itemLen = byte0 - LIST_SHORT_START + 1; - } - - else { - uint dataLen; + } else { + uint256 dataLen; assembly { let byteLen := sub(byte0, 0xf7) memPtr := add(memPtr, 1) @@ -234,20 +227,23 @@ library RLPDecode { } // @return number of bytes until the data - function _payloadOffset(uint memPtr) private pure returns (uint) { - uint byte0; + function _payloadOffset(uint256 memPtr) private pure returns (uint256) { + uint256 byte0; assembly { byte0 := byte(0, mload(memPtr)) } - if (byte0 < STRING_SHORT_START) + if (byte0 < STRING_SHORT_START) { return 0; - else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) + } else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) { return 1; - else if (byte0 < LIST_SHORT_START) // being explicit + } else if ( + byte0 < LIST_SHORT_START // being explicit + ) { return byte0 - (STRING_LONG_START - 1) + 1; - else + } else { return byte0 - (LIST_LONG_START - 1) + 1; + } } /* @@ -255,7 +251,7 @@ library RLPDecode { * @param dest Pointer to destination * @param len Amount of memory to copy from the source */ - function copy(uint src, uint dest, uint len) private pure { + function copy(uint256 src, uint256 dest, uint256 len) private pure { if (len == 0) return; // copy as many word sizes as possible @@ -269,11 +265,11 @@ library RLPDecode { } // left over bytes. Mask is used to remove unwanted bytes from the word - uint mask = 256 ** (WORD_SIZE - len) - 1; + uint256 mask = 256 ** (WORD_SIZE - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) // zero out src let destpart := and(mload(dest), mask) // retrieve the bytes mstore(dest, or(destpart, srcpart)) } } -} \ No newline at end of file +} diff --git a/contracts/lib/RLPEncode.sol b/contracts/lib/RLPEncode.sol index e8153114..e7ba6c4d 100644 --- a/contracts/lib/RLPEncode.sol +++ b/contracts/lib/RLPEncode.sol @@ -1,7 +1,6 @@ pragma solidity 0.6.4; library RLPEncode { - uint8 constant STRING_OFFSET = 0x80; uint8 constant LIST_OFFSET = 0xc0; @@ -38,7 +37,7 @@ library RLPEncode { * @param self The uint to encode * @return The RLP encoded uint in bytes */ - function encodeUint(uint self) internal pure returns (bytes memory) { + function encodeUint(uint256 self) internal pure returns (bytes memory) { return encodeBytes(toBinary(self)); } @@ -47,8 +46,8 @@ library RLPEncode { * @param self The int to encode * @return The RLP encoded int in bytes */ - function encodeInt(int self) internal pure returns (bytes memory) { - return encodeUint(uint(self)); + function encodeInt(int256 self) internal pure returns (bytes memory) { + return encodeUint(uint256(self)); } /** @@ -72,7 +71,7 @@ library RLPEncode { return new bytes(0); } bytes memory payload = self[0]; - for (uint i = 1; i < self.length; ++i) { + for (uint256 i = 1; i < self.length; ++i) { payload = mergeBytes(payload, self[i]); } return mergeBytes(encodeLength(payload.length, LIST_OFFSET), payload); @@ -84,79 +83,71 @@ library RLPEncode { * @param _postBytes The second bytes array * @return The merged bytes array */ - function mergeBytes( - bytes memory _preBytes, - bytes memory _postBytes - ) - internal - pure - returns (bytes memory) - { + function mergeBytes(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. tempBytes := mload(0x40) - // Store the length of the first bytes array at the beginning of - // the memory for tempBytes. + // Store the length of the first bytes array at the beginning of + // the memory for tempBytes. let length := mload(_preBytes) mstore(tempBytes, length) - // Maintain a memory counter for the current write location in the - // temp bytes array by adding the 32 bytes for the array length to - // the starting location. + // Maintain a memory counter for the current write location in the + // temp bytes array by adding the 32 bytes for the array length to + // the starting location. let mc := add(tempBytes, 0x20) - // Stop copying when the memory counter reaches the length of the - // first bytes array. + // Stop copying when the memory counter reaches the length of the + // first bytes array. let end := add(mc, length) for { - // Initialize a copy counter to the start of the _preBytes data, - // 32 bytes into its memory. + // Initialize a copy counter to the start of the _preBytes data, + // 32 bytes into its memory. let cc := add(_preBytes, 0x20) } lt(mc, end) { - // Increase both counters by 32 bytes each iteration. + // Increase both counters by 32 bytes each iteration. mc := add(mc, 0x20) cc := add(cc, 0x20) } { - // Write the _preBytes data into the tempBytes memory 32 bytes - // at a time. + // Write the _preBytes data into the tempBytes memory 32 bytes + // at a time. mstore(mc, mload(cc)) } - // Add the length of _postBytes to the current length of tempBytes - // and store it as the new length in the first 32 bytes of the - // tempBytes memory. + // Add the length of _postBytes to the current length of tempBytes + // and store it as the new length in the first 32 bytes of the + // tempBytes memory. length := mload(_postBytes) mstore(tempBytes, add(length, mload(tempBytes))) - // Move the memory counter back from a multiple of 0x20 to the - // actual end of the _preBytes data. + // Move the memory counter back from a multiple of 0x20 to the + // actual end of the _preBytes data. mc := end - // Stop copying when the memory counter reaches the new combined - // length of the arrays. + // Stop copying when the memory counter reaches the new combined + // length of the arrays. end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { + for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - // Update the free-memory pointer by padding our last write location - // to 32 bytes: add 31 bytes to the end of tempBytes to move to the - // next 32 byte block, then round down to the nearest multiple of - // 32. If the sum of the length of the two arrays is zero then add - // one before rounding down to leave a blank 32 bytes (the length block with 0). - mstore(0x40, and( - add(add(end, iszero(add(length, mload(_preBytes)))), 31), - not(31) // Round down to the nearest 32 bytes. - )) + } { mstore(mc, mload(cc)) } + + // Update the free-memory pointer by padding our last write location + // to 32 bytes: add 31 bytes to the end of tempBytes to move to the + // next 32 byte block, then round down to the nearest multiple of + // 32. If the sum of the length of the two arrays is zero then add + // one before rounding down to leave a blank 32 bytes (the length block with 0). + mstore( + 0x40, + and( + add(add(end, iszero(add(length, mload(_preBytes)))), 31), + not(31) // Round down to the nearest 32 bytes. + ) + ) } return tempBytes; @@ -168,15 +159,15 @@ library RLPEncode { * @param offset `STRING_OFFSET` if item is string, `LIST_OFFSET` if item is list * @return RLP encoded bytes */ - function encodeLength(uint length, uint offset) internal pure returns (bytes memory) { - require(length < 256**8, "input too long"); + function encodeLength(uint256 length, uint256 offset) internal pure returns (bytes memory) { + require(length < 256 ** 8, "input too long"); bytes memory rs = new bytes(1); if (length <= 55) { - rs[0] = byte(uint8(length + offset)); + rs[0] = bytes1(uint8(length + offset)); return rs; } bytes memory bl = toBinary(length); - rs[0] = byte(uint8(bl.length + offset + 55)); + rs[0] = bytes1(uint8(bl.length + offset + 55)); return mergeBytes(rs, bl); } @@ -185,12 +176,12 @@ library RLPEncode { * @param x The integer to encode * @return RLP encoded bytes */ - function toBinary(uint x) internal pure returns (bytes memory) { + function toBinary(uint256 x) internal pure returns (bytes memory) { bytes memory b = new bytes(32); assembly { mstore(add(b, 32), x) } - uint i; + uint256 i; if (x & 0xffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000 == 0) { i = 24; } else if (x & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000 == 0) { @@ -203,7 +194,7 @@ library RLPEncode { break; } } - uint length = 32 - i; + uint256 length = 32 - i; bytes memory rs = new bytes(length); assembly { mstore(add(rs, length), x) @@ -211,4 +202,4 @@ library RLPEncode { } return rs; } -} \ No newline at end of file +} diff --git a/contracts/lib/SizeOf.sol b/contracts/lib/SizeOf.sol index 952261c4..43342547 100644 --- a/contracts/lib/SizeOf.sol +++ b/contracts/lib/SizeOf.sol @@ -6,70 +6,65 @@ pragma solidity 0.6.4; * @dev The SizeOf return the size of the solidity types in byte * @author pouladzade@gmail.com */ - -library SizeOf { - - function sizeOfString(string memory _in) internal pure returns(uint _size) { +library SizeOf { + function sizeOfString(string memory _in) internal pure returns (uint256 _size) { _size = bytes(_in).length / 32; - if (bytes(_in).length % 32 != 0) + if (bytes(_in).length % 32 != 0) { ++_size; - + } + ++_size; // first 32 bytes is reserved for the size of the string _size *= 32; } - function sizeOfInt(uint16 _postfix) internal pure returns(uint size) { - - assembly{ + function sizeOfInt(uint16 _postfix) internal pure returns (uint256 size) { + assembly { switch _postfix - case 8 { size := 1 } - case 16 { size := 2 } - case 24 { size := 3 } - case 32 { size := 4 } - case 40 { size := 5 } - case 48 { size := 6 } - case 56 { size := 7 } - case 64 { size := 8 } - case 72 { size := 9 } - case 80 { size := 10 } - case 88 { size := 11 } - case 96 { size := 12 } - case 104 { size := 13 } - case 112 { size := 14 } - case 120 { size := 15 } - case 128 { size := 16 } - case 136 { size := 17 } - case 144 { size := 18 } - case 152 { size := 19 } - case 160 { size := 20 } - case 168 { size := 21 } - case 176 { size := 22 } - case 184 { size := 23 } - case 192 { size := 24 } - case 200 { size := 25 } - case 208 { size := 26 } - case 216 { size := 27 } - case 224 { size := 28 } - case 232 { size := 29 } - case 240 { size := 30 } - case 248 { size := 31 } - case 256 { size := 32 } - default { size := 32 } + case 8 { size := 1 } + case 16 { size := 2 } + case 24 { size := 3 } + case 32 { size := 4 } + case 40 { size := 5 } + case 48 { size := 6 } + case 56 { size := 7 } + case 64 { size := 8 } + case 72 { size := 9 } + case 80 { size := 10 } + case 88 { size := 11 } + case 96 { size := 12 } + case 104 { size := 13 } + case 112 { size := 14 } + case 120 { size := 15 } + case 128 { size := 16 } + case 136 { size := 17 } + case 144 { size := 18 } + case 152 { size := 19 } + case 160 { size := 20 } + case 168 { size := 21 } + case 176 { size := 22 } + case 184 { size := 23 } + case 192 { size := 24 } + case 200 { size := 25 } + case 208 { size := 26 } + case 216 { size := 27 } + case 224 { size := 28 } + case 232 { size := 29 } + case 240 { size := 30 } + case 248 { size := 31 } + case 256 { size := 32 } + default { size := 32 } } - } - - function sizeOfUint(uint16 _postfix) internal pure returns(uint size) { + + function sizeOfUint(uint16 _postfix) internal pure returns (uint256 size) { return sizeOfInt(_postfix); } - function sizeOfAddress() internal pure returns(uint8) { - return 20; + function sizeOfAddress() internal pure returns (uint8) { + return 20; } - - function sizeOfBool() internal pure returns(uint8) { - return 1; - } - + function sizeOfBool() internal pure returns (uint8) { + return 1; + } } diff --git a/contracts/lib/TypesToBytes.sol b/contracts/lib/TypesToBytes.sol index 1547ae0b..574135ca 100644 --- a/contracts/lib/TypesToBytes.sol +++ b/contracts/lib/TypesToBytes.sol @@ -6,56 +6,49 @@ pragma solidity 0.6.4; * @dev The TypesToBytes contract converts the standard solidity types to the byte array * @author pouladzade@gmail.com */ - library TypesToBytes { - - function addressToBytes(uint _offst, address _input, bytes memory _output) internal pure { - + function addressToBytes(uint256 _offst, address _input, bytes memory _output) internal pure { assembly { mstore(add(_output, _offst), _input) } } - function bytes32ToBytes(uint _offst, bytes32 _input, bytes memory _output) internal pure { - + function bytes32ToBytes(uint256 _offst, bytes32 _input, bytes memory _output) internal pure { assembly { mstore(add(_output, _offst), _input) - mstore(add(add(_output, _offst),32), add(_input,32)) + mstore(add(add(_output, _offst), 32), add(_input, 32)) } } - - function boolToBytes(uint _offst, bool _input, bytes memory _output) internal pure { + + function boolToBytes(uint256 _offst, bool _input, bytes memory _output) internal pure { uint8 x = _input == false ? 0 : 1; assembly { mstore(add(_output, _offst), x) } } - - function stringToBytes(uint _offst, bytes memory _input, bytes memory _output) internal pure { + + function stringToBytes(uint256 _offst, bytes memory _input, bytes memory _output) internal pure { uint256 stack_size = _input.length / 32; if (_input.length % 32 > 0) ++stack_size; - + assembly { - stack_size := add(stack_size,1)//adding because of 32 first bytes memory as the length - for { let index := 0 } lt(index,stack_size) { index := add(index ,1) } { - mstore(add(_output, _offst), mload(add(_input,mul(index,32)))) - _offst := sub(_offst , 32) + stack_size := add(stack_size, 1) //adding because of 32 first bytes memory as the length + for { let index := 0 } lt(index, stack_size) { index := add(index, 1) } { + mstore(add(_output, _offst), mload(add(_input, mul(index, 32)))) + _offst := sub(_offst, 32) } } } - function intToBytes(uint _offst, int _input, bytes memory _output) internal pure { - + function intToBytes(uint256 _offst, int256 _input, bytes memory _output) internal pure { assembly { mstore(add(_output, _offst), _input) } - } - - function uintToBytes(uint _offst, uint _input, bytes memory _output) internal pure { + } + function uintToBytes(uint256 _offst, uint256 _input, bytes memory _output) internal pure { assembly { mstore(add(_output, _offst), _input) } - } - + } } diff --git a/contracts/mock/MockTokenHub.sol b/contracts/mock/MockTokenHub.sol index c751baf5..5754dccb 100644 --- a/contracts/mock/MockTokenHub.sol +++ b/contracts/mock/MockTokenHub.sol @@ -3,63 +3,64 @@ pragma solidity 0.6.4; import "../interface/ITokenHub.sol"; contract MockTokenHub is ITokenHub { + bool panicBatchTransferOut; - bool panicBatchTransferOut; + bytes32 public constant BEP2_TOKEN_SYMBOL_FOR_BNB = + 0x424E420000000000000000000000000000000000000000000000000000000000; // "BNB" - bytes32 constant public BEP2_TOKEN_SYMBOL_FOR_BNB = 0x424E420000000000000000000000000000000000000000000000000000000000; // "BNB" - - function getMiniRelayFee() external view override(ITokenHub) returns (uint256) { - return (1e16); - } + function getMiniRelayFee() external view override(ITokenHub) returns (uint256) { + return (1e16); + } - function getContractAddrByBEP2Symbol(bytes32) external view override(ITokenHub) returns(address) { - return address(0x0); - } + function getContractAddrByBEP2Symbol(bytes32) external view override(ITokenHub) returns (address) { + return address(0x0); + } - function getBep2SymbolByContractAddr(address) external view override(ITokenHub) returns(bytes32) { - return bytes32(0x0); - } + function getBep2SymbolByContractAddr(address) external view override(ITokenHub) returns (bytes32) { + return bytes32(0x0); + } - function bindToken(bytes32 bep2Symbol, address contractAddr, uint256 decimals) external override(ITokenHub) {} + function bindToken(bytes32 bep2Symbol, address contractAddr, uint256 decimals) external override(ITokenHub) { } - function unbindToken(bytes32 bep2Symbol, address contractAddr) external override(ITokenHub) {} + function unbindToken(bytes32 bep2Symbol, address contractAddr) external override(ITokenHub) { } - function recoverBCAsset(bytes32, address, uint256) - external override(ITokenHub) { - } + function recoverBCAsset(bytes32, address, uint256) external override(ITokenHub) { } - function cancelTokenRecoverLock(bytes32, address) external override { - address TOKEN_RECOVER_PORTAL_ADDR = address(0x0000000000000000000000000000000000003000); - require(msg.sender == TOKEN_RECOVER_PORTAL_ADDR, "only token reover portal contract can call this function"); - } + function cancelTokenRecoverLock(bytes32, address) external override { + address TOKEN_RECOVER_PORTAL_ADDR = address(0x0000000000000000000000000000000000003000); + require(msg.sender == TOKEN_RECOVER_PORTAL_ADDR, "only token reover portal contract can call this function"); + } - function transferOut(address, address, uint256, uint64) - external override(ITokenHub) payable returns (bool) { - return true; - } + function transferOut(address, address, uint256, uint64) external payable override(ITokenHub) returns (bool) { + return true; + } - /* solium-disable-next-line */ - function batchTransferOutBNB(address[] calldata, uint256[] calldata, address[] calldata, - uint64) external override(ITokenHub) payable returns (bool) { - require(!panicBatchTransferOut, "panic in batchTransferOut"); - return true; - } + /* solium-disable-next-line */ + function batchTransferOutBNB( + address[] calldata, + uint256[] calldata, + address[] calldata, + uint64 + ) external payable override(ITokenHub) returns (bool) { + require(!panicBatchTransferOut, "panic in batchTransferOut"); + return true; + } - function setPanicBatchTransferOut(bool doPanic)external{ - panicBatchTransferOut = doPanic; - } + function setPanicBatchTransferOut(bool doPanic) external { + panicBatchTransferOut = doPanic; + } - function withdrawStakingBNB(uint256 amount) external override returns(bool) { - address STAKING_CONTRACT_ADDR = address(0x0000000000000000000000000000000000002001); - require(msg.sender == STAKING_CONTRACT_ADDR, "only staking system contract can call this function"); - if (amount != 0) { - payable(STAKING_CONTRACT_ADDR).transfer(amount); + function withdrawStakingBNB(uint256 amount) external override returns (bool) { + address STAKING_CONTRACT_ADDR = address(0x0000000000000000000000000000000000002001); + 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; } - return true; - } - function cancelTransferIn(address, address) override external { - address CROSS_CHAIN_CONTRACT_ADDR = address(0x0000000000000000000000000000000000002000); - require(msg.sender == CROSS_CHAIN_CONTRACT_ADDR, "only cross chain contract can call this function"); - } + function cancelTransferIn(address, address) external override { + address CROSS_CHAIN_CONTRACT_ADDR = address(0x0000000000000000000000000000000000002000); + require(msg.sender == CROSS_CHAIN_CONTRACT_ADDR, "only cross chain contract can call this function"); + } } diff --git a/contracts/tool/BSCValidatorSetTool.sol b/contracts/tool/BSCValidatorSetTool.sol index 71526bec..12173b85 100644 --- a/contracts/tool/BSCValidatorSetTool.sol +++ b/contracts/tool/BSCValidatorSetTool.sol @@ -3,110 +3,109 @@ pragma solidity 0.6.4; import "../lib/RLPDecode.sol"; import "../lib/Memory.sol"; - contract BSCValidatorSetTool { + bytes public constant INIT_VALIDATORSET_BYTES = + hex"f84580f842f840949fb29aac15b9a4b7f17c3385939b007540f4d791949fb29aac15b9a4b7f17c3385939b007540f4d791949fb29aac15b9a4b7f17c3385939b007540f4d79164"; - bytes public constant INIT_VALIDATORSET_BYTES = hex"f84580f842f840949fb29aac15b9a4b7f17c3385939b007540f4d791949fb29aac15b9a4b7f17c3385939b007540f4d791949fb29aac15b9a4b7f17c3385939b007540f4d79164"; - - using RLPDecode for *; + using RLPDecode for *; - struct Validator { - address consensusAddress; - address payable feeAddress; - address BBCFeeAddress; - uint64 votingPower; - } - - struct IbcValidatorSetPackage { - uint8 packageType; - Validator[] validatorSet; - } + struct Validator { + address consensusAddress; + address payable feeAddress; + address BBCFeeAddress; + uint64 votingPower; + } - function init() external pure{ - bool valid= decodeValidatorSetSynPackage(INIT_VALIDATORSET_BYTES); - require(valid, "failed to init"); - } + struct IbcValidatorSetPackage { + uint8 packageType; + Validator[] validatorSet; + } - function decodeValidatorSetSynPackage(bytes memory msgBytes) internal pure returns (bool) { - IbcValidatorSetPackage memory validatorSetPkg; + function init() external pure { + bool valid = decodeValidatorSetSynPackage(INIT_VALIDATORSET_BYTES); + require(valid, "failed to init"); + } - 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); - for (uint j = 0;j