@keep-network/tbtc-v2 0.1.1-dev.124 → 0.1.1-dev.126

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.
Files changed (116) hide show
  1. package/artifacts/BLS.json +1 -1
  2. package/artifacts/Bank.json +8 -8
  3. package/artifacts/BeaconAuthorization.json +1 -1
  4. package/artifacts/BeaconDkg.json +1 -1
  5. package/artifacts/BeaconDkgValidator.json +1 -1
  6. package/artifacts/BeaconInactivity.json +1 -1
  7. package/artifacts/BeaconSortitionPool.json +3 -3
  8. package/artifacts/Bridge.json +5 -5
  9. package/artifacts/BridgeGovernance.json +2 -2
  10. package/artifacts/BridgeGovernanceParameters.json +2 -2
  11. package/artifacts/Deposit.json +2 -2
  12. package/artifacts/DepositSweep.json +2 -2
  13. package/artifacts/EcdsaDkgValidator.json +1 -1
  14. package/artifacts/EcdsaInactivity.json +1 -1
  15. package/artifacts/EcdsaSortitionPool.json +3 -3
  16. package/artifacts/Fraud.json +2 -2
  17. package/artifacts/KeepRegistry.json +1 -1
  18. package/artifacts/KeepStake.json +2 -2
  19. package/artifacts/KeepToken.json +2 -2
  20. package/artifacts/KeepTokenStaking.json +1 -1
  21. package/artifacts/LightRelay.json +19 -19
  22. package/artifacts/MaintainerProxy.json +26 -26
  23. package/artifacts/MovingFunds.json +2 -2
  24. package/artifacts/NuCypherStakingEscrow.json +1 -1
  25. package/artifacts/NuCypherToken.json +2 -2
  26. package/artifacts/RandomBeacon.json +2 -2
  27. package/artifacts/RandomBeaconChaosnet.json +2 -2
  28. package/artifacts/RandomBeaconGovernance.json +2 -2
  29. package/artifacts/Redemption.json +2 -2
  30. package/artifacts/ReimbursementPool.json +2 -2
  31. package/artifacts/T.json +2 -2
  32. package/artifacts/TBTC.json +10 -10
  33. package/artifacts/TBTCToken.json +10 -10
  34. package/artifacts/TBTCVault.json +32 -32
  35. package/artifacts/TokenStaking.json +1 -1
  36. package/artifacts/TokenholderGovernor.json +9 -9
  37. package/artifacts/TokenholderTimelock.json +8 -8
  38. package/artifacts/VendingMachine.json +11 -11
  39. package/artifacts/VendingMachineKeep.json +1 -1
  40. package/artifacts/VendingMachineNuCypher.json +1 -1
  41. package/artifacts/WalletRegistry.json +5 -5
  42. package/artifacts/WalletRegistryGovernance.json +49 -49
  43. package/artifacts/Wallets.json +2 -2
  44. package/artifacts/solcInputs/{a78407b97d2748d5c91e02af49985262.json → 898e0f70d11502bb7c40e2859883a26e.json} +2 -2
  45. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  46. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  47. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
  48. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  49. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  50. package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
  51. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  52. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  53. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  54. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  55. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  56. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  57. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  58. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  59. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  60. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  61. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  62. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  63. package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
  64. package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +1 -1
  65. package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +1 -1
  66. package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +1 -1
  67. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  68. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
  69. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  70. package/build/contracts/vault/TBTCOptimisticMinting.sol/TBTCOptimisticMinting.dbg.json +1 -1
  71. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  72. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +2 -2
  73. package/contracts/vault/TBTCOptimisticMinting.sol +6 -2
  74. package/deploy/10_set_deposit_params.ts +6 -1
  75. package/deploy/91_genesis_relay.ts +35 -0
  76. package/deploy/92_retarget_relay.ts +119 -0
  77. package/deploy/93_authorize_relay_maintainer.ts +24 -0
  78. package/export/artifacts/@keep-network/ecdsa/contracts/EcdsaDkgValidator.sol/EcdsaDkgValidator.json +26 -26
  79. package/export/artifacts/@keep-network/ecdsa/contracts/WalletRegistry.sol/WalletRegistry.json +251 -251
  80. package/export/artifacts/@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol/EcdsaDkg.json +2 -2
  81. package/export/artifacts/@keep-network/ecdsa/contracts/libraries/EcdsaInactivity.sol/EcdsaInactivity.json +25 -25
  82. package/export/artifacts/@keep-network/random-beacon/contracts/ReimbursementPool.sol/ReimbursementPool.json +47 -47
  83. package/export/artifacts/@keep-network/sortition-pools/contracts/Chaosnet.sol/Chaosnet.json +21 -21
  84. package/export/artifacts/@keep-network/sortition-pools/contracts/Rewards.sol/Rewards.json +16 -16
  85. package/export/artifacts/@keep-network/sortition-pools/contracts/SortitionPool.sol/SortitionPool.json +204 -204
  86. package/export/artifacts/@keep-network/sortition-pools/contracts/SortitionTree.sol/SortitionTree.json +26 -26
  87. package/export/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json +42 -42
  88. package/export/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json +34 -34
  89. package/export/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json +74 -74
  90. package/export/artifacts/@openzeppelin/contracts/token/ERC721/ERC721.sol/ERC721.json +62 -62
  91. package/export/artifacts/@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol/ERC20WithPermit.json +77 -77
  92. package/export/artifacts/@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol/MisfundRecovery.json +38 -38
  93. package/export/artifacts/contracts/bank/Bank.sol/Bank.json +75 -75
  94. package/export/artifacts/contracts/bridge/Bridge.sol/Bridge.json +322 -322
  95. package/export/artifacts/contracts/bridge/VendingMachine.sol/VendingMachine.json +104 -104
  96. package/export/artifacts/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.json +147 -147
  97. package/export/artifacts/contracts/relay/LightRelay.sol/LightRelay.json +74 -74
  98. package/export/artifacts/contracts/test/BankStub.sol/BankStub.json +77 -77
  99. package/export/artifacts/contracts/test/BridgeStub.sol/BridgeStub.json +360 -360
  100. package/export/artifacts/contracts/test/HeartbeatStub.sol/HeartbeatStub.json +4 -4
  101. package/export/artifacts/contracts/test/LightRelayStub.sol/LightRelayStub.json +76 -76
  102. package/export/artifacts/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.json +7 -7
  103. package/export/artifacts/contracts/test/TestERC20.sol/TestERC20.json +83 -83
  104. package/export/artifacts/contracts/test/TestERC721.sol/TestERC721.json +72 -72
  105. package/export/artifacts/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.json +4 -4
  106. package/export/artifacts/contracts/test/TestRelay.sol/TestRelay.json +14 -14
  107. package/export/artifacts/contracts/token/TBTC.sol/TBTC.json +100 -100
  108. package/export/artifacts/contracts/vault/DonationVault.sol/DonationVault.json +19 -19
  109. package/export/artifacts/contracts/vault/TBTCVault.sol/TBTCVault.json +261 -261
  110. package/export/deploy/10_set_deposit_params.js +3 -2
  111. package/export/deploy/91_genesis_relay.js +72 -0
  112. package/export/deploy/92_retarget_relay.js +158 -0
  113. package/export/deploy/93_authorize_relay_maintainer.js +64 -0
  114. package/export/typechain/factories/TBTCVault__factory.js +1 -1
  115. package/export/typechain/factories/WalletRegistry__factory.js +1 -1
  116. package/package.json +2 -2
