@keep-network/tbtc-v2 1.5.0-goerli.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/.chainId +1 -1
- package/artifacts/Bank.json +23 -23
- package/artifacts/Bridge.json +26 -39
- package/artifacts/BridgeGovernance.json +25 -25
- package/artifacts/BridgeGovernanceParameters.json +10 -10
- package/artifacts/Deposit.json +14 -14
- package/artifacts/DepositSweep.json +13 -13
- package/artifacts/DonationVault.json +14 -14
- package/artifacts/Fraud.json +13 -13
- package/artifacts/LightRelay.json +52 -70
- package/artifacts/LightRelayMaintainerProxy.json +28 -28
- package/artifacts/MaintainerProxy.json +47 -47
- package/artifacts/MovingFunds.json +13 -13
- package/artifacts/Redemption.json +13 -13
- package/artifacts/TBTC.json +36 -37
- package/artifacts/TBTCVault.json +47 -47
- package/artifacts/VendingMachine.json +34 -35
- package/artifacts/VendingMachineV2.json +360 -0
- package/artifacts/VendingMachineV3.json +405 -0
- package/artifacts/WalletCoordinator.json +58 -20
- package/artifacts/Wallets.json +13 -13
- package/artifacts/solcInputs/7cc3eda3cb3ff2522d18b5e7b31ea228.json +102 -0
- package/artifacts/solcInputs/802132f7da69a8a4226cb9424480847b.json +218 -0
- package/artifacts/solcInputs/{f862bd134ddc06352fa5be0644481600.json → 887fad6b16575ba42183543c324eeb0e.json} +6 -6
- package/artifacts/solcInputs/{037e4828b80945fc4739f966d98aa575.json → b0025f1f7efe4824592ac0c9793776c3.json} +3 -18
- package/artifacts/solcInputs/{bd85e9d9f3c6834572e1a00ba6963ba0.json → d71d4b4434e6669852eaf643ebd2a7bc.json} +2 -2
- package/artifacts/solcInputs/{deff85fc9ae9b5be07226c67809262df.json → fccb130292c8c7cc958ab4fa31a3e180.json} +3 -3
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
- package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
- package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.dbg.json +1 -1
- package/build/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.dbg.json +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
- package/build/contracts/l2/L2TBTC.sol/L2TBTC.dbg.json +1 -1
- package/build/contracts/l2/L2WormholeGateway.sol/IWormholeTokenBridge.dbg.json +1 -1
- package/build/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.dbg.json +1 -1
- package/build/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.json +2 -15
- package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +1 -1
- package/build/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCOptimisticMinting.sol/TBTCOptimisticMinting.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/l2/L2WormholeGateway.sol +7 -27
- package/deploy/81_upgrade_wallet_coordinator_v2.ts +99 -0
- package/export/artifacts/@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol/BTCUtils.json +20 -20
- package/export/artifacts/@keep-network/ecdsa/contracts/EcdsaDkgValidator.sol/EcdsaDkgValidator.json +2010 -2010
- package/export/artifacts/@keep-network/ecdsa/contracts/WalletRegistry.sol/WalletRegistry.json +383 -383
- package/export/artifacts/@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol/EcdsaDkg.json +22 -22
- package/export/artifacts/@keep-network/ecdsa/contracts/libraries/EcdsaInactivity.sol/EcdsaInactivity.json +1859 -1859
- package/export/artifacts/@keep-network/random-beacon/contracts/ReimbursementPool.sol/ReimbursementPool.json +598 -598
- package/export/artifacts/contracts/bridge/Bridge.sol/Bridge.json +7381 -7381
- package/export/artifacts/contracts/bridge/VendingMachine.sol/VendingMachine.json +6 -6
- package/export/artifacts/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.json +6 -6
- package/export/artifacts/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.json +6 -6
- package/export/artifacts/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +5563 -5563
- package/export/artifacts/contracts/l2/L2TBTC.sol/L2TBTC.json +40 -40
- package/export/artifacts/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.json +3064 -3532
- package/export/artifacts/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.json +2454 -2454
- package/export/artifacts/contracts/relay/LightRelay.sol/LightRelay.json +1941 -1941
- package/export/artifacts/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.json +1044 -1044
- package/export/artifacts/contracts/test/BankStub.sol/BankStub.json +2 -2
- package/export/artifacts/contracts/test/BridgeStub.sol/BridgeStub.json +8422 -8422
- package/export/artifacts/contracts/test/GoerliLightRelay.sol/GoerliLightRelay.json +1943 -1943
- package/export/artifacts/contracts/test/HeartbeatStub.sol/HeartbeatStub.json +2 -2
- package/export/artifacts/contracts/test/LightRelayStub.sol/LightRelayStub.json +2066 -2066
- package/export/artifacts/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.json +7 -7
- package/export/artifacts/contracts/test/SystemTestRelay.sol/SystemTestRelay.json +568 -568
- package/export/artifacts/contracts/test/TestERC20.sol/TestERC20.json +6 -6
- package/export/artifacts/contracts/test/TestERC721.sol/TestERC721.json +8 -8
- package/export/artifacts/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.json +2 -2
- package/export/artifacts/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.json +42 -42
- package/export/artifacts/contracts/token/TBTC.sol/TBTC.json +2 -2
- package/export/artifacts/contracts/vault/DonationVault.sol/DonationVault.json +11 -11
- package/export/artifacts/contracts/vault/TBTCVault.sol/TBTCVault.json +3525 -3525
- package/export/deploy/81_upgrade_wallet_coordinator_v2.js +140 -0
- package/export/hardhat.config.js +7 -0
- package/export/typechain/factories/EcdsaAuthorization__factory.js +1 -1
- package/export/typechain/factories/IStaking__factory.js +13 -0
- package/export/typechain/factories/L2WormholeGateway__factory.js +1 -14
- package/export/typechain/factories/WalletRegistry__factory.js +1 -1
- package/export/typechain/factories/WormholeBridgeStub__factory.js +1 -1
- package/export.json +267 -0
- package/package.json +5 -4
- package/artifacts/.DS_Store +0 -0
- package/export/deploy/35_transfer_wallet_coordinator_ownership.js +0 -60
- package/export/deploy/36_deploy_light_relay_maintainer_proxy.js +0 -86
- package/export/deploy/37_authorize_maintainer_in_light_relay_maintainer_proxy.js +0 -65
- package/export/deploy/38_transfer_light_relay_maintainer_proxy_ownership.js +0 -60
- package/export/deploy/39_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.js +0 -70
- package/export/deploy/40_authorize_light_relay_maintainer_proxy_in_light_relay.js +0 -66
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"language": "Solidity",
|
|
3
3
|
"sources": {
|
|
4
4
|
"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol": {
|
|
5
|
-
"content": "pragma solidity ^0.8.4;\n\n/** @title BitcoinSPV */\n/** @author Summa (https://summa.one) */\n\nimport {BytesLib} from \"./BytesLib.sol\";\nimport {SafeMath} from \"./SafeMath.sol\";\n\nlibrary BTCUtils {\n using BytesLib for bytes;\n using SafeMath for uint256;\n\n // The target at minimum Difficulty. Also the target of the genesis block\n uint256 public constant DIFF1_TARGET = 0xffff0000000000000000000000000000000000000000000000000000;\n\n uint256 public constant RETARGET_PERIOD = 2 * 7 * 24 * 60 * 60; // 2 weeks in seconds\n uint256 public constant RETARGET_PERIOD_BLOCKS = 2016; // 2 weeks in blocks\n\n uint256 public constant ERR_BAD_ARG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\n\n /* ***** */\n /* UTILS */\n /* ***** */\n\n /// @notice Determines the length of a VarInt in bytes\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\n /// @param _flag The first byte of a VarInt\n /// @return The number of non-flag bytes in the VarInt\n function determineVarIntDataLength(bytes memory _flag) internal pure returns (uint8) {\n return determineVarIntDataLengthAt(_flag, 0);\n }\n\n /// @notice Determines the length of a VarInt in bytes\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\n /// @param _b The byte array containing a VarInt\n /// @param _at The position of the VarInt in the array\n /// @return The number of non-flag bytes in the VarInt\n function determineVarIntDataLengthAt(bytes memory _b, uint256 _at) internal pure returns (uint8) {\n if (uint8(_b[_at]) == 0xff) {\n return 8; // one-byte flag, 8 bytes data\n }\n if (uint8(_b[_at]) == 0xfe) {\n return 4; // one-byte flag, 4 bytes data\n }\n if (uint8(_b[_at]) == 0xfd) {\n return 2; // one-byte flag, 2 bytes data\n }\n\n return 0; // flag is data\n }\n\n /// @notice Parse a VarInt into its data length and the number it represents\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\n /// Caller SHOULD explicitly handle this case (or bubble it up)\n /// @param _b A byte-string starting with a VarInt\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\n function parseVarInt(bytes memory _b) internal pure returns (uint256, uint256) {\n return parseVarIntAt(_b, 0);\n }\n\n /// @notice Parse a VarInt into its data length and the number it represents\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\n /// Caller SHOULD explicitly handle this case (or bubble it up)\n /// @param _b A byte-string containing a VarInt\n /// @param _at The position of the VarInt\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\n function parseVarIntAt(bytes memory _b, uint256 _at) internal pure returns (uint256, uint256) {\n uint8 _dataLen = determineVarIntDataLengthAt(_b, _at);\n\n if (_dataLen == 0) {\n return (0, uint8(_b[_at]));\n }\n if (_b.length < 1 + _dataLen + _at) {\n return (ERR_BAD_ARG, 0);\n }\n uint256 _number;\n if (_dataLen == 2) {\n _number = reverseUint16(uint16(_b.slice2(1 + _at)));\n } else if (_dataLen == 4) {\n _number = reverseUint32(uint32(_b.slice4(1 + _at)));\n } else if (_dataLen == 8) {\n _number = reverseUint64(uint64(_b.slice8(1 + _at)));\n }\n return (_dataLen, _number);\n }\n\n /// @notice Changes the endianness of a byte array\n /// @dev Returns a new, backwards, bytes\n /// @param _b The bytes to reverse\n /// @return The reversed bytes\n function reverseEndianness(bytes memory _b) internal pure returns (bytes memory) {\n bytes memory _newValue = new bytes(_b.length);\n\n for (uint i = 0; i < _b.length; i++) {\n _newValue[_b.length - i - 1] = _b[i];\n }\n\n return _newValue;\n }\n\n /// @notice Changes the endianness of a uint256\n /// @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint256(uint256 _b) internal pure returns (uint256 v) {\n v = _b;\n\n // swap bytes\n v = ((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |\n ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);\n // swap 2-byte long pairs\n v = ((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |\n ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);\n // swap 4-byte long pairs\n v = ((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |\n ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);\n // swap 8-byte long pairs\n v = ((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |\n ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);\n // swap 16-byte long pairs\n v = (v >> 128) | (v << 128);\n }\n\n /// @notice Changes the endianness of a uint64\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint64(uint64 _b) internal pure returns (uint64 v) {\n v = _b;\n\n // swap bytes\n v = ((v >> 8) & 0x00FF00FF00FF00FF) |\n ((v & 0x00FF00FF00FF00FF) << 8);\n // swap 2-byte long pairs\n v = ((v >> 16) & 0x0000FFFF0000FFFF) |\n ((v & 0x0000FFFF0000FFFF) << 16);\n // swap 4-byte long pairs\n v = (v >> 32) | (v << 32);\n }\n\n /// @notice Changes the endianness of a uint32\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint32(uint32 _b) internal pure returns (uint32 v) {\n v = _b;\n\n // swap bytes\n v = ((v >> 8) & 0x00FF00FF) |\n ((v & 0x00FF00FF) << 8);\n // swap 2-byte long pairs\n v = (v >> 16) | (v << 16);\n }\n\n /// @notice Changes the endianness of a uint24\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint24(uint24 _b) internal pure returns (uint24 v) {\n v = (_b << 16) | (_b & 0x00FF00) | (_b >> 16);\n }\n\n /// @notice Changes the endianness of a uint16\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint16(uint16 _b) internal pure returns (uint16 v) {\n v = (_b << 8) | (_b >> 8);\n }\n\n\n /// @notice Converts big-endian bytes to a uint\n /// @dev Traverses the byte array and sums the bytes\n /// @param _b The big-endian bytes-encoded integer\n /// @return The integer representation\n function bytesToUint(bytes memory _b) internal pure returns (uint256) {\n uint256 _number;\n\n for (uint i = 0; i < _b.length; i++) {\n _number = _number + uint8(_b[i]) * (2 ** (8 * (_b.length - (i + 1))));\n }\n\n return _number;\n }\n\n /// @notice Get the last _num bytes from a byte array\n /// @param _b The byte array to slice\n /// @param _num The number of bytes to extract from the end\n /// @return The last _num bytes of _b\n function lastBytes(bytes memory _b, uint256 _num) internal pure returns (bytes memory) {\n uint256 _start = _b.length.sub(_num);\n\n return _b.slice(_start, _num);\n }\n\n /// @notice Implements bitcoin's hash160 (rmd160(sha2()))\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\n /// @param _b The pre-image\n /// @return The digest\n function hash160(bytes memory _b) internal pure returns (bytes memory) {\n return abi.encodePacked(ripemd160(abi.encodePacked(sha256(_b))));\n }\n\n /// @notice Implements bitcoin's hash160 (sha2 + ripemd160)\n /// @dev sha2 precompile at address(2), ripemd160 at address(3)\n /// @param _b The pre-image\n /// @return res The digest\n function hash160View(bytes memory _b) internal view returns (bytes20 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\n pop(staticcall(gas(), 3, 0x00, 32, 0x00, 32))\n // read from position 12 = 0c\n res := mload(0x0c)\n }\n }\n\n /// @notice Implements bitcoin's hash256 (double sha2)\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\n /// @param _b The pre-image\n /// @return The digest\n function hash256(bytes memory _b) internal pure returns (bytes32) {\n return sha256(abi.encodePacked(sha256(_b)));\n }\n\n /// @notice Implements bitcoin's hash256 (double sha2)\n /// @dev sha2 is precompiled smart contract located at address(2)\n /// @param _b The pre-image\n /// @return res The digest\n function hash256View(bytes memory _b) internal view returns (bytes32 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\n res := mload(0x00)\n }\n }\n\n /// @notice Implements bitcoin's hash256 on a pair of bytes32\n /// @dev sha2 is precompiled smart contract located at address(2)\n /// @param _a The first bytes32 of the pre-image\n /// @param _b The second bytes32 of the pre-image\n /// @return res The digest\n function hash256Pair(bytes32 _a, bytes32 _b) internal view returns (bytes32 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n mstore(0x00, _a)\n mstore(0x20, _b)\n pop(staticcall(gas(), 2, 0x00, 64, 0x00, 32))\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\n res := mload(0x00)\n }\n }\n\n /// @notice Implements bitcoin's hash256 (double sha2)\n /// @dev sha2 is precompiled smart contract located at address(2)\n /// @param _b The array containing the pre-image\n /// @param at The start of the pre-image\n /// @param len The length of the pre-image\n /// @return res The digest\n function hash256Slice(\n bytes memory _b,\n uint256 at,\n uint256 len\n ) internal view returns (bytes32 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n pop(staticcall(gas(), 2, add(_b, add(32, at)), len, 0x00, 32))\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\n res := mload(0x00)\n }\n }\n\n /* ************ */\n /* Legacy Input */\n /* ************ */\n\n /// @notice Extracts the nth input from the vin (0-indexed)\n /// @dev Iterates over the vin. If you need to extract several, write a custom function\n /// @param _vin The vin as a tightly-packed byte array\n /// @param _index The 0-indexed location of the input to extract\n /// @return The input as a byte array\n function extractInputAtIndex(bytes memory _vin, uint256 _index) internal pure returns (bytes memory) {\n uint256 _varIntDataLen;\n uint256 _nIns;\n\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\n require(_varIntDataLen != ERR_BAD_ARG, \"Read overrun during VarInt parsing\");\n require(_index < _nIns, \"Vin read overrun\");\n\n uint256 _len = 0;\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 _i = 0; _i < _index; _i ++) {\n _len = determineInputLengthAt(_vin, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n _offset = _offset + _len;\n }\n\n _len = determineInputLengthAt(_vin, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n return _vin.slice(_offset, _len);\n }\n\n /// @notice Determines whether an input is legacy\n /// @dev False if no scriptSig, otherwise True\n /// @param _input The input\n /// @return True for legacy, False for witness\n function isLegacyInput(bytes memory _input) internal pure returns (bool) {\n return _input[36] != hex\"00\";\n }\n\n /// @notice Determines the length of a scriptSig in an input\n /// @dev Will return 0 if passed a witness input.\n /// @param _input The LEGACY input\n /// @return The length of the script sig\n function extractScriptSigLen(bytes memory _input) internal pure returns (uint256, uint256) {\n return extractScriptSigLenAt(_input, 0);\n }\n\n /// @notice Determines the length of a scriptSig in an input\n /// starting at the specified position\n /// @dev Will return 0 if passed a witness input.\n /// @param _input The byte array containing the LEGACY input\n /// @param _at The position of the input in the array\n /// @return The length of the script sig\n function extractScriptSigLenAt(bytes memory _input, uint256 _at) internal pure returns (uint256, uint256) {\n if (_input.length < 37 + _at) {\n return (ERR_BAD_ARG, 0);\n }\n\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = parseVarIntAt(_input, _at + 36);\n\n return (_varIntDataLen, _scriptSigLen);\n }\n\n /// @notice Determines the length of an input from its scriptSig\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\n /// @param _input The input\n /// @return The length of the input in bytes\n function determineInputLength(bytes memory _input) internal pure returns (uint256) {\n return determineInputLengthAt(_input, 0);\n }\n\n /// @notice Determines the length of an input from its scriptSig,\n /// starting at the specified position\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\n /// @param _input The byte array containing the input\n /// @param _at The position of the input in the array\n /// @return The length of the input in bytes\n function determineInputLengthAt(bytes memory _input, uint256 _at) internal pure returns (uint256) {\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLenAt(_input, _at);\n if (_varIntDataLen == ERR_BAD_ARG) {\n return ERR_BAD_ARG;\n }\n\n return 36 + 1 + _varIntDataLen + _scriptSigLen + 4;\n }\n\n /// @notice Extracts the LE sequence bytes from an input\n /// @dev Sequence is used for relative time locks\n /// @param _input The LEGACY input\n /// @return The sequence bytes (LE uint)\n function extractSequenceLELegacy(bytes memory _input) internal pure returns (bytes4) {\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\n require(_varIntDataLen != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n return _input.slice4(36 + 1 + _varIntDataLen + _scriptSigLen);\n }\n\n /// @notice Extracts the sequence from the input\n /// @dev Sequence is a 4-byte little-endian number\n /// @param _input The LEGACY input\n /// @return The sequence number (big-endian uint)\n function extractSequenceLegacy(bytes memory _input) internal pure returns (uint32) {\n uint32 _leSeqence = uint32(extractSequenceLELegacy(_input));\n uint32 _beSequence = reverseUint32(_leSeqence);\n return _beSequence;\n }\n /// @notice Extracts the VarInt-prepended scriptSig from the input in a tx\n /// @dev Will return hex\"00\" if passed a witness input\n /// @param _input The LEGACY input\n /// @return The length-prepended scriptSig\n function extractScriptSig(bytes memory _input) internal pure returns (bytes memory) {\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\n require(_varIntDataLen != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n return _input.slice(36, 1 + _varIntDataLen + _scriptSigLen);\n }\n\n\n /* ************* */\n /* Witness Input */\n /* ************* */\n\n /// @notice Extracts the LE sequence bytes from an input\n /// @dev Sequence is used for relative time locks\n /// @param _input The WITNESS input\n /// @return The sequence bytes (LE uint)\n function extractSequenceLEWitness(bytes memory _input) internal pure returns (bytes4) {\n return _input.slice4(37);\n }\n\n /// @notice Extracts the sequence from the input in a tx\n /// @dev Sequence is a 4-byte little-endian number\n /// @param _input The WITNESS input\n /// @return The sequence number (big-endian uint)\n function extractSequenceWitness(bytes memory _input) internal pure returns (uint32) {\n uint32 _leSeqence = uint32(extractSequenceLEWitness(_input));\n uint32 _inputeSequence = reverseUint32(_leSeqence);\n return _inputeSequence;\n }\n\n /// @notice Extracts the outpoint from the input in a tx\n /// @dev 32-byte tx id with 4-byte index\n /// @param _input The input\n /// @return The outpoint (LE bytes of prev tx hash + LE bytes of prev tx index)\n function extractOutpoint(bytes memory _input) internal pure returns (bytes memory) {\n return _input.slice(0, 36);\n }\n\n /// @notice Extracts the outpoint tx id from an input\n /// @dev 32-byte tx id\n /// @param _input The input\n /// @return The tx id (little-endian bytes)\n function extractInputTxIdLE(bytes memory _input) internal pure returns (bytes32) {\n return _input.slice32(0);\n }\n\n /// @notice Extracts the outpoint tx id from an input\n /// starting at the specified position\n /// @dev 32-byte tx id\n /// @param _input The byte array containing the input\n /// @param _at The position of the input\n /// @return The tx id (little-endian bytes)\n function extractInputTxIdLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes32) {\n return _input.slice32(_at);\n }\n\n /// @notice Extracts the LE tx input index from the input in a tx\n /// @dev 4-byte tx index\n /// @param _input The input\n /// @return The tx index (little-endian bytes)\n function extractTxIndexLE(bytes memory _input) internal pure returns (bytes4) {\n return _input.slice4(32);\n }\n\n /// @notice Extracts the LE tx input index from the input in a tx\n /// starting at the specified position\n /// @dev 4-byte tx index\n /// @param _input The byte array containing the input\n /// @param _at The position of the input\n /// @return The tx index (little-endian bytes)\n function extractTxIndexLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes4) {\n return _input.slice4(32 + _at);\n }\n\n /* ****** */\n /* Output */\n /* ****** */\n\n /// @notice Determines the length of an output\n /// @dev Works with any properly formatted output\n /// @param _output The output\n /// @return The length indicated by the prefix, error if invalid length\n function determineOutputLength(bytes memory _output) internal pure returns (uint256) {\n return determineOutputLengthAt(_output, 0);\n }\n\n /// @notice Determines the length of an output\n /// starting at the specified position\n /// @dev Works with any properly formatted output\n /// @param _output The byte array containing the output\n /// @param _at The position of the output\n /// @return The length indicated by the prefix, error if invalid length\n function determineOutputLengthAt(bytes memory _output, uint256 _at) internal pure returns (uint256) {\n if (_output.length < 9 + _at) {\n return ERR_BAD_ARG;\n }\n uint256 _varIntDataLen;\n uint256 _scriptPubkeyLength;\n (_varIntDataLen, _scriptPubkeyLength) = parseVarIntAt(_output, 8 + _at);\n\n if (_varIntDataLen == ERR_BAD_ARG) {\n return ERR_BAD_ARG;\n }\n\n // 8-byte value, 1-byte for tag itself\n return 8 + 1 + _varIntDataLen + _scriptPubkeyLength;\n }\n\n /// @notice Extracts the output at a given index in the TxOuts vector\n /// @dev Iterates over the vout. If you need to extract multiple, write a custom function\n /// @param _vout The _vout to extract from\n /// @param _index The 0-indexed location of the output to extract\n /// @return The specified output\n function extractOutputAtIndex(bytes memory _vout, uint256 _index) internal pure returns (bytes memory) {\n uint256 _varIntDataLen;\n uint256 _nOuts;\n\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\n require(_varIntDataLen != ERR_BAD_ARG, \"Read overrun during VarInt parsing\");\n require(_index < _nOuts, \"Vout read overrun\");\n\n uint256 _len = 0;\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 _i = 0; _i < _index; _i ++) {\n _len = determineOutputLengthAt(_vout, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptPubkey\");\n _offset += _len;\n }\n\n _len = determineOutputLengthAt(_vout, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptPubkey\");\n return _vout.slice(_offset, _len);\n }\n\n /// @notice Extracts the value bytes from the output in a tx\n /// @dev Value is an 8-byte little-endian number\n /// @param _output The output\n /// @return The output value as LE bytes\n function extractValueLE(bytes memory _output) internal pure returns (bytes8) {\n return _output.slice8(0);\n }\n\n /// @notice Extracts the value from the output in a tx\n /// @dev Value is an 8-byte little-endian number\n /// @param _output The output\n /// @return The output value\n function extractValue(bytes memory _output) internal pure returns (uint64) {\n uint64 _leValue = uint64(extractValueLE(_output));\n uint64 _beValue = reverseUint64(_leValue);\n return _beValue;\n }\n\n /// @notice Extracts the value from the output in a tx\n /// @dev Value is an 8-byte little-endian number\n /// @param _output The byte array containing the output\n /// @param _at The starting index of the output in the array\n /// @return The output value\n function extractValueAt(bytes memory _output, uint256 _at) internal pure returns (uint64) {\n uint64 _leValue = uint64(_output.slice8(_at));\n uint64 _beValue = reverseUint64(_leValue);\n return _beValue;\n }\n\n /// @notice Extracts the data from an op return output\n /// @dev Returns hex\"\" if no data or not an op return\n /// @param _output The output\n /// @return Any data contained in the opreturn output, null if not an op return\n function extractOpReturnData(bytes memory _output) internal pure returns (bytes memory) {\n if (_output[9] != hex\"6a\") {\n return hex\"\";\n }\n bytes1 _dataLen = _output[10];\n return _output.slice(11, uint256(uint8(_dataLen)));\n }\n\n /// @notice Extracts the hash from the output script\n /// @dev Determines type by the length prefix and validates format\n /// @param _output The output\n /// @return The hash committed to by the pk_script, or null for errors\n function extractHash(bytes memory _output) internal pure returns (bytes memory) {\n return extractHashAt(_output, 8, _output.length - 8);\n }\n\n /// @notice Extracts the hash from the output script\n /// @dev Determines type by the length prefix and validates format\n /// @param _output The byte array containing the output\n /// @param _at The starting index of the output script in the array\n /// (output start + 8)\n /// @param _len The length of the output script\n /// (output length - 8)\n /// @return The hash committed to by the pk_script, or null for errors\n function extractHashAt(\n bytes memory _output,\n uint256 _at,\n uint256 _len\n ) internal pure returns (bytes memory) {\n uint8 _scriptLen = uint8(_output[_at]);\n\n // don't have to worry about overflow here.\n // if _scriptLen + 1 overflows, then output length would have to be < 1\n // for this check to pass. if it's < 1, then we errored when assigning\n // _scriptLen\n if (_scriptLen + 1 != _len) {\n return hex\"\";\n }\n\n if (uint8(_output[_at + 1]) == 0) {\n if (_scriptLen < 2) {\n return hex\"\";\n }\n uint256 _payloadLen = uint8(_output[_at + 2]);\n // Check for maliciously formatted witness outputs.\n // No need to worry about underflow as long b/c of the `< 2` check\n if (_payloadLen != _scriptLen - 2 || (_payloadLen != 0x20 && _payloadLen != 0x14)) {\n return hex\"\";\n }\n return _output.slice(_at + 3, _payloadLen);\n } else {\n bytes3 _tag = _output.slice3(_at);\n // p2pkh\n if (_tag == hex\"1976a9\") {\n // Check for maliciously formatted p2pkh\n // No need to worry about underflow, b/c of _scriptLen check\n if (uint8(_output[_at + 3]) != 0x14 ||\n _output.slice2(_at + _len - 2) != hex\"88ac\") {\n return hex\"\";\n }\n return _output.slice(_at + 4, 20);\n //p2sh\n } else if (_tag == hex\"17a914\") {\n // Check for maliciously formatted p2sh\n // No need to worry about underflow, b/c of _scriptLen check\n if (uint8(_output[_at + _len - 1]) != 0x87) {\n return hex\"\";\n }\n return _output.slice(_at + 3, 20);\n }\n }\n return hex\"\"; /* NB: will trigger on OPRETURN and any non-standard that doesn't overrun */\n }\n\n /* ********** */\n /* Witness TX */\n /* ********** */\n\n\n /// @notice Checks that the vin passed up is properly formatted\n /// @dev Consider a vin with a valid vout in its scriptsig\n /// @param _vin Raw bytes length-prefixed input vector\n /// @return True if it represents a validly formatted vin\n function validateVin(bytes memory _vin) internal pure returns (bool) {\n uint256 _varIntDataLen;\n uint256 _nIns;\n\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\n\n // Not valid if it says there are too many or no inputs\n if (_nIns == 0 || _varIntDataLen == ERR_BAD_ARG) {\n return false;\n }\n\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 i = 0; i < _nIns; i++) {\n // If we're at the end, but still expect more\n if (_offset >= _vin.length) {\n return false;\n }\n\n // Grab the next input and determine its length.\n uint256 _nextLen = determineInputLengthAt(_vin, _offset);\n if (_nextLen == ERR_BAD_ARG) {\n return false;\n }\n\n // Increase the offset by that much\n _offset += _nextLen;\n }\n\n // Returns false if we're not exactly at the end\n return _offset == _vin.length;\n }\n\n /// @notice Checks that the vout passed up is properly formatted\n /// @dev Consider a vout with a valid scriptpubkey\n /// @param _vout Raw bytes length-prefixed output vector\n /// @return True if it represents a validly formatted vout\n function validateVout(bytes memory _vout) internal pure returns (bool) {\n uint256 _varIntDataLen;\n uint256 _nOuts;\n\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\n\n // Not valid if it says there are too many or no outputs\n if (_nOuts == 0 || _varIntDataLen == ERR_BAD_ARG) {\n return false;\n }\n\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 i = 0; i < _nOuts; i++) {\n // If we're at the end, but still expect more\n if (_offset >= _vout.length) {\n return false;\n }\n\n // Grab the next output and determine its length.\n // Increase the offset by that much\n uint256 _nextLen = determineOutputLengthAt(_vout, _offset);\n if (_nextLen == ERR_BAD_ARG) {\n return false;\n }\n\n _offset += _nextLen;\n }\n\n // Returns false if we're not exactly at the end\n return _offset == _vout.length;\n }\n\n\n\n /* ************ */\n /* Block Header */\n /* ************ */\n\n /// @notice Extracts the transaction merkle root from a block header\n /// @dev Use verifyHash256Merkle to verify proofs with this root\n /// @param _header The header\n /// @return The merkle root (little-endian)\n function extractMerkleRootLE(bytes memory _header) internal pure returns (bytes32) {\n return _header.slice32(36);\n }\n\n /// @notice Extracts the target from a block header\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\n /// @param _header The header\n /// @return The target threshold\n function extractTarget(bytes memory _header) internal pure returns (uint256) {\n return extractTargetAt(_header, 0);\n }\n\n /// @notice Extracts the target from a block header\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\n /// @param _header The array containing the header\n /// @param at The start of the header\n /// @return The target threshold\n function extractTargetAt(bytes memory _header, uint256 at) internal pure returns (uint256) {\n uint24 _m = uint24(_header.slice3(72 + at));\n uint8 _e = uint8(_header[75 + at]);\n uint256 _mantissa = uint256(reverseUint24(_m));\n uint _exponent = _e - 3;\n\n return _mantissa * (256 ** _exponent);\n }\n\n /// @notice Calculate difficulty from the difficulty 1 target and current target\n /// @dev Difficulty 1 is 0x1d00ffff on mainnet and testnet\n /// @dev Difficulty 1 is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\n /// @param _target The current target\n /// @return The block difficulty (bdiff)\n function calculateDifficulty(uint256 _target) internal pure returns (uint256) {\n // Difficulty 1 calculated from 0x1d00ffff\n return DIFF1_TARGET.div(_target);\n }\n\n /// @notice Extracts the previous block's hash from a block header\n /// @dev Block headers do NOT include block number :(\n /// @param _header The header\n /// @return The previous block's hash (little-endian)\n function extractPrevBlockLE(bytes memory _header) internal pure returns (bytes32) {\n return _header.slice32(4);\n }\n\n /// @notice Extracts the previous block's hash from a block header\n /// @dev Block headers do NOT include block number :(\n /// @param _header The array containing the header\n /// @param at The start of the header\n /// @return The previous block's hash (little-endian)\n function extractPrevBlockLEAt(\n bytes memory _header,\n uint256 at\n ) internal pure returns (bytes32) {\n return _header.slice32(4 + at);\n }\n\n /// @notice Extracts the timestamp from a block header\n /// @dev Time is not 100% reliable\n /// @param _header The header\n /// @return The timestamp (little-endian bytes)\n function extractTimestampLE(bytes memory _header) internal pure returns (bytes4) {\n return _header.slice4(68);\n }\n\n /// @notice Extracts the timestamp from a block header\n /// @dev Time is not 100% reliable\n /// @param _header The header\n /// @return The timestamp (uint)\n function extractTimestamp(bytes memory _header) internal pure returns (uint32) {\n return reverseUint32(uint32(extractTimestampLE(_header)));\n }\n\n /// @notice Extracts the expected difficulty from a block header\n /// @dev Does NOT verify the work\n /// @param _header The header\n /// @return The difficulty as an integer\n function extractDifficulty(bytes memory _header) internal pure returns (uint256) {\n return calculateDifficulty(extractTarget(_header));\n }\n\n /// @notice Concatenates and hashes two inputs for merkle proving\n /// @param _a The first hash\n /// @param _b The second hash\n /// @return The double-sha256 of the concatenated hashes\n function _hash256MerkleStep(bytes memory _a, bytes memory _b) internal view returns (bytes32) {\n return hash256View(abi.encodePacked(_a, _b));\n }\n\n /// @notice Concatenates and hashes two inputs for merkle proving\n /// @param _a The first hash\n /// @param _b The second hash\n /// @return The double-sha256 of the concatenated hashes\n function _hash256MerkleStep(bytes32 _a, bytes32 _b) internal view returns (bytes32) {\n return hash256Pair(_a, _b);\n }\n\n\n /// @notice Verifies a Bitcoin-style merkle tree\n /// @dev Leaves are 0-indexed. Inefficient version.\n /// @param _proof The proof. Tightly packed LE sha256 hashes. The last hash is the root\n /// @param _index The index of the leaf\n /// @return true if the proof is valid, else false\n function verifyHash256Merkle(bytes memory _proof, uint _index) internal view returns (bool) {\n // Not an even number of hashes\n if (_proof.length % 32 != 0) {\n return false;\n }\n\n // Special case for coinbase-only blocks\n if (_proof.length == 32) {\n return true;\n }\n\n // Should never occur\n if (_proof.length == 64) {\n return false;\n }\n\n bytes32 _root = _proof.slice32(_proof.length - 32);\n bytes32 _current = _proof.slice32(0);\n bytes memory _tree = _proof.slice(32, _proof.length - 64);\n\n return verifyHash256Merkle(_current, _tree, _root, _index);\n }\n\n /// @notice Verifies a Bitcoin-style merkle tree\n /// @dev Leaves are 0-indexed. Efficient version.\n /// @param _leaf The leaf of the proof. LE sha256 hash.\n /// @param _tree The intermediate nodes in the proof.\n /// Tightly packed LE sha256 hashes.\n /// @param _root The root of the proof. LE sha256 hash.\n /// @param _index The index of the leaf\n /// @return true if the proof is valid, else false\n function verifyHash256Merkle(\n bytes32 _leaf,\n bytes memory _tree,\n bytes32 _root,\n uint _index\n ) internal view returns (bool) {\n // Not an even number of hashes\n if (_tree.length % 32 != 0) {\n return false;\n }\n\n // Should never occur\n if (_tree.length == 0) {\n return false;\n }\n\n uint _idx = _index;\n bytes32 _current = _leaf;\n\n // i moves in increments of 32\n for (uint i = 0; i < _tree.length; i += 32) {\n if (_idx % 2 == 1) {\n _current = _hash256MerkleStep(_tree.slice32(i), _current);\n } else {\n _current = _hash256MerkleStep(_current, _tree.slice32(i));\n }\n _idx = _idx >> 1;\n }\n return _current == _root;\n }\n\n /*\n NB: https://github.com/bitcoin/bitcoin/blob/78dae8caccd82cfbfd76557f1fb7d7557c7b5edb/src/pow.cpp#L49-L72\n NB: We get a full-bitlength target from this. For comparison with\n header-encoded targets we need to mask it with the header target\n e.g. (full & truncated) == truncated\n */\n /// @notice performs the bitcoin difficulty retarget\n /// @dev implements the Bitcoin algorithm precisely\n /// @param _previousTarget the target of the previous period\n /// @param _firstTimestamp the timestamp of the first block in the difficulty period\n /// @param _secondTimestamp the timestamp of the last block in the difficulty period\n /// @return the new period's target threshold\n function retargetAlgorithm(\n uint256 _previousTarget,\n uint256 _firstTimestamp,\n uint256 _secondTimestamp\n ) internal pure returns (uint256) {\n uint256 _elapsedTime = _secondTimestamp.sub(_firstTimestamp);\n\n // Normalize ratio to factor of 4 if very long or very short\n if (_elapsedTime < RETARGET_PERIOD.div(4)) {\n _elapsedTime = RETARGET_PERIOD.div(4);\n }\n if (_elapsedTime > RETARGET_PERIOD.mul(4)) {\n _elapsedTime = RETARGET_PERIOD.mul(4);\n }\n\n /*\n NB: high targets e.g. ffff0020 can cause overflows here\n so we divide it by 256**2, then multiply by 256**2 later\n we know the target is evenly divisible by 256**2, so this isn't an issue\n */\n\n uint256 _adjusted = _previousTarget.div(65536).mul(_elapsedTime);\n return _adjusted.div(RETARGET_PERIOD).mul(65536);\n }\n}\n"
|
|
5
|
+
"content": "pragma solidity ^0.8.4;\n\n/** @title BitcoinSPV */\n/** @author Summa (https://summa.one) */\n\nimport {BytesLib} from \"./BytesLib.sol\";\nimport {SafeMath} from \"./SafeMath.sol\";\n\nlibrary BTCUtils {\n using BytesLib for bytes;\n using SafeMath for uint256;\n\n // The target at minimum Difficulty. Also the target of the genesis block\n uint256 public constant DIFF1_TARGET = 0xffff0000000000000000000000000000000000000000000000000000;\n\n uint256 public constant RETARGET_PERIOD = 2 * 7 * 24 * 60 * 60; // 2 weeks in seconds\n uint256 public constant RETARGET_PERIOD_BLOCKS = 2016; // 2 weeks in blocks\n\n uint256 public constant ERR_BAD_ARG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\n\n /* ***** */\n /* UTILS */\n /* ***** */\n\n /// @notice Determines the length of a VarInt in bytes\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\n /// @param _flag The first byte of a VarInt\n /// @return The number of non-flag bytes in the VarInt\n function determineVarIntDataLength(bytes memory _flag) internal pure returns (uint8) {\n return determineVarIntDataLengthAt(_flag, 0);\n }\n\n /// @notice Determines the length of a VarInt in bytes\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\n /// @param _b The byte array containing a VarInt\n /// @param _at The position of the VarInt in the array\n /// @return The number of non-flag bytes in the VarInt\n function determineVarIntDataLengthAt(bytes memory _b, uint256 _at) internal pure returns (uint8) {\n if (uint8(_b[_at]) == 0xff) {\n return 8; // one-byte flag, 8 bytes data\n }\n if (uint8(_b[_at]) == 0xfe) {\n return 4; // one-byte flag, 4 bytes data\n }\n if (uint8(_b[_at]) == 0xfd) {\n return 2; // one-byte flag, 2 bytes data\n }\n\n return 0; // flag is data\n }\n\n /// @notice Parse a VarInt into its data length and the number it represents\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\n /// Caller SHOULD explicitly handle this case (or bubble it up)\n /// @param _b A byte-string starting with a VarInt\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\n function parseVarInt(bytes memory _b) internal pure returns (uint256, uint256) {\n return parseVarIntAt(_b, 0);\n }\n\n /// @notice Parse a VarInt into its data length and the number it represents\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\n /// Caller SHOULD explicitly handle this case (or bubble it up)\n /// @param _b A byte-string containing a VarInt\n /// @param _at The position of the VarInt\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\n function parseVarIntAt(bytes memory _b, uint256 _at) internal pure returns (uint256, uint256) {\n uint8 _dataLen = determineVarIntDataLengthAt(_b, _at);\n\n if (_dataLen == 0) {\n return (0, uint8(_b[_at]));\n }\n if (_b.length < 1 + _dataLen + _at) {\n return (ERR_BAD_ARG, 0);\n }\n uint256 _number;\n if (_dataLen == 2) {\n _number = reverseUint16(uint16(_b.slice2(1 + _at)));\n } else if (_dataLen == 4) {\n _number = reverseUint32(uint32(_b.slice4(1 + _at)));\n } else if (_dataLen == 8) {\n _number = reverseUint64(uint64(_b.slice8(1 + _at)));\n }\n return (_dataLen, _number);\n }\n\n /// @notice Changes the endianness of a byte array\n /// @dev Returns a new, backwards, bytes\n /// @param _b The bytes to reverse\n /// @return The reversed bytes\n function reverseEndianness(bytes memory _b) internal pure returns (bytes memory) {\n bytes memory _newValue = new bytes(_b.length);\n\n for (uint i = 0; i < _b.length; i++) {\n _newValue[_b.length - i - 1] = _b[i];\n }\n\n return _newValue;\n }\n\n /// @notice Changes the endianness of a uint256\n /// @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint256(uint256 _b) internal pure returns (uint256 v) {\n v = _b;\n\n // swap bytes\n v = ((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |\n ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);\n // swap 2-byte long pairs\n v = ((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |\n ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);\n // swap 4-byte long pairs\n v = ((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |\n ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);\n // swap 8-byte long pairs\n v = ((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |\n ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);\n // swap 16-byte long pairs\n v = (v >> 128) | (v << 128);\n }\n\n /// @notice Changes the endianness of a uint64\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint64(uint64 _b) internal pure returns (uint64 v) {\n v = _b;\n\n // swap bytes\n v = ((v >> 8) & 0x00FF00FF00FF00FF) |\n ((v & 0x00FF00FF00FF00FF) << 8);\n // swap 2-byte long pairs\n v = ((v >> 16) & 0x0000FFFF0000FFFF) |\n ((v & 0x0000FFFF0000FFFF) << 16);\n // swap 4-byte long pairs\n v = (v >> 32) | (v << 32);\n }\n\n /// @notice Changes the endianness of a uint32\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint32(uint32 _b) internal pure returns (uint32 v) {\n v = _b;\n\n // swap bytes\n v = ((v >> 8) & 0x00FF00FF) |\n ((v & 0x00FF00FF) << 8);\n // swap 2-byte long pairs\n v = (v >> 16) | (v << 16);\n }\n\n /// @notice Changes the endianness of a uint24\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint24(uint24 _b) internal pure returns (uint24 v) {\n v = (_b << 16) | (_b & 0x00FF00) | (_b >> 16);\n }\n\n /// @notice Changes the endianness of a uint16\n /// @param _b The unsigned integer to reverse\n /// @return v The reversed value\n function reverseUint16(uint16 _b) internal pure returns (uint16 v) {\n v = (_b << 8) | (_b >> 8);\n }\n\n\n /// @notice Converts big-endian bytes to a uint\n /// @dev Traverses the byte array and sums the bytes\n /// @param _b The big-endian bytes-encoded integer\n /// @return The integer representation\n function bytesToUint(bytes memory _b) internal pure returns (uint256) {\n uint256 _number;\n\n for (uint i = 0; i < _b.length; i++) {\n _number = _number + uint8(_b[i]) * (2 ** (8 * (_b.length - (i + 1))));\n }\n\n return _number;\n }\n\n /// @notice Get the last _num bytes from a byte array\n /// @param _b The byte array to slice\n /// @param _num The number of bytes to extract from the end\n /// @return The last _num bytes of _b\n function lastBytes(bytes memory _b, uint256 _num) internal pure returns (bytes memory) {\n uint256 _start = _b.length.sub(_num);\n\n return _b.slice(_start, _num);\n }\n\n /// @notice Implements bitcoin's hash160 (rmd160(sha2()))\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\n /// @param _b The pre-image\n /// @return The digest\n function hash160(bytes memory _b) internal pure returns (bytes memory) {\n return abi.encodePacked(ripemd160(abi.encodePacked(sha256(_b))));\n }\n\n /// @notice Implements bitcoin's hash160 (sha2 + ripemd160)\n /// @dev sha2 precompile at address(2), ripemd160 at address(3)\n /// @param _b The pre-image\n /// @return res The digest\n function hash160View(bytes memory _b) internal view returns (bytes20 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\n pop(staticcall(gas(), 3, 0x00, 32, 0x00, 32))\n // read from position 12 = 0c\n res := mload(0x0c)\n }\n }\n\n /// @notice Implements bitcoin's hash256 (double sha2)\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\n /// @param _b The pre-image\n /// @return The digest\n function hash256(bytes memory _b) internal pure returns (bytes32) {\n return sha256(abi.encodePacked(sha256(_b)));\n }\n\n /// @notice Implements bitcoin's hash256 (double sha2)\n /// @dev sha2 is precompiled smart contract located at address(2)\n /// @param _b The pre-image\n /// @return res The digest\n function hash256View(bytes memory _b) internal view returns (bytes32 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\n res := mload(0x00)\n }\n }\n\n /// @notice Implements bitcoin's hash256 on a pair of bytes32\n /// @dev sha2 is precompiled smart contract located at address(2)\n /// @param _a The first bytes32 of the pre-image\n /// @param _b The second bytes32 of the pre-image\n /// @return res The digest\n function hash256Pair(bytes32 _a, bytes32 _b) internal view returns (bytes32 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n mstore(0x00, _a)\n mstore(0x20, _b)\n pop(staticcall(gas(), 2, 0x00, 64, 0x00, 32))\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\n res := mload(0x00)\n }\n }\n\n /// @notice Implements bitcoin's hash256 (double sha2)\n /// @dev sha2 is precompiled smart contract located at address(2)\n /// @param _b The array containing the pre-image\n /// @param at The start of the pre-image\n /// @param len The length of the pre-image\n /// @return res The digest\n function hash256Slice(\n bytes memory _b,\n uint256 at,\n uint256 len\n ) internal view returns (bytes32 res) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n pop(staticcall(gas(), 2, add(_b, add(32, at)), len, 0x00, 32))\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\n res := mload(0x00)\n }\n }\n\n /* ************ */\n /* Legacy Input */\n /* ************ */\n\n /// @notice Extracts the nth input from the vin (0-indexed)\n /// @dev Iterates over the vin. If you need to extract several, write a custom function\n /// @param _vin The vin as a tightly-packed byte array\n /// @param _index The 0-indexed location of the input to extract\n /// @return The input as a byte array\n function extractInputAtIndex(bytes memory _vin, uint256 _index) internal pure returns (bytes memory) {\n uint256 _varIntDataLen;\n uint256 _nIns;\n\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\n require(_varIntDataLen != ERR_BAD_ARG, \"Read overrun during VarInt parsing\");\n require(_index < _nIns, \"Vin read overrun\");\n\n uint256 _len = 0;\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 _i = 0; _i < _index; _i ++) {\n _len = determineInputLengthAt(_vin, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n _offset = _offset + _len;\n }\n\n _len = determineInputLengthAt(_vin, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n return _vin.slice(_offset, _len);\n }\n\n /// @notice Determines whether an input is legacy\n /// @dev False if no scriptSig, otherwise True\n /// @param _input The input\n /// @return True for legacy, False for witness\n function isLegacyInput(bytes memory _input) internal pure returns (bool) {\n return _input[36] != hex\"00\";\n }\n\n /// @notice Determines the length of a scriptSig in an input\n /// @dev Will return 0 if passed a witness input.\n /// @param _input The LEGACY input\n /// @return The length of the script sig\n function extractScriptSigLen(bytes memory _input) internal pure returns (uint256, uint256) {\n return extractScriptSigLenAt(_input, 0);\n }\n\n /// @notice Determines the length of a scriptSig in an input\n /// starting at the specified position\n /// @dev Will return 0 if passed a witness input.\n /// @param _input The byte array containing the LEGACY input\n /// @param _at The position of the input in the array\n /// @return The length of the script sig\n function extractScriptSigLenAt(bytes memory _input, uint256 _at) internal pure returns (uint256, uint256) {\n if (_input.length < 37 + _at) {\n return (ERR_BAD_ARG, 0);\n }\n\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = parseVarIntAt(_input, _at + 36);\n\n return (_varIntDataLen, _scriptSigLen);\n }\n\n /// @notice Determines the length of an input from its scriptSig\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\n /// @param _input The input\n /// @return The length of the input in bytes\n function determineInputLength(bytes memory _input) internal pure returns (uint256) {\n return determineInputLengthAt(_input, 0);\n }\n\n /// @notice Determines the length of an input from its scriptSig,\n /// starting at the specified position\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\n /// @param _input The byte array containing the input\n /// @param _at The position of the input in the array\n /// @return The length of the input in bytes\n function determineInputLengthAt(bytes memory _input, uint256 _at) internal pure returns (uint256) {\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLenAt(_input, _at);\n if (_varIntDataLen == ERR_BAD_ARG) {\n return ERR_BAD_ARG;\n }\n\n return 36 + 1 + _varIntDataLen + _scriptSigLen + 4;\n }\n\n /// @notice Extracts the LE sequence bytes from an input\n /// @dev Sequence is used for relative time locks\n /// @param _input The LEGACY input\n /// @return The sequence bytes (LE uint)\n function extractSequenceLELegacy(bytes memory _input) internal pure returns (bytes4) {\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\n require(_varIntDataLen != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n return _input.slice4(36 + 1 + _varIntDataLen + _scriptSigLen);\n }\n\n /// @notice Extracts the sequence from the input\n /// @dev Sequence is a 4-byte little-endian number\n /// @param _input The LEGACY input\n /// @return The sequence number (big-endian uint)\n function extractSequenceLegacy(bytes memory _input) internal pure returns (uint32) {\n uint32 _leSeqence = uint32(extractSequenceLELegacy(_input));\n uint32 _beSequence = reverseUint32(_leSeqence);\n return _beSequence;\n }\n /// @notice Extracts the VarInt-prepended scriptSig from the input in a tx\n /// @dev Will return hex\"00\" if passed a witness input\n /// @param _input The LEGACY input\n /// @return The length-prepended scriptSig\n function extractScriptSig(bytes memory _input) internal pure returns (bytes memory) {\n uint256 _varIntDataLen;\n uint256 _scriptSigLen;\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\n require(_varIntDataLen != ERR_BAD_ARG, \"Bad VarInt in scriptSig\");\n return _input.slice(36, 1 + _varIntDataLen + _scriptSigLen);\n }\n\n\n /* ************* */\n /* Witness Input */\n /* ************* */\n\n /// @notice Extracts the LE sequence bytes from an input\n /// @dev Sequence is used for relative time locks\n /// @param _input The WITNESS input\n /// @return The sequence bytes (LE uint)\n function extractSequenceLEWitness(bytes memory _input) internal pure returns (bytes4) {\n return _input.slice4(37);\n }\n\n /// @notice Extracts the sequence from the input in a tx\n /// @dev Sequence is a 4-byte little-endian number\n /// @param _input The WITNESS input\n /// @return The sequence number (big-endian uint)\n function extractSequenceWitness(bytes memory _input) internal pure returns (uint32) {\n uint32 _leSeqence = uint32(extractSequenceLEWitness(_input));\n uint32 _inputeSequence = reverseUint32(_leSeqence);\n return _inputeSequence;\n }\n\n /// @notice Extracts the outpoint from the input in a tx\n /// @dev 32-byte tx id with 4-byte index\n /// @param _input The input\n /// @return The outpoint (LE bytes of prev tx hash + LE bytes of prev tx index)\n function extractOutpoint(bytes memory _input) internal pure returns (bytes memory) {\n return _input.slice(0, 36);\n }\n\n /// @notice Extracts the outpoint tx id from an input\n /// @dev 32-byte tx id\n /// @param _input The input\n /// @return The tx id (little-endian bytes)\n function extractInputTxIdLE(bytes memory _input) internal pure returns (bytes32) {\n return _input.slice32(0);\n }\n\n /// @notice Extracts the outpoint tx id from an input\n /// starting at the specified position\n /// @dev 32-byte tx id\n /// @param _input The byte array containing the input\n /// @param _at The position of the input\n /// @return The tx id (little-endian bytes)\n function extractInputTxIdLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes32) {\n return _input.slice32(_at);\n }\n\n /// @notice Extracts the LE tx input index from the input in a tx\n /// @dev 4-byte tx index\n /// @param _input The input\n /// @return The tx index (little-endian bytes)\n function extractTxIndexLE(bytes memory _input) internal pure returns (bytes4) {\n return _input.slice4(32);\n }\n\n /// @notice Extracts the LE tx input index from the input in a tx\n /// starting at the specified position\n /// @dev 4-byte tx index\n /// @param _input The byte array containing the input\n /// @param _at The position of the input\n /// @return The tx index (little-endian bytes)\n function extractTxIndexLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes4) {\n return _input.slice4(32 + _at);\n }\n\n /* ****** */\n /* Output */\n /* ****** */\n\n /// @notice Determines the length of an output\n /// @dev Works with any properly formatted output\n /// @param _output The output\n /// @return The length indicated by the prefix, error if invalid length\n function determineOutputLength(bytes memory _output) internal pure returns (uint256) {\n return determineOutputLengthAt(_output, 0);\n }\n\n /// @notice Determines the length of an output\n /// starting at the specified position\n /// @dev Works with any properly formatted output\n /// @param _output The byte array containing the output\n /// @param _at The position of the output\n /// @return The length indicated by the prefix, error if invalid length\n function determineOutputLengthAt(bytes memory _output, uint256 _at) internal pure returns (uint256) {\n if (_output.length < 9 + _at) {\n return ERR_BAD_ARG;\n }\n uint256 _varIntDataLen;\n uint256 _scriptPubkeyLength;\n (_varIntDataLen, _scriptPubkeyLength) = parseVarIntAt(_output, 8 + _at);\n\n if (_varIntDataLen == ERR_BAD_ARG) {\n return ERR_BAD_ARG;\n }\n\n // 8-byte value, 1-byte for tag itself\n return 8 + 1 + _varIntDataLen + _scriptPubkeyLength;\n }\n\n /// @notice Extracts the output at a given index in the TxOuts vector\n /// @dev Iterates over the vout. If you need to extract multiple, write a custom function\n /// @param _vout The _vout to extract from\n /// @param _index The 0-indexed location of the output to extract\n /// @return The specified output\n function extractOutputAtIndex(bytes memory _vout, uint256 _index) internal pure returns (bytes memory) {\n uint256 _varIntDataLen;\n uint256 _nOuts;\n\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\n require(_varIntDataLen != ERR_BAD_ARG, \"Read overrun during VarInt parsing\");\n require(_index < _nOuts, \"Vout read overrun\");\n\n uint256 _len = 0;\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 _i = 0; _i < _index; _i ++) {\n _len = determineOutputLengthAt(_vout, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptPubkey\");\n _offset += _len;\n }\n\n _len = determineOutputLengthAt(_vout, _offset);\n require(_len != ERR_BAD_ARG, \"Bad VarInt in scriptPubkey\");\n return _vout.slice(_offset, _len);\n }\n\n /// @notice Extracts the value bytes from the output in a tx\n /// @dev Value is an 8-byte little-endian number\n /// @param _output The output\n /// @return The output value as LE bytes\n function extractValueLE(bytes memory _output) internal pure returns (bytes8) {\n return _output.slice8(0);\n }\n\n /// @notice Extracts the value from the output in a tx\n /// @dev Value is an 8-byte little-endian number\n /// @param _output The output\n /// @return The output value\n function extractValue(bytes memory _output) internal pure returns (uint64) {\n uint64 _leValue = uint64(extractValueLE(_output));\n uint64 _beValue = reverseUint64(_leValue);\n return _beValue;\n }\n\n /// @notice Extracts the value from the output in a tx\n /// @dev Value is an 8-byte little-endian number\n /// @param _output The byte array containing the output\n /// @param _at The starting index of the output in the array\n /// @return The output value\n function extractValueAt(bytes memory _output, uint256 _at) internal pure returns (uint64) {\n uint64 _leValue = uint64(_output.slice8(_at));\n uint64 _beValue = reverseUint64(_leValue);\n return _beValue;\n }\n\n /// @notice Extracts the data from an op return output\n /// @dev Returns hex\"\" if no data or not an op return\n /// @param _output The output\n /// @return Any data contained in the opreturn output, null if not an op return\n function extractOpReturnData(bytes memory _output) internal pure returns (bytes memory) {\n if (_output[9] != hex\"6a\") {\n return hex\"\";\n }\n bytes1 _dataLen = _output[10];\n return _output.slice(11, uint256(uint8(_dataLen)));\n }\n\n /// @notice Extracts the hash from the output script\n /// @dev Determines type by the length prefix and validates format\n /// @param _output The output\n /// @return The hash committed to by the pk_script, or null for errors\n function extractHash(bytes memory _output) internal pure returns (bytes memory) {\n return extractHashAt(_output, 8, _output.length - 8);\n }\n\n /// @notice Extracts the hash from the output script\n /// @dev Determines type by the length prefix and validates format\n /// @param _output The byte array containing the output\n /// @param _at The starting index of the output script in the array\n /// (output start + 8)\n /// @param _len The length of the output script\n /// (output length - 8)\n /// @return The hash committed to by the pk_script, or null for errors\n function extractHashAt(\n bytes memory _output,\n uint256 _at,\n uint256 _len\n ) internal pure returns (bytes memory) {\n uint8 _scriptLen = uint8(_output[_at]);\n\n // don't have to worry about overflow here.\n // if _scriptLen + 1 overflows, then output length would have to be < 1\n // for this check to pass. if it's < 1, then we errored when assigning\n // _scriptLen\n if (_scriptLen + 1 != _len) {\n return hex\"\";\n }\n\n if (uint8(_output[_at + 1]) == 0) {\n if (_scriptLen < 2) {\n return hex\"\";\n }\n uint256 _payloadLen = uint8(_output[_at + 2]);\n // Check for maliciously formatted witness outputs.\n // No need to worry about underflow as long b/c of the `< 2` check\n if (_payloadLen != _scriptLen - 2 || (_payloadLen != 0x20 && _payloadLen != 0x14)) {\n return hex\"\";\n }\n return _output.slice(_at + 3, _payloadLen);\n } else {\n bytes3 _tag = _output.slice3(_at);\n // p2pkh\n if (_tag == hex\"1976a9\") {\n // Check for maliciously formatted p2pkh\n // No need to worry about underflow, b/c of _scriptLen check\n if (uint8(_output[_at + 3]) != 0x14 ||\n _output.slice2(_at + _len - 2) != hex\"88ac\") {\n return hex\"\";\n }\n return _output.slice(_at + 4, 20);\n //p2sh\n } else if (_tag == hex\"17a914\") {\n // Check for maliciously formatted p2sh\n // No need to worry about underflow, b/c of _scriptLen check\n if (uint8(_output[_at + _len - 1]) != 0x87) {\n return hex\"\";\n }\n return _output.slice(_at + 3, 20);\n }\n }\n return hex\"\"; /* NB: will trigger on OPRETURN and any non-standard that doesn't overrun */\n }\n\n /* ********** */\n /* Witness TX */\n /* ********** */\n\n\n /// @notice Checks that the vin passed up is properly formatted\n /// @dev Consider a vin with a valid vout in its scriptsig\n /// @param _vin Raw bytes length-prefixed input vector\n /// @return True if it represents a validly formatted vin\n function validateVin(bytes memory _vin) internal pure returns (bool) {\n uint256 _varIntDataLen;\n uint256 _nIns;\n\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\n\n // Not valid if it says there are too many or no inputs\n if (_nIns == 0 || _varIntDataLen == ERR_BAD_ARG) {\n return false;\n }\n\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 i = 0; i < _nIns; i++) {\n // If we're at the end, but still expect more\n if (_offset >= _vin.length) {\n return false;\n }\n\n // Grab the next input and determine its length.\n uint256 _nextLen = determineInputLengthAt(_vin, _offset);\n if (_nextLen == ERR_BAD_ARG) {\n return false;\n }\n\n // Increase the offset by that much\n _offset += _nextLen;\n }\n\n // Returns false if we're not exactly at the end\n return _offset == _vin.length;\n }\n\n /// @notice Checks that the vout passed up is properly formatted\n /// @dev Consider a vout with a valid scriptpubkey\n /// @param _vout Raw bytes length-prefixed output vector\n /// @return True if it represents a validly formatted vout\n function validateVout(bytes memory _vout) internal pure returns (bool) {\n uint256 _varIntDataLen;\n uint256 _nOuts;\n\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\n\n // Not valid if it says there are too many or no outputs\n if (_nOuts == 0 || _varIntDataLen == ERR_BAD_ARG) {\n return false;\n }\n\n uint256 _offset = 1 + _varIntDataLen;\n\n for (uint256 i = 0; i < _nOuts; i++) {\n // If we're at the end, but still expect more\n if (_offset >= _vout.length) {\n return false;\n }\n\n // Grab the next output and determine its length.\n // Increase the offset by that much\n uint256 _nextLen = determineOutputLengthAt(_vout, _offset);\n if (_nextLen == ERR_BAD_ARG) {\n return false;\n }\n\n _offset += _nextLen;\n }\n\n // Returns false if we're not exactly at the end\n return _offset == _vout.length;\n }\n\n\n\n /* ************ */\n /* Block Header */\n /* ************ */\n\n /// @notice Extracts the transaction merkle root from a block header\n /// @dev Use verifyHash256Merkle to verify proofs with this root\n /// @param _header The header\n /// @return The merkle root (little-endian)\n function extractMerkleRootLE(bytes memory _header) internal pure returns (bytes32) {\n return _header.slice32(36);\n }\n\n /// @notice Extracts the target from a block header\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\n /// @param _header The header\n /// @return The target threshold\n function extractTarget(bytes memory _header) internal pure returns (uint256) {\n return extractTargetAt(_header, 0);\n }\n\n /// @notice Extracts the target from a block header\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\n /// @param _header The array containing the header\n /// @param at The start of the header\n /// @return The target threshold\n function extractTargetAt(bytes memory _header, uint256 at) internal pure returns (uint256) {\n uint24 _m = uint24(_header.slice3(72 + at));\n uint8 _e = uint8(_header[75 + at]);\n uint256 _mantissa = uint256(reverseUint24(_m));\n uint _exponent = _e - 3;\n\n return _mantissa * (256 ** _exponent);\n }\n\n /// @notice Calculate difficulty from the difficulty 1 target and current target\n /// @dev Difficulty 1 is 0x1d00ffff on mainnet and testnet\n /// @dev Difficulty 1 is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\n /// @param _target The current target\n /// @return The block difficulty (bdiff)\n function calculateDifficulty(uint256 _target) internal pure returns (uint256) {\n // Difficulty 1 calculated from 0x1d00ffff\n return DIFF1_TARGET.div(_target);\n }\n\n /// @notice Extracts the previous block's hash from a block header\n /// @dev Block headers do NOT include block number :(\n /// @param _header The header\n /// @return The previous block's hash (little-endian)\n function extractPrevBlockLE(bytes memory _header) internal pure returns (bytes32) {\n return _header.slice32(4);\n }\n\n /// @notice Extracts the previous block's hash from a block header\n /// @dev Block headers do NOT include block number :(\n /// @param _header The array containing the header\n /// @param at The start of the header\n /// @return The previous block's hash (little-endian)\n function extractPrevBlockLEAt(\n bytes memory _header,\n uint256 at\n ) internal pure returns (bytes32) {\n return _header.slice32(4 + at);\n }\n\n /// @notice Extracts the timestamp from a block header\n /// @dev Time is not 100% reliable\n /// @param _header The header\n /// @return The timestamp (little-endian bytes)\n function extractTimestampLE(bytes memory _header) internal pure returns (bytes4) {\n return _header.slice4(68);\n }\n\n /// @notice Extracts the timestamp from a block header\n /// @dev Time is not 100% reliable\n /// @param _header The header\n /// @return The timestamp (uint)\n function extractTimestamp(bytes memory _header) internal pure returns (uint32) {\n return reverseUint32(uint32(extractTimestampLE(_header)));\n }\n\n /// @notice Extracts the expected difficulty from a block header\n /// @dev Does NOT verify the work\n /// @param _header The header\n /// @return The difficulty as an integer\n function extractDifficulty(bytes memory _header) internal pure returns (uint256) {\n return calculateDifficulty(extractTarget(_header));\n }\n\n /// @notice Concatenates and hashes two inputs for merkle proving\n /// @param _a The first hash\n /// @param _b The second hash\n /// @return The double-sha256 of the concatenated hashes\n function _hash256MerkleStep(bytes memory _a, bytes memory _b) internal view returns (bytes32) {\n return hash256View(abi.encodePacked(_a, _b));\n }\n\n /// @notice Concatenates and hashes two inputs for merkle proving\n /// @param _a The first hash\n /// @param _b The second hash\n /// @return The double-sha256 of the concatenated hashes\n function _hash256MerkleStep(bytes32 _a, bytes32 _b) internal view returns (bytes32) {\n return hash256Pair(_a, _b);\n }\n\n\n /// @notice Verifies a Bitcoin-style merkle tree\n /// @dev Leaves are 0-indexed. Inefficient version.\n /// @param _proof The proof. Tightly packed LE sha256 hashes. The last hash is the root\n /// @param _index The index of the leaf\n /// @return true if the proof is valid, else false\n function verifyHash256Merkle(bytes memory _proof, uint _index) internal view returns (bool) {\n // Not an even number of hashes\n if (_proof.length % 32 != 0) {\n return false;\n }\n\n // Special case for coinbase-only blocks\n if (_proof.length == 32) {\n return true;\n }\n\n // Should never occur\n if (_proof.length == 64) {\n return false;\n }\n\n bytes32 _root = _proof.slice32(_proof.length - 32);\n bytes32 _current = _proof.slice32(0);\n bytes memory _tree = _proof.slice(32, _proof.length - 64);\n\n return verifyHash256Merkle(_current, _tree, _root, _index);\n }\n\n /// @notice Verifies a Bitcoin-style merkle tree\n /// @dev Leaves are 0-indexed. Efficient version.\n /// @param _leaf The leaf of the proof. LE sha256 hash.\n /// @param _tree The intermediate nodes in the proof.\n /// Tightly packed LE sha256 hashes.\n /// @param _root The root of the proof. LE sha256 hash.\n /// @param _index The index of the leaf\n /// @return true if the proof is valid, else false\n function verifyHash256Merkle(\n bytes32 _leaf,\n bytes memory _tree,\n bytes32 _root,\n uint _index\n ) internal view returns (bool) {\n // Not an even number of hashes\n if (_tree.length % 32 != 0) {\n return false;\n }\n\n // Should never occur\n if (_tree.length == 0) {\n return false;\n }\n\n uint _idx = _index;\n bytes32 _current = _leaf;\n\n // i moves in increments of 32\n for (uint i = 0; i < _tree.length; i += 32) {\n if (_idx % 2 == 1) {\n _current = _hash256MerkleStep(_tree.slice32(i), _current);\n } else {\n _current = _hash256MerkleStep(_current, _tree.slice32(i));\n }\n _idx = _idx >> 1;\n }\n return _current == _root;\n }\n\n /*\n NB: https://github.com/bitcoin/bitcoin/blob/78dae8caccd82cfbfd76557f1fb7d7557c7b5edb/src/pow.cpp#L49-L72\n NB: We get a full-bitlength target from this. For comparison with\n header-encoded targets we need to mask it with the header target\n e.g. (full & truncated) == truncated\n */\n /// @notice performs the bitcoin difficulty retarget\n /// @dev implements the Bitcoin algorithm precisely\n /// @param _previousTarget the target of the previous period\n /// @param _firstTimestamp the timestamp of the first block in the difficulty period\n /// @param _secondTimestamp the timestamp of the last block in the difficulty period\n /// @return the new period's target threshold\n function retargetAlgorithm(\n uint256 _previousTarget,\n uint256 _firstTimestamp,\n uint256 _secondTimestamp\n ) internal pure returns (uint256) {\n uint256 _elapsedTime = _secondTimestamp.sub(_firstTimestamp);\n\n // Normalize ratio to factor of 4 if very long or very short\n if (_elapsedTime < RETARGET_PERIOD.div(4)) {\n _elapsedTime = RETARGET_PERIOD.div(4);\n }\n if (_elapsedTime > RETARGET_PERIOD.mul(4)) {\n _elapsedTime = RETARGET_PERIOD.mul(4);\n }\n\n /*\n NB: high targets e.g. ffff0020 can cause overflows here\n so we divide it by 256**2, then multiply by 256**2 later\n we know the target is evenly divisible by 256**2, so this isn't an issue\n */\n\n uint256 _adjusted = _previousTarget.div(65536).mul(_elapsedTime);\n return _adjusted.div(RETARGET_PERIOD).mul(65536);\n }\n}\n"
|
|
6
6
|
},
|
|
7
7
|
"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol": {
|
|
8
8
|
"content": "pragma solidity ^0.8.4;\n\n/*\n\nhttps://github.com/GNSPS/solidity-bytes-utils/\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n*/\n\n\n/** @title BytesLib **/\n/** @author https://github.com/GNSPS **/\n\nlibrary BytesLib {\n function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(0x40, and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n ))\n }\n\n return tempBytes;\n }\n\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {\n assembly {\n // Read the first 32 bytes of _preBytes storage, which is the length\n // of the array. (We don't need to use the offset into the slot\n // because arrays use the entire slot.)\n let fslot := sload(_preBytes.slot)\n // Arrays of 31 bytes or less have an even value in their slot,\n // while longer arrays have an odd value. The actual length is\n // the slot divided by two for odd values, and the lowest order\n // byte divided by two for even values.\n // If the slot is even, bitwise and the slot with 255 and divide by\n // two to get the length. If the slot is odd, bitwise and the slot\n // with -1 and divide by two.\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n let newlength := add(slength, mlength)\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n switch add(lt(slength, 32), lt(newlength, 32))\n case 2 {\n // Since the new array still fits in the slot, we just need to\n // update the contents of the slot.\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\n sstore(\n _preBytes.slot,\n // all the modifications to the slot are inside this\n // next block\n add(\n // we can just add to the slot contents because the\n // bytes we want to change are the LSBs\n fslot,\n add(\n mul(\n div(\n // load the bytes from memory\n mload(add(_postBytes, 0x20)),\n // zero all bytes to the right\n exp(0x100, sub(32, mlength))\n ),\n // and now shift left the number of bytes to\n // leave space for the length in the slot\n exp(0x100, sub(32, newlength))\n ),\n // increase length by the double of the memory\n // bytes length\n mul(mlength, 2)\n )\n )\n )\n }\n case 1 {\n // The stored value fits in the slot, but the combined value\n // will exceed it.\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // The contents of the _postBytes array start 32 bytes into\n // the structure. Our first read should obtain the `submod`\n // bytes that can fit into the unused space in the last word\n // of the stored array. To get this, we read 32 bytes starting\n // from `submod`, so the data we read overlaps with the array\n // contents by `submod` bytes. Masking the lowest-order\n // `submod` bytes allows us to add that value directly to the\n // stored value.\n\n let submod := sub(32, slength)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(\n sc,\n add(\n and(\n fslot,\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n ),\n and(mload(mc), mask)\n )\n )\n\n for {\n mc := add(mc, 0x20)\n sc := add(sc, 1)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n default {\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n // Start copying to the last used word of the stored array.\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // Copy over the first `submod` bytes of the new data as in\n // case 1 above.\n let slengthmod := mod(slength, 32)\n let mlengthmod := mod(mlength, 32)\n let submod := sub(32, slengthmod)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\n\n for {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n }\n }\n\n function slice(bytes memory _bytes, uint _start, uint _length) internal pure returns (bytes memory res) {\n if (_length == 0) {\n return hex\"\";\n }\n uint _end = _start + _length;\n require(_end > _start && _bytes.length >= _end, \"Slice out of bounds\");\n\n assembly {\n // Alloc bytes array with additional 32 bytes afterspace and assign it's size\n res := mload(0x40)\n mstore(0x40, add(add(res, 64), _length))\n mstore(res, _length)\n\n // Compute distance between source and destination pointers\n let diff := sub(res, add(_bytes, _start))\n\n for {\n let src := add(add(_bytes, 32), _start)\n let end := add(src, _length)\n } lt(src, end) {\n src := add(src, 32)\n } {\n mstore(add(src, diff), mload(src))\n }\n }\n }\n\n /// @notice Take a slice of the byte array, overwriting the destination.\n /// The length of the slice will equal the length of the destination array.\n /// @dev Make sure the destination array has afterspace if required.\n /// @param _bytes The source array\n /// @param _dest The destination array.\n /// @param _start The location to start in the source array.\n function sliceInPlace(\n bytes memory _bytes,\n bytes memory _dest,\n uint _start\n ) internal pure {\n uint _length = _dest.length;\n uint _end = _start + _length;\n require(_end > _start && _bytes.length >= _end, \"Slice out of bounds\");\n\n assembly {\n for {\n let src := add(add(_bytes, 32), _start)\n let res := add(_dest, 32)\n let end := add(src, _length)\n } lt(src, end) {\n src := add(src, 32)\n res := add(res, 32)\n } {\n mstore(res, mload(src))\n }\n }\n }\n\n // Static slice functions, no bounds checking\n /// @notice take a 32-byte slice from the specified position\n function slice32(bytes memory _bytes, uint _start) internal pure returns (bytes32 res) {\n assembly {\n res := mload(add(add(_bytes, 32), _start))\n }\n }\n\n /// @notice take a 20-byte slice from the specified position\n function slice20(bytes memory _bytes, uint _start) internal pure returns (bytes20) {\n return bytes20(slice32(_bytes, _start));\n }\n\n /// @notice take a 8-byte slice from the specified position\n function slice8(bytes memory _bytes, uint _start) internal pure returns (bytes8) {\n return bytes8(slice32(_bytes, _start));\n }\n\n /// @notice take a 4-byte slice from the specified position\n function slice4(bytes memory _bytes, uint _start) internal pure returns (bytes4) {\n return bytes4(slice32(_bytes, _start));\n }\n\n /// @notice take a 3-byte slice from the specified position\n function slice3(bytes memory _bytes, uint _start) internal pure returns (bytes3) {\n return bytes3(slice32(_bytes, _start));\n }\n\n /// @notice take a 2-byte slice from the specified position\n function slice2(bytes memory _bytes, uint _start) internal pure returns (bytes2) {\n return bytes2(slice32(_bytes, _start));\n }\n\n function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) {\n uint _totalLen = _start + 20;\n require(_totalLen > _start && _bytes.length >= _totalLen, \"Address conversion out of bounds.\");\n address tempAddress;\n\n assembly {\n tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)\n }\n\n return tempAddress;\n }\n\n function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) {\n uint _totalLen = _start + 32;\n require(_totalLen > _start && _bytes.length >= _totalLen, \"Uint conversion out of bounds.\");\n uint256 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempUint;\n }\n\n function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {\n bool success = true;\n\n assembly {\n let length := mload(_preBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(length, mload(_postBytes))\n case 1 {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n let mc := add(_preBytes, 0x20)\n let end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n // the next line is the loop condition:\n // while(uint(mc < end) + cb == 2)\n } eq(add(lt(mc, end), cb), 2) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // if any of these checks fails then arrays are not equal\n if iszero(eq(mload(mc), mload(cc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n\n function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {\n bool success = true;\n\n assembly {\n // we know _preBytes_offset is 0\n let fslot := sload(_preBytes.slot)\n // Decode the length of the stored array like in concatStorage().\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(slength, mlength)\n case 1 {\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n if iszero(iszero(slength)) {\n switch lt(slength, 32)\n case 1 {\n // blank the last byte which is the length\n fslot := mul(div(fslot, 0x100), 0x100)\n\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\n // unsuccess:\n success := 0\n }\n }\n default {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := keccak256(0x0, 0x20)\n\n let mc := add(_postBytes, 0x20)\n let end := add(mc, mlength)\n\n // the next line is the loop condition:\n // while(uint(mc < end) + cb == 2)\n for {} eq(add(lt(mc, end), cb), 2) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n if iszero(eq(sload(sc), mload(mc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n\n function toBytes32(bytes memory _source) pure internal returns (bytes32 result) {\n if (_source.length == 0) {\n return 0x0;\n }\n\n assembly {\n result := mload(add(_source, 32))\n }\n }\n\n function keccak256Slice(bytes memory _bytes, uint _start, uint _length) pure internal returns (bytes32 result) {\n uint _end = _start + _length;\n require(_end > _start && _bytes.length >= _end, \"Slice out of bounds\");\n\n assembly {\n result := keccak256(add(add(_bytes, 32), _start), _length)\n }\n }\n}\n"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nlibrary Wallets {\n struct Wallet {\n // Keccak256 hash of group members identifiers array. Group members do not\n // include operators selected by the sortition pool that misbehaved during DKG.\n bytes32 membersIdsHash;\n // Uncompressed ECDSA public key stored as X and Y coordinates (32 bytes each).\n bytes32 publicKeyX;\n bytes32 publicKeyY;\n // This struct doesn't contain `__gap` property as the structure is stored\n // in a mapping, mappings store values in different slots and they are\n // not contiguous with other values.\n }\n\n struct Data {\n // Mapping of keccak256 hashes of wallet public keys to wallet details.\n // Hash of public key is considered an unique wallet identifier.\n mapping(bytes32 => Wallet) registry;\n // Reserved storage space in case we need to add more variables.\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // slither-disable-next-line unused-state\n uint256[49] __gap;\n }\n\n /// @notice Performs preliminary validation of a new group public key.\n /// The group public key must be unique and have 64 bytes in length.\n /// If the validation fails, the function reverts. This function\n /// must be called first for a public key of a wallet added with\n /// `addWallet` function.\n /// @param publicKey Uncompressed public key of a new wallet.\n function validatePublicKey(Data storage self, bytes calldata publicKey)\n internal\n view\n {\n require(publicKey.length == 64, \"Invalid length of the public key\");\n\n bytes32 walletID = keccak256(publicKey);\n require(\n self.registry[walletID].publicKeyX == bytes32(0),\n \"Wallet with the given public key already exists\"\n );\n\n bytes32 publicKeyX = bytes32(publicKey[:32]);\n require(publicKeyX != bytes32(0), \"Wallet public key must be non-zero\");\n }\n\n /// @notice Registers a new wallet. This function does not validate\n /// parameters. The code calling this function must call\n /// `validatePublicKey` first.\n /// @dev Uses a public key hash as a unique identifier of a wallet.\n /// @param membersIdsHash Keccak256 hash of group members identifiers array\n /// @param publicKey Uncompressed public key\n /// @return walletID Wallet's ID\n /// @return publicKeyX Wallet's public key's X coordinate\n /// @return publicKeyY Wallet's public key's Y coordinate\n function addWallet(\n Data storage self,\n bytes32 membersIdsHash,\n bytes calldata publicKey\n )\n internal\n returns (\n bytes32 walletID,\n bytes32 publicKeyX,\n bytes32 publicKeyY\n )\n {\n walletID = keccak256(publicKey);\n\n publicKeyX = bytes32(publicKey[:32]);\n publicKeyY = bytes32(publicKey[32:]);\n\n self.registry[walletID].membersIdsHash = membersIdsHash;\n self.registry[walletID].publicKeyX = publicKeyX;\n self.registry[walletID].publicKeyY = publicKeyY;\n }\n\n /// @notice Deletes wallet with the given ID from the registry. Reverts\n /// if wallet with the given ID has not been registered or if it\n /// has already been closed.\n function deleteWallet(Data storage self, bytes32 walletID) internal {\n require(\n isWalletRegistered(self, walletID),\n \"Wallet with the given ID has not been registered\"\n );\n\n delete self.registry[walletID];\n }\n\n /// @notice Checks if a wallet with the given ID is registered.\n /// @param walletID Wallet's ID\n /// @return True if a wallet is registered, false otherwise\n function isWalletRegistered(Data storage self, bytes32 walletID)\n internal\n view\n returns (bool)\n {\n return self.registry[walletID].publicKeyX != bytes32(0);\n }\n\n /// @notice Returns Keccak256 hash of the wallet signing group members\n /// identifiers array. Group members do not include operators\n /// selected by the sortition pool that misbehaved during DKG.\n /// Reverts if wallet with the given ID is not registered.\n /// @param walletID ID of the wallet\n /// @return Wallet signing group members hash\n function getWalletMembersIdsHash(Data storage self, bytes32 walletID)\n internal\n view\n returns (bytes32)\n {\n require(\n isWalletRegistered(self, walletID),\n \"Wallet with the given ID has not been registered\"\n );\n\n return self.registry[walletID].membersIdsHash;\n }\n\n /// @notice Gets public key of a wallet with the given wallet ID.\n /// The public key is returned as X and Y coordinates.\n /// Reverts if wallet with the given ID is not registered.\n /// @param walletID ID of the wallet\n /// @return x Public key X coordinate\n /// @return y Public key Y coordinate\n function getWalletPublicKeyCoordinates(Data storage self, bytes32 walletID)\n internal\n view\n returns (bytes32 x, bytes32 y)\n {\n require(\n isWalletRegistered(self, walletID),\n \"Wallet with the given ID has not been registered\"\n );\n\n Wallet storage wallet = self.registry[walletID];\n\n return (wallet.publicKeyX, wallet.publicKeyY);\n }\n\n /// @notice Gets public key of a wallet with the given wallet ID.\n /// The public key is returned in an uncompressed format as a 64-byte\n /// concatenation of X and Y coordinates.\n /// Reverts if wallet with the given ID is not registered.\n /// @param walletID ID of the wallet\n /// @return Uncompressed public key of the wallet\n function getWalletPublicKey(Data storage self, bytes32 walletID)\n internal\n view\n returns (bytes memory)\n {\n (bytes32 x, bytes32 y) = getWalletPublicKeyCoordinates(self, walletID);\n return bytes.concat(x, y);\n }\n}\n"
|
|
39
39
|
},
|
|
40
40
|
"@keep-network/ecdsa/contracts/WalletRegistry.sol": {
|
|
41
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nimport \"./api/IWalletRegistry.sol\";\nimport \"./api/IWalletOwner.sol\";\nimport \"./libraries/Wallets.sol\";\nimport {EcdsaAuthorization as Authorization} from \"./libraries/EcdsaAuthorization.sol\";\nimport {EcdsaDkg as DKG} from \"./libraries/EcdsaDkg.sol\";\nimport {EcdsaInactivity as Inactivity} from \"./libraries/EcdsaInactivity.sol\";\nimport {EcdsaDkgValidator as DKGValidator} from \"./EcdsaDkgValidator.sol\";\n\nimport \"@keep-network/sortition-pools/contracts/SortitionPool.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeacon.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeaconConsumer.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\nimport \"@keep-network/random-beacon/contracts/Governable.sol\";\n\nimport \"@threshold-network/solidity-contracts/contracts/staking/IApplication.sol\";\nimport \"@threshold-network/solidity-contracts/contracts/staking/IStaking.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\n\ncontract WalletRegistry is\n IWalletRegistry,\n IRandomBeaconConsumer,\n IApplication,\n Governable,\n Reimbursable,\n Initializable\n{\n using Authorization for Authorization.Data;\n using DKG for DKG.Data;\n using Wallets for Wallets.Data;\n\n // Libraries data storages\n Authorization.Data internal authorization;\n DKG.Data internal dkg;\n Wallets.Data internal wallets;\n\n /// @notice Slashing amount for submitting a malicious DKG result. Every\n /// DKG result submitted can be challenged for the time of\n /// `dkg.resultChallengePeriodLength`. If the DKG result submitted\n /// is challenged and proven to be malicious, the operator who\n /// submitted the malicious result is slashed for\n /// `_maliciousDkgResultSlashingAmount`.\n uint96 internal _maliciousDkgResultSlashingAmount;\n\n /// @notice Percentage of the staking contract malicious behavior\n /// notification reward which will be transferred to the notifier\n /// reporting about a malicious DKG result. Notifiers are rewarded\n /// from a notifiers treasury pool. For example, if\n /// notification reward is 1000 and the value of the multiplier is\n /// 5, the notifier will receive: 5% of 1000 = 50 per each\n /// operator affected.\n uint256 internal _maliciousDkgResultNotificationRewardMultiplier;\n\n /// @notice Duration of the sortition pool rewards ban imposed on operators\n /// who missed their turn for DKG result submission or who failed\n /// a heartbeat.\n uint256 internal _sortitionPoolRewardsBanDuration;\n\n /// @notice Calculated max gas cost for submitting a DKG result. This will\n /// be refunded as part of the DKG approval process. It is in the\n /// submitter's interest to not skip his priority turn on the approval,\n /// otherwise the refund of the DKG submission will be refunded to\n /// another group member that will call the DKG approve function.\n uint256 internal _dkgResultSubmissionGas;\n\n /// @notice Gas that is meant to balance the DKG result approval's overall\n /// cost. It can be updated by the governance based on the current\n /// market conditions.\n uint256 internal _dkgResultApprovalGasOffset;\n\n /// @notice Gas that is meant to balance the notification of an operator\n /// inactivity. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifyOperatorInactivityGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a seed for DKG\n /// delivery timeout. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifySeedTimeoutGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a DKG protocol\n /// execution timeout. It can be updated by the governance based on the\n /// current market conditions.\n /// @dev The value is subtracted for the refundable gas calculation, as the\n /// DKG timeout notification transaction recovers some gas when cleaning\n /// up the storage.\n uint256 internal _notifyDkgTimeoutNegativeGasOffset;\n\n /// @notice Stores current operator inactivity claim nonce for the given\n /// wallet signing group. Each claim is made with a unique nonce\n /// which protects against claim replay.\n mapping(bytes32 => uint256) public inactivityClaimNonce; // walletID -> nonce\n\n // Address that is set as owner of all wallets. Only this address can request\n // new wallets creation and manage their state.\n IWalletOwner public walletOwner;\n\n // External dependencies\n\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n SortitionPool public immutable sortitionPool;\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStaking public immutable staking;\n IRandomBeacon public randomBeacon;\n\n // Events\n event DkgStarted(uint256 indexed seed);\n\n event DkgResultSubmitted(\n bytes32 indexed resultHash,\n uint256 indexed seed,\n DKG.Result result\n );\n\n event DkgTimedOut();\n\n event DkgResultApproved(\n bytes32 indexed resultHash,\n address indexed approver\n );\n\n event DkgResultChallenged(\n bytes32 indexed resultHash,\n address indexed challenger,\n string reason\n );\n\n event DkgStateLocked();\n\n event DkgSeedTimedOut();\n\n event WalletCreated(\n bytes32 indexed walletID,\n bytes32 indexed dkgResultHash\n );\n\n event WalletClosed(bytes32 indexed walletID);\n\n event DkgMaliciousResultSlashed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event DkgMaliciousResultSlashingFailed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event AuthorizationParametersUpdated(\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n );\n\n event RewardParametersUpdated(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n );\n\n event SlashingParametersUpdated(uint256 maliciousDkgResultSlashingAmount);\n\n event DkgParametersUpdated(\n uint256 seedTimeout,\n uint256 resultChallengePeriodLength,\n uint256 resultChallengeExtraGas,\n uint256 resultSubmissionTimeout,\n uint256 resultSubmitterPrecedencePeriodLength\n );\n\n event GasParametersUpdated(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n );\n\n event RandomBeaconUpgraded(address randomBeacon);\n\n event WalletOwnerUpdated(address walletOwner);\n\n event OperatorRegistered(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event AuthorizationIncreased(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event AuthorizationDecreaseRequested(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount,\n uint64 decreasingAt\n );\n\n event AuthorizationDecreaseApproved(address indexed stakingProvider);\n\n event InvoluntaryAuthorizationDecreaseFailed(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event OperatorJoinedSortitionPool(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event OperatorStatusUpdated(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event InactivityClaimed(\n bytes32 indexed walletID,\n uint256 nonce,\n address notifier\n );\n\n modifier onlyStakingContract() {\n require(\n msg.sender == address(staking),\n \"Caller is not the staking contract\"\n );\n _;\n }\n\n /// @notice Reverts if called not by the Wallet Owner.\n modifier onlyWalletOwner() {\n require(\n msg.sender == address(walletOwner),\n \"Caller is not the Wallet Owner\"\n );\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(governance == msg.sender, \"Caller is not the governance\");\n _;\n }\n\n /// @dev Used to initialize immutable variables only, use `initialize` function\n /// for upgradable contract initialization on deployment.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(SortitionPool _sortitionPool, IStaking _staking) {\n sortitionPool = _sortitionPool;\n staking = _staking;\n\n _disableInitializers();\n }\n\n /// @dev Initializes upgradable contract on deployment.\n function initialize(\n DKGValidator _ecdsaDkgValidator,\n IRandomBeacon _randomBeacon,\n ReimbursementPool _reimbursementPool\n ) external initializer {\n randomBeacon = _randomBeacon;\n reimbursementPool = _reimbursementPool;\n\n _transferGovernance(msg.sender);\n\n //\n // All parameters set in the constructor are initial ones, used at the\n // moment contracts were deployed for the first time. Parameters are\n // governable and values assigned in the constructor do not need to\n // reflect the current ones.\n //\n\n // Minimum authorization is 40k T.\n //\n // Authorization decrease delay is 45 days.\n //\n // Authorization decrease change period is 45 days. It means pending\n // authorization decrease can be overwritten all the time.\n authorization.setMinimumAuthorization(40_000e18);\n authorization.setAuthorizationDecreaseDelay(3_888_000);\n authorization.setAuthorizationDecreaseChangePeriod(3_888_000);\n\n // Malicious DKG result slashing amount is set initially to 1% of the\n // minimum authorization (400 T). This values needs to be increased\n // significantly once the system is fully launched.\n //\n // Notifier of a malicious DKG result receives 100% of the notifier\n // reward from the staking contract.\n //\n // Inactive operators are set as ineligible for rewards for 2 weeks.\n _maliciousDkgResultSlashingAmount = 400e18;\n _maliciousDkgResultNotificationRewardMultiplier = 100;\n _sortitionPoolRewardsBanDuration = 2 weeks;\n\n // DKG seed timeout is set to 48h assuming 15s block time. The same\n // value is used by the Random Beacon as a relay entry hard timeout.\n //\n // DKG result challenge period length is set to 48h as well, assuming\n // 15s block time.\n //\n // DKG result submission timeout, gives each member 20 blocks to submit\n // the result. Assuming 15s block time, it is ~8h to submit the result\n // in the pessimistic case.\n //\n // The original DKG result submitter has 20 blocks to approve it before\n // anyone else can do that.\n //\n // With these parameters, the happy path takes no more than 104 hours.\n // In practice, it should take about 48 hours (just the challenge time).\n dkg.init(sortitionPool, _ecdsaDkgValidator);\n dkg.setSeedTimeout(11_520);\n dkg.setResultChallengePeriodLength(11_520);\n dkg.setResultChallengeExtraGas(50_000);\n dkg.setResultSubmissionTimeout(100 * 20);\n dkg.setSubmitterPrecedencePeriodLength(20);\n\n // Gas parameters were adjusted based on Ethereum state in April 2022.\n // If the cost of EVM opcodes change over time, these parameters will\n // have to be updated.\n _dkgResultSubmissionGas = 290_000;\n _dkgResultApprovalGasOffset = 72_000;\n _notifyOperatorInactivityGasOffset = 93_000;\n _notifySeedTimeoutGasOffset = 7_250;\n _notifyDkgTimeoutNegativeGasOffset = 2_300;\n }\n\n /// @notice Withdraws application rewards for the given staking provider.\n /// Rewards are withdrawn to the staking provider's beneficiary\n /// address set in the staking contract. Reverts if staking provider\n /// has not registered the operator address.\n /// @dev Emits `RewardsWithdrawn` event.\n function withdrawRewards(address stakingProvider) external {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n (, address beneficiary, ) = staking.rolesOf(stakingProvider);\n uint96 amount = sortitionPool.withdrawRewards(operator, beneficiary);\n // slither-disable-next-line reentrancy-events\n emit RewardsWithdrawn(stakingProvider, amount);\n }\n\n /// @notice Withdraws rewards belonging to operators marked as ineligible\n /// for sortition pool rewards.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract.\n /// @param recipient Recipient of withdrawn rewards.\n function withdrawIneligibleRewards(address recipient)\n external\n onlyGovernance\n {\n sortitionPool.withdrawIneligible(recipient);\n }\n\n /// @notice Used by staking provider to set operator address that will\n /// operate ECDSA node. The given staking provider can set operator\n /// address only one time. The operator address can not be changed\n /// and must be unique. Reverts if the operator is already set for\n /// the staking provider or if the operator address is already in\n /// use. Reverts if there is a pending authorization decrease for\n /// the staking provider.\n function registerOperator(address operator) external {\n authorization.registerOperator(operator);\n }\n\n /// @notice Lets the operator join the sortition pool. The operator address\n /// must be known - before calling this function, it has to be\n /// appointed by the staking provider by calling `registerOperator`.\n /// Also, the operator must have the minimum authorization required\n /// by ECDSA. Function reverts if there is no minimum stake\n /// authorized or if the operator is not known. If there was an\n /// authorization decrease requested, it is activated by starting\n /// the authorization decrease delay.\n function joinSortitionPool() external {\n authorization.joinSortitionPool(staking, sortitionPool);\n }\n\n /// @notice Updates status of the operator in the sortition pool. If there\n /// was an authorization decrease requested, it is activated by\n /// starting the authorization decrease delay.\n /// Function reverts if the operator is not known.\n function updateOperatorStatus(address operator) external {\n authorization.updateOperatorStatus(staking, sortitionPool, operator);\n }\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorized stake amount for the given staking provider increased.\n ///\n /// Reverts if the authorization amount is below the minimum.\n ///\n /// The function is not updating the sortition pool. Sortition pool\n /// state needs to be updated by the operator with a call to\n /// `joinSortitionPool` or `updateOperatorStatus`.\n ///\n /// @dev Can only be called by T staking contract.\n function authorizationIncreased(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.authorizationIncreased(\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorization decrease for the given staking provider has been\n /// requested.\n ///\n /// Reverts if the amount after deauthorization would be non-zero\n /// and lower than the minimum authorization.\n ///\n /// If the operator is not known (`registerOperator` was not called)\n /// it lets to `approveAuthorizationDecrease` immediatelly. If the\n /// operator is known (`registerOperator` was called), the operator\n /// needs to update state of the sortition pool with a call to\n /// `joinSortitionPool` or `updateOperatorStatus`. After the\n /// sortition pool state is in sync, authorization decrease delay\n /// starts.\n ///\n /// After authorization decrease delay passes, authorization\n /// decrease request needs to be approved with a call to\n /// `approveAuthorizationDecrease` function.\n ///\n /// If there is a pending authorization decrease request, it is\n /// overwritten.\n ///\n /// @dev Can only be called by T staking contract.\n function authorizationDecreaseRequested(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.authorizationDecreaseRequested(\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Approves the previously registered authorization decrease\n /// request. Reverts if authorization decrease delay has not passed\n /// yet or if the authorization decrease was not requested for the\n /// given staking provider.\n function approveAuthorizationDecrease(address stakingProvider) external {\n authorization.approveAuthorizationDecrease(staking, stakingProvider);\n }\n\n /// @notice Used by T staking contract to inform the application the\n /// authorization has been decreased for the given staking provider\n /// involuntarily, as a result of slashing.\n ///\n /// If the operator is not known (`registerOperator` was not called)\n /// the function does nothing. The operator was never in a sortition\n /// pool so there is nothing to update.\n ///\n /// If the operator is known, sortition pool is unlocked, and the\n /// operator is in the sortition pool, the sortition pool state is\n /// updated. If the sortition pool is locked, update needs to be\n /// postponed. Every other staker is incentivized to call\n /// `updateOperatorStatus` for the problematic operator to increase\n /// their own rewards in the pool.\n function involuntaryAuthorizationDecrease(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.involuntaryAuthorizationDecrease(\n staking,\n sortitionPool,\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Updates address of the Random Beacon.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _randomBeacon Random Beacon address.\n function upgradeRandomBeacon(IRandomBeacon _randomBeacon)\n external\n onlyGovernance\n {\n randomBeacon = _randomBeacon;\n emit RandomBeaconUpgraded(address(_randomBeacon));\n }\n\n /// @notice Updates the wallet owner.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters. The wallet owner has to implement `IWalletOwner`\n /// interface.\n /// @param _walletOwner New wallet owner address.\n function updateWalletOwner(IWalletOwner _walletOwner)\n external\n onlyGovernance\n {\n walletOwner = _walletOwner;\n emit WalletOwnerUpdated(address(_walletOwner));\n }\n\n /// @notice Updates the values of authorization parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _minimumAuthorization New minimum authorization amount.\n /// @param _authorizationDecreaseDelay New authorization decrease delay in\n /// seconds.\n /// @param _authorizationDecreaseChangePeriod New authorization decrease\n /// change period in seconds.\n function updateAuthorizationParameters(\n uint96 _minimumAuthorization,\n uint64 _authorizationDecreaseDelay,\n uint64 _authorizationDecreaseChangePeriod\n ) external onlyGovernance {\n authorization.setMinimumAuthorization(_minimumAuthorization);\n authorization.setAuthorizationDecreaseDelay(\n _authorizationDecreaseDelay\n );\n authorization.setAuthorizationDecreaseChangePeriod(\n _authorizationDecreaseChangePeriod\n );\n\n emit AuthorizationParametersUpdated(\n _minimumAuthorization,\n _authorizationDecreaseDelay,\n _authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Updates the values of DKG parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _seedTimeout New seed timeout.\n /// @param _resultChallengePeriodLength New DKG result challenge period\n /// length.\n /// @param _resultChallengeExtraGas New extra gas value required to be left\n /// at the end of the DKG result challenge transaction.\n /// @param _resultSubmissionTimeout New DKG result submission timeout.\n /// @param _submitterPrecedencePeriodLength New submitter precedence period\n /// length.\n function updateDkgParameters(\n uint256 _seedTimeout,\n uint256 _resultChallengePeriodLength,\n uint256 _resultChallengeExtraGas,\n uint256 _resultSubmissionTimeout,\n uint256 _submitterPrecedencePeriodLength\n ) external onlyGovernance {\n dkg.setSeedTimeout(_seedTimeout);\n dkg.setResultChallengePeriodLength(_resultChallengePeriodLength);\n dkg.setResultChallengeExtraGas(_resultChallengeExtraGas);\n dkg.setResultSubmissionTimeout(_resultSubmissionTimeout);\n dkg.setSubmitterPrecedencePeriodLength(\n _submitterPrecedencePeriodLength\n );\n\n // slither-disable-next-line reentrancy-events\n emit DkgParametersUpdated(\n _seedTimeout,\n _resultChallengePeriodLength,\n _resultChallengeExtraGas,\n _resultSubmissionTimeout,\n _submitterPrecedencePeriodLength\n );\n }\n\n /// @notice Updates the values of reward parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param maliciousDkgResultNotificationRewardMultiplier New value of the\n /// DKG malicious result notification reward multiplier.\n /// @param sortitionPoolRewardsBanDuration New sortition pool rewards\n /// ban duration in seconds.\n function updateRewardParameters(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n ) external onlyGovernance {\n _maliciousDkgResultNotificationRewardMultiplier = maliciousDkgResultNotificationRewardMultiplier;\n _sortitionPoolRewardsBanDuration = sortitionPoolRewardsBanDuration;\n emit RewardParametersUpdated(\n maliciousDkgResultNotificationRewardMultiplier,\n sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Updates the values of slashing parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param maliciousDkgResultSlashingAmount New malicious DKG result\n /// slashing amount.\n function updateSlashingParameters(uint96 maliciousDkgResultSlashingAmount)\n external\n onlyGovernance\n {\n _maliciousDkgResultSlashingAmount = maliciousDkgResultSlashingAmount;\n emit SlashingParametersUpdated(maliciousDkgResultSlashingAmount);\n }\n\n /// @notice Updates the values of gas-related parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param dkgResultSubmissionGas New DKG result submission gas.\n /// @param dkgResultApprovalGasOffset New DKG result approval gas offset.\n /// @param notifyOperatorInactivityGasOffset New operator inactivity\n /// notification gas offset.\n /// @param notifySeedTimeoutGasOffset New seed for DKG delivery timeout\n /// notification gas offset.\n /// @param notifyDkgTimeoutNegativeGasOffset New DKG timeout notification gas\n /// offset.\n function updateGasParameters(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n ) external onlyGovernance {\n _dkgResultSubmissionGas = dkgResultSubmissionGas;\n _dkgResultApprovalGasOffset = dkgResultApprovalGasOffset;\n _notifyOperatorInactivityGasOffset = notifyOperatorInactivityGasOffset;\n _notifySeedTimeoutGasOffset = notifySeedTimeoutGasOffset;\n _notifyDkgTimeoutNegativeGasOffset = notifyDkgTimeoutNegativeGasOffset;\n\n emit GasParametersUpdated(\n dkgResultSubmissionGas,\n dkgResultApprovalGasOffset,\n notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n\n /// @notice Requests a new wallet creation.\n /// @dev Can be called only by the owner of wallets.\n /// It locks the DKG and request a new relay entry. It expects\n /// that the DKG process will be started once a new relay entry\n /// gets generated.\n function requestNewWallet() external onlyWalletOwner {\n dkg.lockState();\n\n randomBeacon.requestRelayEntry(this);\n }\n\n /// @notice Closes an existing wallet. Reverts if wallet with the given ID\n /// does not exist or if it has already been closed.\n /// @param walletID ID of the wallet.\n /// @dev Only a Wallet Owner can call this function.\n function closeWallet(bytes32 walletID) external onlyWalletOwner {\n wallets.deleteWallet(walletID);\n emit WalletClosed(walletID);\n }\n\n /// @notice A callback that is executed once a new relay entry gets\n /// generated. It starts the DKG process.\n /// @dev Can be called only by the random beacon contract.\n /// @param relayEntry Relay entry.\n function __beaconCallback(uint256 relayEntry, uint256) external {\n require(\n msg.sender == address(randomBeacon),\n \"Caller is not the Random Beacon\"\n );\n\n dkg.start(relayEntry);\n }\n\n /// @notice Submits result of DKG protocol.\n /// The DKG result consists of result submitting member index,\n /// calculated group public key, bytes array of misbehaved members,\n /// concatenation of signatures from group members, indices of members\n /// corresponding to each signature and the list of group members.\n /// The result is registered optimistically and waits for an approval.\n /// The result can be challenged when it is believed to be incorrect.\n /// The challenge verifies the registered result i.a. it checks if members\n /// list corresponds to the expected set of members determined\n /// by the sortition pool.\n /// @dev The message to be signed by each member is keccak256 hash of the\n /// chain ID, calculated group public key, misbehaved members indices\n /// and DKG start block. The calculated hash should be prefixed with\n /// `\\x19Ethereum signed message:\\n` before signing, so the message to\n /// sign is:\n /// `\\x19Ethereum signed message:\\n${keccak256(chainID,groupPubKey,misbehavedIndices,startBlock)}`\n /// @param dkgResult DKG result.\n function submitDkgResult(DKG.Result calldata dkgResult) external {\n wallets.validatePublicKey(dkgResult.groupPubKey);\n dkg.submitResult(dkgResult);\n }\n\n /// @notice Approves DKG result. Can be called when the challenge period for\n /// the submitted result is finished. Considers the submitted result\n /// as valid, bans misbehaved group members from the sortition pool\n /// rewards, and completes the group creation by activating the\n /// candidate group. For the first `resultSubmissionTimeout` blocks\n /// after the end of the challenge period can be called only by the\n /// DKG result submitter. After that time, can be called by anyone.\n /// A new wallet based on the DKG result details.\n /// @param dkgResult Result to approve. Must match the submitted result\n /// stored during `submitDkgResult`.\n function approveDkgResult(DKG.Result calldata dkgResult) external {\n uint256 gasStart = gasleft();\n uint32[] memory misbehavedMembers = dkg.approveResult(dkgResult);\n\n (bytes32 walletID, bytes32 publicKeyX, bytes32 publicKeyY) = wallets\n .addWallet(dkgResult.membersHash, dkgResult.groupPubKey);\n\n emit WalletCreated(walletID, keccak256(abi.encode(dkgResult)));\n\n if (misbehavedMembers.length > 0) {\n sortitionPool.setRewardIneligibility(\n misbehavedMembers,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n }\n\n walletOwner.__ecdsaWalletCreatedCallback(\n walletID,\n publicKeyX,\n publicKeyY\n );\n\n dkg.complete();\n\n // Refund msg.sender's ETH for DKG result submission and result approval\n reimbursementPool.refund(\n _dkgResultSubmissionGas +\n (gasStart - gasleft()) +\n _dkgResultApprovalGasOffset,\n msg.sender\n );\n }\n\n /// @notice Notifies about seed for DKG delivery timeout. It is expected\n /// that a seed is delivered by the Random Beacon as a relay entry in a\n /// callback function.\n function notifySeedTimeout() external {\n uint256 gasStart = gasleft();\n\n dkg.notifySeedTimeout();\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifySeedTimeoutGasOffset,\n msg.sender\n );\n }\n\n /// @notice Notifies about DKG timeout.\n function notifyDkgTimeout() external {\n uint256 gasStart = gasleft();\n\n dkg.notifyDkgTimeout();\n\n // Note that the offset is subtracted as it is expected that the cleanup\n // performed on DKG timeout notification removes data from the storage\n // which is recovering gas for the transaction.\n reimbursementPool.refund(\n (gasStart - gasleft()) - _notifyDkgTimeoutNegativeGasOffset,\n msg.sender\n );\n }\n\n /// @notice Challenges DKG result. If the submitted result is proved to be\n /// invalid it reverts the DKG back to the result submission phase.\n /// @param dkgResult Result to challenge. Must match the submitted result\n /// stored during `submitDkgResult`.\n /// @dev Due to EIP-150 1/64 of the gas is not forwarded to the call, and\n /// will be kept to execute the remaining operations in the function\n /// after the call inside the try-catch. To eliminate a class of\n /// attacks related to the gas limit manipulation, this function\n /// requires an extra amount of gas to be left at the end of the\n /// execution.\n function challengeDkgResult(DKG.Result calldata dkgResult) external {\n (\n bytes32 maliciousDkgResultHash,\n uint32 maliciousDkgResultSubmitterId\n ) = dkg.challengeResult(dkgResult);\n\n address maliciousDkgResultSubmitterAddress = sortitionPool\n .getIDOperator(maliciousDkgResultSubmitterId);\n\n address[] memory operatorWrapper = new address[](1);\n operatorWrapper[0] = operatorToStakingProvider(\n maliciousDkgResultSubmitterAddress\n );\n\n try\n staking.seize(\n _maliciousDkgResultSlashingAmount,\n _maliciousDkgResultNotificationRewardMultiplier,\n msg.sender,\n operatorWrapper\n )\n {\n // slither-disable-next-line reentrancy-events\n emit DkgMaliciousResultSlashed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n } catch {\n // Should never happen but we want to ensure a non-critical path\n // failure from an external contract does not stop the challenge\n // to complete.\n emit DkgMaliciousResultSlashingFailed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n }\n\n // Due to EIP-150, 1/64 of the gas is not forwarded to the call, and\n // will be kept to execute the remaining operations in the function\n // after the call inside the try-catch.\n //\n // To ensure there is no way for the caller to manipulate gas limit in\n // such a way that the call inside try-catch fails with out-of-gas and\n // the rest of the function is executed with the remaining 1/64 of gas,\n // we require an extra gas amount to be left at the end of the call to\n // `challengeDkgResult`.\n dkg.requireChallengeExtraGas();\n }\n\n /// @notice Notifies about operators who are inactive. Using this function,\n /// a majority of the wallet signing group can decide about\n /// punishing specific group members who constantly fail doing their\n /// job. If the provided claim is proved to be valid and signed by\n /// sufficient number of group members, operators of members deemed\n /// as inactive are banned from sortition pool rewards for the\n /// duration specified by `sortitionPoolRewardsBanDuration` parameter.\n /// The function allows to signal about single operators being\n /// inactive as well as to signal wallet-wide heartbeat failures\n /// that are propagated to the wallet owner who should begin the\n /// procedure of moving responsibilities to another wallet given\n /// that the wallet who failed the heartbeat may soon be not able to\n /// function and provide new signatures.\n /// The sender of the claim must be one of the claim signers. This\n /// function can be called only for registered wallets\n /// @param claim Operator inactivity claim.\n /// @param nonce Current inactivity claim nonce for the given wallet signing\n /// group. Must be the same as the stored one.\n /// @param groupMembers Identifiers of the wallet signing group members.\n function notifyOperatorInactivity(\n Inactivity.Claim calldata claim,\n uint256 nonce,\n uint32[] calldata groupMembers\n ) external {\n uint256 gasStart = gasleft();\n\n bytes32 walletID = claim.walletID;\n\n require(nonce == inactivityClaimNonce[walletID], \"Invalid nonce\");\n\n (bytes32 pubKeyX, bytes32 pubKeyY) = wallets\n .getWalletPublicKeyCoordinates(walletID);\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(groupMembers)),\n \"Invalid group members\"\n );\n\n uint32[] memory ineligibleOperators = Inactivity.verifyClaim(\n sortitionPool,\n claim,\n bytes.concat(pubKeyX, pubKeyY),\n nonce,\n groupMembers\n );\n\n inactivityClaimNonce[walletID]++;\n\n emit InactivityClaimed(walletID, nonce, msg.sender);\n\n sortitionPool.setRewardIneligibility(\n ineligibleOperators,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n\n if (claim.heartbeatFailed) {\n walletOwner.__ecdsaWalletHeartbeatFailedCallback(\n walletID,\n pubKeyX,\n pubKeyY\n );\n }\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifyOperatorInactivityGasOffset,\n msg.sender\n );\n }\n\n /// @notice Allows the wallet owner to add all signing group members of the\n /// wallet with the given ID to the slashing queue of the staking .\n /// contract. The notifier will receive reward per each group member\n /// from the staking contract notifiers treasury. The reward is\n /// scaled by the `rewardMultiplier` provided as a parameter.\n /// @param amount Amount of tokens to seize from each signing group member.\n /// @param rewardMultiplier Fraction of the staking contract notifiers\n /// reward the notifier should receive; should be between [0, 100].\n /// @param notifier Address of the misbehavior notifier.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - `rewardMultiplier` must be between [0, 100].\n /// - This function does revert if staking contract call reverts.\n /// The calling code needs to handle the potential revert.\n function seize(\n uint96 amount,\n uint256 rewardMultiplier,\n address notifier,\n bytes32 walletID,\n uint32[] calldata walletMembersIDs\n ) external onlyWalletOwner {\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n address[] memory groupMembersAddresses = sortitionPool.getIDOperators(\n walletMembersIDs\n );\n address[] memory stakingProvidersAddresses = new address[](\n walletMembersIDs.length\n );\n for (uint256 i = 0; i < groupMembersAddresses.length; i++) {\n stakingProvidersAddresses[i] = operatorToStakingProvider(\n groupMembersAddresses[i]\n );\n }\n\n staking.seize(\n amount,\n rewardMultiplier,\n notifier,\n stakingProvidersAddresses\n );\n }\n\n /// @notice Checks if DKG result is valid for the current DKG.\n /// @param result DKG result.\n /// @return True if the result is valid. If the result is invalid it returns\n /// false and an error message.\n function isDkgResultValid(DKG.Result calldata result)\n external\n view\n returns (bool, string memory)\n {\n return dkg.isResultValid(result);\n }\n\n /// @notice Check current wallet creation state.\n function getWalletCreationState() external view returns (DKG.State) {\n return dkg.currentState();\n }\n\n /// @notice Checks whether the given operator is a member of the given\n /// wallet signing group.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @param operator Address of the checked operator.\n /// @param walletMemberIndex Position of the operator in the wallet signing\n /// group members list.\n /// @return True - if the operator is a member of the given wallet signing\n /// group. False - otherwise.\n /// @dev Requirements:\n /// - The `operator` parameter must be an actual sortition pool operator.\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]\n function isWalletMember(\n bytes32 walletID,\n uint32[] calldata walletMembersIDs,\n address operator,\n uint256 walletMemberIndex\n ) external view returns (bool) {\n uint32 operatorID = sortitionPool.getOperatorID(operator);\n\n require(operatorID != 0, \"Not a sortition pool operator\");\n\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n require(\n 1 <= walletMemberIndex &&\n walletMemberIndex <= walletMembersIDs.length,\n \"Wallet member index is out of range\"\n );\n\n return walletMembersIDs[walletMemberIndex - 1] == operatorID;\n }\n\n /// @notice Checks if awaiting seed timed out.\n /// @return True if awaiting seed timed out, false otherwise.\n function hasSeedTimedOut() external view returns (bool) {\n return dkg.hasSeedTimedOut();\n }\n\n /// @notice Checks if DKG timed out. The DKG timeout period includes time required\n /// for off-chain protocol execution and time for the result publication\n /// for all group members. After this time result cannot be submitted\n /// and DKG can be notified about the timeout.\n /// @return True if DKG timed out, false otherwise.\n function hasDkgTimedOut() external view returns (bool) {\n return dkg.hasDkgTimedOut();\n }\n\n function getWallet(bytes32 walletID)\n external\n view\n returns (Wallets.Wallet memory)\n {\n return wallets.registry[walletID];\n }\n\n /// @notice Gets public key of a wallet with a given wallet ID.\n /// The public key is returned in an uncompressed format as a 64-byte\n /// concatenation of X and Y coordinates.\n /// @param walletID ID of the wallet.\n /// @return Uncompressed public key of the wallet.\n function getWalletPublicKey(bytes32 walletID)\n external\n view\n returns (bytes memory)\n {\n return wallets.getWalletPublicKey(walletID);\n }\n\n /// @notice Checks if a wallet with the given ID is registered.\n /// @param walletID Wallet's ID.\n /// @return True if wallet is registered, false otherwise.\n function isWalletRegistered(bytes32 walletID) external view returns (bool) {\n return wallets.isWalletRegistered(walletID);\n }\n\n /// @notice The minimum authorization amount required so that operator can\n /// participate in ECDSA Wallet operations.\n function minimumAuthorization() external view returns (uint96) {\n return authorization.parameters.minimumAuthorization;\n }\n\n /// @notice Returns the current value of the staking provider's eligible\n /// stake. Eligible stake is defined as the currently authorized\n /// stake minus the pending authorization decrease. Eligible stake\n /// is what is used for operator's weight in the sortition pool.\n /// If the authorized stake minus the pending authorization decrease\n /// is below the minimum authorization, eligible stake is 0.\n function eligibleStake(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.eligibleStake(staking, stakingProvider);\n }\n\n /// @notice Returns the amount of rewards available for withdrawal for the\n /// given staking provider. Reverts if staking provider has not\n /// registered the operator address.\n function availableRewards(address stakingProvider)\n external\n view\n returns (uint96)\n {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n return sortitionPool.getAvailableRewards(operator);\n }\n\n /// @notice Returns the amount of stake that is pending authorization\n /// decrease for the given staking provider. If no authorization\n /// decrease has been requested, returns zero.\n function pendingAuthorizationDecrease(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.pendingAuthorizationDecrease(stakingProvider);\n }\n\n /// @notice Returns the remaining time in seconds that needs to pass before\n /// the requested authorization decrease can be approved.\n /// If the sortition pool state was not updated yet by the operator\n /// after requesting the authorization decrease, returns\n /// `type(uint64).max`.\n function remainingAuthorizationDecreaseDelay(address stakingProvider)\n external\n view\n returns (uint64)\n {\n return\n authorization.remainingAuthorizationDecreaseDelay(stakingProvider);\n }\n\n /// @notice Returns operator registered for the given staking provider.\n function stakingProviderToOperator(address stakingProvider)\n public\n view\n returns (address)\n {\n return authorization.stakingProviderToOperator[stakingProvider];\n }\n\n /// @notice Returns staking provider of the given operator.\n function operatorToStakingProvider(address operator)\n public\n view\n returns (address)\n {\n return authorization.operatorToStakingProvider[operator];\n }\n\n /// @notice Checks if the operator's authorized stake is in sync with\n /// operator's weight in the sortition pool.\n /// If the operator is not in the sortition pool and their\n /// authorized stake is non-zero, function returns false.\n function isOperatorUpToDate(address operator) external view returns (bool) {\n return\n authorization.isOperatorUpToDate(staking, sortitionPool, operator);\n }\n\n /// @notice Returns true if the given operator is in the sortition pool.\n /// Otherwise, returns false.\n function isOperatorInPool(address operator) external view returns (bool) {\n return sortitionPool.isOperatorInPool(operator);\n }\n\n /// @notice Selects a new group of operators. Can only be called when DKG\n /// is in progress and the pool is locked.\n /// At least one operator has to be registered in the pool,\n /// otherwise the function fails reverting the transaction.\n /// @return IDs of selected group members.\n function selectGroup() external view returns (uint32[] memory) {\n return sortitionPool.selectGroup(DKG.groupSize, bytes32(dkg.seed));\n }\n\n /// @notice Retrieves dkg parameters that were set in DKG library.\n function dkgParameters() external view returns (DKG.Parameters memory) {\n return dkg.parameters;\n }\n\n /// @notice Returns authorization-related parameters.\n /// @dev The minimum authorization is also returned by `minimumAuthorization()`\n /// function, as a requirement of `IApplication` interface.\n /// @return minimumAuthorization The minimum authorization amount required\n /// so that operator can participate in the random beacon. This\n /// amount is required to execute slashing for providing a malicious\n /// DKG result or when a relay entry times out.\n /// @return authorizationDecreaseDelay Delay in seconds that needs to pass\n /// between the time authorization decrease is requested and the\n /// time that request gets approved. Protects against free-riders\n /// earning rewards and not being active in the network.\n /// @return authorizationDecreaseChangePeriod Authorization decrease change\n /// period in seconds. It is the time, before authorization decrease\n /// delay end, during which the pending authorization decrease\n /// request can be overwritten.\n /// If set to 0, pending authorization decrease request can not be\n /// overwritten until the entire `authorizationDecreaseDelay` ends.\n /// If set to value equal `authorizationDecreaseDelay`, request can\n /// always be overwritten.\n function authorizationParameters()\n external\n view\n returns (\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n )\n {\n return (\n authorization.parameters.minimumAuthorization,\n authorization.parameters.authorizationDecreaseDelay,\n authorization.parameters.authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Retrieves reward-related parameters.\n /// @return maliciousDkgResultNotificationRewardMultiplier Percentage of the\n /// staking contract malicious behavior notification reward which\n /// will be transferred to the notifier reporting about a malicious\n /// DKG result. Notifiers are rewarded from a notifiers treasury\n /// pool. For example, if notification reward is 1000 and the value\n /// of the multiplier is 5, the notifier will receive:\n /// 5% of 1000 = 50 per each operator affected.\n /// @return sortitionPoolRewardsBanDuration Duration of the sortition pool\n /// rewards ban imposed on operators who missed their turn for DKG\n /// result submission or who failed a heartbeat.\n function rewardParameters()\n external\n view\n returns (\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n )\n {\n return (\n _maliciousDkgResultNotificationRewardMultiplier,\n _sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Retrieves slashing-related parameters.\n /// @return maliciousDkgResultSlashingAmount Slashing amount for submitting\n /// a malicious DKG result. Every DKG result submitted can be\n /// challenged for the time of `dkg.resultChallengePeriodLength`.\n /// If the DKG result submitted is challenged and proven to be\n /// malicious, the operator who submitted the malicious result is\n /// slashed for `_maliciousDkgResultSlashingAmount`.\n function slashingParameters()\n external\n view\n returns (uint96 maliciousDkgResultSlashingAmount)\n {\n return _maliciousDkgResultSlashingAmount;\n }\n\n /// @notice Retrieves gas-related parameters.\n /// @return dkgResultSubmissionGas Calculated max gas cost for submitting\n /// a DKG result. This will be refunded as part of the DKG approval\n /// process. It is in the submitter's interest to not skip his\n /// priority turn on the approval, otherwise the refund of the DKG\n /// submission will be refunded to another group member that will\n /// call the DKG approve function.\n /// @return dkgResultApprovalGasOffset Gas that is meant to balance the DKG\n /// result approval's overall cost. It can be updated by the\n /// governance based on the current market conditions.\n /// @return notifyOperatorInactivityGasOffset Gas that is meant to balance\n /// the notification of an operator inactivity. It can be updated by\n /// the governance based on the current market conditions.\n /// @return notifySeedTimeoutGasOffset Gas that is meant to balance the\n /// notification of a seed for DKG delivery timeout. It can be updated\n /// by the governance based on the current market conditions.\n /// @return notifyDkgTimeoutNegativeGasOffset Gas that is meant to balance\n /// the notification of a DKG protocol execution timeout. It can be\n /// updated by the governance based on the current market conditions.\n function gasParameters()\n external\n view\n returns (\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n )\n {\n return (\n _dkgResultSubmissionGas,\n _dkgResultApprovalGasOffset,\n _notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n}\n"
|
|
41
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nimport \"./api/IWalletRegistry.sol\";\nimport \"./api/IWalletOwner.sol\";\nimport \"./libraries/Wallets.sol\";\nimport {EcdsaAuthorization as Authorization} from \"./libraries/EcdsaAuthorization.sol\";\nimport {EcdsaDkg as DKG} from \"./libraries/EcdsaDkg.sol\";\nimport {EcdsaInactivity as Inactivity} from \"./libraries/EcdsaInactivity.sol\";\nimport {EcdsaDkgValidator as DKGValidator} from \"./EcdsaDkgValidator.sol\";\n\nimport \"@keep-network/sortition-pools/contracts/SortitionPool.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeacon.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeaconConsumer.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\nimport \"@keep-network/random-beacon/contracts/Governable.sol\";\n\nimport \"@threshold-network/solidity-contracts/contracts/staking/IApplication.sol\";\nimport \"@threshold-network/solidity-contracts/contracts/staking/IStaking.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\n\ncontract WalletRegistry is\n IWalletRegistry,\n IRandomBeaconConsumer,\n IApplication,\n Governable,\n Reimbursable,\n Initializable\n{\n using Authorization for Authorization.Data;\n using DKG for DKG.Data;\n using Wallets for Wallets.Data;\n\n // Libraries data storages\n Authorization.Data internal authorization;\n DKG.Data internal dkg;\n Wallets.Data internal wallets;\n\n /// @notice Slashing amount for submitting a malicious DKG result. Every\n /// DKG result submitted can be challenged for the time of\n /// `dkg.resultChallengePeriodLength`. If the DKG result submitted\n /// is challenged and proven to be malicious, the operator who\n /// submitted the malicious result is slashed for\n /// `_maliciousDkgResultSlashingAmount`.\n uint96 internal _maliciousDkgResultSlashingAmount;\n\n /// @notice Percentage of the staking contract malicious behavior\n /// notification reward which will be transferred to the notifier\n /// reporting about a malicious DKG result. Notifiers are rewarded\n /// from a notifiers treasury pool. For example, if\n /// notification reward is 1000 and the value of the multiplier is\n /// 5, the notifier will receive: 5% of 1000 = 50 per each\n /// operator affected.\n uint256 internal _maliciousDkgResultNotificationRewardMultiplier;\n\n /// @notice Duration of the sortition pool rewards ban imposed on operators\n /// who missed their turn for DKG result submission or who failed\n /// a heartbeat.\n uint256 internal _sortitionPoolRewardsBanDuration;\n\n /// @notice Calculated max gas cost for submitting a DKG result. This will\n /// be refunded as part of the DKG approval process. It is in the\n /// submitter's interest to not skip his priority turn on the approval,\n /// otherwise the refund of the DKG submission will be refunded to\n /// another group member that will call the DKG approve function.\n uint256 internal _dkgResultSubmissionGas;\n\n /// @notice Gas that is meant to balance the DKG result approval's overall\n /// cost. It can be updated by the governance based on the current\n /// market conditions.\n uint256 internal _dkgResultApprovalGasOffset;\n\n /// @notice Gas that is meant to balance the notification of an operator\n /// inactivity. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifyOperatorInactivityGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a seed for DKG\n /// delivery timeout. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifySeedTimeoutGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a DKG protocol\n /// execution timeout. It can be updated by the governance based on the\n /// current market conditions.\n /// @dev The value is subtracted for the refundable gas calculation, as the\n /// DKG timeout notification transaction recovers some gas when cleaning\n /// up the storage.\n uint256 internal _notifyDkgTimeoutNegativeGasOffset;\n\n /// @notice Stores current operator inactivity claim nonce for the given\n /// wallet signing group. Each claim is made with a unique nonce\n /// which protects against claim replay.\n mapping(bytes32 => uint256) public inactivityClaimNonce; // walletID -> nonce\n\n // Address that is set as owner of all wallets. Only this address can request\n // new wallets creation and manage their state.\n IWalletOwner public walletOwner;\n\n // External dependencies\n\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n SortitionPool public immutable sortitionPool;\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStaking public immutable staking;\n IRandomBeacon public randomBeacon;\n\n // Events\n event DkgStarted(uint256 indexed seed);\n\n event DkgResultSubmitted(\n bytes32 indexed resultHash,\n uint256 indexed seed,\n DKG.Result result\n );\n\n event DkgTimedOut();\n\n event DkgResultApproved(\n bytes32 indexed resultHash,\n address indexed approver\n );\n\n event DkgResultChallenged(\n bytes32 indexed resultHash,\n address indexed challenger,\n string reason\n );\n\n event DkgStateLocked();\n\n event DkgSeedTimedOut();\n\n event WalletCreated(\n bytes32 indexed walletID,\n bytes32 indexed dkgResultHash\n );\n\n event WalletClosed(bytes32 indexed walletID);\n\n event DkgMaliciousResultSlashed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event DkgMaliciousResultSlashingFailed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event AuthorizationParametersUpdated(\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n );\n\n event RewardParametersUpdated(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n );\n\n event SlashingParametersUpdated(uint256 maliciousDkgResultSlashingAmount);\n\n event DkgParametersUpdated(\n uint256 seedTimeout,\n uint256 resultChallengePeriodLength,\n uint256 resultChallengeExtraGas,\n uint256 resultSubmissionTimeout,\n uint256 resultSubmitterPrecedencePeriodLength\n );\n\n event GasParametersUpdated(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n );\n\n event RandomBeaconUpgraded(address randomBeacon);\n\n event WalletOwnerUpdated(address walletOwner);\n\n event OperatorRegistered(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event AuthorizationIncreased(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event AuthorizationDecreaseRequested(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount,\n uint64 decreasingAt\n );\n\n event AuthorizationDecreaseApproved(address indexed stakingProvider);\n\n event InvoluntaryAuthorizationDecreaseFailed(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event OperatorJoinedSortitionPool(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event OperatorStatusUpdated(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event InactivityClaimed(\n bytes32 indexed walletID,\n uint256 nonce,\n address notifier\n );\n\n modifier onlyStakingContract() {\n require(\n msg.sender == address(staking),\n \"Caller is not the staking contract\"\n );\n _;\n }\n\n /// @notice Reverts if called not by the Wallet Owner.\n modifier onlyWalletOwner() {\n require(\n msg.sender == address(walletOwner),\n \"Caller is not the Wallet Owner\"\n );\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(governance == msg.sender, \"Caller is not the governance\");\n _;\n }\n\n /// @dev Used to initialize immutable variables only, use `initialize` function\n /// for upgradable contract initialization on deployment.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(SortitionPool _sortitionPool, IStaking _staking) {\n sortitionPool = _sortitionPool;\n staking = _staking;\n\n _disableInitializers();\n }\n\n /// @dev Initializes upgradable contract on deployment.\n function initialize(\n DKGValidator _ecdsaDkgValidator,\n IRandomBeacon _randomBeacon,\n ReimbursementPool _reimbursementPool\n ) external initializer {\n randomBeacon = _randomBeacon;\n reimbursementPool = _reimbursementPool;\n\n _transferGovernance(msg.sender);\n\n //\n // All parameters set in the constructor are initial ones, used at the\n // moment contracts were deployed for the first time. Parameters are\n // governable and values assigned in the constructor do not need to\n // reflect the current ones.\n //\n\n // Minimum authorization is 40k T.\n //\n // Authorization decrease delay is 45 days.\n //\n // Authorization decrease change period is 45 days. It means pending\n // authorization decrease can be overwritten all the time.\n authorization.setMinimumAuthorization(40_000e18);\n authorization.setAuthorizationDecreaseDelay(3_888_000);\n authorization.setAuthorizationDecreaseChangePeriod(3_888_000);\n\n // Malicious DKG result slashing amount is set initially to 1% of the\n // minimum authorization (400 T). This values needs to be increased\n // significantly once the system is fully launched.\n //\n // Notifier of a malicious DKG result receives 100% of the notifier\n // reward from the staking contract.\n //\n // Inactive operators are set as ineligible for rewards for 2 weeks.\n _maliciousDkgResultSlashingAmount = 400e18;\n _maliciousDkgResultNotificationRewardMultiplier = 100;\n _sortitionPoolRewardsBanDuration = 2 weeks;\n\n // DKG seed timeout is set to 48h assuming 15s block time. The same\n // value is used by the Random Beacon as a relay entry hard timeout.\n //\n // DKG result challenge period length is set to 48h as well, assuming\n // 15s block time.\n //\n // DKG result submission timeout covers:\n // - 20 blocks required to confirm the DkgStarted event off-chain\n // - 5 retries of the off-chain protocol that takes 161 blocks at most\n // - 15 blocks to submit the result for each of the 100 members\n // That gives: 20 + (5 * 161) + (15 * 100) = 2325\n //\n //\n // The original DKG result submitter has 20 blocks to approve it before\n // anyone else can do that.\n //\n // With these parameters, the happy path takes no more than 104 hours.\n // In practice, it should take about 48 hours (just the challenge time).\n dkg.init(sortitionPool, _ecdsaDkgValidator);\n dkg.setSeedTimeout(11_520);\n dkg.setResultChallengePeriodLength(11_520);\n dkg.setResultChallengeExtraGas(50_000);\n dkg.setResultSubmissionTimeout(2325);\n dkg.setSubmitterPrecedencePeriodLength(20);\n\n // Gas parameters were adjusted based on Ethereum state in April 2022.\n // If the cost of EVM opcodes change over time, these parameters will\n // have to be updated.\n _dkgResultSubmissionGas = 290_000;\n _dkgResultApprovalGasOffset = 72_000;\n _notifyOperatorInactivityGasOffset = 93_000;\n _notifySeedTimeoutGasOffset = 7_250;\n _notifyDkgTimeoutNegativeGasOffset = 2_300;\n }\n\n /// @notice Withdraws application rewards for the given staking provider.\n /// Rewards are withdrawn to the staking provider's beneficiary\n /// address set in the staking contract. Reverts if staking provider\n /// has not registered the operator address.\n /// @dev Emits `RewardsWithdrawn` event.\n function withdrawRewards(address stakingProvider) external {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n (, address beneficiary, ) = staking.rolesOf(stakingProvider);\n uint96 amount = sortitionPool.withdrawRewards(operator, beneficiary);\n // slither-disable-next-line reentrancy-events\n emit RewardsWithdrawn(stakingProvider, amount);\n }\n\n /// @notice Withdraws rewards belonging to operators marked as ineligible\n /// for sortition pool rewards.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract.\n /// @param recipient Recipient of withdrawn rewards.\n function withdrawIneligibleRewards(address recipient)\n external\n onlyGovernance\n {\n sortitionPool.withdrawIneligible(recipient);\n }\n\n /// @notice Used by staking provider to set operator address that will\n /// operate ECDSA node. The given staking provider can set operator\n /// address only one time. The operator address can not be changed\n /// and must be unique. Reverts if the operator is already set for\n /// the staking provider or if the operator address is already in\n /// use. Reverts if there is a pending authorization decrease for\n /// the staking provider.\n function registerOperator(address operator) external {\n authorization.registerOperator(operator);\n }\n\n /// @notice Lets the operator join the sortition pool. The operator address\n /// must be known - before calling this function, it has to be\n /// appointed by the staking provider by calling `registerOperator`.\n /// Also, the operator must have the minimum authorization required\n /// by ECDSA. Function reverts if there is no minimum stake\n /// authorized or if the operator is not known. If there was an\n /// authorization decrease requested, it is activated by starting\n /// the authorization decrease delay.\n function joinSortitionPool() external {\n authorization.joinSortitionPool(staking, sortitionPool);\n }\n\n /// @notice Updates status of the operator in the sortition pool. If there\n /// was an authorization decrease requested, it is activated by\n /// starting the authorization decrease delay.\n /// Function reverts if the operator is not known.\n function updateOperatorStatus(address operator) external {\n authorization.updateOperatorStatus(staking, sortitionPool, operator);\n }\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorized stake amount for the given staking provider increased.\n ///\n /// Reverts if the authorization amount is below the minimum.\n ///\n /// The function is not updating the sortition pool. Sortition pool\n /// state needs to be updated by the operator with a call to\n /// `joinSortitionPool` or `updateOperatorStatus`.\n ///\n /// @dev Can only be called by T staking contract.\n function authorizationIncreased(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.authorizationIncreased(\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorization decrease for the given staking provider has been\n /// requested.\n ///\n /// Reverts if the amount after deauthorization would be non-zero\n /// and lower than the minimum authorization.\n ///\n /// If the operator is not known (`registerOperator` was not called)\n /// it lets to `approveAuthorizationDecrease` immediatelly. If the\n /// operator is known (`registerOperator` was called), the operator\n /// needs to update state of the sortition pool with a call to\n /// `joinSortitionPool` or `updateOperatorStatus`. After the\n /// sortition pool state is in sync, authorization decrease delay\n /// starts.\n ///\n /// After authorization decrease delay passes, authorization\n /// decrease request needs to be approved with a call to\n /// `approveAuthorizationDecrease` function.\n ///\n /// If there is a pending authorization decrease request, it is\n /// overwritten.\n ///\n /// @dev Can only be called by T staking contract.\n function authorizationDecreaseRequested(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.authorizationDecreaseRequested(\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Approves the previously registered authorization decrease\n /// request. Reverts if authorization decrease delay has not passed\n /// yet or if the authorization decrease was not requested for the\n /// given staking provider.\n function approveAuthorizationDecrease(address stakingProvider) external {\n authorization.approveAuthorizationDecrease(staking, stakingProvider);\n }\n\n /// @notice Used by T staking contract to inform the application the\n /// authorization has been decreased for the given staking provider\n /// involuntarily, as a result of slashing.\n ///\n /// If the operator is not known (`registerOperator` was not called)\n /// the function does nothing. The operator was never in a sortition\n /// pool so there is nothing to update.\n ///\n /// If the operator is known, sortition pool is unlocked, and the\n /// operator is in the sortition pool, the sortition pool state is\n /// updated. If the sortition pool is locked, update needs to be\n /// postponed. Every other staker is incentivized to call\n /// `updateOperatorStatus` for the problematic operator to increase\n /// their own rewards in the pool.\n function involuntaryAuthorizationDecrease(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.involuntaryAuthorizationDecrease(\n staking,\n sortitionPool,\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Updates address of the Random Beacon.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _randomBeacon Random Beacon address.\n function upgradeRandomBeacon(IRandomBeacon _randomBeacon)\n external\n onlyGovernance\n {\n randomBeacon = _randomBeacon;\n emit RandomBeaconUpgraded(address(_randomBeacon));\n }\n\n /// @notice Updates the wallet owner.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters. The wallet owner has to implement `IWalletOwner`\n /// interface.\n /// @param _walletOwner New wallet owner address.\n function updateWalletOwner(IWalletOwner _walletOwner)\n external\n onlyGovernance\n {\n walletOwner = _walletOwner;\n emit WalletOwnerUpdated(address(_walletOwner));\n }\n\n /// @notice Updates the values of authorization parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _minimumAuthorization New minimum authorization amount.\n /// @param _authorizationDecreaseDelay New authorization decrease delay in\n /// seconds.\n /// @param _authorizationDecreaseChangePeriod New authorization decrease\n /// change period in seconds.\n function updateAuthorizationParameters(\n uint96 _minimumAuthorization,\n uint64 _authorizationDecreaseDelay,\n uint64 _authorizationDecreaseChangePeriod\n ) external onlyGovernance {\n authorization.setMinimumAuthorization(_minimumAuthorization);\n authorization.setAuthorizationDecreaseDelay(\n _authorizationDecreaseDelay\n );\n authorization.setAuthorizationDecreaseChangePeriod(\n _authorizationDecreaseChangePeriod\n );\n\n emit AuthorizationParametersUpdated(\n _minimumAuthorization,\n _authorizationDecreaseDelay,\n _authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Updates the values of DKG parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _seedTimeout New seed timeout.\n /// @param _resultChallengePeriodLength New DKG result challenge period\n /// length.\n /// @param _resultChallengeExtraGas New extra gas value required to be left\n /// at the end of the DKG result challenge transaction.\n /// @param _resultSubmissionTimeout New DKG result submission timeout.\n /// @param _submitterPrecedencePeriodLength New submitter precedence period\n /// length.\n function updateDkgParameters(\n uint256 _seedTimeout,\n uint256 _resultChallengePeriodLength,\n uint256 _resultChallengeExtraGas,\n uint256 _resultSubmissionTimeout,\n uint256 _submitterPrecedencePeriodLength\n ) external onlyGovernance {\n dkg.setSeedTimeout(_seedTimeout);\n dkg.setResultChallengePeriodLength(_resultChallengePeriodLength);\n dkg.setResultChallengeExtraGas(_resultChallengeExtraGas);\n dkg.setResultSubmissionTimeout(_resultSubmissionTimeout);\n dkg.setSubmitterPrecedencePeriodLength(\n _submitterPrecedencePeriodLength\n );\n\n // slither-disable-next-line reentrancy-events\n emit DkgParametersUpdated(\n _seedTimeout,\n _resultChallengePeriodLength,\n _resultChallengeExtraGas,\n _resultSubmissionTimeout,\n _submitterPrecedencePeriodLength\n );\n }\n\n /// @notice Updates the values of reward parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param maliciousDkgResultNotificationRewardMultiplier New value of the\n /// DKG malicious result notification reward multiplier.\n /// @param sortitionPoolRewardsBanDuration New sortition pool rewards\n /// ban duration in seconds.\n function updateRewardParameters(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n ) external onlyGovernance {\n _maliciousDkgResultNotificationRewardMultiplier = maliciousDkgResultNotificationRewardMultiplier;\n _sortitionPoolRewardsBanDuration = sortitionPoolRewardsBanDuration;\n emit RewardParametersUpdated(\n maliciousDkgResultNotificationRewardMultiplier,\n sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Updates the values of slashing parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param maliciousDkgResultSlashingAmount New malicious DKG result\n /// slashing amount.\n function updateSlashingParameters(uint96 maliciousDkgResultSlashingAmount)\n external\n onlyGovernance\n {\n _maliciousDkgResultSlashingAmount = maliciousDkgResultSlashingAmount;\n emit SlashingParametersUpdated(maliciousDkgResultSlashingAmount);\n }\n\n /// @notice Updates the values of gas-related parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param dkgResultSubmissionGas New DKG result submission gas.\n /// @param dkgResultApprovalGasOffset New DKG result approval gas offset.\n /// @param notifyOperatorInactivityGasOffset New operator inactivity\n /// notification gas offset.\n /// @param notifySeedTimeoutGasOffset New seed for DKG delivery timeout\n /// notification gas offset.\n /// @param notifyDkgTimeoutNegativeGasOffset New DKG timeout notification gas\n /// offset.\n function updateGasParameters(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n ) external onlyGovernance {\n _dkgResultSubmissionGas = dkgResultSubmissionGas;\n _dkgResultApprovalGasOffset = dkgResultApprovalGasOffset;\n _notifyOperatorInactivityGasOffset = notifyOperatorInactivityGasOffset;\n _notifySeedTimeoutGasOffset = notifySeedTimeoutGasOffset;\n _notifyDkgTimeoutNegativeGasOffset = notifyDkgTimeoutNegativeGasOffset;\n\n emit GasParametersUpdated(\n dkgResultSubmissionGas,\n dkgResultApprovalGasOffset,\n notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n\n /// @notice Requests a new wallet creation.\n /// @dev Can be called only by the owner of wallets.\n /// It locks the DKG and request a new relay entry. It expects\n /// that the DKG process will be started once a new relay entry\n /// gets generated.\n function requestNewWallet() external onlyWalletOwner {\n dkg.lockState();\n\n randomBeacon.requestRelayEntry(this);\n }\n\n /// @notice Closes an existing wallet. Reverts if wallet with the given ID\n /// does not exist or if it has already been closed.\n /// @param walletID ID of the wallet.\n /// @dev Only a Wallet Owner can call this function.\n function closeWallet(bytes32 walletID) external onlyWalletOwner {\n wallets.deleteWallet(walletID);\n emit WalletClosed(walletID);\n }\n\n /// @notice A callback that is executed once a new relay entry gets\n /// generated. It starts the DKG process.\n /// @dev Can be called only by the random beacon contract.\n /// @param relayEntry Relay entry.\n function __beaconCallback(uint256 relayEntry, uint256) external {\n require(\n msg.sender == address(randomBeacon),\n \"Caller is not the Random Beacon\"\n );\n\n dkg.start(relayEntry);\n }\n\n /// @notice Submits result of DKG protocol.\n /// The DKG result consists of result submitting member index,\n /// calculated group public key, bytes array of misbehaved members,\n /// concatenation of signatures from group members, indices of members\n /// corresponding to each signature and the list of group members.\n /// The result is registered optimistically and waits for an approval.\n /// The result can be challenged when it is believed to be incorrect.\n /// The challenge verifies the registered result i.a. it checks if members\n /// list corresponds to the expected set of members determined\n /// by the sortition pool.\n /// @dev The message to be signed by each member is keccak256 hash of the\n /// chain ID, calculated group public key, misbehaved members indices\n /// and DKG start block. The calculated hash should be prefixed with\n /// `\\x19Ethereum signed message:\\n` before signing, so the message to\n /// sign is:\n /// `\\x19Ethereum signed message:\\n${keccak256(chainID,groupPubKey,misbehavedIndices,startBlock)}`\n /// @param dkgResult DKG result.\n function submitDkgResult(DKG.Result calldata dkgResult) external {\n wallets.validatePublicKey(dkgResult.groupPubKey);\n dkg.submitResult(dkgResult);\n }\n\n /// @notice Approves DKG result. Can be called when the challenge period for\n /// the submitted result is finished. Considers the submitted result\n /// as valid, bans misbehaved group members from the sortition pool\n /// rewards, and completes the group creation by activating the\n /// candidate group. For the first `resultSubmissionTimeout` blocks\n /// after the end of the challenge period can be called only by the\n /// DKG result submitter. After that time, can be called by anyone.\n /// A new wallet based on the DKG result details.\n /// @param dkgResult Result to approve. Must match the submitted result\n /// stored during `submitDkgResult`.\n function approveDkgResult(DKG.Result calldata dkgResult) external {\n uint256 gasStart = gasleft();\n uint32[] memory misbehavedMembers = dkg.approveResult(dkgResult);\n\n (bytes32 walletID, bytes32 publicKeyX, bytes32 publicKeyY) = wallets\n .addWallet(dkgResult.membersHash, dkgResult.groupPubKey);\n\n emit WalletCreated(walletID, keccak256(abi.encode(dkgResult)));\n\n if (misbehavedMembers.length > 0) {\n sortitionPool.setRewardIneligibility(\n misbehavedMembers,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n }\n\n walletOwner.__ecdsaWalletCreatedCallback(\n walletID,\n publicKeyX,\n publicKeyY\n );\n\n dkg.complete();\n\n // Refund msg.sender's ETH for DKG result submission and result approval\n reimbursementPool.refund(\n _dkgResultSubmissionGas +\n (gasStart - gasleft()) +\n _dkgResultApprovalGasOffset,\n msg.sender\n );\n }\n\n /// @notice Notifies about seed for DKG delivery timeout. It is expected\n /// that a seed is delivered by the Random Beacon as a relay entry in a\n /// callback function.\n function notifySeedTimeout() external {\n uint256 gasStart = gasleft();\n\n dkg.notifySeedTimeout();\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifySeedTimeoutGasOffset,\n msg.sender\n );\n }\n\n /// @notice Notifies about DKG timeout.\n function notifyDkgTimeout() external {\n uint256 gasStart = gasleft();\n\n dkg.notifyDkgTimeout();\n\n // Note that the offset is subtracted as it is expected that the cleanup\n // performed on DKG timeout notification removes data from the storage\n // which is recovering gas for the transaction.\n reimbursementPool.refund(\n (gasStart - gasleft()) - _notifyDkgTimeoutNegativeGasOffset,\n msg.sender\n );\n }\n\n /// @notice Challenges DKG result. If the submitted result is proved to be\n /// invalid it reverts the DKG back to the result submission phase.\n /// @param dkgResult Result to challenge. Must match the submitted result\n /// stored during `submitDkgResult`.\n /// @dev Due to EIP-150 1/64 of the gas is not forwarded to the call, and\n /// will be kept to execute the remaining operations in the function\n /// after the call inside the try-catch. To eliminate a class of\n /// attacks related to the gas limit manipulation, this function\n /// requires an extra amount of gas to be left at the end of the\n /// execution.\n function challengeDkgResult(DKG.Result calldata dkgResult) external {\n (\n bytes32 maliciousDkgResultHash,\n uint32 maliciousDkgResultSubmitterId\n ) = dkg.challengeResult(dkgResult);\n\n address maliciousDkgResultSubmitterAddress = sortitionPool\n .getIDOperator(maliciousDkgResultSubmitterId);\n\n address[] memory operatorWrapper = new address[](1);\n operatorWrapper[0] = operatorToStakingProvider(\n maliciousDkgResultSubmitterAddress\n );\n\n try\n staking.seize(\n _maliciousDkgResultSlashingAmount,\n _maliciousDkgResultNotificationRewardMultiplier,\n msg.sender,\n operatorWrapper\n )\n {\n // slither-disable-next-line reentrancy-events\n emit DkgMaliciousResultSlashed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n } catch {\n // Should never happen but we want to ensure a non-critical path\n // failure from an external contract does not stop the challenge\n // to complete.\n emit DkgMaliciousResultSlashingFailed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n }\n\n // Due to EIP-150, 1/64 of the gas is not forwarded to the call, and\n // will be kept to execute the remaining operations in the function\n // after the call inside the try-catch.\n //\n // To ensure there is no way for the caller to manipulate gas limit in\n // such a way that the call inside try-catch fails with out-of-gas and\n // the rest of the function is executed with the remaining 1/64 of gas,\n // we require an extra gas amount to be left at the end of the call to\n // `challengeDkgResult`.\n dkg.requireChallengeExtraGas();\n }\n\n /// @notice Notifies about operators who are inactive. Using this function,\n /// a majority of the wallet signing group can decide about\n /// punishing specific group members who constantly fail doing their\n /// job. If the provided claim is proved to be valid and signed by\n /// sufficient number of group members, operators of members deemed\n /// as inactive are banned from sortition pool rewards for the\n /// duration specified by `sortitionPoolRewardsBanDuration` parameter.\n /// The function allows to signal about single operators being\n /// inactive as well as to signal wallet-wide heartbeat failures\n /// that are propagated to the wallet owner who should begin the\n /// procedure of moving responsibilities to another wallet given\n /// that the wallet who failed the heartbeat may soon be not able to\n /// function and provide new signatures.\n /// The sender of the claim must be one of the claim signers. This\n /// function can be called only for registered wallets\n /// @param claim Operator inactivity claim.\n /// @param nonce Current inactivity claim nonce for the given wallet signing\n /// group. Must be the same as the stored one.\n /// @param groupMembers Identifiers of the wallet signing group members.\n function notifyOperatorInactivity(\n Inactivity.Claim calldata claim,\n uint256 nonce,\n uint32[] calldata groupMembers\n ) external {\n uint256 gasStart = gasleft();\n\n bytes32 walletID = claim.walletID;\n\n require(nonce == inactivityClaimNonce[walletID], \"Invalid nonce\");\n\n (bytes32 pubKeyX, bytes32 pubKeyY) = wallets\n .getWalletPublicKeyCoordinates(walletID);\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(groupMembers)),\n \"Invalid group members\"\n );\n\n uint32[] memory ineligibleOperators = Inactivity.verifyClaim(\n sortitionPool,\n claim,\n bytes.concat(pubKeyX, pubKeyY),\n nonce,\n groupMembers\n );\n\n inactivityClaimNonce[walletID]++;\n\n emit InactivityClaimed(walletID, nonce, msg.sender);\n\n sortitionPool.setRewardIneligibility(\n ineligibleOperators,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n\n if (claim.heartbeatFailed) {\n walletOwner.__ecdsaWalletHeartbeatFailedCallback(\n walletID,\n pubKeyX,\n pubKeyY\n );\n }\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifyOperatorInactivityGasOffset,\n msg.sender\n );\n }\n\n /// @notice Allows the wallet owner to add all signing group members of the\n /// wallet with the given ID to the slashing queue of the staking .\n /// contract. The notifier will receive reward per each group member\n /// from the staking contract notifiers treasury. The reward is\n /// scaled by the `rewardMultiplier` provided as a parameter.\n /// @param amount Amount of tokens to seize from each signing group member.\n /// @param rewardMultiplier Fraction of the staking contract notifiers\n /// reward the notifier should receive; should be between [0, 100].\n /// @param notifier Address of the misbehavior notifier.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - `rewardMultiplier` must be between [0, 100].\n /// - This function does revert if staking contract call reverts.\n /// The calling code needs to handle the potential revert.\n function seize(\n uint96 amount,\n uint256 rewardMultiplier,\n address notifier,\n bytes32 walletID,\n uint32[] calldata walletMembersIDs\n ) external onlyWalletOwner {\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n address[] memory groupMembersAddresses = sortitionPool.getIDOperators(\n walletMembersIDs\n );\n address[] memory stakingProvidersAddresses = new address[](\n walletMembersIDs.length\n );\n for (uint256 i = 0; i < groupMembersAddresses.length; i++) {\n stakingProvidersAddresses[i] = operatorToStakingProvider(\n groupMembersAddresses[i]\n );\n }\n\n staking.seize(\n amount,\n rewardMultiplier,\n notifier,\n stakingProvidersAddresses\n );\n }\n\n /// @notice Checks if DKG result is valid for the current DKG.\n /// @param result DKG result.\n /// @return True if the result is valid. If the result is invalid it returns\n /// false and an error message.\n function isDkgResultValid(DKG.Result calldata result)\n external\n view\n returns (bool, string memory)\n {\n return dkg.isResultValid(result);\n }\n\n /// @notice Check current wallet creation state.\n function getWalletCreationState() external view returns (DKG.State) {\n return dkg.currentState();\n }\n\n /// @notice Checks whether the given operator is a member of the given\n /// wallet signing group.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @param operator Address of the checked operator.\n /// @param walletMemberIndex Position of the operator in the wallet signing\n /// group members list.\n /// @return True - if the operator is a member of the given wallet signing\n /// group. False - otherwise.\n /// @dev Requirements:\n /// - The `operator` parameter must be an actual sortition pool operator.\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]\n function isWalletMember(\n bytes32 walletID,\n uint32[] calldata walletMembersIDs,\n address operator,\n uint256 walletMemberIndex\n ) external view returns (bool) {\n uint32 operatorID = sortitionPool.getOperatorID(operator);\n\n require(operatorID != 0, \"Not a sortition pool operator\");\n\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n require(\n 1 <= walletMemberIndex &&\n walletMemberIndex <= walletMembersIDs.length,\n \"Wallet member index is out of range\"\n );\n\n return walletMembersIDs[walletMemberIndex - 1] == operatorID;\n }\n\n /// @notice Checks if awaiting seed timed out.\n /// @return True if awaiting seed timed out, false otherwise.\n function hasSeedTimedOut() external view returns (bool) {\n return dkg.hasSeedTimedOut();\n }\n\n /// @notice Checks if DKG timed out. The DKG timeout period includes time required\n /// for off-chain protocol execution and time for the result publication\n /// for all group members. After this time result cannot be submitted\n /// and DKG can be notified about the timeout.\n /// @return True if DKG timed out, false otherwise.\n function hasDkgTimedOut() external view returns (bool) {\n return dkg.hasDkgTimedOut();\n }\n\n function getWallet(bytes32 walletID)\n external\n view\n returns (Wallets.Wallet memory)\n {\n return wallets.registry[walletID];\n }\n\n /// @notice Gets public key of a wallet with a given wallet ID.\n /// The public key is returned in an uncompressed format as a 64-byte\n /// concatenation of X and Y coordinates.\n /// @param walletID ID of the wallet.\n /// @return Uncompressed public key of the wallet.\n function getWalletPublicKey(bytes32 walletID)\n external\n view\n returns (bytes memory)\n {\n return wallets.getWalletPublicKey(walletID);\n }\n\n /// @notice Checks if a wallet with the given ID is registered.\n /// @param walletID Wallet's ID.\n /// @return True if wallet is registered, false otherwise.\n function isWalletRegistered(bytes32 walletID) external view returns (bool) {\n return wallets.isWalletRegistered(walletID);\n }\n\n /// @notice The minimum authorization amount required so that operator can\n /// participate in ECDSA Wallet operations.\n function minimumAuthorization() external view returns (uint96) {\n return authorization.parameters.minimumAuthorization;\n }\n\n /// @notice Returns the current value of the staking provider's eligible\n /// stake. Eligible stake is defined as the currently authorized\n /// stake minus the pending authorization decrease. Eligible stake\n /// is what is used for operator's weight in the sortition pool.\n /// If the authorized stake minus the pending authorization decrease\n /// is below the minimum authorization, eligible stake is 0.\n function eligibleStake(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.eligibleStake(staking, stakingProvider);\n }\n\n /// @notice Returns the amount of rewards available for withdrawal for the\n /// given staking provider. Reverts if staking provider has not\n /// registered the operator address.\n function availableRewards(address stakingProvider)\n external\n view\n returns (uint96)\n {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n return sortitionPool.getAvailableRewards(operator);\n }\n\n /// @notice Returns the amount of stake that is pending authorization\n /// decrease for the given staking provider. If no authorization\n /// decrease has been requested, returns zero.\n function pendingAuthorizationDecrease(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.pendingAuthorizationDecrease(stakingProvider);\n }\n\n /// @notice Returns the remaining time in seconds that needs to pass before\n /// the requested authorization decrease can be approved.\n /// If the sortition pool state was not updated yet by the operator\n /// after requesting the authorization decrease, returns\n /// `type(uint64).max`.\n function remainingAuthorizationDecreaseDelay(address stakingProvider)\n external\n view\n returns (uint64)\n {\n return\n authorization.remainingAuthorizationDecreaseDelay(stakingProvider);\n }\n\n /// @notice Returns operator registered for the given staking provider.\n function stakingProviderToOperator(address stakingProvider)\n public\n view\n returns (address)\n {\n return authorization.stakingProviderToOperator[stakingProvider];\n }\n\n /// @notice Returns staking provider of the given operator.\n function operatorToStakingProvider(address operator)\n public\n view\n returns (address)\n {\n return authorization.operatorToStakingProvider[operator];\n }\n\n /// @notice Checks if the operator's authorized stake is in sync with\n /// operator's weight in the sortition pool.\n /// If the operator is not in the sortition pool and their\n /// authorized stake is non-zero, function returns false.\n function isOperatorUpToDate(address operator) external view returns (bool) {\n return\n authorization.isOperatorUpToDate(staking, sortitionPool, operator);\n }\n\n /// @notice Returns true if the given operator is in the sortition pool.\n /// Otherwise, returns false.\n function isOperatorInPool(address operator) external view returns (bool) {\n return sortitionPool.isOperatorInPool(operator);\n }\n\n /// @notice Selects a new group of operators. Can only be called when DKG\n /// is in progress and the pool is locked.\n /// At least one operator has to be registered in the pool,\n /// otherwise the function fails reverting the transaction.\n /// @return IDs of selected group members.\n function selectGroup() external view returns (uint32[] memory) {\n return sortitionPool.selectGroup(DKG.groupSize, bytes32(dkg.seed));\n }\n\n /// @notice Retrieves dkg parameters that were set in DKG library.\n function dkgParameters() external view returns (DKG.Parameters memory) {\n return dkg.parameters;\n }\n\n /// @notice Returns authorization-related parameters.\n /// @dev The minimum authorization is also returned by `minimumAuthorization()`\n /// function, as a requirement of `IApplication` interface.\n /// @return minimumAuthorization The minimum authorization amount required\n /// so that operator can participate in the random beacon. This\n /// amount is required to execute slashing for providing a malicious\n /// DKG result or when a relay entry times out.\n /// @return authorizationDecreaseDelay Delay in seconds that needs to pass\n /// between the time authorization decrease is requested and the\n /// time that request gets approved. Protects against free-riders\n /// earning rewards and not being active in the network.\n /// @return authorizationDecreaseChangePeriod Authorization decrease change\n /// period in seconds. It is the time, before authorization decrease\n /// delay end, during which the pending authorization decrease\n /// request can be overwritten.\n /// If set to 0, pending authorization decrease request can not be\n /// overwritten until the entire `authorizationDecreaseDelay` ends.\n /// If set to value equal `authorizationDecreaseDelay`, request can\n /// always be overwritten.\n function authorizationParameters()\n external\n view\n returns (\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n )\n {\n return (\n authorization.parameters.minimumAuthorization,\n authorization.parameters.authorizationDecreaseDelay,\n authorization.parameters.authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Retrieves reward-related parameters.\n /// @return maliciousDkgResultNotificationRewardMultiplier Percentage of the\n /// staking contract malicious behavior notification reward which\n /// will be transferred to the notifier reporting about a malicious\n /// DKG result. Notifiers are rewarded from a notifiers treasury\n /// pool. For example, if notification reward is 1000 and the value\n /// of the multiplier is 5, the notifier will receive:\n /// 5% of 1000 = 50 per each operator affected.\n /// @return sortitionPoolRewardsBanDuration Duration of the sortition pool\n /// rewards ban imposed on operators who missed their turn for DKG\n /// result submission or who failed a heartbeat.\n function rewardParameters()\n external\n view\n returns (\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n )\n {\n return (\n _maliciousDkgResultNotificationRewardMultiplier,\n _sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Retrieves slashing-related parameters.\n /// @return maliciousDkgResultSlashingAmount Slashing amount for submitting\n /// a malicious DKG result. Every DKG result submitted can be\n /// challenged for the time of `dkg.resultChallengePeriodLength`.\n /// If the DKG result submitted is challenged and proven to be\n /// malicious, the operator who submitted the malicious result is\n /// slashed for `_maliciousDkgResultSlashingAmount`.\n function slashingParameters()\n external\n view\n returns (uint96 maliciousDkgResultSlashingAmount)\n {\n return _maliciousDkgResultSlashingAmount;\n }\n\n /// @notice Retrieves gas-related parameters.\n /// @return dkgResultSubmissionGas Calculated max gas cost for submitting\n /// a DKG result. This will be refunded as part of the DKG approval\n /// process. It is in the submitter's interest to not skip his\n /// priority turn on the approval, otherwise the refund of the DKG\n /// submission will be refunded to another group member that will\n /// call the DKG approve function.\n /// @return dkgResultApprovalGasOffset Gas that is meant to balance the DKG\n /// result approval's overall cost. It can be updated by the\n /// governance based on the current market conditions.\n /// @return notifyOperatorInactivityGasOffset Gas that is meant to balance\n /// the notification of an operator inactivity. It can be updated by\n /// the governance based on the current market conditions.\n /// @return notifySeedTimeoutGasOffset Gas that is meant to balance the\n /// notification of a seed for DKG delivery timeout. It can be updated\n /// by the governance based on the current market conditions.\n /// @return notifyDkgTimeoutNegativeGasOffset Gas that is meant to balance\n /// the notification of a DKG protocol execution timeout. It can be\n /// updated by the governance based on the current market conditions.\n function gasParameters()\n external\n view\n returns (\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n )\n {\n return (\n _dkgResultSubmissionGas,\n _dkgResultApprovalGasOffset,\n _notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n}\n"
|
|
42
42
|
},
|
|
43
43
|
"@keep-network/random-beacon/contracts/api/IRandomBeacon.sol": {
|
|
44
44
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nimport \"./IRandomBeaconConsumer.sol\";\n\n/// @title Random Beacon interface\ninterface IRandomBeacon {\n /// @notice Creates a request to generate a new relay entry. Requires a\n /// request fee denominated in T token.\n /// @param callbackContract Beacon consumer callback contract.\n function requestRelayEntry(IRandomBeaconConsumer callbackContract) external;\n}\n"
|
|
@@ -94,9 +94,6 @@
|
|
|
94
94
|
"@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol": {
|
|
95
95
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract PausableUpgradeable is Initializable, ContextUpgradeable {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n function __Pausable_init() internal onlyInitializing {\n __Pausable_init_unchained();\n }\n\n function __Pausable_init_unchained() internal onlyInitializing {\n _paused = false;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n require(!paused(), \"Pausable: paused\");\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n require(paused(), \"Pausable: not paused\");\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n"
|
|
96
96
|
},
|
|
97
|
-
"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol": {
|
|
98
|
-
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\nimport \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuardUpgradeable is Initializable {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n function __ReentrancyGuard_init() internal onlyInitializing {\n __ReentrancyGuard_init_unchained();\n }\n\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n"
|
|
99
|
-
},
|
|
100
97
|
"@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol": {
|
|
101
98
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20Upgradeable.sol\";\nimport \"./extensions/IERC20MetadataUpgradeable.sol\";\nimport \"../../utils/ContextUpgradeable.sol\";\nimport \"../../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {\n __ERC20_init_unchained(name_, symbol_);\n }\n\n function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[45] private __gap;\n}\n"
|
|
102
99
|
},
|
|
@@ -295,9 +292,6 @@
|
|
|
295
292
|
"contracts/bridge/VendingMachineV3.sol": {
|
|
296
293
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport \"../token/TBTC.sol\";\n\n/// @title VendingMachineV3\n/// @notice VendingMachineV3 is used to exchange tBTC v1 to tBTC v2 in a 1:1\n/// ratio after the tBTC v1 bridge sunsetting is completed. Since\n/// tBTC v1 bridge is no longer working, tBTC v1 tokens can not be used\n/// to perform BTC redemptions. This contract allows tBTC v1 owners to\n/// upgrade to tBTC v2 without any deadline. This way, tBTC v1 tokens\n/// left on the market are always backed by Bitcoin. The governance will\n/// deposit tBTC v2 into the contract in the amount equal to tBTC v1\n/// supply. The governance is allowed to withdraw tBTC v2 only if tBTC\n/// v2 left in this contract is enough to cover the upgrade of all tBTC\n/// v1 left on the market. This contract is owned by the governance.\ncontract VendingMachineV3 is Ownable {\n using SafeERC20 for IERC20;\n using SafeERC20 for TBTC;\n\n IERC20 public immutable tbtcV1;\n TBTC public immutable tbtcV2;\n\n event Exchanged(address indexed to, uint256 amount);\n event Deposited(address from, uint256 amount);\n event TbtcV2Withdrawn(address to, uint256 amount);\n event FundsRecovered(address token, address to, uint256 amount);\n\n constructor(IERC20 _tbtcV1, TBTC _tbtcV2) {\n tbtcV1 = _tbtcV1;\n tbtcV2 = _tbtcV2;\n }\n\n /// @notice Exchange tBTC v1 for tBTC v2 in a 1:1 ratio.\n /// The caller needs to have at least `amount` of tBTC v1 balance\n /// approved for transfer to the `VendingMachineV3` before calling\n /// this function.\n /// @param amount The amount of tBTC v1 to exchange for tBTC v2.\n function exchange(uint256 amount) external {\n _exchange(msg.sender, amount);\n }\n\n /// @notice Exchange tBTC v1 for tBTC v2 in a 1:1 ratio.\n /// The caller needs to have at least `amount` of tBTC v1 balance\n /// approved for transfer to the `VendingMachineV3` before calling\n /// this function.\n /// @dev This function is a shortcut for `approve` + `exchange`. Only tBTC\n /// v1 caller is allowed and only tBTC v1 is allowed as a token to\n /// transfer.\n /// @param from tBTC v1 token holder exchanging tBTC v1 to tBTC v2.\n /// @param amount The amount of tBTC v1 to exchange for tBTC v2.\n /// @param token tBTC v1 token address.\n function receiveApproval(\n address from,\n uint256 amount,\n address token,\n bytes calldata\n ) external {\n require(token == address(tbtcV1), \"Token is not tBTC v1\");\n require(msg.sender == address(tbtcV1), \"Only tBTC v1 caller allowed\");\n _exchange(from, amount);\n }\n\n /// @notice Allows to deposit tBTC v2 tokens to the contract.\n /// `VendingMachineV3` can not mint tBTC v2 tokens so tBTC v2 needs\n /// to be deposited into the contract so that tBTC v1 to tBTC v2\n /// exchange can happen.\n /// The caller needs to have at least `amount` of tBTC v2 balance\n /// approved for transfer to the `VendingMachineV3` before calling\n /// this function.\n /// @dev This function is for the redeemer and tBTC v1 operators. This is\n /// NOT a function for tBTC v1 token holders.\n /// @param amount The amount of tBTC v2 to deposit into the contract.\n function depositTbtcV2(uint256 amount) external {\n emit Deposited(msg.sender, amount);\n tbtcV2.safeTransferFrom(msg.sender, address(this), amount);\n }\n\n /// @notice Allows the governance to withdraw tBTC v2 deposited into this\n /// contract. The governance is allowed to withdraw tBTC v2\n /// only if tBTC v2 left in this contract is enough to cover the\n /// upgrade of all tBTC v1 left on the market.\n /// @param recipient The address which should receive withdrawn tokens.\n /// @param amount The amount to withdraw.\n function withdrawTbtcV2(address recipient, uint256 amount)\n external\n onlyOwner\n {\n require(\n tbtcV1.totalSupply() <= tbtcV2.balanceOf(address(this)) - amount,\n \"tBTC v1 must not be left unbacked\"\n );\n\n emit TbtcV2Withdrawn(recipient, amount);\n tbtcV2.safeTransfer(recipient, amount);\n }\n\n /// @notice Allows the governance to recover ERC20 sent to this contract\n /// by mistake or tBTC v1 locked in the contract to exchange to\n /// tBTC v2. No tBTC v2 can be withdrawn using this function.\n /// @param token The address of a token to recover.\n /// @param recipient The address which should receive recovered tokens.\n /// @param amount The amount to recover.\n function recoverFunds(\n IERC20 token,\n address recipient,\n uint256 amount\n ) external onlyOwner {\n require(\n address(token) != address(tbtcV2),\n \"tBTC v2 tokens can not be recovered, use withdrawTbtcV2 instead\"\n );\n\n emit FundsRecovered(address(token), recipient, amount);\n token.safeTransfer(recipient, amount);\n }\n\n function _exchange(address tokenOwner, uint256 amount) internal {\n require(\n tbtcV2.balanceOf(address(this)) >= amount,\n \"Not enough tBTC v2 available in the Vending Machine\"\n );\n\n emit Exchanged(tokenOwner, amount);\n tbtcV1.safeTransferFrom(tokenOwner, address(this), amount);\n\n tbtcV2.safeTransfer(tokenOwner, amount);\n }\n}\n"
|
|
297
294
|
},
|
|
298
|
-
"contracts/bridge/WalletCoordinator.sol": {
|
|
299
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\nimport {BytesLib} from \"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\n\nimport \"./BitcoinTx.sol\";\nimport \"./Bridge.sol\";\nimport \"./Deposit.sol\";\nimport \"./Wallets.sol\";\n\n/// @title Wallet coordinator.\n/// @notice The wallet coordinator contract aims to facilitate the coordination\n/// of the off-chain wallet members during complex multi-chain wallet\n/// operations like deposit sweeping, redemptions, or moving funds.\n/// Such processes involve various moving parts and many steps that each\n/// individual wallet member must do. Given the distributed nature of\n/// the off-chain wallet software, full off-chain implementation is\n/// challenging and prone to errors, especially byzantine faults.\n/// This contract provides a single and trusted on-chain coordination\n/// point thus taking the riskiest part out of the off-chain software.\n/// The off-chain wallet members can focus on the core tasks and do not\n/// bother about electing a trusted coordinator or aligning internal\n/// states using complex consensus algorithms.\ncontract WalletCoordinator is OwnableUpgradeable, Reimbursable {\n using BTCUtils for bytes;\n using BytesLib for bytes;\n\n /// @notice Represents wallet action:\n enum WalletAction {\n /// @dev The wallet does not perform any action.\n Idle,\n /// @dev The wallet is executing heartbeat.\n Heartbeat,\n /// @dev The wallet is handling a deposit sweep action.\n DepositSweep,\n /// @dev The wallet is handling a redemption action.\n Redemption,\n /// @dev The wallet is handling a moving funds action.\n MovingFunds,\n /// @dev The wallet is handling a moved funds sweep action.\n MovedFundsSweep\n }\n\n /// @notice Holds information about a wallet time lock.\n struct WalletLock {\n /// @notice A UNIX timestamp defining the moment until which the wallet\n /// is locked and cannot receive new proposals. The value of 0\n /// means the wallet is not locked and can receive a proposal\n /// at any time.\n uint32 expiresAt;\n /// @notice The wallet action being the cause of the lock.\n WalletAction cause;\n }\n\n /// @notice Helper structure representing a deposit sweep proposal.\n struct DepositSweepProposal {\n // 20-byte public key hash of the target wallet.\n bytes20 walletPubKeyHash;\n // Deposits that should be part of the sweep.\n DepositKey[] depositsKeys;\n // Proposed BTC fee for the entire transaction.\n uint256 sweepTxFee;\n // Array containing the reveal blocks of each deposit. This information\n // strongly facilitates the off-chain processing. Using those blocks,\n // wallet operators can quickly fetch corresponding Bridge.DepositRevealed\n // events carrying deposit data necessary to perform proposal validation.\n // This field is not explicitly validated within the validateDepositSweepProposal\n // function because if something is wrong here the off-chain wallet\n // operators will fail anyway as they won't be able to gather deposit\n // data necessary to perform the on-chain validation using the\n // validateDepositSweepProposal function.\n uint256[] depositsRevealBlocks;\n }\n\n /// @notice Helper structure representing a plain-text deposit key.\n /// Each deposit can be identified by their 32-byte funding\n /// transaction hash (Bitcoin internal byte order) an the funding\n /// output index (0-based).\n /// @dev Do not confuse this structure with the deposit key used within the\n /// Bridge contract to store deposits. Here we have the plain-text\n /// components of the key while the Bridge uses a uint representation of\n /// keccak256(fundingTxHash | fundingOutputIndex) for gas efficiency.\n struct DepositKey {\n bytes32 fundingTxHash;\n uint32 fundingOutputIndex;\n }\n\n /// @notice Helper structure holding deposit extra data required during\n /// deposit sweep proposal validation. Basically, this structure\n /// is a combination of BitcoinTx.Info and relevant parts of\n /// Deposit.DepositRevealInfo.\n /// @dev These data can be pulled from respective `DepositRevealed` events\n /// emitted by the `Bridge.revealDeposit` function. The `fundingTx`\n /// field must be taken directly from the Bitcoin chain, using the\n /// `DepositRevealed.fundingTxHash` as transaction identifier.\n struct DepositExtraInfo {\n BitcoinTx.Info fundingTx;\n bytes8 blindingFactor;\n bytes20 walletPubKeyHash;\n bytes20 refundPubKeyHash;\n bytes4 refundLocktime;\n }\n\n /// @notice Mapping that holds addresses allowed to submit proposals and\n /// request heartbeats.\n mapping(address => bool) public isCoordinator;\n\n /// @notice Mapping that holds wallet time locks. The key is a 20-byte\n /// wallet public key hash.\n mapping(bytes20 => WalletLock) public walletLock;\n\n /// @notice Handle to the Bridge contract.\n Bridge public bridge;\n\n /// @notice Determines the wallet heartbeat request validity time. In other\n /// words, this is the worst-case time for a wallet heartbeat\n /// during which the wallet is busy and canot take other actions.\n /// This is also the duration of the time lock applied to the wallet\n /// once a new heartbeat request is submitted.\n ///\n /// For example, if a deposit sweep proposal was submitted at\n /// 2 pm and heartbeatRequestValidity is 1 hour, the next request or\n /// proposal (of any type) can be submitted after 3 pm.\n uint32 public heartbeatRequestValidity;\n\n /// @notice Gas that is meant to balance the heartbeat request overall cost.\n /// Can be updated by the owner based on the current conditions.\n uint32 public heartbeatRequestGasOffset;\n\n /// @notice Determines the deposit sweep proposal validity time. In other\n /// words, this is the worst-case time for a deposit sweep during\n /// which the wallet is busy and cannot take another actions. This\n /// is also the duration of the time lock applied to the wallet\n /// once a new deposit sweep proposal is submitted.\n ///\n /// For example, if a deposit sweep proposal was submitted at\n /// 2 pm and depositSweepProposalValidity is 4 hours, the next\n /// proposal (of any type) can be submitted after 6 pm.\n uint32 public depositSweepProposalValidity;\n\n /// @notice The minimum time that must elapse since the deposit reveal\n /// before a deposit becomes eligible for a deposit sweep.\n ///\n /// For example, if a deposit was revealed at 9 am and depositMinAge\n /// is 2 hours, the deposit is eligible for sweep after 11 am.\n ///\n /// @dev Forcing deposit minimum age ensures block finality for Ethereum.\n /// In the happy path case, i.e. where the deposit is revealed immediately\n /// after being broadcast on the Bitcoin network, the minimum age\n /// check also ensures block finality for Bitcoin.\n uint32 public depositMinAge;\n\n /// @notice Each deposit can be technically swept until it reaches its\n /// refund timestamp after which it can be taken back by the depositor.\n /// However, allowing the wallet to sweep deposits that are close\n /// to their refund timestamp may cause a race between the wallet\n /// and the depositor. In result, the wallet may sign an invalid\n /// sweep transaction that aims to sweep an already refunded deposit.\n /// Such tx signature may be used to create an undefeatable fraud\n /// challenge against the wallet. In order to mitigate that problem,\n /// this parameter determines a safety margin that puts the latest\n /// moment a deposit can be swept far before the point after which\n /// the deposit becomes refundable.\n ///\n /// For example, if a deposit becomes refundable after 8 pm and\n /// depositRefundSafetyMargin is 6 hours, the deposit is valid for\n /// for a sweep only before 2 pm.\n uint32 public depositRefundSafetyMargin;\n\n /// @notice The maximum count of deposits that can be swept within a\n /// single sweep.\n uint16 public depositSweepMaxSize;\n\n /// @notice Gas that is meant to balance the deposit sweep proposal\n /// submission overall cost. Can be updated by the owner based on\n /// the current conditions.\n uint32 public depositSweepProposalSubmissionGasOffset;\n\n event CoordinatorAdded(address indexed coordinator);\n\n event CoordinatorRemoved(address indexed coordinator);\n\n event WalletManuallyUnlocked(bytes20 indexed walletPubKeyHash);\n\n event HeartbeatRequestParametersUpdated(\n uint32 heartbeatRequestValidity,\n uint32 heartbeatRequestGasOffset\n );\n\n event HeartbeatRequestSubmitted(\n bytes20 walletPubKeyHash,\n bytes message,\n address indexed coordinator\n );\n\n event DepositSweepProposalParametersUpdated(\n uint32 depositSweepProposalValidity,\n uint32 depositMinAge,\n uint32 depositRefundSafetyMargin,\n uint16 depositSweepMaxSize,\n uint32 depositSweepProposalSubmissionGasOffset\n );\n\n event DepositSweepProposalSubmitted(\n DepositSweepProposal proposal,\n address indexed coordinator\n );\n\n modifier onlyCoordinator() {\n require(isCoordinator[msg.sender], \"Caller is not a coordinator\");\n _;\n }\n\n modifier onlyAfterWalletLock(bytes20 walletPubKeyHash) {\n require(\n /* solhint-disable-next-line not-rely-on-time */\n block.timestamp > walletLock[walletPubKeyHash].expiresAt,\n \"Wallet locked\"\n );\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(owner() == msg.sender, \"Caller is not the owner\");\n _;\n }\n\n function initialize(Bridge _bridge) external initializer {\n __Ownable_init();\n\n bridge = _bridge;\n // Pre-fetch addresses to save gas later.\n (, , , reimbursementPool) = _bridge.contractReferences();\n\n heartbeatRequestValidity = 1 hours;\n heartbeatRequestGasOffset = 5_000;\n\n depositSweepProposalValidity = 4 hours;\n depositMinAge = 2 hours;\n depositRefundSafetyMargin = 24 hours;\n depositSweepMaxSize = 5;\n depositSweepProposalSubmissionGasOffset = 5_000;\n }\n\n /// @notice Adds the given address to the set of coordinator addresses.\n /// @param coordinator Address of the new coordinator.\n /// @dev Requirements:\n /// - The caller must be the owner,\n /// - The `coordinator` must not be an existing coordinator.\n function addCoordinator(address coordinator) external onlyOwner {\n require(\n !isCoordinator[coordinator],\n \"This address is already a coordinator\"\n );\n isCoordinator[coordinator] = true;\n emit CoordinatorAdded(coordinator);\n }\n\n /// @notice Removes the given address from the set of coordinator addresses.\n /// @param coordinator Address of the existing coordinator.\n /// @dev Requirements:\n /// - The caller must be the owner,\n /// - The `coordinator` must be an existing coordinator.\n function removeCoordinator(address coordinator) external onlyOwner {\n require(\n isCoordinator[coordinator],\n \"This address is not a coordinator\"\n );\n delete isCoordinator[coordinator];\n emit CoordinatorRemoved(coordinator);\n }\n\n /// @notice Allows to unlock the given wallet before their time lock expires.\n /// This function should be used in exceptional cases where\n /// something went wrong and there is a need to unlock the wallet\n /// without waiting.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet\n /// @dev Requirements:\n /// - The caller must be the owner.\n function unlockWallet(bytes20 walletPubKeyHash) external onlyOwner {\n // Just in case, allow the owner to unlock the wallet earlier.\n walletLock[walletPubKeyHash] = WalletLock(0, WalletAction.Idle);\n emit WalletManuallyUnlocked(walletPubKeyHash);\n }\n\n /// @notice Updates parameters related to heartbeat request.\n /// @param _heartbeatRequestValidity The new value of `heartbeatRequestValidity`.\n /// @param _heartbeatRequestGasOffset The new value of `heartbeatRequestGasOffset`.\n /// @dev Requirements:\n /// - The caller must be the owner.\n function updateHeartbeatRequestParameters(\n uint32 _heartbeatRequestValidity,\n uint32 _heartbeatRequestGasOffset\n ) external onlyOwner {\n heartbeatRequestValidity = _heartbeatRequestValidity;\n heartbeatRequestGasOffset = _heartbeatRequestGasOffset;\n emit HeartbeatRequestParametersUpdated(\n _heartbeatRequestValidity,\n _heartbeatRequestGasOffset\n );\n }\n\n /// @notice Updates parameters related to deposit sweep proposal.\n /// @param _depositSweepProposalValidity The new value of `depositSweepProposalValidity`.\n /// @param _depositMinAge The new value of `depositMinAge`.\n /// @param _depositRefundSafetyMargin The new value of `depositRefundSafetyMargin`.\n /// @param _depositSweepMaxSize The new value of `depositSweepMaxSize`.\n /// @dev Requirements:\n /// - The caller must be the owner.\n function updateDepositSweepProposalParameters(\n uint32 _depositSweepProposalValidity,\n uint32 _depositMinAge,\n uint32 _depositRefundSafetyMargin,\n uint16 _depositSweepMaxSize,\n uint32 _depositSweepProposalSubmissionGasOffset\n ) external onlyOwner {\n depositSweepProposalValidity = _depositSweepProposalValidity;\n depositMinAge = _depositMinAge;\n depositRefundSafetyMargin = _depositRefundSafetyMargin;\n depositSweepMaxSize = _depositSweepMaxSize;\n depositSweepProposalSubmissionGasOffset = _depositSweepProposalSubmissionGasOffset;\n\n emit DepositSweepProposalParametersUpdated(\n _depositSweepProposalValidity,\n _depositMinAge,\n _depositRefundSafetyMargin,\n _depositSweepMaxSize,\n _depositSweepProposalSubmissionGasOffset\n );\n }\n\n /// @notice Submits a heartbeat request from the wallet. Locks the wallet\n /// for a specific time, equal to the request validity period.\n /// This function validates the proposed heartbeat messge to see\n /// if it matches the heartbeat format expected by the Bridge.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet that is\n /// supposed to execute the heartbeat.\n /// @param message The proposed heartbeat message for the wallet to sign.\n /// @dev Requirements:\n /// - The caller is a coordinator,\n /// - The wallet is not time-locked,\n /// - The message to sign is a valid heartbeat message.\n function requestHeartbeat(bytes20 walletPubKeyHash, bytes calldata message)\n public\n onlyCoordinator\n onlyAfterWalletLock(walletPubKeyHash)\n {\n require(\n Heartbeat.isValidHeartbeatMessage(message),\n \"Not a valid heartbeat message\"\n );\n\n walletLock[walletPubKeyHash] = WalletLock(\n /* solhint-disable-next-line not-rely-on-time */\n uint32(block.timestamp) + heartbeatRequestValidity,\n WalletAction.Heartbeat\n );\n\n emit HeartbeatRequestSubmitted(walletPubKeyHash, message, msg.sender);\n }\n\n /// @notice Wraps `requestHeartbeat` call and reimburses the caller's\n /// transaction cost.\n /// @dev See `requestHeartbeat` function documentation.\n function requestHeartbeatWithReimbursement(\n bytes20 walletPubKeyHash,\n bytes calldata message\n ) external {\n uint256 gasStart = gasleft();\n\n requestHeartbeat(walletPubKeyHash, message);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + heartbeatRequestGasOffset,\n msg.sender\n );\n }\n\n /// @notice Submits a deposit sweep proposal. Locks the target wallet\n /// for a specific time, equal to the proposal validity period.\n /// This function does not store the proposal in the state but\n /// just emits an event that serves as a guiding light for wallet\n /// off-chain members. Wallet members are supposed to validate\n /// the proposal on their own, before taking any action.\n /// @param proposal The deposit sweep proposal\n /// @dev Requirements:\n /// - The caller is a coordinator,\n /// - The wallet is not time-locked.\n function submitDepositSweepProposal(DepositSweepProposal calldata proposal)\n public\n onlyCoordinator\n onlyAfterWalletLock(proposal.walletPubKeyHash)\n {\n walletLock[proposal.walletPubKeyHash] = WalletLock(\n /* solhint-disable-next-line not-rely-on-time */\n uint32(block.timestamp) + depositSweepProposalValidity,\n WalletAction.DepositSweep\n );\n\n emit DepositSweepProposalSubmitted(proposal, msg.sender);\n }\n\n /// @notice Wraps `submitDepositSweepProposal` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `submitDepositSweepProposal` function documentation.\n function submitDepositSweepProposalWithReimbursement(\n DepositSweepProposal calldata proposal\n ) external {\n uint256 gasStart = gasleft();\n\n submitDepositSweepProposal(proposal);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + depositSweepProposalSubmissionGasOffset,\n msg.sender\n );\n }\n\n /// @notice View function encapsulating the main rules of a valid deposit\n /// sweep proposal. This function is meant to facilitate the off-chain\n /// validation of the incoming proposals. Thanks to it, most\n /// of the work can be done using a single readonly contract call.\n /// Worth noting, the validation done here is not exhaustive as some\n /// conditions may not be verifiable within the on-chain function or\n /// checking them may be easier on the off-chain side. For example,\n /// this function does not check the SPV proofs and confirmations of\n /// the deposit funding transactions as this would require an\n /// integration with the difficulty relay that greatly increases\n /// complexity. Instead of that, each off-chain wallet member is\n /// supposed to do that check on their own.\n /// @param proposal The sweeping proposal to validate.\n /// @param depositsExtraInfo Deposits extra data required to perform the validation.\n /// @return True if the proposal is valid. Reverts otherwise.\n /// @dev Requirements:\n /// - The target wallet must be in the Live state,\n /// - The number of deposits included in the sweep must be in\n /// the range [1, `depositSweepMaxSize`],\n /// - The length of `depositsExtraInfo` array must be equal to the\n /// length of `proposal.depositsKeys`, i.e. each deposit must\n /// have exactly one set of corresponding extra data,\n /// - The proposed sweep tx fee must be grater than zero,\n /// - The proposed maximum per-deposit sweep tx fee must be lesser than\n /// or equal the maximum fee allowed by the Bridge (`Bridge.depositTxMaxFee`),\n /// - Each deposit must be revealed to the Bridge,\n /// - Each deposit must be old enough, i.e. at least `depositMinAge`\n /// elapsed since their reveal time,\n /// - Each deposit must not be swept yet,\n /// - Each deposit must have valid extra data (see `validateDepositExtraInfo`),\n /// - Each deposit must have the refund safety margin preserved,\n /// - Each deposit must be controlled by the same wallet,\n /// - Each deposit must target the same vault.\n ///\n /// The following off-chain validation must be performed as a bare minimum:\n /// - Inputs used for the sweep transaction have enough Bitcoin confirmations,\n /// - Deposits revealed to the Bridge have enough Ethereum confirmations.\n function validateDepositSweepProposal(\n DepositSweepProposal calldata proposal,\n DepositExtraInfo[] calldata depositsExtraInfo\n ) external view returns (bool) {\n require(\n bridge.wallets(proposal.walletPubKeyHash).state ==\n Wallets.WalletState.Live,\n \"Wallet is not in Live state\"\n );\n\n require(proposal.depositsKeys.length > 0, \"Sweep below the min size\");\n\n require(\n proposal.depositsKeys.length <= depositSweepMaxSize,\n \"Sweep exceeds the max size\"\n );\n\n require(\n proposal.depositsKeys.length == depositsExtraInfo.length,\n \"Each deposit key must have matching extra data\"\n );\n\n validateSweepTxFee(proposal.sweepTxFee, proposal.depositsKeys.length);\n\n address proposalVault = address(0);\n\n for (uint256 i = 0; i < proposal.depositsKeys.length; i++) {\n DepositKey memory depositKey = proposal.depositsKeys[i];\n DepositExtraInfo memory depositExtraInfo = depositsExtraInfo[i];\n\n // slither-disable-next-line calls-loop\n Deposit.DepositRequest memory depositRequest = bridge.deposits(\n uint256(\n keccak256(\n abi.encodePacked(\n depositKey.fundingTxHash,\n depositKey.fundingOutputIndex\n )\n )\n )\n );\n\n require(depositRequest.revealedAt != 0, \"Deposit not revealed\");\n\n require(\n /* solhint-disable-next-line not-rely-on-time */\n block.timestamp > depositRequest.revealedAt + depositMinAge,\n \"Deposit min age not achieved yet\"\n );\n\n require(depositRequest.sweptAt == 0, \"Deposit already swept\");\n\n validateDepositExtraInfo(\n depositKey,\n depositRequest.depositor,\n depositExtraInfo\n );\n\n uint32 depositRefundableTimestamp = BTCUtils.reverseUint32(\n uint32(depositExtraInfo.refundLocktime)\n );\n require(\n /* solhint-disable-next-line not-rely-on-time */\n block.timestamp <\n depositRefundableTimestamp - depositRefundSafetyMargin,\n \"Deposit refund safety margin is not preserved\"\n );\n\n require(\n depositExtraInfo.walletPubKeyHash == proposal.walletPubKeyHash,\n \"Deposit controlled by different wallet\"\n );\n\n // Make sure all deposits target the same vault by using the\n // vault of the first deposit as a reference.\n if (i == 0) {\n proposalVault = depositRequest.vault;\n }\n require(\n depositRequest.vault == proposalVault,\n \"Deposit targets different vault\"\n );\n }\n\n return true;\n }\n\n /// @notice Validates the sweep tx fee by checking if the part of the fee\n /// incurred by each deposit does not exceed the maximum value\n /// allowed by the Bridge. This function is heavily based on\n /// `DepositSweep.depositSweepTxFeeDistribution` function.\n /// @param sweepTxFee The sweep transaction fee.\n /// @param depositsCount Count of the deposits swept by the sweep transaction.\n /// @dev Requirements:\n /// - The sweep tx fee must be grater than zero,\n /// - The maximum per-deposit sweep tx fee must be lesser than or equal\n /// the maximum fee allowed by the Bridge (`Bridge.depositTxMaxFee`).\n function validateSweepTxFee(uint256 sweepTxFee, uint256 depositsCount)\n internal\n view\n {\n require(sweepTxFee > 0, \"Proposed transaction fee cannot be zero\");\n\n // Compute the indivisible remainder that remains after dividing the\n // sweep transaction fee over all deposits evenly.\n uint256 depositTxFeeRemainder = sweepTxFee % depositsCount;\n // Compute the transaction fee per deposit by dividing the sweep\n // transaction fee (reduced by the remainder) by the number of deposits.\n uint256 depositTxFee = (sweepTxFee - depositTxFeeRemainder) /\n depositsCount;\n\n (, , uint64 depositTxMaxFee, ) = bridge.depositParameters();\n\n // The transaction fee is incurred by each deposit evenly except for the last\n // deposit that has the indivisible remainder additionally incurred.\n // See `DepositSweep.submitDepositSweepProof`.\n // We must make sure the highest value of the deposit transaction fee does\n // not exceed the maximum value limited by the governable parameter.\n require(\n depositTxFee + depositTxFeeRemainder <= depositTxMaxFee,\n \"Proposed transaction fee is too high\"\n );\n }\n\n /// @notice Validates the extra data for the given deposit. This function\n /// is heavily based on `Deposit.revealDeposit` function.\n /// @param depositKey Key of the given deposit.\n /// @param depositor Depositor that revealed the deposit.\n /// @param depositExtraInfo Extra data being subject of the validation.\n /// @dev Requirements:\n /// - The transaction hash computed using `depositExtraInfo.fundingTx`\n /// must match the `depositKey.fundingTxHash`. This requirement\n /// ensures the funding transaction data provided in the extra\n /// data container actually represent the funding transaction of\n /// the given deposit.\n /// - The P2(W)SH script inferred from `depositExtraInfo` is actually\n /// used to lock funds by the `depositKey.fundingOutputIndex` output\n /// of the `depositExtraInfo.fundingTx` transaction. This requirement\n /// ensures the reveal data provided in the extra data container\n /// actually matches the given deposit.\n function validateDepositExtraInfo(\n DepositKey memory depositKey,\n address depositor,\n DepositExtraInfo memory depositExtraInfo\n ) internal view {\n bytes32 depositExtraFundingTxHash = abi\n .encodePacked(\n depositExtraInfo.fundingTx.version,\n depositExtraInfo.fundingTx.inputVector,\n depositExtraInfo.fundingTx.outputVector,\n depositExtraInfo.fundingTx.locktime\n )\n .hash256View();\n\n // Make sure the funding tx provided as part of deposit extra data\n // actually matches the deposit referred by the given deposit key.\n if (depositKey.fundingTxHash != depositExtraFundingTxHash) {\n revert(\"Extra info funding tx hash does not match\");\n }\n\n bytes memory expectedScript = abi.encodePacked(\n hex\"14\", // Byte length of depositor Ethereum address.\n depositor,\n hex\"75\", // OP_DROP\n hex\"08\", // Byte length of blinding factor value.\n depositExtraInfo.blindingFactor,\n hex\"75\", // OP_DROP\n hex\"76\", // OP_DUP\n hex\"a9\", // OP_HASH160\n hex\"14\", // Byte length of a compressed Bitcoin public key hash.\n depositExtraInfo.walletPubKeyHash,\n hex\"87\", // OP_EQUAL\n hex\"63\", // OP_IF\n hex\"ac\", // OP_CHECKSIG\n hex\"67\", // OP_ELSE\n hex\"76\", // OP_DUP\n hex\"a9\", // OP_HASH160\n hex\"14\", // Byte length of a compressed Bitcoin public key hash.\n depositExtraInfo.refundPubKeyHash,\n hex\"88\", // OP_EQUALVERIFY\n hex\"04\", // Byte length of refund locktime value.\n depositExtraInfo.refundLocktime,\n hex\"b1\", // OP_CHECKLOCKTIMEVERIFY\n hex\"75\", // OP_DROP\n hex\"ac\", // OP_CHECKSIG\n hex\"68\" // OP_ENDIF\n );\n\n bytes memory fundingOutput = depositExtraInfo\n .fundingTx\n .outputVector\n .extractOutputAtIndex(depositKey.fundingOutputIndex);\n bytes memory fundingOutputHash = fundingOutput.extractHash();\n\n // Path that checks the deposit extra data validity in case the\n // referred deposit is a P2SH.\n if (\n // slither-disable-next-line calls-loop\n fundingOutputHash.length == 20 &&\n fundingOutputHash.slice20(0) == expectedScript.hash160View()\n ) {\n return;\n }\n\n // Path that checks the deposit extra data validity in case the\n // referred deposit is a P2WSH.\n if (\n fundingOutputHash.length == 32 &&\n fundingOutputHash.toBytes32() == sha256(expectedScript)\n ) {\n return;\n }\n\n revert(\"Extra info funding output script does not match\");\n }\n}\n"
|
|
300
|
-
},
|
|
301
295
|
"contracts/bridge/Wallets.sol": {
|
|
302
296
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\nimport {EcdsaDkg} from \"@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol\";\nimport {Math} from \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport \"./BitcoinTx.sol\";\nimport \"./EcdsaLib.sol\";\nimport \"./BridgeState.sol\";\n\n/// @title Wallet library\n/// @notice Library responsible for handling integration between Bridge\n/// contract and ECDSA wallets.\nlibrary Wallets {\n using BTCUtils for bytes;\n\n /// @notice Represents wallet state:\n enum WalletState {\n /// @dev The wallet is unknown to the Bridge.\n Unknown,\n /// @dev The wallet can sweep deposits and accept redemption requests.\n Live,\n /// @dev The wallet was deemed unhealthy and is expected to move their\n /// outstanding funds to another wallet. The wallet can still\n /// fulfill their pending redemption requests although new\n /// redemption requests and new deposit reveals are not accepted.\n MovingFunds,\n /// @dev The wallet moved or redeemed all their funds and is in the\n /// closing period where it is still a subject of fraud challenges\n /// and must defend against them. This state is needed to protect\n /// against deposit frauds on deposits revealed but not swept.\n /// The closing period must be greater that the deposit refund\n /// time plus some time margin.\n Closing,\n /// @dev The wallet finalized the closing period successfully and\n /// can no longer perform any action in the Bridge.\n Closed,\n /// @dev The wallet committed a fraud that was reported, did not move\n /// funds to another wallet before a timeout, or did not sweep\n /// funds moved to if from another wallet before a timeout. The\n /// wallet is blocked and can not perform any actions in the Bridge.\n /// Off-chain coordination with the wallet operators is needed to\n /// recover funds.\n Terminated\n }\n\n /// @notice Holds information about a wallet.\n struct Wallet {\n // Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry.\n bytes32 ecdsaWalletID;\n // Latest wallet's main UTXO hash computed as\n // keccak256(txHash | txOutputIndex | txOutputValue). The `tx` prefix\n // refers to the transaction which created that main UTXO. The `txHash`\n // is `bytes32` (ordered as in Bitcoin internally), `txOutputIndex`\n // an `uint32`, and `txOutputValue` an `uint64` value.\n bytes32 mainUtxoHash;\n // The total redeemable value of pending redemption requests targeting\n // that wallet.\n uint64 pendingRedemptionsValue;\n // UNIX timestamp the wallet was created at.\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\n uint32 createdAt;\n // UNIX timestamp indicating the moment the wallet was requested to\n // move their funds.\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\n uint32 movingFundsRequestedAt;\n // UNIX timestamp indicating the moment the wallet's closing period\n // started.\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\n uint32 closingStartedAt;\n // Total count of pending moved funds sweep requests targeting this wallet.\n uint32 pendingMovedFundsSweepRequestsCount;\n // Current state of the wallet.\n WalletState state;\n // Moving funds target wallet commitment submitted by the wallet. It\n // is built by applying the keccak256 hash over the list of 20-byte\n // public key hashes of the target wallets.\n bytes32 movingFundsTargetWalletsCommitmentHash;\n // This struct doesn't contain `__gap` property as the structure is stored\n // in a mapping, mappings store values in different slots and they are\n // not contiguous with other values.\n }\n\n event NewWalletRequested();\n\n event NewWalletRegistered(\n bytes32 indexed ecdsaWalletID,\n bytes20 indexed walletPubKeyHash\n );\n\n event WalletMovingFunds(\n bytes32 indexed ecdsaWalletID,\n bytes20 indexed walletPubKeyHash\n );\n\n event WalletClosing(\n bytes32 indexed ecdsaWalletID,\n bytes20 indexed walletPubKeyHash\n );\n\n event WalletClosed(\n bytes32 indexed ecdsaWalletID,\n bytes20 indexed walletPubKeyHash\n );\n\n event WalletTerminated(\n bytes32 indexed ecdsaWalletID,\n bytes20 indexed walletPubKeyHash\n );\n\n /// @notice Requests creation of a new wallet. This function just\n /// forms a request and the creation process is performed\n /// asynchronously. Outcome of that process should be delivered\n /// using `registerNewWallet` function.\n /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as\n /// currently known on the Ethereum chain.\n /// @dev Requirements:\n /// - `activeWalletMainUtxo` components must point to the recent main\n /// UTXO of the given active wallet, as currently known on the\n /// Ethereum chain. If there is no active wallet at the moment, or\n /// the active wallet has no main UTXO, this parameter can be\n /// empty as it is ignored,\n /// - Wallet creation must not be in progress,\n /// - If the active wallet is set, one of the following\n /// conditions must be true:\n /// - The active wallet BTC balance is above the minimum threshold\n /// and the active wallet is old enough, i.e. the creation period\n /// was elapsed since its creation time,\n /// - The active wallet BTC balance is above the maximum threshold.\n function requestNewWallet(\n BridgeState.Storage storage self,\n BitcoinTx.UTXO calldata activeWalletMainUtxo\n ) external {\n require(\n self.ecdsaWalletRegistry.getWalletCreationState() ==\n EcdsaDkg.State.IDLE,\n \"Wallet creation already in progress\"\n );\n\n bytes20 activeWalletPubKeyHash = self.activeWalletPubKeyHash;\n\n // If the active wallet is set, fetch this wallet's details from\n // storage to perform conditions check. The `registerNewWallet`\n // function guarantees an active wallet is always one of the\n // registered ones.\n if (activeWalletPubKeyHash != bytes20(0)) {\n uint64 activeWalletBtcBalance = getWalletBtcBalance(\n self,\n activeWalletPubKeyHash,\n activeWalletMainUtxo\n );\n uint32 activeWalletCreatedAt = self\n .registeredWallets[activeWalletPubKeyHash]\n .createdAt;\n /* solhint-disable-next-line not-rely-on-time */\n bool activeWalletOldEnough = block.timestamp >=\n activeWalletCreatedAt + self.walletCreationPeriod;\n\n require(\n (activeWalletOldEnough &&\n activeWalletBtcBalance >=\n self.walletCreationMinBtcBalance) ||\n activeWalletBtcBalance >= self.walletCreationMaxBtcBalance,\n \"Wallet creation conditions are not met\"\n );\n }\n\n emit NewWalletRequested();\n\n self.ecdsaWalletRegistry.requestNewWallet();\n }\n\n /// @notice Registers a new wallet. This function should be called\n /// after the wallet creation process initiated using\n /// `requestNewWallet` completes and brings the outcomes.\n /// @param ecdsaWalletID Wallet's unique identifier.\n /// @param publicKeyX Wallet's public key's X coordinate.\n /// @param publicKeyY Wallet's public key's Y coordinate.\n /// @dev Requirements:\n /// - The only caller authorized to call this function is `registry`,\n /// - Given wallet data must not belong to an already registered wallet.\n function registerNewWallet(\n BridgeState.Storage storage self,\n bytes32 ecdsaWalletID,\n bytes32 publicKeyX,\n bytes32 publicKeyY\n ) external {\n require(\n msg.sender == address(self.ecdsaWalletRegistry),\n \"Caller is not the ECDSA Wallet Registry\"\n );\n\n // Compress wallet's public key and calculate Bitcoin's hash160 of it.\n bytes20 walletPubKeyHash = bytes20(\n EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()\n );\n\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n require(\n wallet.state == WalletState.Unknown,\n \"ECDSA wallet has been already registered\"\n );\n wallet.ecdsaWalletID = ecdsaWalletID;\n wallet.state = WalletState.Live;\n /* solhint-disable-next-line not-rely-on-time */\n wallet.createdAt = uint32(block.timestamp);\n\n // Set the freshly created wallet as the new active wallet.\n self.activeWalletPubKeyHash = walletPubKeyHash;\n\n self.liveWalletsCount++;\n\n emit NewWalletRegistered(ecdsaWalletID, walletPubKeyHash);\n }\n\n /// @notice Handles a notification about a wallet redemption timeout.\n /// Triggers the wallet moving funds process only if the wallet is\n /// still in the Live state. That means multiple action timeouts can\n /// be reported for the same wallet but only the first report\n /// requests the wallet to move their funds. Executes slashing if\n /// the wallet is in Live or MovingFunds state. Allows to notify\n /// redemption timeout also for a Terminated wallet in case the\n /// redemption was requested before the wallet got terminated.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The wallet must be in the `Live`, `MovingFunds`,\n /// or `Terminated` state.\n function notifyWalletRedemptionTimeout(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n uint32[] calldata walletMembersIDs\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n WalletState walletState = wallet.state;\n\n require(\n walletState == WalletState.Live ||\n walletState == WalletState.MovingFunds ||\n walletState == WalletState.Terminated,\n \"Wallet must be in Live or MovingFunds or Terminated state\"\n );\n\n if (\n walletState == Wallets.WalletState.Live ||\n walletState == Wallets.WalletState.MovingFunds\n ) {\n // Slash the wallet operators and reward the notifier\n self.ecdsaWalletRegistry.seize(\n self.redemptionTimeoutSlashingAmount,\n self.redemptionTimeoutNotifierRewardMultiplier,\n msg.sender,\n wallet.ecdsaWalletID,\n walletMembersIDs\n );\n }\n\n if (walletState == WalletState.Live) {\n moveFunds(self, walletPubKeyHash);\n }\n }\n\n /// @notice Handles a notification about a wallet heartbeat failure and\n /// triggers the wallet moving funds process.\n /// @param publicKeyX Wallet's public key's X coordinate.\n /// @param publicKeyY Wallet's public key's Y coordinate.\n /// @dev Requirements:\n /// - The only caller authorized to call this function is `registry`,\n /// - Wallet must be in Live state.\n function notifyWalletHeartbeatFailed(\n BridgeState.Storage storage self,\n bytes32 publicKeyX,\n bytes32 publicKeyY\n ) external {\n require(\n msg.sender == address(self.ecdsaWalletRegistry),\n \"Caller is not the ECDSA Wallet Registry\"\n );\n\n // Compress wallet's public key and calculate Bitcoin's hash160 of it.\n bytes20 walletPubKeyHash = bytes20(\n EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()\n );\n\n require(\n self.registeredWallets[walletPubKeyHash].state == WalletState.Live,\n \"Wallet must be in Live state\"\n );\n\n moveFunds(self, walletPubKeyHash);\n }\n\n /// @notice Notifies that the wallet is either old enough or has too few\n /// satoshis left and qualifies to be closed.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\n /// known on the Ethereum chain.\n /// @dev Requirements:\n /// - Wallet must not be set as the current active wallet,\n /// - Wallet must exceed the wallet maximum age OR the wallet BTC\n /// balance must be lesser than the minimum threshold. If the latter\n /// case is true, the `walletMainUtxo` components must point to the\n /// recent main UTXO of the given wallet, as currently known on the\n /// Ethereum chain. If the wallet has no main UTXO, this parameter\n /// can be empty as it is ignored since the wallet balance is\n /// assumed to be zero,\n /// - Wallet must be in Live state.\n function notifyWalletCloseable(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata walletMainUtxo\n ) external {\n require(\n self.activeWalletPubKeyHash != walletPubKeyHash,\n \"Active wallet cannot be considered closeable\"\n );\n\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n require(\n wallet.state == WalletState.Live,\n \"Wallet must be in Live state\"\n );\n\n /* solhint-disable-next-line not-rely-on-time */\n bool walletOldEnough = block.timestamp >=\n wallet.createdAt + self.walletMaxAge;\n\n require(\n walletOldEnough ||\n getWalletBtcBalance(self, walletPubKeyHash, walletMainUtxo) <\n self.walletClosureMinBtcBalance,\n \"Wallet needs to be old enough or have too few satoshis\"\n );\n\n moveFunds(self, walletPubKeyHash);\n }\n\n /// @notice Notifies about the end of the closing period for the given wallet.\n /// Closes the wallet ultimately and notifies the ECDSA registry\n /// about this fact.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @dev Requirements:\n /// - The wallet must be in the Closing state,\n /// - The wallet closing period must have elapsed.\n function notifyWalletClosingPeriodElapsed(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n\n require(\n wallet.state == WalletState.Closing,\n \"Wallet must be in Closing state\"\n );\n\n require(\n /* solhint-disable-next-line not-rely-on-time */\n block.timestamp >\n wallet.closingStartedAt + self.walletClosingPeriod,\n \"Closing period has not elapsed yet\"\n );\n\n finalizeWalletClosing(self, walletPubKeyHash);\n }\n\n /// @notice Notifies that the wallet completed the moving funds process\n /// successfully. Checks if the funds were moved to the expected\n /// target wallets. Closes the source wallet if everything went\n /// good and reverts otherwise.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @param targetWalletsHash 32-byte keccak256 hash over the list of\n /// 20-byte public key hashes of the target wallets actually used\n /// within the moving funds transactions.\n /// @dev Requirements:\n /// - The caller must make sure the moving funds transaction actually\n /// happened on Bitcoin chain and fits the protocol requirements,\n /// - The source wallet must be in the MovingFunds state,\n /// - The target wallets commitment must be submitted by the source\n /// wallet,\n /// - The actual target wallets used in the moving funds transaction\n /// must be exactly the same as the target wallets commitment.\n function notifyWalletFundsMoved(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n bytes32 targetWalletsHash\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n // Check that the wallet is in the MovingFunds state but don't check\n // if the moving funds timeout is exceeded. That should give a\n // possibility to move funds in case when timeout was hit but was\n // not reported yet.\n require(\n wallet.state == WalletState.MovingFunds,\n \"Wallet must be in MovingFunds state\"\n );\n\n bytes32 targetWalletsCommitmentHash = wallet\n .movingFundsTargetWalletsCommitmentHash;\n\n require(\n targetWalletsCommitmentHash != bytes32(0),\n \"Target wallets commitment not submitted yet\"\n );\n\n // Make sure that the target wallets where funds were moved to are\n // exactly the same as the ones the source wallet committed to.\n require(\n targetWalletsCommitmentHash == targetWalletsHash,\n \"Target wallets don't correspond to the commitment\"\n );\n\n // If funds were moved, the wallet has no longer a main UTXO.\n delete wallet.mainUtxoHash;\n\n beginWalletClosing(self, walletPubKeyHash);\n }\n\n /// @notice Called when a MovingFunds wallet has a balance below the dust\n /// threshold. Begins the wallet closing.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @dev Requirements:\n /// - The wallet must be in the MovingFunds state.\n function notifyWalletMovingFundsBelowDust(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash\n ) internal {\n WalletState walletState = self\n .registeredWallets[walletPubKeyHash]\n .state;\n\n require(\n walletState == Wallets.WalletState.MovingFunds,\n \"Wallet must be in MovingFunds state\"\n );\n\n beginWalletClosing(self, walletPubKeyHash);\n }\n\n /// @notice Called when the timeout for MovingFunds for the wallet elapsed.\n /// Slashes wallet members and terminates the wallet.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The wallet must be in the MovingFunds state.\n function notifyWalletMovingFundsTimeout(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n uint32[] calldata walletMembersIDs\n ) internal {\n Wallets.Wallet storage wallet = self.registeredWallets[\n walletPubKeyHash\n ];\n\n require(\n wallet.state == Wallets.WalletState.MovingFunds,\n \"Wallet must be in MovingFunds state\"\n );\n\n self.ecdsaWalletRegistry.seize(\n self.movingFundsTimeoutSlashingAmount,\n self.movingFundsTimeoutNotifierRewardMultiplier,\n msg.sender,\n wallet.ecdsaWalletID,\n walletMembersIDs\n );\n\n terminateWallet(self, walletPubKeyHash);\n }\n\n /// @notice Called when a wallet which was asked to sweep funds moved from\n /// another wallet did not provide a sweeping proof before a timeout.\n /// Slashes and terminates the wallet who failed to provide a proof.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet which was\n /// supposed to sweep funds.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The wallet must be in the `Live`, `MovingFunds`,\n /// or `Terminated` state.\n function notifyWalletMovedFundsSweepTimeout(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n uint32[] calldata walletMembersIDs\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n WalletState walletState = wallet.state;\n\n require(\n walletState == WalletState.Live ||\n walletState == WalletState.MovingFunds ||\n walletState == WalletState.Terminated,\n \"Wallet must be in Live or MovingFunds or Terminated state\"\n );\n\n if (\n walletState == Wallets.WalletState.Live ||\n walletState == Wallets.WalletState.MovingFunds\n ) {\n self.ecdsaWalletRegistry.seize(\n self.movedFundsSweepTimeoutSlashingAmount,\n self.movedFundsSweepTimeoutNotifierRewardMultiplier,\n msg.sender,\n wallet.ecdsaWalletID,\n walletMembersIDs\n );\n\n terminateWallet(self, walletPubKeyHash);\n }\n }\n\n /// @notice Called when a wallet which was challenged for a fraud did not\n /// defeat the challenge before the timeout. Slashes and terminates\n /// the wallet who failed to defeat the challenge. If the wallet is\n /// already terminated, it does nothing.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet which was\n /// supposed to sweep funds.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @param challenger Address of the party which submitted the fraud\n /// challenge.\n /// @dev Requirements:\n /// - The wallet must be in the `Live`, `MovingFunds`, `Closing`\n /// or `Terminated` state.\n function notifyWalletFraudChallengeDefeatTimeout(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n uint32[] calldata walletMembersIDs,\n address challenger\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n WalletState walletState = wallet.state;\n\n if (\n walletState == Wallets.WalletState.Live ||\n walletState == Wallets.WalletState.MovingFunds ||\n walletState == Wallets.WalletState.Closing\n ) {\n self.ecdsaWalletRegistry.seize(\n self.fraudSlashingAmount,\n self.fraudNotifierRewardMultiplier,\n challenger,\n wallet.ecdsaWalletID,\n walletMembersIDs\n );\n\n terminateWallet(self, walletPubKeyHash);\n } else if (walletState == Wallets.WalletState.Terminated) {\n // This is a special case when the wallet was already terminated\n // due to a previous deliberate protocol violation. In that\n // case, this function should be still callable for other fraud\n // challenges timeouts in order to let the challenger unlock its\n // ETH deposit back. However, the wallet termination logic is\n // not called and the challenger is not rewarded.\n } else {\n revert(\n \"Wallet must be in Live or MovingFunds or Closing or Terminated state\"\n );\n }\n }\n\n /// @notice Requests a wallet to move their funds. If the wallet balance\n /// is zero, the wallet closing begins immediately. If the move\n /// funds request refers to the current active wallet, such a wallet\n /// is no longer considered active and the active wallet slot\n /// is unset allowing to trigger a new wallet creation immediately.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @dev Requirements:\n /// - The caller must make sure that the wallet is in the Live state.\n function moveFunds(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n\n if (wallet.mainUtxoHash == bytes32(0)) {\n // If the wallet has no main UTXO, that means its BTC balance\n // is zero and the wallet closing should begin immediately.\n beginWalletClosing(self, walletPubKeyHash);\n } else {\n // Otherwise, initialize the moving funds process.\n wallet.state = WalletState.MovingFunds;\n /* solhint-disable-next-line not-rely-on-time */\n wallet.movingFundsRequestedAt = uint32(block.timestamp);\n\n // slither-disable-next-line reentrancy-events\n emit WalletMovingFunds(wallet.ecdsaWalletID, walletPubKeyHash);\n }\n\n if (self.activeWalletPubKeyHash == walletPubKeyHash) {\n // If the move funds request refers to the current active wallet,\n // unset the active wallet and make the wallet creation process\n // possible in order to get a new healthy active wallet.\n delete self.activeWalletPubKeyHash;\n }\n\n self.liveWalletsCount--;\n }\n\n /// @notice Begins the closing period of the given wallet.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @dev Requirements:\n /// - The caller must make sure that the wallet is in the\n /// MovingFunds state.\n function beginWalletClosing(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n // Initialize the closing period.\n wallet.state = WalletState.Closing;\n /* solhint-disable-next-line not-rely-on-time */\n wallet.closingStartedAt = uint32(block.timestamp);\n\n // slither-disable-next-line reentrancy-events\n emit WalletClosing(wallet.ecdsaWalletID, walletPubKeyHash);\n }\n\n /// @notice Finalizes the closing period of the given wallet and notifies\n /// the ECDSA registry about this fact.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @dev Requirements:\n /// - The caller must make sure that the wallet is in the Closing state.\n function finalizeWalletClosing(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n\n wallet.state = WalletState.Closed;\n\n emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);\n\n self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);\n }\n\n /// @notice Terminates the given wallet and notifies the ECDSA registry\n /// about this fact. If the wallet termination refers to the current\n /// active wallet, such a wallet is no longer considered active and\n /// the active wallet slot is unset allowing to trigger a new wallet\n /// creation immediately.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @dev Requirements:\n /// - The caller must make sure that the wallet is in the\n /// Live or MovingFunds or Closing state.\n function terminateWallet(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash\n ) internal {\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\n\n if (wallet.state == WalletState.Live) {\n self.liveWalletsCount--;\n }\n\n wallet.state = WalletState.Terminated;\n\n // slither-disable-next-line reentrancy-events\n emit WalletTerminated(wallet.ecdsaWalletID, walletPubKeyHash);\n\n if (self.activeWalletPubKeyHash == walletPubKeyHash) {\n // If termination refers to the current active wallet,\n // unset the active wallet and make the wallet creation process\n // possible in order to get a new healthy active wallet.\n delete self.activeWalletPubKeyHash;\n }\n\n self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);\n }\n\n /// @notice Gets BTC balance for given the wallet.\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\n /// known on the Ethereum chain.\n /// @return walletBtcBalance Current BTC balance for the given wallet.\n /// @dev Requirements:\n /// - `walletMainUtxo` components must point to the recent main UTXO\n /// of the given wallet, as currently known on the Ethereum chain.\n /// If the wallet has no main UTXO, this parameter can be empty as it\n /// is ignored.\n function getWalletBtcBalance(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata walletMainUtxo\n ) internal view returns (uint64 walletBtcBalance) {\n bytes32 walletMainUtxoHash = self\n .registeredWallets[walletPubKeyHash]\n .mainUtxoHash;\n\n // If the wallet has a main UTXO hash set, cross-check it with the\n // provided plain-text parameter and get the transaction output value\n // as BTC balance. Otherwise, the BTC balance is just zero.\n if (walletMainUtxoHash != bytes32(0)) {\n require(\n keccak256(\n abi.encodePacked(\n walletMainUtxo.txHash,\n walletMainUtxo.txOutputIndex,\n walletMainUtxo.txOutputValue\n )\n ) == walletMainUtxoHash,\n \"Invalid wallet main UTXO data\"\n );\n\n walletBtcBalance = walletMainUtxo.txOutputValue;\n }\n\n return walletBtcBalance;\n }\n}\n"
|
|
303
297
|
},
|
|
@@ -316,18 +310,12 @@
|
|
|
316
310
|
"contracts/l2/L2TBTC.sol": {
|
|
317
311
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol\";\n\n/// @title L2TBTC\n/// @notice Canonical L2/sidechain token implementation. tBTC token is minted on\n/// L1 and locked there to be moved to L2/sidechain. By deploying\n/// a canonical token on each L2/sidechain, we can ensure the supply of\n/// tBTC remains sacrosanct, while enabling quick, interoperable\n/// cross-chain bridges and localizing ecosystem risk.\n///\n/// This contract is flexible enough to:\n/// - Delegate minting authority to a native bridge on the chain, if\n/// present.\n/// - Delegate minting authority to a short list of ecosystem bridges.\n/// - Have mints and burns paused by any one of n guardians, allowing\n/// avoidance of contagion in case of a chain- or bridge-specific\n/// incident.\n/// - Be governed and upgradeable.\n///\n/// The token is burnable by the token holder and supports EIP2612\n/// permits. Token holder can authorize a transfer of their token with\n/// a signature conforming EIP712 standard instead of an on-chain\n/// transaction from their address. Anyone can submit this signature on\n/// the user's behalf by calling the permit function, paying gas fees,\n/// and possibly performing other actions in the same transaction.\n/// The governance can recover ERC20 and ERC721 tokens sent mistakenly\n/// to L2TBTC token contract.\ncontract L2TBTC is\n ERC20Upgradeable,\n ERC20BurnableUpgradeable,\n ERC20PermitUpgradeable,\n OwnableUpgradeable,\n PausableUpgradeable\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Indicates if the given address is a minter. Only minters can\n /// mint the token.\n mapping(address => bool) public isMinter;\n\n /// @notice List of all minters.\n address[] public minters;\n\n /// @notice Indicates if the given address is a guardian. Only guardians can\n /// pause token mints and burns.\n mapping(address => bool) public isGuardian;\n\n /// @notice List of all guardians.\n address[] public guardians;\n\n event MinterAdded(address indexed minter);\n event MinterRemoved(address indexed minter);\n\n event GuardianAdded(address indexed guardian);\n event GuardianRemoved(address indexed guardian);\n\n modifier onlyMinter() {\n require(isMinter[msg.sender], \"Caller is not a minter\");\n _;\n }\n\n modifier onlyGuardian() {\n require(isGuardian[msg.sender], \"Caller is not a guardian\");\n _;\n }\n\n /// @notice Initializes the token contract.\n /// @param _name The name of the token.\n /// @param _symbol The symbol of the token, usually a shorter version of the\n /// name.\n function initialize(string memory _name, string memory _symbol)\n external\n initializer\n {\n // OpenZeppelin upgradeable contracts documentation says:\n //\n // \"Use with multiple inheritance requires special care. Initializer\n // functions are not linearized by the compiler like constructors.\n // Because of this, each __{ContractName}_init function embeds the\n // linearized calls to all parent initializers. As a consequence,\n // calling two of these init functions can potentially initialize the\n // same contract twice.\"\n //\n // Note that ERC20 extensions do not linearize calls to ERC20Upgradeable\n // initializer so we call all extension initializers individually. At\n // the same time, ERC20PermitUpgradeable does linearize the call to\n // EIP712Upgradeable so we are not using the unchained initializer\n // versions.\n __ERC20_init(_name, _symbol);\n __ERC20Burnable_init();\n __ERC20Permit_init(_name);\n __Ownable_init();\n __Pausable_init();\n }\n\n /// @notice Adds the address to the minters list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `minter` must not be a minter address already.\n /// @param minter The address to be added as a minter.\n function addMinter(address minter) external onlyOwner {\n require(!isMinter[minter], \"This address is already a minter\");\n isMinter[minter] = true;\n minters.push(minter);\n emit MinterAdded(minter);\n }\n\n /// @notice Removes the address from the minters list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `minter` must be a minter address.\n /// @param minter The address to be removed from the minters list.\n function removeMinter(address minter) external onlyOwner {\n require(isMinter[minter], \"This address is not a minter\");\n delete isMinter[minter];\n\n // We do not expect too many minters so a simple loop is safe.\n for (uint256 i = 0; i < minters.length; i++) {\n if (minters[i] == minter) {\n minters[i] = minters[minters.length - 1];\n // slither-disable-next-line costly-loop\n minters.pop();\n break;\n }\n }\n\n emit MinterRemoved(minter);\n }\n\n /// @notice Adds the address to the guardians list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `guardian` must not be a guardian address already.\n /// @param guardian The address to be added as a guardian.\n function addGuardian(address guardian) external onlyOwner {\n require(!isGuardian[guardian], \"This address is already a guardian\");\n isGuardian[guardian] = true;\n guardians.push(guardian);\n emit GuardianAdded(guardian);\n }\n\n /// @notice Removes the address from the guardians list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `guardian` must be a guardian address.\n /// @param guardian The address to be removed from the guardians list.\n function removeGuardian(address guardian) external onlyOwner {\n require(isGuardian[guardian], \"This address is not a guardian\");\n delete isGuardian[guardian];\n\n // We do not expect too many guardians so a simple loop is safe.\n for (uint256 i = 0; i < guardians.length; i++) {\n if (guardians[i] == guardian) {\n guardians[i] = guardians[guardians.length - 1];\n // slither-disable-next-line costly-loop\n guardians.pop();\n break;\n }\n }\n\n emit GuardianRemoved(guardian);\n }\n\n /// @notice Allows the governance of the token contract to recover any ERC20\n /// sent mistakenly to the token contract address.\n /// @param token The address of the token to be recovered.\n /// @param recipient The token recipient address that will receive recovered\n /// tokens.\n /// @param amount The amount to be recovered.\n function recoverERC20(\n IERC20Upgradeable token,\n address recipient,\n uint256 amount\n ) external onlyOwner {\n token.safeTransfer(recipient, amount);\n }\n\n /// @notice Allows the governance of the token contract to recover any\n /// ERC721 sent mistakenly to the token contract address.\n /// @param token The address of the token to be recovered.\n /// @param recipient The token recipient address that will receive the\n /// recovered token.\n /// @param tokenId The ID of the ERC721 token to be recovered.\n function recoverERC721(\n IERC721Upgradeable token,\n address recipient,\n uint256 tokenId,\n bytes calldata data\n ) external onlyOwner {\n token.safeTransferFrom(address(this), recipient, tokenId, data);\n }\n\n /// @notice Allows one of the guardians to pause mints and burns allowing\n /// avoidance of contagion in case of a chain- or bridge-specific\n /// incident.\n /// @dev Requirements:\n /// - The caller must be a guardian.\n /// - The contract must not be already paused.\n function pause() external onlyGuardian {\n _pause();\n }\n\n /// @notice Allows the governance to unpause mints and burns previously\n /// paused by one of the guardians.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - The contract must be paused.\n function unpause() external onlyOwner {\n _unpause();\n }\n\n /// @notice Allows one of the minters to mint `amount` tokens and assign\n /// them to `account`, increasing the total supply. Emits\n /// a `Transfer` event with `from` set to the zero address.\n /// @dev Requirements:\n /// - The caller must be a minter.\n /// - `account` must not be the zero address.\n /// @param account The address to receive tokens.\n /// @param amount The amount of token to be minted.\n function mint(address account, uint256 amount)\n external\n whenNotPaused\n onlyMinter\n {\n _mint(account, amount);\n }\n\n /// @notice Destroys `amount` tokens from the caller. Emits a `Transfer`\n /// event with `to` set to the zero address.\n /// @dev Requirements:\n /// - The caller must have at least `amount` tokens.\n /// @param amount The amount of token to be burned.\n function burn(uint256 amount) public override whenNotPaused {\n super.burn(amount);\n }\n\n /// @notice Destroys `amount` tokens from `account`, deducting from the\n /// caller's allowance. Emits a `Transfer` event with `to` set to\n /// the zero address.\n /// @dev Requirements:\n /// - The che caller must have allowance for `accounts`'s tokens of at\n /// least `amount`.\n /// - `account` must not be the zero address.\n /// - `account` must have at least `amount` tokens.\n /// @param account The address owning tokens to be burned.\n /// @param amount The amount of token to be burned.\n function burnFrom(address account, uint256 amount)\n public\n override\n whenNotPaused\n {\n super.burnFrom(account, amount);\n }\n\n /// @notice Allows to fetch a list of all minters.\n function getMinters() external view returns (address[] memory) {\n return minters;\n }\n\n /// @notice Allows to fetch a list of all guardians.\n function getGuardians() external view returns (address[] memory) {\n return guardians;\n }\n}\n"
|
|
318
312
|
},
|
|
319
|
-
"contracts/l2/L2WormholeGateway.sol": {
|
|
320
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\n\nimport \"./L2TBTC.sol\";\n\n/// @title IWormholeTokenBridge\n/// @notice Wormhole Token Bridge interface. Contains only selected functions\n/// used by L2WormholeGateway.\ninterface IWormholeTokenBridge {\n function completeTransferWithPayload(bytes memory encodedVm)\n external\n returns (bytes memory);\n\n function parseTransferWithPayload(bytes memory encoded)\n external\n pure\n returns (TransferWithPayload memory transfer);\n\n function transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable returns (uint64 sequence);\n\n function transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes memory payload\n ) external payable returns (uint64 sequence);\n\n struct TransferWithPayload {\n uint8 payloadID;\n uint256 amount;\n bytes32 tokenAddress;\n uint16 tokenChain;\n bytes32 to;\n uint16 toChain;\n bytes32 fromAddress;\n bytes payload;\n }\n}\n\n/// @title L2WormholeGateway\n/// @notice Selected cross-ecosystem bridges are given the minting authority for\n/// tBTC token on L2 and sidechains. This contract gives a minting\n/// authority to the Wormhole Bridge.\n///\n/// The process of bridging from L1 to L2 (or sidechain) looks as\n/// follows:\n/// 1. There is a tBTC holder on L1. The holder goes to the Wormhole\n/// Portal and selects the chain they want to bridge to.\n/// 2. The holder submits one transaction to L1 locking their tBTC\n/// tokens in the bridge’s smart contract. After the transaction is\n/// mined, they wait about 15 minutes for the Ethereum block\n/// finality.\n/// 3. The holder submits one transaction to L2 that is minting tokens.\n/// After that transaction is mined, they have their tBTC on L2.\n///\n/// The process of bridging from L2 (or sidechain) to L1 looks as\n/// follows:\n/// 1. There is a tBTC holder on L2. That holder goes to the Wormhole\n/// Portal and selects one of the L2 chains they want to bridge from.\n/// 2. The holder submits one transaction to L2 that is burning the\n/// token. After the transaction is mined, they wait about 15 minutes\n/// for the L2 block finality.\n/// 3. The holder submits one transaction to L1 unlocking their tBTC\n/// tokens from the bridge’s smart contract. After that transaction\n/// is mined, they have their tBTC on L1.\n///\n/// This smart contract is integrated with step 3 of L1->L2 bridging and\n/// step 1 of L2->L1 or L2->L2 bridging. When the user redeems token on\n/// L2, this contract receives the Wormhole tBTC representation and\n/// mints the canonical tBTC in an equal amount. When user sends their\n/// token from L1, this contract burns the canonical tBTC and sends\n/// Wormhole tBTC representation through the bridge in an equal amount.\n/// @dev This contract is supposed to be deployed behind a transparent\n/// upgradeable proxy.\ncontract L2WormholeGateway is\n Initializable,\n OwnableUpgradeable,\n ReentrancyGuardUpgradeable\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Reference to the Wormhole Token Bridge contract.\n IWormholeTokenBridge public bridge;\n\n /// @notice Wormhole tBTC token representation.\n IERC20Upgradeable public bridgeToken;\n\n /// @notice Canonical tBTC token.\n L2TBTC public tbtc;\n\n /// @notice Maps Wormhole chain ID to the Wormhole tBTC gateway address on\n /// that chain. For example, this chain's ID should be mapped to\n /// this contract's address. If there is no Wormhole tBTC gateway\n /// address on the given chain, there is no entry in this mapping.\n /// The mapping holds addresses in a Wormhole-specific format, where\n /// Ethereum address is left-padded with zeros.\n mapping(uint16 => bytes32) public gateways;\n\n /// @notice Minting limit for this gateway. Useful for early days of testing\n /// the system. The gateway can not mint more canonical tBTC than\n /// this limit.\n uint256 public mintingLimit;\n\n /// @notice The amount of tBTC minted by this contract. tBTC burned by this\n /// contract decreases this amount.\n uint256 public mintedAmount;\n\n event WormholeTbtcReceived(address receiver, uint256 amount);\n\n event WormholeTbtcSent(\n uint256 amount,\n uint16 recipientChain,\n bytes32 gateway,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n );\n\n event WormholeTbtcDeposited(address depositor, uint256 amount);\n\n event GatewayAddressUpdated(uint16 chainId, bytes32 gateway);\n\n event MintingLimitUpdated(uint256 mintingLimit);\n\n function initialize(\n IWormholeTokenBridge _bridge,\n IERC20Upgradeable _bridgeToken,\n L2TBTC _tbtc\n ) external initializer {\n __Ownable_init();\n __ReentrancyGuard_init();\n\n require(\n address(_bridge) != address(0),\n \"Wormhole bridge address must not be 0x0\"\n );\n require(\n address(_bridgeToken) != address(0),\n \"Bridge token address must not be 0x0\"\n );\n require(\n address(_tbtc) != address(0),\n \"L2TBTC token address must not be 0x0\"\n );\n\n bridge = _bridge;\n bridgeToken = _bridgeToken;\n tbtc = _tbtc;\n mintingLimit = type(uint256).max;\n }\n\n /// @notice This function is called when the user sends their token from L2.\n /// The contract burns the canonical tBTC from the user and sends\n /// wormhole tBTC representation over the bridge.\n /// Keep in mind that when multiple bridges receive a minting\n /// authority on the canonical tBTC, this function may not be able\n /// to send all amounts of tBTC through the Wormhole bridge. The\n /// capability of Wormhole Bridge to send tBTC from the chain is\n /// limited to the amount of tBTC bridged through Wormhole to that\n /// chain.\n /// @dev Requirements:\n /// - The sender must have at least `amount` of the canonical tBTC and\n /// it has to be approved for L2WormholeGateway.\n /// - The L2WormholeGateway must have at least `amount` of the wormhole\n /// tBTC.\n /// - The recipient must not be 0x0.\n /// - The amount to transfer must not be 0,\n /// - The amount to transfer must be >= 10^10 (1e18 precision).\n /// Depending if Wormhole tBTC gateway is registered on the target\n /// chain, this function uses transfer or transfer with payload over\n /// the Wormhole bridge.\n /// @param amount The amount of tBTC to be sent.\n /// @param recipientChain The Wormhole recipient chain ID.\n /// @param recipient The address of the recipient in the Wormhole format.\n /// @param arbiterFee The Wormhole arbiter fee. Ignored if sending\n /// tBTC to chain with Wormhole tBTC gateway.\n /// @param nonce The Wormhole nonce used to batch messages together.\n /// @return The Wormhole sequence number.\n function sendTbtc(\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable nonReentrant returns (uint64) {\n require(recipient != bytes32(0), \"0x0 recipient not allowed\");\n require(amount != 0, \"Amount must not be 0\");\n\n // Normalize the amount to bridge. The dust can not be bridged due to\n // the decimal shift in the Wormhole Bridge contract.\n amount = normalize(amount);\n\n // Check again after dropping the dust.\n require(amount != 0, \"Amount too low to bridge\");\n\n require(\n bridgeToken.balanceOf(address(this)) >= amount,\n \"Not enough wormhole tBTC in the gateway to bridge\"\n );\n\n bytes32 gateway = gateways[recipientChain];\n\n emit WormholeTbtcSent(\n amount,\n recipientChain,\n gateway,\n recipient,\n arbiterFee,\n nonce\n );\n\n mintedAmount -= amount;\n tbtc.burnFrom(msg.sender, amount);\n bridgeToken.safeApprove(address(bridge), amount);\n\n if (gateway == bytes32(0)) {\n // No Wormhole tBTC gateway on the target chain. The token minted\n // by Wormhole should be considered canonical.\n return\n bridge.transferTokens{value: msg.value}(\n address(bridgeToken),\n amount,\n recipientChain,\n recipient,\n arbiterFee,\n nonce\n );\n } else {\n // There is a Wormhole tBTC gateway on the target chain.\n // The gateway needs to mint canonical tBTC for the recipient\n // encoded in the payload.\n return\n bridge.transferTokensWithPayload{value: msg.value}(\n address(bridgeToken),\n amount,\n recipientChain,\n gateway,\n nonce,\n abi.encode(recipient)\n );\n }\n }\n\n /// @notice This function is called when the user redeems their token on L2.\n /// The contract receives Wormhole tBTC representation and mints the\n /// canonical tBTC for the user.\n /// If the tBTC minting limit has been reached by this contract,\n /// instead of minting tBTC the receiver address receives Wormhole\n /// tBTC representation.\n /// @dev Requirements:\n /// - The receiver of Wormhole tBTC should be the L2WormholeGateway\n /// contract.\n /// - The receiver of the canonical tBTC should be abi-encoded in the\n /// payload.\n /// - The receiver of the canonical tBTC must not be the zero address.\n /// The Wormhole Token Bridge contract has protection against redeeming\n /// the same VAA again. When a Token Bridge VAA is redeemed, its\n /// message body hash is stored in a map. This map is used to check\n /// whether the hash has already been set in this map. For this reason,\n /// this function does not have to be nonReentrant.\n /// @param encodedVm A byte array containing a Wormhole VAA signed by the\n /// guardians.\n function receiveTbtc(bytes calldata encodedVm) external {\n // ITokenBridge.completeTransferWithPayload completes a contract-controlled\n // transfer of an ERC20 token. Calling this function is not enough to\n // ensure L2WormholeGateway received Wormhole tBTC representation.\n // Instead of going too deep into the ITokenBridge implementation,\n // asserting who is the receiver of the token, and which token it is,\n // we check the balance before the ITokenBridge call and the balance\n // after ITokenBridge call. This way, we are sure this contract received\n // Wormhole tBTC token in the given amount. This is transparent to\n // all potential upgrades of ITokenBridge implementation and no other\n // validations are needed.\n uint256 balanceBefore = bridgeToken.balanceOf(address(this));\n bytes memory encoded = bridge.completeTransferWithPayload(encodedVm);\n uint256 balanceAfter = bridgeToken.balanceOf(address(this));\n\n uint256 amount = balanceAfter - balanceBefore;\n // Protect against the custody of irrelevant tokens.\n require(amount > 0, \"No tBTC transferred\");\n\n address receiver = fromWormholeAddress(\n bytes32(bridge.parseTransferWithPayload(encoded).payload)\n );\n require(receiver != address(0), \"0x0 receiver not allowed\");\n\n // We send wormhole tBTC OR mint canonical tBTC. We do not want to send\n // dust. Sending wormhole tBTC is an exceptional situation and we want\n // to keep it simple.\n if (mintedAmount + amount > mintingLimit) {\n bridgeToken.safeTransfer(receiver, amount);\n } else {\n // The function is non-reentrant given bridge.completeTransferWithPayload\n // call that does not allow to use the same VAA again.\n // slither-disable-next-line reentrancy-benign\n mintedAmount += amount;\n tbtc.mint(receiver, amount);\n }\n\n // The function is non-reentrant given bridge.completeTransferWithPayload\n // call that does not allow to use the same VAA again.\n // slither-disable-next-line reentrancy-events\n emit WormholeTbtcReceived(receiver, amount);\n }\n\n /// @notice Allows to deposit Wormhole tBTC token in exchange for canonical\n /// tBTC. Useful in a situation when user received wormhole tBTC\n /// instead of canonical tBTC. One example of such situation is\n /// when the minting limit was exceeded but the user minted anyway.\n /// @dev Requirements:\n /// - The sender must have at least `amount` of the Wormhole tBTC and\n /// it has to be approved for L2WormholeGateway.\n /// - The minting limit must allow for minting the given amount.\n /// @param amount The amount of Wormhole tBTC to deposit.\n function depositWormholeTbtc(uint256 amount) external {\n require(\n mintedAmount + amount <= mintingLimit,\n \"Minting limit exceeded\"\n );\n\n emit WormholeTbtcDeposited(msg.sender, amount);\n mintedAmount += amount;\n bridgeToken.safeTransferFrom(msg.sender, address(this), amount);\n tbtc.mint(msg.sender, amount);\n }\n\n /// @notice Lets the governance to update the tBTC gateway address on the\n /// chain with the given Wormhole ID.\n /// @dev Use toWormholeAddress function to convert between Ethereum and\n /// Wormhole address formats.\n /// @param chainId Wormhole ID of the chain.\n /// @param gateway Address of tBTC gateway on the given chain in a Wormhole\n /// format.\n function updateGatewayAddress(uint16 chainId, bytes32 gateway)\n external\n onlyOwner\n {\n gateways[chainId] = gateway;\n emit GatewayAddressUpdated(chainId, gateway);\n }\n\n /// @notice Lets the governance to update the tBTC minting limit for this\n /// contract.\n /// @param _mintingLimit The new minting limit.\n function updateMintingLimit(uint256 _mintingLimit) external onlyOwner {\n mintingLimit = _mintingLimit;\n emit MintingLimitUpdated(_mintingLimit);\n }\n\n /// @notice Converts Ethereum address into Wormhole format.\n /// @param _address The address to convert.\n function toWormholeAddress(address _address)\n external\n pure\n returns (bytes32)\n {\n return bytes32(uint256(uint160(_address)));\n }\n\n /// @notice Converts Wormhole address into Ethereum format.\n /// @param _address The address to convert.\n function fromWormholeAddress(bytes32 _address)\n public\n pure\n returns (address)\n {\n return address(uint160(uint256(_address)));\n }\n\n /// @dev Eliminates the dust that cannot be bridged with Wormhole\n /// due to the decimal shift in the Wormhole Bridge contract.\n /// See https://github.com/wormhole-foundation/wormhole/blob/96682bdbeb7c87bfa110eade0554b3d8cbf788d2/ethereum/contracts/bridge/Bridge.sol#L276-L288\n function normalize(uint256 amount) internal pure returns (uint256) {\n // slither-disable-next-line divide-before-multiply\n amount /= 10**10;\n amount *= 10**10;\n return amount;\n }\n}\n"
|
|
321
|
-
},
|
|
322
313
|
"contracts/maintainer/MaintainerProxy.sol": {
|
|
323
314
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\n\nimport \"../bridge/BitcoinTx.sol\";\nimport \"../bridge/Bridge.sol\";\n\n/// @title Maintainer Proxy\n/// @notice Maintainers are the willing off-chain clients approved by the governance.\n/// Maintainers proxy calls to the `Bridge` contract via 'MaintainerProxy'\n/// and are refunded for the spent gas from the `ReimbursementPool`.\n/// There are two types of maintainers: wallet maintainers and SPV\n/// maintainers.\ncontract MaintainerProxy is Ownable, Reimbursable {\n Bridge public bridge;\n\n /// @notice Authorized wallet maintainers that can interact with the set of\n /// functions for wallet maintainers only. Authorization can be\n /// granted and removed by the governance.\n /// @dev 'Key' is the address of the maintainer. 'Value' represents an index+1\n /// in the 'maintainers' array. 1 was added so the maintainer index can\n /// never be 0 which is a reserved index for a non-existent maintainer\n /// in this map.\n mapping(address => uint256) public isWalletMaintainer;\n\n /// @notice This list of wallet maintainers keeps the order of which wallet\n /// maintainer should be submitting a next transaction. It does not\n /// enforce the order but only tracks who should be next in line.\n address[] public walletMaintainers;\n\n /// @notice Authorized SPV maintainers that can interact with the set of\n /// functions for SPV maintainers only. Authorization can be\n /// granted and removed by the governance.\n /// @dev 'Key' is the address of the maintainer. 'Value' represents an index+1\n /// in the 'maintainers' array. 1 was added so the maintainer index can\n /// never be 0 which is a reserved index for a non-existent maintainer\n /// in this map.\n mapping(address => uint256) public isSpvMaintainer;\n\n /// @notice This list of SPV maintainers keeps the order of which SPV\n /// maintainer should be submitting a next transaction. It does not\n /// enforce the order but only tracks who should be next in line.\n address[] public spvMaintainers;\n\n /// @notice Gas that is meant to balance the submission of deposit sweep proof\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public submitDepositSweepProofGasOffset;\n\n /// @notice Gas that is meant to balance the submission of redemption proof\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public submitRedemptionProofGasOffset;\n\n /// @notice Gas that is meant to balance the reset of moving funds timeout\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public resetMovingFundsTimeoutGasOffset;\n\n /// @notice Gas that is meant to balance the submission of moving funds proof\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public submitMovingFundsProofGasOffset;\n\n /// @notice Gas that is meant to balance the notification of moving funds below\n /// dust overall cost. Can be updated by the governance based on the\n /// current market conditions.\n uint256 public notifyMovingFundsBelowDustGasOffset;\n\n /// @notice Gas that is meant to balance the submission of moved funds sweep\n /// proof overall cost. Can be updated by the governance based on the\n /// current market conditions.\n uint256 public submitMovedFundsSweepProofGasOffset;\n\n /// @notice Gas that is meant to balance the request of a new wallet overall\n /// cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public requestNewWalletGasOffset;\n\n /// @notice Gas that is meant to balance the notification of closeable wallet\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public notifyWalletCloseableGasOffset;\n\n /// @notice Gas that is meant to balance the notification of wallet closing\n /// period elapsed overall cost. Can be updated by the governance\n /// based on the current market conditions.\n uint256 public notifyWalletClosingPeriodElapsedGasOffset;\n\n /// @notice Gas that is meant to balance the defeat fraud challenge\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public defeatFraudChallengeGasOffset;\n\n /// @notice Gas that is meant to balance the defeat fraud challenge with heartbeat\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public defeatFraudChallengeWithHeartbeatGasOffset;\n\n event WalletMaintainerAuthorized(address indexed maintainer);\n\n event WalletMaintainerUnauthorized(address indexed maintainer);\n\n event SpvMaintainerAuthorized(address indexed maintainer);\n\n event SpvMaintainerUnauthorized(address indexed maintainer);\n\n event BridgeUpdated(address newBridge);\n\n event GasOffsetParametersUpdated(\n uint256 submitDepositSweepProofGasOffset,\n uint256 submitRedemptionProofGasOffset,\n uint256 resetMovingFundsTimeoutGasOffset,\n uint256 submitMovingFundsProofGasOffset,\n uint256 notifyMovingFundsBelowDustGasOffset,\n uint256 submitMovedFundsSweepProofGasOffset,\n uint256 requestNewWalletGasOffset,\n uint256 notifyWalletCloseableGasOffset,\n uint256 notifyWalletClosingPeriodElapsedGasOffset,\n uint256 defeatFraudChallengeGasOffset,\n uint256 defeatFraudChallengeWithHeartbeatGasOffset\n );\n\n modifier onlyWalletMaintainer() {\n require(\n isWalletMaintainer[msg.sender] != 0,\n \"Caller is not authorized\"\n );\n _;\n }\n\n modifier onlySpvMaintainer() {\n require(isSpvMaintainer[msg.sender] != 0, \"Caller is not authorized\");\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(owner() == msg.sender, \"Caller is not the owner\");\n _;\n }\n\n constructor(Bridge _bridge, ReimbursementPool _reimbursementPool) {\n bridge = _bridge;\n reimbursementPool = _reimbursementPool;\n submitDepositSweepProofGasOffset = 27000;\n submitRedemptionProofGasOffset = 0;\n resetMovingFundsTimeoutGasOffset = 1000;\n submitMovingFundsProofGasOffset = 15000;\n notifyMovingFundsBelowDustGasOffset = 3500;\n submitMovedFundsSweepProofGasOffset = 22000;\n requestNewWalletGasOffset = 3500;\n notifyWalletCloseableGasOffset = 4000;\n notifyWalletClosingPeriodElapsedGasOffset = 3000;\n defeatFraudChallengeGasOffset = 10000;\n defeatFraudChallengeWithHeartbeatGasOffset = 5000;\n }\n\n /// @notice Wraps `Bridge.submitDepositSweepProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitDepositSweepProof` function documentation.\n function submitDepositSweepProof(\n BitcoinTx.Info calldata sweepTx,\n BitcoinTx.Proof calldata sweepProof,\n BitcoinTx.UTXO calldata mainUtxo,\n address vault\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitDepositSweepProof(sweepTx, sweepProof, mainUtxo, vault);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitDepositSweepProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.submitRedemptionProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitRedemptionProof` function documentation.\n function submitRedemptionProof(\n BitcoinTx.Info calldata redemptionTx,\n BitcoinTx.Proof calldata redemptionProof,\n BitcoinTx.UTXO calldata mainUtxo,\n bytes20 walletPubKeyHash\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitRedemptionProof(\n redemptionTx,\n redemptionProof,\n mainUtxo,\n walletPubKeyHash\n );\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitRedemptionProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.resetMovingFundsTimeout` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.resetMovingFundsTimeout` function documentation.\n function resetMovingFundsTimeout(bytes20 walletPubKeyHash) external {\n uint256 gasStart = gasleft();\n\n bridge.resetMovingFundsTimeout(walletPubKeyHash);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + resetMovingFundsTimeoutGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.submitMovingFundsProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitMovingFundsProof` function documentation.\n function submitMovingFundsProof(\n BitcoinTx.Info calldata movingFundsTx,\n BitcoinTx.Proof calldata movingFundsProof,\n BitcoinTx.UTXO calldata mainUtxo,\n bytes20 walletPubKeyHash\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitMovingFundsProof(\n movingFundsTx,\n movingFundsProof,\n mainUtxo,\n walletPubKeyHash\n );\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitMovingFundsProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.notifyMovingFundsBelowDust` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.notifyMovingFundsBelowDust` function documentation.\n function notifyMovingFundsBelowDust(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata mainUtxo\n ) external onlyWalletMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.notifyMovingFundsBelowDust(walletPubKeyHash, mainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + notifyMovingFundsBelowDustGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.submitMovedFundsSweepProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitMovedFundsSweepProof` function documentation.\n function submitMovedFundsSweepProof(\n BitcoinTx.Info calldata sweepTx,\n BitcoinTx.Proof calldata sweepProof,\n BitcoinTx.UTXO calldata mainUtxo\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitMovedFundsSweepProof(sweepTx, sweepProof, mainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitMovedFundsSweepProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.requestNewWallet` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.requestNewWallet` function documentation.\n function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)\n external\n onlyWalletMaintainer\n {\n uint256 gasStart = gasleft();\n\n bridge.requestNewWallet(activeWalletMainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + requestNewWalletGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.notifyWalletCloseable` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.notifyWalletCloseable` function documentation.\n function notifyWalletCloseable(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata walletMainUtxo\n ) external onlyWalletMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.notifyWalletCloseable(walletPubKeyHash, walletMainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + notifyWalletCloseableGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.notifyWalletClosingPeriodElapsed` call and reimburses\n /// the caller's transaction cost.\n /// @dev See `Bridge.notifyWalletClosingPeriodElapsed` function documentation.\n function notifyWalletClosingPeriodElapsed(bytes20 walletPubKeyHash)\n external\n onlyWalletMaintainer\n {\n uint256 gasStart = gasleft();\n\n bridge.notifyWalletClosingPeriodElapsed(walletPubKeyHash);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + notifyWalletClosingPeriodElapsedGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.defeatFraudChallenge` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.defeatFraudChallenge` function documentation.\n function defeatFraudChallenge(\n bytes calldata walletPublicKey,\n bytes calldata preimage,\n bool witness\n ) external {\n uint256 gasStart = gasleft();\n\n bridge.defeatFraudChallenge(walletPublicKey, preimage, witness);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + defeatFraudChallengeGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.defeatFraudChallengeWithHeartbeat` call and\n /// reimburses the caller's transaction cost.\n /// @dev See `Bridge.defeatFraudChallengeWithHeartbeat` function documentation.\n function defeatFraudChallengeWithHeartbeat(\n bytes calldata walletPublicKey,\n bytes calldata heartbeatMessage\n ) external {\n uint256 gasStart = gasleft();\n\n bridge.defeatFraudChallengeWithHeartbeat(\n walletPublicKey,\n heartbeatMessage\n );\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + defeatFraudChallengeWithHeartbeatGasOffset,\n msg.sender\n );\n }\n\n /// @notice Authorize a wallet maintainer that can interact with this\n /// reimbursement pool. Can be authorized by the owner only.\n /// @param maintainer Wallet maintainer to authorize.\n function authorizeWalletMaintainer(address maintainer) external onlyOwner {\n walletMaintainers.push(maintainer);\n isWalletMaintainer[maintainer] = walletMaintainers.length;\n\n emit WalletMaintainerAuthorized(maintainer);\n }\n\n /// @notice Authorize an SPV maintainer that can interact with this\n /// reimbursement pool. Can be authorized by the owner only.\n /// @param maintainer SPV maintainer to authorize.\n function authorizeSpvMaintainer(address maintainer) external onlyOwner {\n spvMaintainers.push(maintainer);\n isSpvMaintainer[maintainer] = spvMaintainers.length;\n\n emit SpvMaintainerAuthorized(maintainer);\n }\n\n /// @notice Unauthorize a wallet maintainer that was previously authorized to\n /// interact with the Maintainer Proxy contract. Can be unauthorized\n /// by the owner only.\n /// @dev The last maintainer is swapped with the one to be unauthorized.\n /// The unauthorized maintainer is then removed from the list. An index\n /// of the last maintainer is changed with the removed maintainer.\n /// Ex.\n /// 'walletMaintainers' list: [0x1, 0x2, 0x3, 0x4, 0x5]\n /// 'isWalletMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x3 -> 3, 0x4 -> 4, 0x5 -> 5]\n /// unauthorize: 0x3\n /// new 'walletMaintainers' list: [0x1, 0x2, 0x5, 0x4]\n /// new 'isWalletMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x4 -> 4, 0x5 -> 3]\n /// @param maintainerToUnauthorize Maintainer to unauthorize.\n function unauthorizeWalletMaintainer(address maintainerToUnauthorize)\n external\n onlyOwner\n {\n uint256 maintainerIdToUnauthorize = isWalletMaintainer[\n maintainerToUnauthorize\n ];\n\n require(maintainerIdToUnauthorize != 0, \"No maintainer to unauthorize\");\n\n address lastMaintainerAddress = walletMaintainers[\n walletMaintainers.length - 1\n ];\n\n walletMaintainers[\n maintainerIdToUnauthorize - 1\n ] = lastMaintainerAddress;\n walletMaintainers.pop();\n\n isWalletMaintainer[lastMaintainerAddress] = maintainerIdToUnauthorize;\n\n delete isWalletMaintainer[maintainerToUnauthorize];\n\n emit WalletMaintainerUnauthorized(maintainerToUnauthorize);\n }\n\n /// @notice Unauthorize an SPV maintainer that was previously authorized to\n /// interact with the Maintainer Proxy contract. Can be unauthorized\n /// by the owner only.\n /// @dev The last maintainer is swapped with the one to be unauthorized.\n /// The unauthorized maintainer is then removed from the list. An index\n /// of the last maintainer is changed with the removed maintainer.\n /// Ex.\n /// 'spvMaintainers' list: [0x1, 0x2, 0x3, 0x4, 0x5]\n /// 'isSpvMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x3 -> 3, 0x4 -> 4, 0x5 -> 5]\n /// unauthorize: 0x3\n /// new 'spvMaintainers' list: [0x1, 0x2, 0x5, 0x4]\n /// new 'isSpvMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x4 -> 4, 0x5 -> 3]\n /// @param maintainerToUnauthorize Maintainer to unauthorize.\n function unauthorizeSpvMaintainer(address maintainerToUnauthorize)\n external\n onlyOwner\n {\n uint256 maintainerIdToUnauthorize = isSpvMaintainer[\n maintainerToUnauthorize\n ];\n\n require(maintainerIdToUnauthorize != 0, \"No maintainer to unauthorize\");\n\n address lastMaintainerAddress = spvMaintainers[\n spvMaintainers.length - 1\n ];\n\n spvMaintainers[maintainerIdToUnauthorize - 1] = lastMaintainerAddress;\n spvMaintainers.pop();\n\n isSpvMaintainer[lastMaintainerAddress] = maintainerIdToUnauthorize;\n\n delete isSpvMaintainer[maintainerToUnauthorize];\n\n emit SpvMaintainerUnauthorized(maintainerToUnauthorize);\n }\n\n /// @notice Allows the Governance to upgrade the Bridge address.\n /// @dev The function does not implement any governance delay and does not\n /// check the status of the Bridge. The Governance implementation needs\n /// to ensure all requirements for the upgrade are satisfied before\n /// executing this function.\n function updateBridge(Bridge _bridge) external onlyOwner {\n bridge = _bridge;\n\n emit BridgeUpdated(address(_bridge));\n }\n\n /// @notice Updates the values of gas offset parameters.\n /// @dev Can be called only by the contract owner. The caller is responsible\n /// for validating parameters.\n /// @param newSubmitDepositSweepProofGasOffset New submit deposit sweep\n /// proof gas offset.\n /// @param newSubmitRedemptionProofGasOffset New submit redemption proof gas\n /// offset.\n /// @param newResetMovingFundsTimeoutGasOffset New reset moving funds\n /// timeout gas offset.\n /// @param newSubmitMovingFundsProofGasOffset New submit moving funds proof\n /// gas offset.\n /// @param newNotifyMovingFundsBelowDustGasOffset New notify moving funds\n /// below dust gas offset.\n /// @param newSubmitMovedFundsSweepProofGasOffset New submit moved funds\n /// sweep proof gas offset.\n /// @param newRequestNewWalletGasOffset New request new wallet gas offset.\n /// @param newNotifyWalletCloseableGasOffset New notify closeable wallet gas\n /// offset.\n /// @param newNotifyWalletClosingPeriodElapsedGasOffset New notify wallet\n /// closing period elapsed gas offset.\n /// @param newDefeatFraudChallengeGasOffset New defeat fraud challenge gas\n /// offset.\n /// @param newDefeatFraudChallengeWithHeartbeatGasOffset New defeat fraud\n /// challenge with heartbeat gas offset.\n function updateGasOffsetParameters(\n uint256 newSubmitDepositSweepProofGasOffset,\n uint256 newSubmitRedemptionProofGasOffset,\n uint256 newResetMovingFundsTimeoutGasOffset,\n uint256 newSubmitMovingFundsProofGasOffset,\n uint256 newNotifyMovingFundsBelowDustGasOffset,\n uint256 newSubmitMovedFundsSweepProofGasOffset,\n uint256 newRequestNewWalletGasOffset,\n uint256 newNotifyWalletCloseableGasOffset,\n uint256 newNotifyWalletClosingPeriodElapsedGasOffset,\n uint256 newDefeatFraudChallengeGasOffset,\n uint256 newDefeatFraudChallengeWithHeartbeatGasOffset\n ) external onlyOwner {\n submitDepositSweepProofGasOffset = newSubmitDepositSweepProofGasOffset;\n submitRedemptionProofGasOffset = newSubmitRedemptionProofGasOffset;\n resetMovingFundsTimeoutGasOffset = newResetMovingFundsTimeoutGasOffset;\n submitMovingFundsProofGasOffset = newSubmitMovingFundsProofGasOffset;\n notifyMovingFundsBelowDustGasOffset = newNotifyMovingFundsBelowDustGasOffset;\n submitMovedFundsSweepProofGasOffset = newSubmitMovedFundsSweepProofGasOffset;\n requestNewWalletGasOffset = newRequestNewWalletGasOffset;\n notifyWalletCloseableGasOffset = newNotifyWalletCloseableGasOffset;\n notifyWalletClosingPeriodElapsedGasOffset = newNotifyWalletClosingPeriodElapsedGasOffset;\n defeatFraudChallengeGasOffset = newDefeatFraudChallengeGasOffset;\n defeatFraudChallengeWithHeartbeatGasOffset = newDefeatFraudChallengeWithHeartbeatGasOffset;\n\n emit GasOffsetParametersUpdated(\n submitDepositSweepProofGasOffset,\n submitRedemptionProofGasOffset,\n resetMovingFundsTimeoutGasOffset,\n submitMovingFundsProofGasOffset,\n notifyMovingFundsBelowDustGasOffset,\n submitMovedFundsSweepProofGasOffset,\n requestNewWalletGasOffset,\n notifyWalletCloseableGasOffset,\n notifyWalletClosingPeriodElapsedGasOffset,\n defeatFraudChallengeGasOffset,\n defeatFraudChallengeWithHeartbeatGasOffset\n );\n }\n\n /// @notice Gets an entire array of wallet maintainer addresses.\n function allWalletMaintainers() external view returns (address[] memory) {\n return walletMaintainers;\n }\n\n /// @notice Gets an entire array of SPV maintainer addresses.\n function allSpvMaintainers() external view returns (address[] memory) {\n return spvMaintainers;\n }\n}\n"
|
|
324
315
|
},
|
|
325
316
|
"contracts/relay/LightRelay.sol": {
|
|
326
317
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\nimport {BytesLib} from \"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\";\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\nimport {ValidateSPV} from \"@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol\";\n\nimport \"../bridge/IRelay.sol\";\n\nstruct Epoch {\n uint32 timestamp;\n // By definition, bitcoin targets have at least 32 leading zero bits.\n // Thus we can only store the bits that aren't guaranteed to be 0.\n uint224 target;\n}\n\ninterface ILightRelay is IRelay {\n event Genesis(uint256 blockHeight);\n event Retarget(uint256 oldDifficulty, uint256 newDifficulty);\n event ProofLengthChanged(uint256 newLength);\n event AuthorizationRequirementChanged(bool newStatus);\n event SubmitterAuthorized(address submitter);\n event SubmitterDeauthorized(address submitter);\n\n function retarget(bytes memory headers) external;\n\n function validateChain(bytes memory headers)\n external\n view\n returns (uint256 startingHeaderTimestamp, uint256 headerCount);\n\n function getBlockDifficulty(uint256 blockNumber)\n external\n view\n returns (uint256);\n\n function getEpochDifficulty(uint256 epochNumber)\n external\n view\n returns (uint256);\n\n function getRelayRange()\n external\n view\n returns (uint256 relayGenesis, uint256 currentEpochEnd);\n}\n\nlibrary RelayUtils {\n using BytesLib for bytes;\n\n /// @notice Extract the timestamp of the header at the given position.\n /// @param headers Byte array containing the header of interest.\n /// @param at The start of the header in the array.\n /// @return The timestamp of the header.\n /// @dev Assumes that the specified position contains a valid header.\n /// Performs no validation whatsoever.\n function extractTimestampAt(bytes memory headers, uint256 at)\n internal\n pure\n returns (uint32)\n {\n return BTCUtils.reverseUint32(uint32(headers.slice4(68 + at)));\n }\n}\n\n/// @dev THE RELAY MUST NOT BE USED BEFORE GENESIS AND AT LEAST ONE RETARGET.\ncontract LightRelay is Ownable, ILightRelay {\n using BytesLib for bytes;\n using BTCUtils for bytes;\n using ValidateSPV for bytes;\n using RelayUtils for bytes;\n\n bool public ready;\n // Whether the relay requires the address submitting a retarget to be\n // authorised in advance by governance.\n bool public authorizationRequired;\n // Number of blocks required for each side of a retarget proof:\n // a retarget must provide `proofLength` blocks before the retarget\n // and `proofLength` blocks after it.\n // Governable\n // Should be set to a fairly high number (e.g. 20-50) in production.\n uint64 public proofLength;\n // The number of the first epoch recorded by the relay.\n // This should equal the height of the block starting the genesis epoch,\n // divided by 2016, but this is not enforced as the relay has no\n // information about block numbers.\n uint64 public genesisEpoch;\n // The number of the latest epoch whose difficulty is proven to the relay.\n // If the genesis epoch's number is set correctly, and retargets along the\n // way have been legitimate, this equals the height of the block starting\n // the most recent epoch, divided by 2016.\n uint64 public currentEpoch;\n\n uint256 internal currentEpochDifficulty;\n uint256 internal prevEpochDifficulty;\n\n // Each epoch from genesis to the current one, keyed by their numbers.\n mapping(uint256 => Epoch) internal epochs;\n\n mapping(address => bool) public isAuthorized;\n\n modifier relayActive() {\n require(ready, \"Relay is not ready for use\");\n _;\n }\n\n /// @notice Establish a starting point for the relay by providing the\n /// target, timestamp and blockheight of the first block of the relay\n /// genesis epoch.\n /// @param genesisHeader The first block header of the genesis epoch.\n /// @param genesisHeight The block number of the first block of the epoch.\n /// @param genesisProofLength The number of blocks required to accept a\n /// proof.\n /// @dev If the relay is used by querying the current and previous epoch\n /// difficulty, at least one retarget needs to be provided after genesis;\n /// otherwise the prevEpochDifficulty will be uninitialised and zero.\n function genesis(\n bytes calldata genesisHeader,\n uint256 genesisHeight,\n uint64 genesisProofLength\n ) external onlyOwner {\n require(!ready, \"Genesis already performed\");\n\n require(genesisHeader.length == 80, \"Invalid genesis header length\");\n\n require(\n genesisHeight % 2016 == 0,\n \"Invalid height of relay genesis block\"\n );\n\n require(genesisProofLength < 2016, \"Proof length excessive\");\n require(genesisProofLength > 0, \"Proof length may not be zero\");\n\n genesisEpoch = uint64(genesisHeight / 2016);\n currentEpoch = genesisEpoch;\n uint256 genesisTarget = genesisHeader.extractTarget();\n uint256 genesisTimestamp = genesisHeader.extractTimestamp();\n epochs[genesisEpoch] = Epoch(\n uint32(genesisTimestamp),\n uint224(genesisTarget)\n );\n proofLength = genesisProofLength;\n currentEpochDifficulty = BTCUtils.calculateDifficulty(genesisTarget);\n ready = true;\n\n emit Genesis(genesisHeight);\n }\n\n /// @notice Set the number of blocks required to accept a header chain.\n /// @param newLength The required number of blocks. Must be less than 2016.\n /// @dev For production, a high number (e.g. 20-50) is recommended.\n /// Small numbers are accepted but should only be used for testing.\n function setProofLength(uint64 newLength) external relayActive onlyOwner {\n require(newLength < 2016, \"Proof length excessive\");\n require(newLength > 0, \"Proof length may not be zero\");\n require(newLength != proofLength, \"Proof length unchanged\");\n proofLength = newLength;\n emit ProofLengthChanged(newLength);\n }\n\n /// @notice Set whether the relay requires retarget submitters to be\n /// pre-authorised by governance.\n /// @param status True if authorisation is to be required, false if not.\n function setAuthorizationStatus(bool status) external onlyOwner {\n authorizationRequired = status;\n emit AuthorizationRequirementChanged(status);\n }\n\n /// @notice Authorise the given address to submit retarget proofs.\n /// @param submitter The address to be authorised.\n function authorize(address submitter) external onlyOwner {\n isAuthorized[submitter] = true;\n emit SubmitterAuthorized(submitter);\n }\n\n /// @notice Rescind the authorisation of the submitter to retarget.\n /// @param submitter The address to be deauthorised.\n function deauthorize(address submitter) external onlyOwner {\n isAuthorized[submitter] = false;\n emit SubmitterDeauthorized(submitter);\n }\n\n /// @notice Add a new epoch to the relay by providing a proof\n /// of the difficulty before and after the retarget.\n /// @param headers A chain of headers including the last X blocks before\n /// the retarget, followed by the first X blocks after the retarget,\n /// where X equals the current proof length.\n /// @dev Checks that the first X blocks are valid in the most recent epoch,\n /// that the difficulty of the new epoch is calculated correctly according\n /// to the block timestamps, and that the next X blocks would be valid in\n /// the new epoch.\n /// We have no information of block heights, so we cannot enforce that\n /// retargets only happen every 2016 blocks; instead, we assume that this\n /// is the case if a valid proof of work is provided.\n /// It is possible to cheat the relay by providing X blocks from earlier in\n /// the most recent epoch, and then mining X new blocks after them.\n /// However, each of these malicious blocks would have to be mined to a\n /// higher difficulty than the legitimate ones.\n /// Alternatively, if the retarget has not been performed yet, one could\n /// first mine X blocks in the old difficulty with timestamps set far in\n /// the future, and then another X blocks at a greatly reduced difficulty.\n /// In either case, cheating the realy requires more work than mining X\n /// legitimate blocks.\n /// Only the most recent epoch is vulnerable to these attacks; once a\n /// retarget has been proven to the relay, the epoch is immutable even if a\n /// contradictory proof were to be presented later.\n function retarget(bytes memory headers) external relayActive {\n if (authorizationRequired) {\n require(isAuthorized[msg.sender], \"Submitter unauthorized\");\n }\n\n require(\n // Require proofLength headers on both sides of the retarget\n headers.length == (proofLength * 2 * 80),\n \"Invalid header length\"\n );\n\n Epoch storage latest = epochs[currentEpoch];\n\n uint256 oldTarget = latest.target;\n\n bytes32 previousHeaderDigest = bytes32(0);\n\n // Validate old chain\n for (uint256 i = 0; i < proofLength; i++) {\n (\n bytes32 currentDigest,\n uint256 currentHeaderTarget\n ) = validateHeader(headers, i * 80, previousHeaderDigest);\n\n require(\n currentHeaderTarget == oldTarget,\n \"Invalid target in pre-retarget headers\"\n );\n\n previousHeaderDigest = currentDigest;\n }\n\n // get timestamp of retarget block\n uint256 epochEndTimestamp = headers.extractTimestampAt(\n (proofLength - 1) * 80\n );\n\n // An attacker could produce blocks with timestamps in the future,\n // in an attempt to reduce the difficulty after the retarget\n // to make mining the second part of the retarget proof easier.\n // In particular, the attacker could reuse all but one block\n // from the legitimate chain, and only mine the last block.\n // To hinder this, require that the epoch end timestamp does not\n // exceed the ethereum timestamp.\n // NOTE: both are unix seconds, so this comparison should be valid.\n require(\n /* solhint-disable-next-line not-rely-on-time */\n epochEndTimestamp < block.timestamp,\n \"Epoch cannot end in the future\"\n );\n\n // Expected target is the full-length target\n uint256 expectedTarget = BTCUtils.retargetAlgorithm(\n oldTarget,\n latest.timestamp,\n epochEndTimestamp\n );\n\n // Mined target is the header-encoded target\n uint256 minedTarget = 0;\n\n uint256 epochStartTimestamp = headers.extractTimestampAt(\n proofLength * 80\n );\n\n // validate new chain\n for (uint256 j = proofLength; j < proofLength * 2; j++) {\n (\n bytes32 _currentDigest,\n uint256 _currentHeaderTarget\n ) = validateHeader(headers, j * 80, previousHeaderDigest);\n\n if (minedTarget == 0) {\n // The new target has not been set, so check its correctness\n minedTarget = _currentHeaderTarget;\n require(\n // Although the target is a 256-bit number, there are only 32 bits of\n // space in the Bitcoin header. Because of that, the version stored in\n // the header is a less-precise representation of the actual target\n // using base-256 version of scientific notation.\n //\n // The 256-bit unsigned integer returned from BTCUtils.retargetAlgorithm\n // is the precise target value.\n // The 256-bit unsigned integer returned from validateHeader is the less\n // precise target value because it was read from 32 bits of space of\n // Bitcoin block header.\n //\n // We can't compare the precise and less precise representations together\n // so we first mask them to obtain the less precise version:\n // (full & truncated) == truncated\n _currentHeaderTarget ==\n (expectedTarget & _currentHeaderTarget),\n \"Invalid target in new epoch\"\n );\n } else {\n // The new target has been set, so remaining targets should match.\n require(\n _currentHeaderTarget == minedTarget,\n \"Unexpected target change after retarget\"\n );\n }\n\n previousHeaderDigest = _currentDigest;\n }\n\n currentEpoch = currentEpoch + 1;\n\n epochs[currentEpoch] = Epoch(\n uint32(epochStartTimestamp),\n uint224(minedTarget)\n );\n\n uint256 oldDifficulty = currentEpochDifficulty;\n uint256 newDifficulty = BTCUtils.calculateDifficulty(minedTarget);\n\n prevEpochDifficulty = oldDifficulty;\n currentEpochDifficulty = newDifficulty;\n\n emit Retarget(oldDifficulty, newDifficulty);\n }\n\n /// @notice Check whether a given chain of headers should be accepted as\n /// valid within the rules of the relay.\n /// If the validation fails, this function throws an exception.\n /// @param headers A chain of 2 to 2015 bitcoin headers.\n /// @return startingHeaderTimestamp The timestamp of the first header.\n /// @return headerCount The number of headers.\n /// @dev A chain of headers is accepted as valid if:\n /// - Its length is between 2 and 2015 headers.\n /// - Headers in the chain are sequential and refer to previous digests.\n /// - Each header is mined with the correct amount of work.\n /// - The difficulty in each header matches an epoch of the relay,\n /// as determined by the headers' timestamps. The headers must be between\n /// the genesis epoch and the latest proven epoch (inclusive).\n /// If the chain contains a retarget, it is accepted if the retarget has\n /// already been proven to the relay.\n /// If the chain contains blocks of an epoch that has not been proven to\n /// the relay (after a retarget within the header chain, or when the entire\n /// chain falls within an epoch that has not been proven yet), it will be\n /// rejected.\n /// One exception to this is when two subsequent epochs have exactly the\n /// same difficulty; headers from the latter epoch will be accepted if the\n /// previous epoch has been proven to the relay.\n /// This is because it is not possible to distinguish such headers from\n /// headers of the previous epoch.\n ///\n /// If the difficulty increases significantly between relay genesis and the\n /// present, creating fraudulent proofs for earlier epochs becomes easier.\n /// Users of the relay should check the timestamps of valid headers and\n /// only accept appropriately recent ones.\n function validateChain(bytes memory headers)\n external\n view\n returns (uint256 startingHeaderTimestamp, uint256 headerCount)\n {\n require(headers.length % 80 == 0, \"Invalid header length\");\n\n headerCount = headers.length / 80;\n\n require(\n headerCount > 1 && headerCount < 2016,\n \"Invalid number of headers\"\n );\n\n startingHeaderTimestamp = headers.extractTimestamp();\n\n // Short-circuit the first header's validation.\n // We validate the header here to get the target which is needed to\n // precisely identify the epoch.\n (\n bytes32 previousHeaderDigest,\n uint256 currentHeaderTarget\n ) = validateHeader(headers, 0, bytes32(0));\n\n Epoch memory nullEpoch = Epoch(0, 0);\n\n uint256 startingEpochNumber = currentEpoch;\n Epoch memory startingEpoch = epochs[startingEpochNumber];\n Epoch memory nextEpoch = nullEpoch;\n\n // Find the correct epoch for the given chain\n // Fastest with recent epochs, but able to handle anything after genesis\n //\n // The rules for bitcoin timestamps are:\n // - must be greater than the median of the last 11 blocks' timestamps\n // - must be less than the network-adjusted time +2 hours\n //\n // Because of this, the timestamp of a header may be smaller than the\n // starting time, or greater than the ending time of its epoch.\n // However, a valid timestamp is guaranteed to fall within the window\n // formed by the epochs immediately before and after its timestamp.\n // We can identify cases like these by comparing the targets.\n while (startingHeaderTimestamp < startingEpoch.timestamp) {\n startingEpochNumber -= 1;\n nextEpoch = startingEpoch;\n startingEpoch = epochs[startingEpochNumber];\n }\n\n // We have identified the centre of the window,\n // by reaching the most recent epoch whose starting timestamp\n // or reached before the genesis where epoch slots are empty.\n // Therefore check that the timestamp is nonzero.\n require(\n startingEpoch.timestamp > 0,\n \"Cannot validate chains before relay genesis\"\n );\n\n // The targets don't match. This could be because the block is invalid,\n // or it could be because of timestamp inaccuracy.\n // To cover the latter case, check adjacent epochs.\n if (currentHeaderTarget != startingEpoch.target) {\n // The target matches the next epoch.\n // This means we are right at the beginning of the next epoch,\n // and retargets during the chain should not be possible.\n if (currentHeaderTarget == nextEpoch.target) {\n startingEpoch = nextEpoch;\n nextEpoch = nullEpoch;\n }\n // The target doesn't match the next epoch.\n // Therefore the only valid epoch is the previous one.\n // Because the timestamp can't be more than 2 hours into the future\n // we must be right near the end of the epoch,\n // so a retarget is possible.\n else {\n startingEpochNumber -= 1;\n nextEpoch = startingEpoch;\n startingEpoch = epochs[startingEpochNumber];\n\n // We have failed to find a match,\n // therefore the target has to be invalid.\n require(\n currentHeaderTarget == startingEpoch.target,\n \"Invalid target in header chain\"\n );\n }\n }\n\n // We've found the correct epoch for the first header.\n // Validate the rest.\n for (uint256 i = 1; i < headerCount; i++) {\n bytes32 currentDigest;\n (currentDigest, currentHeaderTarget) = validateHeader(\n headers,\n i * 80,\n previousHeaderDigest\n );\n\n // If the header's target does not match the expected target,\n // check if a retarget is possible.\n //\n // If next epoch timestamp exists, a valid retarget is possible\n // (if next epoch timestamp doesn't exist, either a retarget has\n // already happened in this chain, the relay needs a retarget\n // before this chain can be validated, or a retarget is not allowed\n // because we know the headers are within a timestamp irregularity\n // of the previous retarget).\n //\n // In this case the target must match the next epoch's target,\n // and the header's timestamp must match the epoch's start.\n if (currentHeaderTarget != startingEpoch.target) {\n uint256 currentHeaderTimestamp = headers.extractTimestampAt(\n i * 80\n );\n\n require(\n nextEpoch.timestamp != 0 &&\n currentHeaderTarget == nextEpoch.target &&\n currentHeaderTimestamp == nextEpoch.timestamp,\n \"Invalid target in header chain\"\n );\n\n startingEpoch = nextEpoch;\n nextEpoch = nullEpoch;\n }\n\n previousHeaderDigest = currentDigest;\n }\n\n return (startingHeaderTimestamp, headerCount);\n }\n\n /// @notice Get the difficulty of the specified block.\n /// @param blockNumber The number of the block. Must fall within the relay\n /// range (at or after the relay genesis, and at or before the end of the\n /// most recent epoch proven to the relay).\n /// @return The difficulty of the epoch.\n function getBlockDifficulty(uint256 blockNumber)\n external\n view\n returns (uint256)\n {\n return getEpochDifficulty(blockNumber / 2016);\n }\n\n /// @notice Get the range of blocks the relay can accept proofs for.\n /// @dev Assumes that the genesis has been set correctly.\n /// Additionally, if the next epoch after the current one has the exact\n /// same difficulty, headers for it can be validated as well.\n /// This function should be used for informative purposes,\n /// e.g. to determine whether a retarget must be provided before submitting\n /// a header chain for validation.\n /// @return relayGenesis The height of the earliest block that can be\n /// included in header chains for the relay to validate.\n /// @return currentEpochEnd The height of the last block that can be\n /// included in header chains for the relay to validate.\n function getRelayRange()\n external\n view\n returns (uint256 relayGenesis, uint256 currentEpochEnd)\n {\n relayGenesis = genesisEpoch * 2016;\n currentEpochEnd = (currentEpoch * 2016) + 2015;\n }\n\n /// @notice Returns the difficulty of the current epoch.\n /// @dev returns 0 if the relay is not ready.\n /// @return The difficulty of the current epoch.\n function getCurrentEpochDifficulty()\n external\n view\n virtual\n returns (uint256)\n {\n return currentEpochDifficulty;\n }\n\n /// @notice Returns the difficulty of the previous epoch.\n /// @dev Returns 0 if the relay is not ready or has not had a retarget.\n /// @return The difficulty of the previous epoch.\n function getPrevEpochDifficulty() external view virtual returns (uint256) {\n return prevEpochDifficulty;\n }\n\n function getCurrentAndPrevEpochDifficulty()\n external\n view\n returns (uint256 current, uint256 previous)\n {\n return (currentEpochDifficulty, prevEpochDifficulty);\n }\n\n /// @notice Get the difficulty of the specified epoch.\n /// @param epochNumber The number of the epoch (the height of the first\n /// block of the epoch, divided by 2016). Must fall within the relay range.\n /// @return The difficulty of the epoch.\n function getEpochDifficulty(uint256 epochNumber)\n public\n view\n returns (uint256)\n {\n require(epochNumber >= genesisEpoch, \"Epoch is before relay genesis\");\n require(\n epochNumber <= currentEpoch,\n \"Epoch is not proven to the relay yet\"\n );\n return BTCUtils.calculateDifficulty(epochs[epochNumber].target);\n }\n\n /// @notice Check that the specified header forms a correct chain with the\n /// digest of the previous header (if provided), and has sufficient work.\n /// @param headers The byte array containing the header of interest.\n /// @param start The start of the header in the array.\n /// @param prevDigest The digest of the previous header\n /// (optional; providing zeros for the digest skips the check).\n /// @return digest The digest of the current header.\n /// @return target The PoW target of the header.\n /// @dev Throws an exception if the header's chain or PoW are invalid.\n /// Performs no other validation.\n function validateHeader(\n bytes memory headers,\n uint256 start,\n bytes32 prevDigest\n ) internal view returns (bytes32 digest, uint256 target) {\n // If previous block digest has been provided, require that it matches\n if (prevDigest != bytes32(0)) {\n require(\n headers.validateHeaderPrevHash(start, prevDigest),\n \"Invalid chain\"\n );\n }\n\n // Require that the header has sufficient work for its stated target\n target = headers.extractTargetAt(start);\n digest = headers.hash256Slice(start, 80);\n require(ValidateSPV.validateHeaderWork(digest, target), \"Invalid work\");\n\n return (digest, target);\n }\n}\n"
|
|
327
318
|
},
|
|
328
|
-
"contracts/relay/LightRelayMaintainerProxy.sol": {
|
|
329
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\n\nimport \"./LightRelay.sol\";\n\n/// @title LightRelayMaintainerProxy\n/// @notice The proxy contract that allows the relay maintainers to be refunded\n/// for the spent gas from the `ReimbursementPool`. When proving the\n/// next Bitcoin difficulty epoch, the maintainer calls the\n/// `LightRelayMaintainerProxy` which in turn calls the actual `LightRelay`\n/// contract.\ncontract LightRelayMaintainerProxy is Ownable, Reimbursable {\n ILightRelay public lightRelay;\n\n /// @notice Stores the addresses that can maintain the relay. Those\n /// addresses are attested by the DAO.\n /// @dev The goal is to prevent a griefing attack by frontrunning relay\n /// maintainer which is responsible for retargetting the relay in the\n /// given round. The maintainer's transaction would revert with no gas\n /// refund. Having the ability to restrict maintainer addresses is also\n /// important in case the underlying relay contract has authorization\n /// requirements for callers.\n mapping(address => bool) public isAuthorized;\n\n /// @notice Gas that is meant to balance the retarget overall cost. Can be\n // updated by the governance based on the current market conditions.\n uint256 public retargetGasOffset;\n\n event LightRelayUpdated(address newRelay);\n\n event MaintainerAuthorized(address indexed maintainer);\n\n event MaintainerDeauthorized(address indexed maintainer);\n\n event RetargetGasOffsetUpdated(uint256 retargetGasOffset);\n\n modifier onlyRelayMaintainer() {\n require(isAuthorized[msg.sender], \"Caller is not authorized\");\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(owner() == msg.sender, \"Caller is not the owner\");\n _;\n }\n\n constructor(ILightRelay _lightRelay, ReimbursementPool _reimbursementPool) {\n require(\n address(_lightRelay) != address(0),\n \"Light relay must not be zero address\"\n );\n require(\n address(_reimbursementPool) != address(0),\n \"Reimbursement pool must not be zero address\"\n );\n\n lightRelay = _lightRelay;\n reimbursementPool = _reimbursementPool;\n\n retargetGasOffset = 54000;\n }\n\n /// @notice Allows the governance to upgrade the `LightRelay` address.\n /// @dev The function does not implement any governance delay and does not\n /// check the status of the `LightRelay`. The Governance implementation\n /// needs to ensure all requirements for the upgrade are satisfied\n /// before executing this function.\n function updateLightRelay(ILightRelay _lightRelay) external onlyOwner {\n require(\n address(_lightRelay) != address(0),\n \"New light relay must not be zero address\"\n );\n\n lightRelay = _lightRelay;\n emit LightRelayUpdated(address(_lightRelay));\n }\n\n /// @notice Authorizes the given address as a maintainer. Can only be called\n /// by the owner and the address of the maintainer must not be\n /// already authorized.\n /// @dev The function does not implement any governance delay.\n /// @param maintainer The address of the maintainer to be authorized.\n function authorize(address maintainer) external onlyOwner {\n require(!isAuthorized[maintainer], \"Maintainer is already authorized\");\n\n isAuthorized[maintainer] = true;\n emit MaintainerAuthorized(maintainer);\n }\n\n /// @notice Deauthorizes the given address as a maintainer. Can only be\n /// called by the owner and the address of the maintainer must be\n /// authorized.\n /// @dev The function does not implement any governance delay.\n /// @param maintainer The address of the maintainer to be deauthorized.\n function deauthorize(address maintainer) external onlyOwner {\n require(isAuthorized[maintainer], \"Maintainer is not authorized\");\n\n isAuthorized[maintainer] = false;\n emit MaintainerDeauthorized(maintainer);\n }\n\n /// @notice Updates the values of retarget gas offset.\n /// @dev Can be called only by the contract owner. The caller is responsible\n /// for validating the parameter. The function does not implement any\n /// governance delay.\n /// @param newRetargetGasOffset New retarget gas offset.\n function updateRetargetGasOffset(uint256 newRetargetGasOffset)\n external\n onlyOwner\n {\n retargetGasOffset = newRetargetGasOffset;\n emit RetargetGasOffsetUpdated(retargetGasOffset);\n }\n\n /// @notice Wraps `LightRelay.retarget` call and reimburses the caller's\n /// transaction cost. Can only be called by an authorized relay\n /// maintainer.\n /// @dev See `LightRelay.retarget` function documentation.\n function retarget(bytes memory headers) external onlyRelayMaintainer {\n uint256 gasStart = gasleft();\n\n lightRelay.retarget(headers);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + retargetGasOffset,\n msg.sender\n );\n }\n}\n"
|
|
330
|
-
},
|
|
331
319
|
"contracts/test/BankStub.sol": {
|
|
332
320
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"../bank/Bank.sol\";\n\ncontract BankStub is Bank {\n function setBalance(address addr, uint256 amount) external {\n balanceOf[addr] = amount;\n }\n}\n"
|
|
333
321
|
},
|
|
@@ -335,7 +323,7 @@
|
|
|
335
323
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"../bridge/BitcoinTx.sol\";\nimport \"../bridge/Bridge.sol\";\nimport \"../bridge/MovingFunds.sol\";\nimport \"../bridge/Wallets.sol\";\n\ncontract BridgeStub is Bridge {\n function setSweptDeposits(BitcoinTx.UTXO[] calldata utxos) external {\n for (uint256 i = 0; i < utxos.length; i++) {\n uint256 utxoKey = uint256(\n keccak256(\n abi.encodePacked(utxos[i].txHash, utxos[i].txOutputIndex)\n )\n );\n self.deposits[utxoKey].sweptAt = 1641650400;\n }\n }\n\n function setSpentMainUtxos(BitcoinTx.UTXO[] calldata utxos) external {\n for (uint256 i = 0; i < utxos.length; i++) {\n uint256 utxoKey = uint256(\n keccak256(\n abi.encodePacked(utxos[i].txHash, utxos[i].txOutputIndex)\n )\n );\n self.spentMainUTXOs[utxoKey] = true;\n }\n }\n\n function setProcessedMovedFundsSweepRequests(\n BitcoinTx.UTXO[] calldata utxos\n ) external {\n for (uint256 i = 0; i < utxos.length; i++) {\n uint256 utxoKey = uint256(\n keccak256(\n abi.encodePacked(utxos[i].txHash, utxos[i].txOutputIndex)\n )\n );\n self.movedFundsSweepRequests[utxoKey].state = MovingFunds\n .MovedFundsSweepRequestState\n .Processed;\n }\n }\n\n function setActiveWallet(bytes20 activeWalletPubKeyHash) external {\n self.activeWalletPubKeyHash = activeWalletPubKeyHash;\n }\n\n function setWalletMainUtxo(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata utxo\n ) external {\n self.registeredWallets[walletPubKeyHash].mainUtxoHash = keccak256(\n abi.encodePacked(\n utxo.txHash,\n utxo.txOutputIndex,\n utxo.txOutputValue\n )\n );\n }\n\n function setWallet(bytes20 walletPubKeyHash, Wallets.Wallet calldata wallet)\n external\n {\n self.registeredWallets[walletPubKeyHash] = wallet;\n\n if (wallet.state == Wallets.WalletState.Live) {\n self.liveWalletsCount++;\n }\n }\n\n function setDepositDustThreshold(uint64 _depositDustThreshold) external {\n self.depositDustThreshold = _depositDustThreshold;\n }\n\n function setDepositTxMaxFee(uint64 _depositTxMaxFee) external {\n self.depositTxMaxFee = _depositTxMaxFee;\n }\n\n function setRedemptionDustThreshold(uint64 _redemptionDustThreshold)\n external\n {\n self.redemptionDustThreshold = _redemptionDustThreshold;\n }\n\n function setRedemptionTreasuryFeeDivisor(\n uint64 _redemptionTreasuryFeeDivisor\n ) external {\n self.redemptionTreasuryFeeDivisor = _redemptionTreasuryFeeDivisor;\n }\n\n function setPendingMovedFundsSweepRequest(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata utxo\n ) external {\n uint256 requestKey = uint256(\n keccak256(abi.encodePacked(utxo.txHash, utxo.txOutputIndex))\n );\n\n self.movedFundsSweepRequests[requestKey] = MovingFunds\n .MovedFundsSweepRequest(\n walletPubKeyHash,\n utxo.txOutputValue,\n /* solhint-disable-next-line not-rely-on-time */\n uint32(block.timestamp),\n MovingFunds.MovedFundsSweepRequestState.Pending\n );\n\n self\n .registeredWallets[walletPubKeyHash]\n .pendingMovedFundsSweepRequestsCount++;\n }\n\n function processPendingMovedFundsSweepRequest(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata utxo\n ) external {\n uint256 requestKey = uint256(\n keccak256(abi.encodePacked(utxo.txHash, utxo.txOutputIndex))\n );\n\n MovingFunds.MovedFundsSweepRequest storage request = self\n .movedFundsSweepRequests[requestKey];\n\n require(\n request.state == MovingFunds.MovedFundsSweepRequestState.Pending,\n \"Stub sweep request must be in Pending state\"\n );\n\n request.state = MovingFunds.MovedFundsSweepRequestState.Processed;\n\n self\n .registeredWallets[walletPubKeyHash]\n .pendingMovedFundsSweepRequestsCount--;\n }\n\n function timeoutPendingMovedFundsSweepRequest(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata utxo\n ) external {\n uint256 requestKey = uint256(\n keccak256(abi.encodePacked(utxo.txHash, utxo.txOutputIndex))\n );\n\n MovingFunds.MovedFundsSweepRequest storage request = self\n .movedFundsSweepRequests[requestKey];\n\n require(\n request.state == MovingFunds.MovedFundsSweepRequestState.Pending,\n \"Stub sweep request must be in Pending state\"\n );\n\n request.state = MovingFunds.MovedFundsSweepRequestState.TimedOut;\n\n self\n .registeredWallets[walletPubKeyHash]\n .pendingMovedFundsSweepRequestsCount--;\n }\n\n function setMovedFundsSweepTxMaxTotalFee(\n uint64 _movedFundsSweepTxMaxTotalFee\n ) external {\n self.movedFundsSweepTxMaxTotalFee = _movedFundsSweepTxMaxTotalFee;\n }\n\n function setDepositRevealAheadPeriod(uint32 _depositRevealAheadPeriod)\n external\n {\n self.depositRevealAheadPeriod = _depositRevealAheadPeriod;\n }\n}\n"
|
|
336
324
|
},
|
|
337
325
|
"contracts/test/GoerliLightRelay.sol": {
|
|
338
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport
|
|
326
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"../relay/LightRelay.sol\";\n\n/// @title Goerli Light Relay\n/// @notice GoerliLightRelay is a stub version of LightRelay intended to be\n/// used on the Goerli test network. It always returns `1` as the current\n/// and previous difficulties making it possible to bypass validation of\n/// difficulties of Bitcoin testnet blocks. Since difficulty in Bitcoin\n/// testnet often falls to `1` it would not be possible to validate\n/// blocks with the real LightRelay.\n/// @dev Since the returned difficulties are fixed and impossible to be modified\n/// with any setters, it is safe to use GoerliLightRelay without risking\n/// somebody may change the difficulties and block the test network.\n/// Notice that GoerliLightRelay is derived from LightRelay so that the two\n/// contracts have the same API and correct bindings can be generated.\ncontract GoerliLightRelay is LightRelay {\n /// @notice Returns `1` as the difficulty of the previous epoch.\n function getPrevEpochDifficulty() external view override returns (uint256) {\n return 1;\n }\n\n /// @notice Returns `1` as the difficulty of the current epoch.\n function getCurrentEpochDifficulty()\n external\n view\n override\n returns (uint256)\n {\n return 1;\n }\n}\n"
|
|
339
327
|
},
|
|
340
328
|
"contracts/test/HeartbeatStub.sol": {
|
|
341
329
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"../bridge/Heartbeat.sol\";\n\n/// @dev This is a contract implemented to test Heartbeat library directly.\ncontract HeartbeatStub {\n function isValidHeartbeatMessage(bytes calldata message)\n public\n pure\n returns (bool)\n {\n return Heartbeat.isValidHeartbeatMessage(message);\n }\n}\n"
|
|
@@ -358,9 +346,6 @@
|
|
|
358
346
|
"contracts/test/TestERC721.sol": {
|
|
359
347
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\n\ncontract TestERC721 is ERC721 {\n string public constant NAME = \"Test ERC721 Token\";\n string public constant SYMBOL = \"TT\";\n\n constructor() ERC721(NAME, SYMBOL) {}\n\n function mint(address to, uint256 tokenId) public {\n _mint(to, tokenId);\n }\n}\n"
|
|
360
348
|
},
|
|
361
|
-
"contracts/test/WormholeBridgeStub.sol": {
|
|
362
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"./TestERC20.sol\";\nimport \"../l2/L2WormholeGateway.sol\";\n\n/// @dev Stub contract used in L2WormholeGateway unit tests.\n/// Stub contract is used instead of a smock because of the token transfer\n/// that needs to happen in completeTransferWithPayload function.\ncontract WormholeBridgeStub is IWormholeTokenBridge {\n TestERC20 public wormholeToken;\n\n uint256 public transferAmount;\n bytes32 public receiverAddress;\n\n // Two simple events allowing to assert Wormhole bridge functions are\n // called.\n event WormholeBridgeStub_completeTransferWithPayload(bytes encodedVm);\n event WormholeBridgeStub_transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n );\n event WormholeBridgeStub_transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes payload\n );\n\n constructor(TestERC20 _wormholeToken) {\n wormholeToken = _wormholeToken;\n }\n\n function completeTransferWithPayload(bytes memory encodedVm)\n external\n returns (bytes memory)\n {\n emit WormholeBridgeStub_completeTransferWithPayload(encodedVm);\n wormholeToken.mint(msg.sender, transferAmount);\n\n // In a real implementation, encodedVm is parsed. To avoid copy-pasting\n // Wormhole code to this contract and then encoding parmaters in unit\n // tests, we allow to set the receiver address on the stub contract and\n // we return it here. The rest of the parameters does not matter.\n IWormholeTokenBridge.TransferWithPayload memory transfer = IWormholeTokenBridge\n .TransferWithPayload(\n 1, // payloadID\n 2, // amount\n 0x3000000000000000000000000000000000000000000000000000000000000000, // tokenAddress\n 4, // tokenChain\n 0x5000000000000000000000000000000000000000000000000000000000000000, // to\n 6, // toChain\n 0x7000000000000000000000000000000000000000000000000000000000000000, // fromAddress\n abi.encode(receiverAddress) // payload\n );\n\n return abi.encode(transfer);\n }\n\n function transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable returns (uint64 sequence) {\n emit WormholeBridgeStub_transferTokens(\n token,\n amount,\n recipientChain,\n recipient,\n arbiterFee,\n nonce\n );\n return 777;\n }\n\n function transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes memory payload\n ) external payable returns (uint64 sequence) {\n emit WormholeBridgeStub_transferTokensWithPayload(\n token,\n amount,\n recipientChain,\n recipient,\n nonce,\n payload\n );\n return 888;\n }\n\n function parseTransferWithPayload(bytes memory encoded)\n external\n pure\n returns (IWormholeTokenBridge.TransferWithPayload memory transfer)\n {\n return abi.decode(encoded, (IWormholeTokenBridge.TransferWithPayload));\n }\n\n function setTransferAmount(uint256 _transferAmount) external {\n transferAmount = _transferAmount;\n }\n\n function setReceiverAddress(bytes32 _receiverAddress) external {\n receiverAddress = _receiverAddress;\n }\n\n // Allows to mint Wormhole tBTC for depositWormholeTbtc unit tests.\n function mintWormholeToken(address to, uint256 amount) external {\n wormholeToken.mint(to, amount);\n }\n}\n"
|
|
363
|
-
},
|
|
364
349
|
"contracts/token/TBTC.sol": {
|
|
365
350
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\";\nimport \"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\";\n\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\n constructor() ERC20WithPermit(\"tBTC v2\", \"tBTC\") {}\n}\n"
|
|
366
351
|
},
|