@@ -20,7 +20,7 @@
20
20
  "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 \"./IVault.sol\";\nimport \"./TBTCOptimisticMinting.sol\";\nimport \"../bank/Bank.sol\";\nimport \"../token/TBTC.sol\";\n\n/// @title TBTC application vault\n/// @notice TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of\n/// Bitcoin. It facilitates Bitcoin holders to act on the Ethereum\n/// blockchain and access the decentralized finance (DeFi) ecosystem.\n/// TBTC Vault mints and unmints TBTC based on Bitcoin balances in the\n/// Bank.\n/// @dev TBTC Vault is the owner of TBTC token contract and is the only contract\n/// minting the token.\ncontract TBTCVault is IVault, Ownable, TBTCOptimisticMinting {\n using SafeERC20 for IERC20;\n\n Bank public immutable bank;\n TBTC public immutable tbtcToken;\n\n /// @notice The address of a new TBTC vault. Set only when the upgrade\n /// process is pending. Once the upgrade gets finalized, the new\n /// TBTC vault will become an owner of TBTC token.\n address public newVault;\n /// @notice The timestamp at which an upgrade to a new TBTC vault was\n /// initiated. Set only when the upgrade process is pending.\n uint256 public upgradeInitiatedTimestamp;\n\n event Minted(address indexed to, uint256 amount);\n event Unminted(address indexed from, uint256 amount);\n\n event UpgradeInitiated(address newVault, uint256 timestamp);\n event UpgradeFinalized(address newVault);\n\n modifier onlyBank() {\n require(msg.sender == address(bank), \"Caller is not the Bank\");\n _;\n }\n\n constructor(\n Bank _bank,\n TBTC _tbtcToken,\n Bridge _bridge\n ) TBTCOptimisticMinting(_bridge) {\n require(\n address(_bank) != address(0),\n \"Bank can not be the zero address\"\n );\n\n require(\n address(_tbtcToken) != address(0),\n \"TBTC token can not be the zero address\"\n );\n\n bank = _bank;\n tbtcToken = _tbtcToken;\n }\n\n /// @notice Transfers the given `amount` of the Bank balance from caller\n /// to TBTC Vault, and mints `amount` of TBTC to the caller.\n /// @dev TBTC Vault must have an allowance for caller's balance in the Bank\n /// for at least `amount`.\n /// @param amount Amount of TBTC to mint.\n function mint(uint256 amount) external {\n require(\n bank.balanceOf(msg.sender) >= amount,\n \"Amount exceeds balance in the bank\"\n );\n _mint(msg.sender, amount);\n bank.transferBalanceFrom(msg.sender, address(this), amount);\n }\n\n /// @notice Transfers the given `amount` of the Bank balance from the caller\n /// to TBTC Vault and mints `amount` of TBTC to the caller.\n /// @dev Can only be called by the Bank via `approveBalanceAndCall`.\n /// @param owner The owner who approved their Bank balance.\n /// @param amount Amount of TBTC to mint.\n function receiveBalanceApproval(\n address owner,\n uint256 amount,\n bytes calldata\n ) external override onlyBank {\n require(\n bank.balanceOf(owner) >= amount,\n \"Amount exceeds balance in the bank\"\n );\n _mint(owner, amount);\n bank.transferBalanceFrom(owner, address(this), amount);\n }\n\n /// @notice Mints the same amount of TBTC as the deposited amount for each\n /// depositor in the array. Can only be called by the Bank after the\n /// Bridge swept deposits and Bank increased balance for the\n /// vault.\n /// @dev Fails if `depositors` array is empty. Expects the length of\n /// `depositors` and `depositedAmounts` is the same.\n function receiveBalanceIncrease(\n address[] calldata depositors,\n uint256[] calldata depositedAmounts\n ) external override onlyBank {\n require(depositors.length != 0, \"No depositors specified\");\n for (uint256 i = 0; i < depositors.length; i++) {\n address depositor = depositors[i];\n uint256 amount = depositedAmounts[i];\n _mint(depositor, repayOptimisticMintingDebt(depositor, amount));\n }\n }\n\n /// @notice Burns `amount` of TBTC from the caller's balance and transfers\n /// `amount` back to the caller's balance in the Bank.\n /// @dev Caller must have at least `amount` of TBTC approved to\n /// TBTC Vault.\n /// @param amount Amount of TBTC to unmint.\n function unmint(uint256 amount) external {\n _unmint(msg.sender, amount);\n }\n\n /// @notice Burns `amount` of TBTC from the caller's balance and transfers\n /// `amount` of Bank balance to the Bridge requesting redemption\n /// based on the provided `redemptionData`.\n /// @dev Caller must have at least `amount` of TBTC approved to\n /// TBTC Vault.\n /// @param amount Amount of TBTC to unmint and request to redeem in Bridge.\n /// @param redemptionData Redemption data in a format expected from\n /// `redemptionData` parameter of Bridge's `receiveBalanceApproval`\n /// function.\n function unmintAndRedeem(uint256 amount, bytes calldata redemptionData)\n external\n {\n _unmintAndRedeem(msg.sender, amount, redemptionData);\n }\n\n /// @notice Burns `amount` of TBTC from the caller's balance. If `extraData`\n /// is empty, transfers `amount` back to the caller's balance in the\n /// Bank. If `extraData` is not empty, requests redemption in the\n /// Bridge using the `extraData` as a `redemptionData` parameter to\n /// Bridge's `receiveBalanceApproval` function.\n /// @dev This function is doing the same as `unmint` or `unmintAndRedeem`\n /// (depending on `extraData` parameter) but it allows to execute\n /// unminting without a separate approval transaction. The function can\n /// be called only via `approveAndCall` of TBTC token.\n /// @param from TBTC token holder executing unminting.\n /// @param amount Amount of TBTC to unmint.\n /// @param token TBTC token address.\n /// @param extraData Redemption data in a format expected from\n /// `redemptionData` parameter of Bridge's `receiveBalanceApproval`\n /// function. If empty, `receiveApproval` is not requesting a\n /// redemption of Bank balance but is instead performing just TBTC\n /// unminting to a Bank balance.\n function receiveApproval(\n address from,\n uint256 amount,\n address token,\n bytes calldata extraData\n ) external {\n require(token == address(tbtcToken), \"Token is not TBTC\");\n require(msg.sender == token, \"Only TBTC caller allowed\");\n if (extraData.length == 0) {\n _unmint(from, amount);\n } else {\n _unmintAndRedeem(from, amount, extraData);\n }\n }\n\n /// @notice Initiates vault upgrade process. The upgrade process needs to be\n /// finalized with a call to `finalizeUpgrade` function after the\n /// `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can\n /// initiate the upgrade.\n /// @param _newVault The new vault address.\n function initiateUpgrade(address _newVault) external onlyOwner {\n require(_newVault != address(0), \"New vault address cannot be zero\");\n /* solhint-disable-next-line not-rely-on-time */\n emit UpgradeInitiated(_newVault, block.timestamp);\n /* solhint-disable-next-line not-rely-on-time */\n upgradeInitiatedTimestamp = block.timestamp;\n newVault = _newVault;\n }\n\n /// @notice Allows the governance to finalize vault upgrade process. The\n /// upgrade process needs to be first initiated with a call to\n /// `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass.\n /// Once the upgrade is finalized, the new vault becomes the owner\n /// of the TBTC token and receives the whole Bank balance of this\n /// vault.\n function finalizeUpgrade()\n external\n onlyOwner\n onlyAfterGovernanceDelay(upgradeInitiatedTimestamp)\n {\n emit UpgradeFinalized(newVault);\n // slither-disable-next-line reentrancy-no-eth\n tbtcToken.transferOwnership(newVault);\n bank.transferBalance(newVault, bank.balanceOf(address(this)));\n newVault = address(0);\n upgradeInitiatedTimestamp = 0;\n }\n\n /// @notice Allows the governance of the TBTCVault to recover any ERC20\n /// token sent mistakenly to the TBTC token contract address.\n /// @param token Address of the recovered ERC20 token contract.\n /// @param recipient Address the recovered token should be sent to.\n /// @param amount Recovered amount.\n function recoverERC20FromToken(\n IERC20 token,\n address recipient,\n uint256 amount\n ) external onlyOwner {\n tbtcToken.recoverERC20(token, recipient, amount);\n }\n\n /// @notice Allows the governance of the TBTCVault to recover any ERC721\n /// token sent mistakenly to the TBTC token contract address.\n /// @param token Address of the recovered ERC721 token contract.\n /// @param recipient Address the recovered token should be sent to.\n /// @param tokenId Identifier of the recovered token.\n /// @param data Additional data.\n function recoverERC721FromToken(\n IERC721 token,\n address recipient,\n uint256 tokenId,\n bytes calldata data\n ) external onlyOwner {\n tbtcToken.recoverERC721(token, recipient, tokenId, data);\n }\n\n /// @notice Allows the governance of the TBTCVault to recover any ERC20\n /// token sent - mistakenly or not - to the vault address. This\n /// function should be used to withdraw TBTC v1 tokens transferred\n /// to TBTCVault as a result of VendingMachine > TBTCVault upgrade.\n /// @param token Address of the recovered ERC20 token contract.\n /// @param recipient Address the recovered token should be sent to.\n /// @param amount Recovered amount.\n function recoverERC20(\n IERC20 token,\n address recipient,\n uint256 amount\n ) external onlyOwner {\n token.safeTransfer(recipient, amount);\n }\n\n /// @notice Allows the governance of the TBTCVault to recover any ERC721\n /// token sent mistakenly to the vault address.\n /// @param token Address of the recovered ERC721 token contract.\n /// @param recipient Address the recovered token should be sent to.\n /// @param tokenId Identifier of the recovered token.\n /// @param data Additional data.\n function recoverERC721(\n IERC721 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 // slither-disable-next-line calls-loop\n function _mint(address minter, uint256 amount) internal override {\n emit Minted(minter, amount);\n tbtcToken.mint(minter, amount);\n }\n\n function _unmint(address unminter, uint256 amount) internal {\n emit Unminted(unminter, amount);\n tbtcToken.burnFrom(unminter, amount);\n bank.transferBalance(unminter, amount);\n }\n\n function _unmintAndRedeem(\n address redeemer,\n uint256 amount,\n bytes calldata redemptionData\n ) internal {\n emit Unminted(redeemer, amount);\n tbtcToken.burnFrom(redeemer, amount);\n bank.approveBalanceAndCall(address(bridge), amount, redemptionData);\n }\n}\n"
21
21
  },
22
22
  "contracts/vault/TBTCOptimisticMinting.sol": {
23
- "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 \"../bridge/Bridge.sol\";\nimport \"../bridge/Deposit.sol\";\nimport \"../GovernanceUtils.sol\";\n\n/// @title TBTC Optimistic Minting\n/// @notice The Optimistic Minting mechanism allows to mint TBTC before\n/// `TBTCVault` receives the Bank balance. There are two permissioned\n/// sets in the system: Minters and Guardians, both set up in 1-of-n\n/// mode. Minters observe the revealed deposits and request minting TBTC.\n/// Any single Minter can perform this action. There is an\n/// `optimisticMintingDelay` between the time of the request from\n/// a Minter to the time TBTC is minted. During the time of the delay,\n/// any Guardian can cancel the minting.\n/// @dev This functionality is a part of `TBTCVault`. It is implemented in\n/// a separate abstract contract to achieve better separation of concerns\n/// and easier-to-follow code.\nabstract contract TBTCOptimisticMinting is Ownable {\n // Represents optimistic minting request for the given deposit revealed\n // to the Bridge.\n struct OptimisticMintingRequest {\n // UNIX timestamp at which the optimistic minting was requested.\n uint64 requestedAt;\n // UNIX timestamp at which the optimistic minting was finalized.\n // 0 if not yet finalized.\n uint64 finalizedAt;\n }\n\n /// @notice The time delay that needs to pass between initializing and\n /// finalizing the upgrade of governable parameters.\n uint256 public constant GOVERNANCE_DELAY = 24 hours;\n\n Bridge public immutable bridge;\n\n /// @notice Indicates if the optimistic minting has been paused. Only the\n /// Governance can pause optimistic minting. Note that the pause of\n /// the optimistic minting does not stop the standard minting flow\n /// where wallets sweep deposits.\n bool public isOptimisticMintingPaused;\n\n /// @notice Divisor used to compute the treasury fee taken from each\n /// optimistically minted deposit and transferred to the treasury\n /// upon finalization of the optimistic mint. This fee is computed\n /// as follows: `fee = amount / optimisticMintingFeeDivisor`.\n /// For example, if the fee needs to be 2%, the\n /// `optimisticMintingFeeDivisor` should be set to `50` because\n /// `1/50 = 0.02 = 2%`.\n /// The optimistic minting fee does not replace the deposit treasury\n /// fee cut by the Bridge. The optimistic fee is a percentage AFTER\n /// the treasury fee is cut:\n /// `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`\n uint32 public optimisticMintingFeeDivisor = 500; // 1/500 = 0.002 = 0.2%\n\n /// @notice The time that needs to pass between the moment the optimistic\n /// minting is requested and the moment optimistic minting is\n /// finalized with minting TBTC.\n uint32 public optimisticMintingDelay = 3 hours;\n\n /// @notice Indicates if the given address is a Minter. Only Minters can\n /// request optimistic minting.\n mapping(address => bool) public isMinter;\n\n /// @notice List of all Minters.\n /// @dev May be used to establish an order in which the Minters should\n /// request for an optimistic minting.\n address[] public minters;\n\n /// @notice Indicates if the given address is a Guardian. Only Guardians can\n /// cancel requested optimistic minting.\n mapping(address => bool) public isGuardian;\n\n /// @notice Collection of all revealed deposits for which the optimistic\n /// minting was requested. Indexed by a deposit key computed as\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\n mapping(uint256 => OptimisticMintingRequest)\n public optimisticMintingRequests;\n\n /// @notice Optimistic minting debt value per depositor's address. The debt\n /// represents the total value of all depositor's deposits revealed\n /// to the Bridge that has not been yet swept and led to the\n /// optimistic minting of TBTC. When `TBTCVault` sweeps a deposit,\n /// the debt is fully or partially paid off, no matter if that\n /// particular swept deposit was used for the optimistic minting or\n /// not.\n mapping(address => uint256) public optimisticMintingDebt;\n\n /// @notice New optimistic minting fee divisor value. Set only when the\n /// parameter update process is pending. Once the update gets\n // finalized, this will be the value of the divisor.\n uint32 public newOptimisticMintingFeeDivisor;\n /// @notice The timestamp at which the update of the optimistic minting fee\n /// divisor started. Zero if update is not in progress.\n uint256 public optimisticMintingFeeUpdateInitiatedTimestamp;\n\n /// @notice New optimistic minting delay value. Set only when the parameter\n /// update process is pending. Once the update gets finalized, this\n // will be the value of the delay.\n uint32 public newOptimisticMintingDelay;\n /// @notice The timestamp at which the update of the optimistic minting\n /// delay started. Zero if update is not in progress.\n uint256 public optimisticMintingDelayUpdateInitiatedTimestamp;\n\n event OptimisticMintingRequested(\n address indexed minter,\n uint256 indexed depositKey,\n address indexed depositor,\n uint256 amount,\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n );\n event OptimisticMintingFinalized(\n address indexed minter,\n uint256 indexed depositKey,\n address indexed depositor,\n uint256 optimisticMintingDebt\n );\n event OptimisticMintingCancelled(\n address indexed guardian,\n uint256 indexed depositKey\n );\n event OptimisticMintingDebtRepaid(\n address indexed depositor,\n uint256 optimisticMintingDebt\n );\n event MinterAdded(address indexed minter);\n event MinterRemoved(address indexed minter);\n event GuardianAdded(address indexed guardian);\n event GuardianRemoved(address indexed guardian);\n event OptimisticMintingPaused();\n event OptimisticMintingUnpaused();\n\n event OptimisticMintingFeeUpdateStarted(\n uint32 newOptimisticMintingFeeDivisor\n );\n event OptimisticMintingFeeUpdated(uint32 newOptimisticMintingFeeDivisor);\n\n event OptimisticMintingDelayUpdateStarted(uint32 newOptimisticMintingDelay);\n event OptimisticMintingDelayUpdated(uint32 newOptimisticMintingDelay);\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 modifier onlyOwnerOrGuardian() {\n require(\n owner() == msg.sender || isGuardian[msg.sender],\n \"Caller is not the owner or guardian\"\n );\n _;\n }\n\n modifier whenOptimisticMintingNotPaused() {\n require(!isOptimisticMintingPaused, \"Optimistic minting paused\");\n _;\n }\n\n modifier onlyAfterGovernanceDelay(uint256 updateInitiatedTimestamp) {\n GovernanceUtils.onlyAfterGovernanceDelay(\n updateInitiatedTimestamp,\n GOVERNANCE_DELAY\n );\n _;\n }\n\n constructor(Bridge _bridge) {\n require(\n address(_bridge) != address(0),\n \"Bridge can not be the zero address\"\n );\n\n bridge = _bridge;\n }\n\n /// @dev Mints the given amount of TBTC to the given depositor's address.\n /// Implemented by TBTCVault.\n function _mint(address minter, uint256 amount) internal virtual;\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 a Minter to request for an optimistic minting of TBTC.\n /// The following conditions must be met:\n /// - There is no optimistic minting request for the deposit,\n /// finalized or not.\n /// - The deposit with the given Bitcoin funding transaction hash\n /// and output index has been revealed to the Bridge.\n /// - The deposit has not been swept yet.\n /// - The deposit is targeted into the TBTCVault.\n /// - The optimistic minting is not paused.\n /// After calling this function, the Minter has to wait for\n /// `optimisticMintingDelay` before finalizing the mint with a call\n /// to finalizeOptimisticMint.\n /// @dev The deposit done on the Bitcoin side must be revealed early enough\n /// to the Bridge on Ethereum to pass the Bridge's validation. The\n /// validation passes successfully only if the deposit reveal is done\n /// respectively earlier than the moment when the deposit refund\n /// locktime is reached, i.e. the deposit becomes refundable. It may\n /// happen that the wallet does not sweep a revealed deposit and one of\n /// the Minters requests an optimistic mint for that deposit just\n /// before the locktime is reached. Guardians must cancel optimistic\n /// minting for this deposit because the wallet will not be able to\n /// sweep it. The on-chain optimistic minting code does not perform any\n /// validation for gas efficiency: it would have to perform the same\n /// validation as `validateDepositRefundLocktime` and expect the entire\n /// `DepositRevealInfo` to be passed to assemble the expected script\n /// hash on-chain. Guardians must validate if the deposit happened on\n /// Bitcoin, that the script hash has the expected format, and that the\n /// wallet is an active one so they can also validate the time left for\n /// the refund.\n function requestOptimisticMint(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) external onlyMinter whenOptimisticMintingNotPaused {\n uint256 depositKey = calculateDepositKey(\n fundingTxHash,\n fundingOutputIndex\n );\n\n OptimisticMintingRequest storage request = optimisticMintingRequests[\n depositKey\n ];\n require(\n request.requestedAt == 0,\n \"Optimistic minting already requested for the deposit\"\n );\n\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\n\n require(deposit.revealedAt != 0, \"The deposit has not been revealed\");\n require(deposit.sweptAt == 0, \"The deposit is already swept\");\n require(deposit.vault == address(this), \"Unexpected vault address\");\n\n /* solhint-disable-next-line not-rely-on-time */\n request.requestedAt = uint64(block.timestamp);\n\n emit OptimisticMintingRequested(\n msg.sender,\n depositKey,\n deposit.depositor,\n deposit.amount,\n fundingTxHash,\n fundingOutputIndex\n );\n }\n\n /// @notice Allows a Minter to finalize previously requested optimistic\n /// minting. The following conditions must be met:\n /// - The optimistic minting has been requested for the given\n /// deposit.\n /// - The deposit has not been swept yet.\n /// - At least `optimisticMintingDelay` passed since the optimistic\n /// minting was requested for the given deposit.\n /// - The optimistic minting has not been finalized earlier for the\n /// given deposit.\n /// - The optimistic minting request for the given deposit has not\n /// been canceled by a Guardian.\n /// - The optimistic minting is not paused.\n /// This function mints TBTC and increases `optimisticMintingDebt`\n /// for the given depositor. The optimistic minting request is\n /// marked as finalized.\n function finalizeOptimisticMint(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) external onlyMinter whenOptimisticMintingNotPaused {\n uint256 depositKey = calculateDepositKey(\n fundingTxHash,\n fundingOutputIndex\n );\n\n OptimisticMintingRequest storage request = optimisticMintingRequests[\n depositKey\n ];\n require(\n request.requestedAt != 0,\n \"Optimistic minting not requested for the deposit\"\n );\n require(\n request.finalizedAt == 0,\n \"Optimistic minting already finalized for the deposit\"\n );\n\n require(\n /* solhint-disable-next-line not-rely-on-time */\n block.timestamp > request.requestedAt + optimisticMintingDelay,\n \"Optimistic minting delay has not passed yet\"\n );\n\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\n require(deposit.sweptAt == 0, \"The deposit is already swept\");\n\n // Bridge, when sweeping, cuts a deposit treasury fee and splits\n // Bitcoin miner fee for the sweep transaction evenly between the\n // depositors in the sweep.\n //\n // When tokens are optimistically minted, we do not know what the\n // Bitcoin miner fee for the sweep transaction will look like.\n // The Bitcoin miner fee is ignored. When sweeping, the miner fee is\n // subtracted so the optimisticMintingDebt may stay non-zero after the\n // deposit is swept.\n //\n // This imbalance is supposed to be solved by a donation to the Bridge.\n uint256 amountToMint = deposit.amount - deposit.treasuryFee;\n\n // The Optimistic Minting mechanism may additionally cut a fee from the\n // amount that is left after deducting the Bridge deposit treasury fee.\n // Think of this fee as an extra payment for faster processing of\n // deposits. One does not need to use the Optimistic Minting mechanism\n // and they may wait for the Bridge to sweep their deposit if they do\n // not want to pay the Optimistic Minting fee.\n uint256 optimisticMintFee = optimisticMintingFeeDivisor > 0\n ? amountToMint / optimisticMintingFeeDivisor\n : 0;\n amountToMint -= optimisticMintFee;\n\n uint256 newDebt = optimisticMintingDebt[deposit.depositor] +\n amountToMint;\n optimisticMintingDebt[deposit.depositor] = newDebt;\n\n _mint(deposit.depositor, amountToMint);\n if (optimisticMintFee > 0) {\n _mint(bridge.treasury(), optimisticMintFee);\n }\n\n /* solhint-disable-next-line not-rely-on-time */\n request.finalizedAt = uint64(block.timestamp);\n\n emit OptimisticMintingFinalized(\n msg.sender,\n depositKey,\n deposit.depositor,\n newDebt\n );\n }\n\n /// @notice Allows a Guardian to cancel optimistic minting request. The\n /// following conditions must be met:\n /// - The optimistic minting request for the given deposit exists.\n /// - The optimistic minting request for the given deposit has not\n /// been finalized yet.\n /// Optimistic minting request is removed. It is possible to request\n /// optimistic minting again for the same deposit later.\n /// @dev Guardians must validate the following conditions for every deposit\n /// for which the optimistic minting was requested:\n /// - The deposit happened on Bitcoin side and it has enough\n /// confirmations.\n /// - The optimistic minting has been requested early enough so that\n /// the wallet has enough time to sweep the deposit.\n /// - The wallet is an active one and it does perform sweeps or it will\n /// perform sweeps once the sweeps are activated.\n function cancelOptimisticMint(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) external onlyGuardian {\n uint256 depositKey = calculateDepositKey(\n fundingTxHash,\n fundingOutputIndex\n );\n\n OptimisticMintingRequest storage request = optimisticMintingRequests[\n depositKey\n ];\n require(\n request.requestedAt != 0,\n \"Optimistic minting not requested for the deposit\"\n );\n require(\n request.finalizedAt == 0,\n \"Optimistic minting already finalized for the deposit\"\n );\n\n // Delete it. It allows to request optimistic minting for the given\n // deposit again. Useful in case of an errant Guardian.\n delete optimisticMintingRequests[depositKey];\n\n emit OptimisticMintingCancelled(msg.sender, depositKey);\n }\n\n /// @notice Adds the address to the Minter list.\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 Minter list.\n function removeMinter(address minter) external onlyOwnerOrGuardian {\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 Guardian set.\n function addGuardian(address guardian) external onlyOwner {\n require(!isGuardian[guardian], \"This address is already a guardian\");\n isGuardian[guardian] = true;\n emit GuardianAdded(guardian);\n }\n\n /// @notice Removes the address from the Guardian set.\n function removeGuardian(address guardian) external onlyOwner {\n require(isGuardian[guardian], \"This address is not a guardian\");\n delete isGuardian[guardian];\n emit GuardianRemoved(guardian);\n }\n\n /// @notice Pauses the optimistic minting. Note that the pause of the\n /// optimistic minting does not stop the standard minting flow\n /// where wallets sweep deposits.\n function pauseOptimisticMinting() external onlyOwner {\n require(\n !isOptimisticMintingPaused,\n \"Optimistic minting already paused\"\n );\n isOptimisticMintingPaused = true;\n emit OptimisticMintingPaused();\n }\n\n /// @notice Unpauses the optimistic minting.\n function unpauseOptimisticMinting() external onlyOwner {\n require(isOptimisticMintingPaused, \"Optimistic minting is not paused\");\n isOptimisticMintingPaused = false;\n emit OptimisticMintingUnpaused();\n }\n\n /// @notice Begins the process of updating optimistic minting fee.\n /// The fee is computed as follows:\n /// `fee = amount / optimisticMintingFeeDivisor`.\n /// For example, if the fee needs to be 2% of each deposit,\n /// the `optimisticMintingFeeDivisor` should be set to `50` because\n /// `1/50 = 0.02 = 2%`.\n /// @dev See the documentation for optimisticMintingFeeDivisor.\n function beginOptimisticMintingFeeUpdate(\n uint32 _newOptimisticMintingFeeDivisor\n ) external onlyOwner {\n /* solhint-disable-next-line not-rely-on-time */\n optimisticMintingFeeUpdateInitiatedTimestamp = block.timestamp;\n newOptimisticMintingFeeDivisor = _newOptimisticMintingFeeDivisor;\n emit OptimisticMintingFeeUpdateStarted(_newOptimisticMintingFeeDivisor);\n }\n\n /// @notice Finalizes the update process of the optimistic minting fee.\n function finalizeOptimisticMintingFeeUpdate()\n external\n onlyOwner\n onlyAfterGovernanceDelay(optimisticMintingFeeUpdateInitiatedTimestamp)\n {\n optimisticMintingFeeDivisor = newOptimisticMintingFeeDivisor;\n emit OptimisticMintingFeeUpdated(newOptimisticMintingFeeDivisor);\n\n newOptimisticMintingFeeDivisor = 0;\n optimisticMintingFeeUpdateInitiatedTimestamp = 0;\n }\n\n /// @notice Begins the process of updating optimistic minting delay.\n function beginOptimisticMintingDelayUpdate(\n uint32 _newOptimisticMintingDelay\n ) external onlyOwner {\n /* solhint-disable-next-line not-rely-on-time */\n optimisticMintingDelayUpdateInitiatedTimestamp = block.timestamp;\n newOptimisticMintingDelay = _newOptimisticMintingDelay;\n emit OptimisticMintingDelayUpdateStarted(_newOptimisticMintingDelay);\n }\n\n /// @notice Finalizes the update process of the optimistic minting delay.\n function finalizeOptimisticMintingDelayUpdate()\n external\n onlyOwner\n onlyAfterGovernanceDelay(optimisticMintingDelayUpdateInitiatedTimestamp)\n {\n optimisticMintingDelay = newOptimisticMintingDelay;\n emit OptimisticMintingDelayUpdated(newOptimisticMintingDelay);\n\n newOptimisticMintingDelay = 0;\n optimisticMintingDelayUpdateInitiatedTimestamp = 0;\n }\n\n /// @notice Calculates deposit key the same way as the Bridge contract.\n /// The deposit key is computed as\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\n function calculateDepositKey(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) public pure returns (uint256) {\n return\n uint256(\n keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex))\n );\n }\n\n /// @notice Used by `TBTCVault.receiveBalanceIncrease` to repay the optimistic\n /// minting debt before TBTC is minted. When optimistic minting is\n /// finalized, debt equal to the value of the deposit being\n /// a subject of the optimistic minting is incurred. When `TBTCVault`\n /// sweeps a deposit, the debt is fully or partially paid off, no\n /// matter if that particular deposit was used for the optimistic\n /// minting or not.\n /// @dev See `TBTCVault.receiveBalanceIncrease`\n /// @param depositor The depositor whose balance increase is received.\n /// @param amount The balance increase amount for the depositor received.\n /// @return The TBTC amount that should be minted after paying off the\n /// optimistic minting debt.\n function repayOptimisticMintingDebt(address depositor, uint256 amount)\n internal\n returns (uint256)\n {\n uint256 debt = optimisticMintingDebt[depositor];\n if (debt == 0) {\n return amount;\n }\n\n if (amount > debt) {\n optimisticMintingDebt[depositor] = 0;\n emit OptimisticMintingDebtRepaid(depositor, 0);\n return amount - debt;\n } else {\n optimisticMintingDebt[depositor] = debt - amount;\n emit OptimisticMintingDebtRepaid(depositor, debt - amount);\n return 0;\n }\n }\n}\n"
23
+ "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 \"../bridge/Bridge.sol\";\nimport \"../bridge/Deposit.sol\";\nimport \"../GovernanceUtils.sol\";\n\n/// @title TBTC Optimistic Minting\n/// @notice The Optimistic Minting mechanism allows to mint TBTC before\n/// `TBTCVault` receives the Bank balance. There are two permissioned\n/// sets in the system: Minters and Guardians, both set up in 1-of-n\n/// mode. Minters observe the revealed deposits and request minting TBTC.\n/// Any single Minter can perform this action. There is an\n/// `optimisticMintingDelay` between the time of the request from\n/// a Minter to the time TBTC is minted. During the time of the delay,\n/// any Guardian can cancel the minting.\n/// @dev This functionality is a part of `TBTCVault`. It is implemented in\n/// a separate abstract contract to achieve better separation of concerns\n/// and easier-to-follow code.\nabstract contract TBTCOptimisticMinting is Ownable {\n // Represents optimistic minting request for the given deposit revealed\n // to the Bridge.\n struct OptimisticMintingRequest {\n // UNIX timestamp at which the optimistic minting was requested.\n uint64 requestedAt;\n // UNIX timestamp at which the optimistic minting was finalized.\n // 0 if not yet finalized.\n uint64 finalizedAt;\n }\n\n /// @notice The time delay that needs to pass between initializing and\n /// finalizing the upgrade of governable parameters.\n uint256 public constant GOVERNANCE_DELAY = 24 hours;\n\n Bridge public immutable bridge;\n\n /// @notice Indicates if the optimistic minting has been paused. Only the\n /// Governance can pause optimistic minting. Note that the pause of\n /// the optimistic minting does not stop the standard minting flow\n /// where wallets sweep deposits.\n bool public isOptimisticMintingPaused;\n\n /// @notice Divisor used to compute the treasury fee taken from each\n /// optimistically minted deposit and transferred to the treasury\n /// upon finalization of the optimistic mint. This fee is computed\n /// as follows: `fee = amount / optimisticMintingFeeDivisor`.\n /// For example, if the fee needs to be 2%, the\n /// `optimisticMintingFeeDivisor` should be set to `50` because\n /// `1/50 = 0.02 = 2%`.\n /// The optimistic minting fee does not replace the deposit treasury\n /// fee cut by the Bridge. The optimistic fee is a percentage AFTER\n /// the treasury fee is cut:\n /// `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`\n uint32 public optimisticMintingFeeDivisor = 500; // 1/500 = 0.002 = 0.2%\n\n /// @notice The time that needs to pass between the moment the optimistic\n /// minting is requested and the moment optimistic minting is\n /// finalized with minting TBTC.\n uint32 public optimisticMintingDelay = 3 hours;\n\n /// @notice Indicates if the given address is a Minter. Only Minters can\n /// request optimistic minting.\n mapping(address => bool) public isMinter;\n\n /// @notice List of all Minters.\n /// @dev May be used to establish an order in which the Minters should\n /// request for an optimistic minting.\n address[] public minters;\n\n /// @notice Indicates if the given address is a Guardian. Only Guardians can\n /// cancel requested optimistic minting.\n mapping(address => bool) public isGuardian;\n\n /// @notice Collection of all revealed deposits for which the optimistic\n /// minting was requested. Indexed by a deposit key computed as\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\n mapping(uint256 => OptimisticMintingRequest)\n public optimisticMintingRequests;\n\n /// @notice Optimistic minting debt value per depositor's address. The debt\n /// represents the total value of all depositor's deposits revealed\n /// to the Bridge that has not been yet swept and led to the\n /// optimistic minting of TBTC. When `TBTCVault` sweeps a deposit,\n /// the debt is fully or partially paid off, no matter if that\n /// particular swept deposit was used for the optimistic minting or\n /// not.\n mapping(address => uint256) public optimisticMintingDebt;\n\n /// @notice New optimistic minting fee divisor value. Set only when the\n /// parameter update process is pending. Once the update gets\n // finalized, this will be the value of the divisor.\n uint32 public newOptimisticMintingFeeDivisor;\n /// @notice The timestamp at which the update of the optimistic minting fee\n /// divisor started. Zero if update is not in progress.\n uint256 public optimisticMintingFeeUpdateInitiatedTimestamp;\n\n /// @notice New optimistic minting delay value. Set only when the parameter\n /// update process is pending. Once the update gets finalized, this\n // will be the value of the delay.\n uint32 public newOptimisticMintingDelay;\n /// @notice The timestamp at which the update of the optimistic minting\n /// delay started. Zero if update is not in progress.\n uint256 public optimisticMintingDelayUpdateInitiatedTimestamp;\n\n event OptimisticMintingRequested(\n address indexed minter,\n uint256 indexed depositKey,\n address indexed depositor,\n uint256 amount,\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n );\n event OptimisticMintingFinalized(\n address indexed minter,\n uint256 indexed depositKey,\n address indexed depositor,\n uint256 optimisticMintingDebt\n );\n event OptimisticMintingCancelled(\n address indexed guardian,\n uint256 indexed depositKey\n );\n event OptimisticMintingDebtRepaid(\n address indexed depositor,\n uint256 optimisticMintingDebt\n );\n event MinterAdded(address indexed minter);\n event MinterRemoved(address indexed minter);\n event GuardianAdded(address indexed guardian);\n event GuardianRemoved(address indexed guardian);\n event OptimisticMintingPaused();\n event OptimisticMintingUnpaused();\n\n event OptimisticMintingFeeUpdateStarted(\n uint32 newOptimisticMintingFeeDivisor\n );\n event OptimisticMintingFeeUpdated(uint32 newOptimisticMintingFeeDivisor);\n\n event OptimisticMintingDelayUpdateStarted(uint32 newOptimisticMintingDelay);\n event OptimisticMintingDelayUpdated(uint32 newOptimisticMintingDelay);\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 modifier onlyOwnerOrGuardian() {\n require(\n owner() == msg.sender || isGuardian[msg.sender],\n \"Caller is not the owner or guardian\"\n );\n _;\n }\n\n modifier whenOptimisticMintingNotPaused() {\n require(!isOptimisticMintingPaused, \"Optimistic minting paused\");\n _;\n }\n\n modifier onlyAfterGovernanceDelay(uint256 updateInitiatedTimestamp) {\n GovernanceUtils.onlyAfterGovernanceDelay(\n updateInitiatedTimestamp,\n GOVERNANCE_DELAY\n );\n _;\n }\n\n constructor(Bridge _bridge) {\n require(\n address(_bridge) != address(0),\n \"Bridge can not be the zero address\"\n );\n\n bridge = _bridge;\n }\n\n /// @dev Mints the given amount of TBTC to the given depositor's address.\n /// Implemented by TBTCVault.\n function _mint(address minter, uint256 amount) internal virtual;\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 a Minter to request for an optimistic minting of TBTC.\n /// The following conditions must be met:\n /// - There is no optimistic minting request for the deposit,\n /// finalized or not.\n /// - The deposit with the given Bitcoin funding transaction hash\n /// and output index has been revealed to the Bridge.\n /// - The deposit has not been swept yet.\n /// - The deposit is targeted into the TBTCVault.\n /// - The optimistic minting is not paused.\n /// After calling this function, the Minter has to wait for\n /// `optimisticMintingDelay` before finalizing the mint with a call\n /// to finalizeOptimisticMint.\n /// @dev The deposit done on the Bitcoin side must be revealed early enough\n /// to the Bridge on Ethereum to pass the Bridge's validation. The\n /// validation passes successfully only if the deposit reveal is done\n /// respectively earlier than the moment when the deposit refund\n /// locktime is reached, i.e. the deposit becomes refundable. It may\n /// happen that the wallet does not sweep a revealed deposit and one of\n /// the Minters requests an optimistic mint for that deposit just\n /// before the locktime is reached. Guardians must cancel optimistic\n /// minting for this deposit because the wallet will not be able to\n /// sweep it. The on-chain optimistic minting code does not perform any\n /// validation for gas efficiency: it would have to perform the same\n /// validation as `validateDepositRefundLocktime` and expect the entire\n /// `DepositRevealInfo` to be passed to assemble the expected script\n /// hash on-chain. Guardians must validate if the deposit happened on\n /// Bitcoin, that the script hash has the expected format, and that the\n /// wallet is an active one so they can also validate the time left for\n /// the refund.\n function requestOptimisticMint(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) external onlyMinter whenOptimisticMintingNotPaused {\n uint256 depositKey = calculateDepositKey(\n fundingTxHash,\n fundingOutputIndex\n );\n\n OptimisticMintingRequest storage request = optimisticMintingRequests[\n depositKey\n ];\n require(\n request.requestedAt == 0,\n \"Optimistic minting already requested for the deposit\"\n );\n\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\n\n require(deposit.revealedAt != 0, \"The deposit has not been revealed\");\n require(deposit.sweptAt == 0, \"The deposit is already swept\");\n require(deposit.vault == address(this), \"Unexpected vault address\");\n\n /* solhint-disable-next-line not-rely-on-time */\n request.requestedAt = uint64(block.timestamp);\n\n emit OptimisticMintingRequested(\n msg.sender,\n depositKey,\n deposit.depositor,\n deposit.amount,\n fundingTxHash,\n fundingOutputIndex\n );\n }\n\n /// @notice Allows a Minter to finalize previously requested optimistic\n /// minting. The following conditions must be met:\n /// - The optimistic minting has been requested for the given\n /// deposit.\n /// - The deposit has not been swept yet.\n /// - At least `optimisticMintingDelay` passed since the optimistic\n /// minting was requested for the given deposit.\n /// - The optimistic minting has not been finalized earlier for the\n /// given deposit.\n /// - The optimistic minting request for the given deposit has not\n /// been canceled by a Guardian.\n /// - The optimistic minting is not paused.\n /// This function mints TBTC and increases `optimisticMintingDebt`\n /// for the given depositor. The optimistic minting request is\n /// marked as finalized.\n function finalizeOptimisticMint(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) external onlyMinter whenOptimisticMintingNotPaused {\n uint256 depositKey = calculateDepositKey(\n fundingTxHash,\n fundingOutputIndex\n );\n\n OptimisticMintingRequest storage request = optimisticMintingRequests[\n depositKey\n ];\n require(\n request.requestedAt != 0,\n \"Optimistic minting not requested for the deposit\"\n );\n require(\n request.finalizedAt == 0,\n \"Optimistic minting already finalized for the deposit\"\n );\n\n require(\n /* solhint-disable-next-line not-rely-on-time */\n block.timestamp > request.requestedAt + optimisticMintingDelay,\n \"Optimistic minting delay has not passed yet\"\n );\n\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\n require(deposit.sweptAt == 0, \"The deposit is already swept\");\n\n // Bridge, when sweeping, cuts a deposit treasury fee and splits\n // Bitcoin miner fee for the sweep transaction evenly between the\n // depositors in the sweep.\n //\n // When tokens are optimistically minted, we do not know what the\n // Bitcoin miner fee for the sweep transaction will look like.\n // The Bitcoin miner fee is ignored. When sweeping, the miner fee is\n // subtracted so the optimisticMintingDebt may stay non-zero after the\n // deposit is swept.\n //\n // This imbalance is supposed to be solved by a donation to the Bridge.\n uint256 amountToMint = deposit.amount - deposit.treasuryFee;\n\n // The Optimistic Minting mechanism may additionally cut a fee from the\n // amount that is left after deducting the Bridge deposit treasury fee.\n // Think of this fee as an extra payment for faster processing of\n // deposits. One does not need to use the Optimistic Minting mechanism\n // and they may wait for the Bridge to sweep their deposit if they do\n // not want to pay the Optimistic Minting fee.\n uint256 optimisticMintFee = optimisticMintingFeeDivisor > 0\n ? amountToMint / optimisticMintingFeeDivisor\n : 0;\n\n // Both the optimistic minting fee and the share that goes to the\n // depositor are optimistically minted. All TBTC that is optimistically\n // minted should be added to the optimistic minting debt. When the\n // deposit is swept, it is paying off both the depositor's share and the\n // treasury's share (optimistic minting fee).\n uint256 newDebt = optimisticMintingDebt[deposit.depositor] +\n amountToMint;\n optimisticMintingDebt[deposit.depositor] = newDebt;\n\n _mint(deposit.depositor, amountToMint - optimisticMintFee);\n if (optimisticMintFee > 0) {\n _mint(bridge.treasury(), optimisticMintFee);\n }\n\n /* solhint-disable-next-line not-rely-on-time */\n request.finalizedAt = uint64(block.timestamp);\n\n emit OptimisticMintingFinalized(\n msg.sender,\n depositKey,\n deposit.depositor,\n newDebt\n );\n }\n\n /// @notice Allows a Guardian to cancel optimistic minting request. The\n /// following conditions must be met:\n /// - The optimistic minting request for the given deposit exists.\n /// - The optimistic minting request for the given deposit has not\n /// been finalized yet.\n /// Optimistic minting request is removed. It is possible to request\n /// optimistic minting again for the same deposit later.\n /// @dev Guardians must validate the following conditions for every deposit\n /// for which the optimistic minting was requested:\n /// - The deposit happened on Bitcoin side and it has enough\n /// confirmations.\n /// - The optimistic minting has been requested early enough so that\n /// the wallet has enough time to sweep the deposit.\n /// - The wallet is an active one and it does perform sweeps or it will\n /// perform sweeps once the sweeps are activated.\n function cancelOptimisticMint(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) external onlyGuardian {\n uint256 depositKey = calculateDepositKey(\n fundingTxHash,\n fundingOutputIndex\n );\n\n OptimisticMintingRequest storage request = optimisticMintingRequests[\n depositKey\n ];\n require(\n request.requestedAt != 0,\n \"Optimistic minting not requested for the deposit\"\n );\n require(\n request.finalizedAt == 0,\n \"Optimistic minting already finalized for the deposit\"\n );\n\n // Delete it. It allows to request optimistic minting for the given\n // deposit again. Useful in case of an errant Guardian.\n delete optimisticMintingRequests[depositKey];\n\n emit OptimisticMintingCancelled(msg.sender, depositKey);\n }\n\n /// @notice Adds the address to the Minter list.\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 Minter list.\n function removeMinter(address minter) external onlyOwnerOrGuardian {\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 Guardian set.\n function addGuardian(address guardian) external onlyOwner {\n require(!isGuardian[guardian], \"This address is already a guardian\");\n isGuardian[guardian] = true;\n emit GuardianAdded(guardian);\n }\n\n /// @notice Removes the address from the Guardian set.\n function removeGuardian(address guardian) external onlyOwner {\n require(isGuardian[guardian], \"This address is not a guardian\");\n delete isGuardian[guardian];\n emit GuardianRemoved(guardian);\n }\n\n /// @notice Pauses the optimistic minting. Note that the pause of the\n /// optimistic minting does not stop the standard minting flow\n /// where wallets sweep deposits.\n function pauseOptimisticMinting() external onlyOwner {\n require(\n !isOptimisticMintingPaused,\n \"Optimistic minting already paused\"\n );\n isOptimisticMintingPaused = true;\n emit OptimisticMintingPaused();\n }\n\n /// @notice Unpauses the optimistic minting.\n function unpauseOptimisticMinting() external onlyOwner {\n require(isOptimisticMintingPaused, \"Optimistic minting is not paused\");\n isOptimisticMintingPaused = false;\n emit OptimisticMintingUnpaused();\n }\n\n /// @notice Begins the process of updating optimistic minting fee.\n /// The fee is computed as follows:\n /// `fee = amount / optimisticMintingFeeDivisor`.\n /// For example, if the fee needs to be 2% of each deposit,\n /// the `optimisticMintingFeeDivisor` should be set to `50` because\n /// `1/50 = 0.02 = 2%`.\n /// @dev See the documentation for optimisticMintingFeeDivisor.\n function beginOptimisticMintingFeeUpdate(\n uint32 _newOptimisticMintingFeeDivisor\n ) external onlyOwner {\n /* solhint-disable-next-line not-rely-on-time */\n optimisticMintingFeeUpdateInitiatedTimestamp = block.timestamp;\n newOptimisticMintingFeeDivisor = _newOptimisticMintingFeeDivisor;\n emit OptimisticMintingFeeUpdateStarted(_newOptimisticMintingFeeDivisor);\n }\n\n /// @notice Finalizes the update process of the optimistic minting fee.\n function finalizeOptimisticMintingFeeUpdate()\n external\n onlyOwner\n onlyAfterGovernanceDelay(optimisticMintingFeeUpdateInitiatedTimestamp)\n {\n optimisticMintingFeeDivisor = newOptimisticMintingFeeDivisor;\n emit OptimisticMintingFeeUpdated(newOptimisticMintingFeeDivisor);\n\n newOptimisticMintingFeeDivisor = 0;\n optimisticMintingFeeUpdateInitiatedTimestamp = 0;\n }\n\n /// @notice Begins the process of updating optimistic minting delay.\n function beginOptimisticMintingDelayUpdate(\n uint32 _newOptimisticMintingDelay\n ) external onlyOwner {\n /* solhint-disable-next-line not-rely-on-time */\n optimisticMintingDelayUpdateInitiatedTimestamp = block.timestamp;\n newOptimisticMintingDelay = _newOptimisticMintingDelay;\n emit OptimisticMintingDelayUpdateStarted(_newOptimisticMintingDelay);\n }\n\n /// @notice Finalizes the update process of the optimistic minting delay.\n function finalizeOptimisticMintingDelayUpdate()\n external\n onlyOwner\n onlyAfterGovernanceDelay(optimisticMintingDelayUpdateInitiatedTimestamp)\n {\n optimisticMintingDelay = newOptimisticMintingDelay;\n emit OptimisticMintingDelayUpdated(newOptimisticMintingDelay);\n\n newOptimisticMintingDelay = 0;\n optimisticMintingDelayUpdateInitiatedTimestamp = 0;\n }\n\n /// @notice Calculates deposit key the same way as the Bridge contract.\n /// The deposit key is computed as\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\n function calculateDepositKey(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) public pure returns (uint256) {\n return\n uint256(\n keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex))\n );\n }\n\n /// @notice Used by `TBTCVault.receiveBalanceIncrease` to repay the optimistic\n /// minting debt before TBTC is minted. When optimistic minting is\n /// finalized, debt equal to the value of the deposit being\n /// a subject of the optimistic minting is incurred. When `TBTCVault`\n /// sweeps a deposit, the debt is fully or partially paid off, no\n /// matter if that particular deposit was used for the optimistic\n /// minting or not.\n /// @dev See `TBTCVault.receiveBalanceIncrease`\n /// @param depositor The depositor whose balance increase is received.\n /// @param amount The balance increase amount for the depositor received.\n /// @return The TBTC amount that should be minted after paying off the\n /// optimistic minting debt.\n function repayOptimisticMintingDebt(address depositor, uint256 amount)\n internal\n returns (uint256)\n {\n uint256 debt = optimisticMintingDebt[depositor];\n if (debt == 0) {\n return amount;\n }\n\n if (amount > debt) {\n optimisticMintingDebt[depositor] = 0;\n emit OptimisticMintingDebtRepaid(depositor, 0);\n return amount - debt;\n } else {\n optimisticMintingDebt[depositor] = debt - amount;\n emit OptimisticMintingDebtRepaid(depositor, debt - amount);\n return 0;\n }\n }\n}\n"
24
24
  },
25
25
  "contracts/token/TBTC.sol": {
26
26
  "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"
@@ -293,7 +293,7 @@
293
293
  "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport '@keep-network/ecdsa/contracts/WalletRegistry.sol';\n"
294
294
  },
295
295
  "@keep-network/ecdsa/contracts/WalletRegistry.sol": {
296
- "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"
296
+ "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"
297
297
  },
298
298
  "contracts/test/TestERC721.sol": {
299
299
  "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"
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/37a54d25ac31bedfc71e7a20fa2352f5.json"
3
+ "buildInfo": "../../../build-info/2502d470e576bbb1c54af5dfc59670d4.json"
4
4
  }