@keep-network/tbtc-v2 1.7.0 → 1.8.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/.chainId +1 -1
- package/artifacts/BLS.json +223 -0
- package/artifacts/Bank.json +50 -40
- package/artifacts/BeaconAuthorization.json +273 -0
- package/artifacts/BeaconDkg.json +241 -0
- package/artifacts/BeaconDkgValidator.json +501 -0
- package/artifacts/BeaconInactivity.json +151 -0
- package/artifacts/BeaconSortitionPool.json +1187 -0
- package/artifacts/Bridge.json +449 -32
- package/artifacts/BridgeGovernance.json +22 -22
- package/artifacts/BridgeGovernanceParameters.json +10 -10
- package/artifacts/Deposit.json +12 -12
- package/artifacts/DepositSweep.json +12 -12
- package/artifacts/DonationVault.json +17 -17
- package/artifacts/EcdsaDkgValidator.json +517 -0
- package/artifacts/EcdsaInactivity.json +156 -0
- package/artifacts/EcdsaSortitionPool.json +1187 -0
- package/artifacts/Fraud.json +12 -12
- package/artifacts/LightRelay.json +38 -38
- package/artifacts/LightRelayMaintainerProxy.json +31 -31
- package/artifacts/MaintainerProxy.json +48 -48
- package/artifacts/MovingFunds.json +12 -12
- package/artifacts/NuCypherToken.json +711 -0
- package/artifacts/RandomBeacon.json +3271 -0
- package/artifacts/RandomBeaconChaosnet.json +252 -0
- package/artifacts/RandomBeaconGovernance.json +3499 -0
- package/artifacts/Redemption.json +12 -12
- package/artifacts/RedemptionWatchtower.json +39 -39
- package/artifacts/ReimbursementPool.json +509 -0
- package/artifacts/T.json +1148 -0
- package/artifacts/TBTC.json +37 -36
- package/artifacts/TBTCToken.json +738 -0
- package/artifacts/TBTCVault.json +47 -47
- package/artifacts/Timelock.json +148 -148
- package/artifacts/TokenStaking.json +2117 -0
- package/artifacts/TokenholderGovernor.json +1795 -0
- package/artifacts/TokenholderTimelock.json +1058 -0
- package/artifacts/VendingMachine.json +35 -34
- package/artifacts/VendingMachineNuCypher.json +400 -0
- package/artifacts/VendingMachineV2.json +21 -21
- package/artifacts/VendingMachineV3.json +21 -21
- package/artifacts/WalletProposalValidator.json +12 -12
- package/artifacts/WalletRegistry.json +1962 -0
- package/artifacts/WalletRegistryGovernance.json +2863 -0
- package/artifacts/Wallets.json +12 -12
- package/artifacts/solcInputs/{be146ce112252bdda3a03de3614fbbf1.json → fe0eebcebee1dc423b54e929e5519304.json} +31 -7
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/Timelock.sol/Timelock.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
- package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
- package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/IRedemptionWatchtower.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
- package/build/contracts/bridge/RedemptionWatchtower.sol/RedemptionWatchtower.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.dbg.json +1 -1
- package/build/contracts/bridge/WalletProposalValidator.sol/WalletProposalValidator.dbg.json +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
- package/build/contracts/integrator/AbstractTBTCDepositor.sol/AbstractTBTCDepositor.dbg.json +1 -1
- package/build/contracts/integrator/IBridge.sol/IBridge.dbg.json +1 -1
- package/build/contracts/integrator/IBridge.sol/IBridgeTypes.dbg.json +1 -1
- package/build/contracts/integrator/ITBTCVault.sol/ITBTCVault.dbg.json +1 -1
- package/build/contracts/l2/L1BitcoinDepositor.sol/L1BitcoinDepositor.dbg.json +1 -1
- package/build/contracts/l2/L1BitcoinDepositor.sol/L1BitcoinDepositor.json +41 -2
- package/build/contracts/l2/L2BitcoinDepositor.sol/IL2WormholeGateway.dbg.json +1 -1
- package/build/contracts/l2/L2BitcoinDepositor.sol/L2BitcoinDepositor.dbg.json +1 -1
- package/build/contracts/l2/L2TBTC.sol/L2TBTC.dbg.json +1 -1
- package/build/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.dbg.json +1 -1
- package/build/contracts/l2/Wormhole.sol/IWormhole.dbg.json +1 -1
- package/build/contracts/l2/Wormhole.sol/IWormholeReceiver.dbg.json +1 -1
- package/build/contracts/l2/Wormhole.sol/IWormholeRelayer.dbg.json +1 -1
- package/build/contracts/l2/Wormhole.sol/IWormholeTokenBridge.dbg.json +1 -1
- package/build/contracts/l2/Wormhole.sol/WormholeTypes.dbg.json +1 -1
- package/build/contracts/l2/Wormhole.sol/WormholeUtils.dbg.json +1 -1
- package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +1 -1
- package/build/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.dbg.json +1 -1
- package/build/contracts/test/BankStub.sol/BankStub.dbg.json +1 -1
- package/build/contracts/test/BridgeStub.sol/BridgeStub.dbg.json +1 -1
- package/build/contracts/test/HeartbeatStub.sol/HeartbeatStub.dbg.json +1 -1
- package/build/contracts/test/LightRelayStub.sol/LightRelayStub.dbg.json +1 -1
- package/build/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.dbg.json +1 -1
- package/build/contracts/test/SepoliaLightRelay.sol/SepoliaLightRelay.dbg.json +1 -1
- package/build/contracts/test/SystemTestRelay.sol/SystemTestRelay.dbg.json +1 -1
- package/build/contracts/test/TestBitcoinTx.sol/TestBitcoinTx.dbg.json +1 -1
- package/build/contracts/test/TestERC20.sol/TestERC20.dbg.json +1 -1
- package/build/contracts/test/TestERC721.sol/TestERC721.dbg.json +1 -1
- package/build/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.dbg.json +1 -1
- package/build/contracts/test/TestTBTCDepositor.sol/MockBridge.dbg.json +1 -1
- package/build/contracts/test/TestTBTCDepositor.sol/MockTBTCVault.dbg.json +1 -1
- package/build/contracts/test/TestTBTCDepositor.sol/TestTBTCDepositor.dbg.json +1 -1
- package/build/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCOptimisticMinting.sol/TBTCOptimisticMinting.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/l2/L1BitcoinDepositor.sol +31 -0
- package/export/artifacts/@keep-network/ecdsa/contracts/WalletRegistry.sol/WalletRegistry.json +4999 -4814
- package/export/artifacts/contracts/bridge/VendingMachine.sol/VendingMachine.json +6 -6
- package/export/artifacts/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.json +6 -6
- package/export/artifacts/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.json +6 -6
- package/export/artifacts/contracts/l2/L1BitcoinDepositor.sol/L1BitcoinDepositor.json +4288 -4028
- package/export/artifacts/contracts/l2/L2BitcoinDepositor.sol/L2BitcoinDepositor.json +33 -33
- package/export/artifacts/contracts/l2/L2TBTC.sol/L2TBTC.json +40 -40
- package/export/artifacts/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.json +51 -51
- package/export/artifacts/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.json +88 -88
- package/export/artifacts/contracts/relay/LightRelay.sol/LightRelay.json +57 -57
- package/export/artifacts/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.json +31 -31
- package/export/artifacts/contracts/test/BankStub.sol/BankStub.json +2 -2
- package/export/artifacts/contracts/test/BridgeStub.sol/BridgeStub.json +30 -30
- package/export/artifacts/contracts/test/HeartbeatStub.sol/HeartbeatStub.json +2 -2
- package/export/artifacts/contracts/test/LightRelayStub.sol/LightRelayStub.json +59 -59
- package/export/artifacts/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.json +7 -7
- package/export/artifacts/contracts/test/SepoliaLightRelay.sol/SepoliaLightRelay.json +59 -59
- package/export/artifacts/contracts/test/SystemTestRelay.sol/SystemTestRelay.json +14 -14
- package/export/artifacts/contracts/test/TestBitcoinTx.sol/TestBitcoinTx.json +5 -5
- package/export/artifacts/contracts/test/TestERC20.sol/TestERC20.json +6 -6
- package/export/artifacts/contracts/test/TestERC721.sol/TestERC721.json +8 -8
- package/export/artifacts/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.json +2 -2
- package/export/artifacts/contracts/test/TestTBTCDepositor.sol/MockBridge.json +18 -18
- package/export/artifacts/contracts/test/TestTBTCDepositor.sol/MockTBTCVault.json +20 -20
- package/export/artifacts/contracts/test/TestTBTCDepositor.sol/TestTBTCDepositor.json +10 -10
- package/export/artifacts/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.json +37 -37
- package/export/artifacts/contracts/token/TBTC.sol/TBTC.json +2 -2
- package/export/artifacts/contracts/vault/DonationVault.sol/DonationVault.json +11 -11
- package/export/artifacts/contracts/vault/TBTCVault.sol/TBTCVault.json +135 -135
- package/export/typechain/factories/EcdsaAuthorization__factory.js +1 -1
- package/export/typechain/factories/IStaking__factory.js +24 -103
- package/export/typechain/factories/L1BitcoinDepositor__factory.js +40 -1
- package/export/typechain/factories/WalletRegistry__factory.js +1 -1
- package/package.json +4 -4
- package/artifacts/solcInputs/7c1f8f1dbded4c787d87a982d1fc8349.json +0 -410
- package/artifacts/solcInputs/7cc3eda3cb3ff2522d18b5e7b31ea228.json +0 -102
- package/artifacts/solcInputs/802132f7da69a8a4226cb9424480847b.json +0 -218
- package/artifacts/solcInputs/887fad6b16575ba42183543c324eeb0e.json +0 -335
- package/artifacts/solcInputs/98e272e2fbf178ec895387b6c503f9de.json +0 -68
- package/artifacts/solcInputs/b0025f1f7efe4824592ac0c9793776c3.json +0 -392
- package/artifacts/solcInputs/b7c5f3550cc22e16e6b6ea9582ccbee3.json +0 -341
- package/artifacts/solcInputs/d71d4b4434e6669852eaf643ebd2a7bc.json +0 -209
- package/artifacts/solcInputs/fccb130292c8c7cc958ab4fa31a3e180.json +0 -187
- package/export/deploy/00_resolve_relay.js +0 -83
- package/export/deploy/01_deploy_tbtc_v2_token.js +0 -70
- package/export/deploy/02_deploy_vending_machine.js +0 -84
- package/export/deploy/03_transfer_vending_machine_roles.js +0 -69
- package/export/deploy/04_deploy_bank.js +0 -73
- package/export/deploy/05_deploy_bridge.js +0 -178
- package/export/deploy/06_deploy_tbtc_vault.js +0 -80
- package/export/deploy/07_deploy_bridge_governance.js +0 -87
- package/export/deploy/08_deploy_maintainer_proxy.js +0 -80
- package/export/deploy/09_bank_update_bridge.js +0 -63
- package/export/deploy/10_authorize_spv_maintainer_in_bridge.js +0 -61
- package/export/deploy/11_transfer_bank_ownership.js +0 -60
- package/export/deploy/12_transfer_bridge_governance.js +0 -63
- package/export/deploy/13_transfer_bridge_governance_ownership.js +0 -60
- package/export/deploy/14_transfer_tbtc_vault_ownership.js +0 -60
- package/export/deploy/15_transfer_maintainer_proxy_ownership.js +0 -60
- package/export/deploy/16_initialize_wallet_owner.js +0 -63
- package/export/deploy/16_transfer_proxy_admin_ownership.js +0 -73
- package/export/deploy/17_authorize_maintainer_proxy_in_bridge.js +0 -63
- package/export/deploy/17_transfer_proxy_admin_ownership.js +0 -73
- package/export/deploy/18_authorize_maintainer_proxy_in_bridge.js +0 -63
- package/export/deploy/18_authorize_maintainer_proxy_in_reimbursement_pool.js +0 -63
- package/export/deploy/19_authorize_bridge_in_reimbursement_pool.js +0 -63
- package/export/deploy/19_authorize_maintainer_proxy_in_reimbursement_pool.js +0 -63
- package/export/deploy/20_authorize_bridge_in_reimbursement_pool.js +0 -63
- package/export/deploy/20_deploy_proxy_admin_with_deputy.js +0 -80
- package/export/deploy/21_transfer_reimbursement_pool_ownership.js +0 -60
- package/export/deploy/22_deploy_proxy_admin_with_deputy.js +0 -80
- package/export/deploy/24_transfer_maintainer_proxy_ownership.js +0 -60
- package/export/deploy/25_transfer_proxy_admin_ownership.js +0 -73
- package/export/deploy/26_authorize_maintainer_proxy_in_reimbursement_pool.js +0 -70
- package/export/deploy/27_authorize_bridge_in_reimbursement_pool.js +0 -70
- package/export/deploy/28_deploy_proxy_admin_with_deputy.js +0 -80
- package/export/deploy/34_deploy_wallet_coordinator.js +0 -115
- package/export/deploy/35_add_coordinator_address.js +0 -60
- package/export/deploy/35_transfer_wallet_coordinator_ownership.js +0 -60
- package/export/deploy/36_deploy_light_relay_maintainer_proxy.js +0 -86
- package/export/deploy/36_transfer_wallet_coordinator_ownership.js +0 -60
- package/export/deploy/37_authorize_maintainer_in_light_relay_maintainer_proxy.js +0 -65
- package/export/deploy/37_deploy_light_relay_maintainer_proxy.js +0 -86
- package/export/deploy/37_transfer_light_relay_maintainer_proxy_ownership.js +0 -60
- package/export/deploy/38_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.js +0 -70
- package/export/deploy/38_authorize_maintainer_in_light_relay_maintainer_proxy.js +0 -65
- package/export/deploy/38_transfer_light_relay_maintainer_proxy_ownership.js +0 -60
- package/export/deploy/39_authorize_light_relay_maintainer_proxy_in_light_relay.js +0 -63
- package/export/deploy/39_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.js +0 -70
- package/export/deploy/39_transfer_light_relay_maintainer_proxy_ownership.js +0 -60
- package/export/deploy/40_authorize_light_relay_maintainer_proxy_in_light_relay.js +0 -66
- package/export/deploy/40_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.js +0 -70
- package/export/deploy/41_authorize_light_relay_maintainer_proxy_in_light_relay.js +0 -66
- package/export/typechain/GoerliLightRelay.js +0 -2
- package/export/typechain/RelayStub.js +0 -2
- package/export/typechain/TestRelay.js +0 -2
- package/export/typechain/WalletCoordinator.js +0 -2
- package/export/typechain/factories/GoerliLightRelay__factory.js +0 -535
- package/export/typechain/factories/RelayStub__factory.js +0 -546
- package/export/typechain/factories/TestRelay__factory.js +0 -168
- package/export/typechain/factories/WalletCoordinator__factory.js +0 -1121
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nlibrary Wallets {\n struct Wallet {\n // Keccak256 hash of group members identifiers array. Group members do not\n // include operators selected by the sortition pool that misbehaved during DKG.\n bytes32 membersIdsHash;\n // Uncompressed ECDSA public key stored as X and Y coordinates (32 bytes each).\n bytes32 publicKeyX;\n bytes32 publicKeyY;\n // This struct doesn't contain `__gap` property as the structure is stored\n // in a mapping, mappings store values in different slots and they are\n // not contiguous with other values.\n }\n\n struct Data {\n // Mapping of keccak256 hashes of wallet public keys to wallet details.\n // Hash of public key is considered an unique wallet identifier.\n mapping(bytes32 => Wallet) registry;\n // Reserved storage space in case we need to add more variables.\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // slither-disable-next-line unused-state\n uint256[49] __gap;\n }\n\n /// @notice Performs preliminary validation of a new group public key.\n /// The group public key must be unique and have 64 bytes in length.\n /// If the validation fails, the function reverts. This function\n /// must be called first for a public key of a wallet added with\n /// `addWallet` function.\n /// @param publicKey Uncompressed public key of a new wallet.\n function validatePublicKey(Data storage self, bytes calldata publicKey)\n internal\n view\n {\n require(publicKey.length == 64, \"Invalid length of the public key\");\n\n bytes32 walletID = keccak256(publicKey);\n require(\n self.registry[walletID].publicKeyX == bytes32(0),\n \"Wallet with the given public key already exists\"\n );\n\n bytes32 publicKeyX = bytes32(publicKey[:32]);\n require(publicKeyX != bytes32(0), \"Wallet public key must be non-zero\");\n }\n\n /// @notice Registers a new wallet. This function does not validate\n /// parameters. The code calling this function must call\n /// `validatePublicKey` first.\n /// @dev Uses a public key hash as a unique identifier of a wallet.\n /// @param membersIdsHash Keccak256 hash of group members identifiers array\n /// @param publicKey Uncompressed public key\n /// @return walletID Wallet's ID\n /// @return publicKeyX Wallet's public key's X coordinate\n /// @return publicKeyY Wallet's public key's Y coordinate\n function addWallet(\n Data storage self,\n bytes32 membersIdsHash,\n bytes calldata publicKey\n )\n internal\n returns (\n bytes32 walletID,\n bytes32 publicKeyX,\n bytes32 publicKeyY\n )\n {\n walletID = keccak256(publicKey);\n\n publicKeyX = bytes32(publicKey[:32]);\n publicKeyY = bytes32(publicKey[32:]);\n\n self.registry[walletID].membersIdsHash = membersIdsHash;\n self.registry[walletID].publicKeyX = publicKeyX;\n self.registry[walletID].publicKeyY = publicKeyY;\n }\n\n /// @notice Deletes wallet with the given ID from the registry. Reverts\n /// if wallet with the given ID has not been registered or if it\n /// has already been closed.\n function deleteWallet(Data storage self, bytes32 walletID) internal {\n require(\n isWalletRegistered(self, walletID),\n \"Wallet with the given ID has not been registered\"\n );\n\n delete self.registry[walletID];\n }\n\n /// @notice Checks if a wallet with the given ID is registered.\n /// @param walletID Wallet's ID\n /// @return True if a wallet is registered, false otherwise\n function isWalletRegistered(Data storage self, bytes32 walletID)\n internal\n view\n returns (bool)\n {\n return self.registry[walletID].publicKeyX != bytes32(0);\n }\n\n /// @notice Returns Keccak256 hash of the wallet signing group members\n /// identifiers array. Group members do not include operators\n /// selected by the sortition pool that misbehaved during DKG.\n /// Reverts if wallet with the given ID is not registered.\n /// @param walletID ID of the wallet\n /// @return Wallet signing group members hash\n function getWalletMembersIdsHash(Data storage self, bytes32 walletID)\n internal\n view\n returns (bytes32)\n {\n require(\n isWalletRegistered(self, walletID),\n \"Wallet with the given ID has not been registered\"\n );\n\n return self.registry[walletID].membersIdsHash;\n }\n\n /// @notice Gets public key of a wallet with the given wallet ID.\n /// The public key is returned as X and Y coordinates.\n /// Reverts if wallet with the given ID is not registered.\n /// @param walletID ID of the wallet\n /// @return x Public key X coordinate\n /// @return y Public key Y coordinate\n function getWalletPublicKeyCoordinates(Data storage self, bytes32 walletID)\n internal\n view\n returns (bytes32 x, bytes32 y)\n {\n require(\n isWalletRegistered(self, walletID),\n \"Wallet with the given ID has not been registered\"\n );\n\n Wallet storage wallet = self.registry[walletID];\n\n return (wallet.publicKeyX, wallet.publicKeyY);\n }\n\n /// @notice Gets public key of a wallet with the given wallet ID.\n /// The public key is returned in an uncompressed format as a 64-byte\n /// concatenation of X and Y coordinates.\n /// Reverts if wallet with the given ID is not registered.\n /// @param walletID ID of the wallet\n /// @return Uncompressed public key of the wallet\n function getWalletPublicKey(Data storage self, bytes32 walletID)\n internal\n view\n returns (bytes memory)\n {\n (bytes32 x, bytes32 y) = getWalletPublicKeyCoordinates(self, walletID);\n return bytes.concat(x, y);\n }\n}\n"
|
|
39
39
|
},
|
|
40
40
|
"@keep-network/ecdsa/contracts/WalletRegistry.sol": {
|
|
41
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nimport \"./api/IWalletRegistry.sol\";\nimport \"./api/IWalletOwner.sol\";\nimport \"./libraries/Wallets.sol\";\nimport {EcdsaAuthorization as Authorization} from \"./libraries/EcdsaAuthorization.sol\";\nimport {EcdsaDkg as DKG} from \"./libraries/EcdsaDkg.sol\";\nimport {EcdsaInactivity as Inactivity} from \"./libraries/EcdsaInactivity.sol\";\nimport {EcdsaDkgValidator as DKGValidator} from \"./EcdsaDkgValidator.sol\";\n\nimport \"@keep-network/sortition-pools/contracts/SortitionPool.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeacon.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeaconConsumer.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\nimport \"@keep-network/random-beacon/contracts/Governable.sol\";\n\nimport \"@threshold-network/solidity-contracts/contracts/staking/IApplication.sol\";\nimport \"@threshold-network/solidity-contracts/contracts/staking/IStaking.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\n\ncontract WalletRegistry is\n IWalletRegistry,\n IRandomBeaconConsumer,\n IApplication,\n Governable,\n Reimbursable,\n Initializable\n{\n using Authorization for Authorization.Data;\n using DKG for DKG.Data;\n using Wallets for Wallets.Data;\n\n // Libraries data storages\n Authorization.Data internal authorization;\n DKG.Data internal dkg;\n Wallets.Data internal wallets;\n\n /// @notice Slashing amount for submitting a malicious DKG result. Every\n /// DKG result submitted can be challenged for the time of\n /// `dkg.resultChallengePeriodLength`. If the DKG result submitted\n /// is challenged and proven to be malicious, the operator who\n /// submitted the malicious result is slashed for\n /// `_maliciousDkgResultSlashingAmount`.\n uint96 internal _maliciousDkgResultSlashingAmount;\n\n /// @notice Percentage of the staking contract malicious behavior\n /// notification reward which will be transferred to the notifier\n /// reporting about a malicious DKG result. Notifiers are rewarded\n /// from a notifiers treasury pool. For example, if\n /// notification reward is 1000 and the value of the multiplier is\n /// 5, the notifier will receive: 5% of 1000 = 50 per each\n /// operator affected.\n uint256 internal _maliciousDkgResultNotificationRewardMultiplier;\n\n /// @notice Duration of the sortition pool rewards ban imposed on operators\n /// who missed their turn for DKG result submission or who failed\n /// a heartbeat.\n uint256 internal _sortitionPoolRewardsBanDuration;\n\n /// @notice Calculated max gas cost for submitting a DKG result. This will\n /// be refunded as part of the DKG approval process. It is in the\n /// submitter's interest to not skip his priority turn on the approval,\n /// otherwise the refund of the DKG submission will be refunded to\n /// another group member that will call the DKG approve function.\n uint256 internal _dkgResultSubmissionGas;\n\n /// @notice Gas that is meant to balance the DKG result approval's overall\n /// cost. It can be updated by the governance based on the current\n /// market conditions.\n uint256 internal _dkgResultApprovalGasOffset;\n\n /// @notice Gas that is meant to balance the notification of an operator\n /// inactivity. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifyOperatorInactivityGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a seed for DKG\n /// delivery timeout. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifySeedTimeoutGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a DKG protocol\n /// execution timeout. It can be updated by the governance based on the\n /// current market conditions.\n /// @dev The value is subtracted for the refundable gas calculation, as the\n /// DKG timeout notification transaction recovers some gas when cleaning\n /// up the storage.\n uint256 internal _notifyDkgTimeoutNegativeGasOffset;\n\n /// @notice Stores current operator inactivity claim nonce for the given\n /// wallet signing group. Each claim is made with a unique nonce\n /// which protects against claim replay.\n mapping(bytes32 => uint256) public inactivityClaimNonce; // walletID -> nonce\n\n // Address that is set as owner of all wallets. Only this address can request\n // new wallets creation and manage their state.\n IWalletOwner public walletOwner;\n\n // External dependencies\n\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n SortitionPool public immutable sortitionPool;\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStaking public immutable staking;\n IRandomBeacon public randomBeacon;\n\n // Events\n event DkgStarted(uint256 indexed seed);\n\n event DkgResultSubmitted(\n bytes32 indexed resultHash,\n uint256 indexed seed,\n DKG.Result result\n );\n\n event DkgTimedOut();\n\n event DkgResultApproved(\n bytes32 indexed resultHash,\n address indexed approver\n );\n\n event DkgResultChallenged(\n bytes32 indexed resultHash,\n address indexed challenger,\n string reason\n );\n\n event DkgStateLocked();\n\n event DkgSeedTimedOut();\n\n event WalletCreated(\n bytes32 indexed walletID,\n bytes32 indexed dkgResultHash\n );\n\n event WalletClosed(bytes32 indexed walletID);\n\n event DkgMaliciousResultSlashed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event DkgMaliciousResultSlashingFailed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event AuthorizationParametersUpdated(\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n );\n\n event RewardParametersUpdated(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n );\n\n event SlashingParametersUpdated(uint256 maliciousDkgResultSlashingAmount);\n\n event DkgParametersUpdated(\n uint256 seedTimeout,\n uint256 resultChallengePeriodLength,\n uint256 resultChallengeExtraGas,\n uint256 resultSubmissionTimeout,\n uint256 resultSubmitterPrecedencePeriodLength\n );\n\n event GasParametersUpdated(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n );\n\n event RandomBeaconUpgraded(address randomBeacon);\n\n event WalletOwnerUpdated(address walletOwner);\n\n event OperatorRegistered(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event AuthorizationIncreased(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event AuthorizationDecreaseRequested(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount,\n uint64 decreasingAt\n );\n\n event AuthorizationDecreaseApproved(address indexed stakingProvider);\n\n event InvoluntaryAuthorizationDecreaseFailed(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event OperatorJoinedSortitionPool(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event OperatorStatusUpdated(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event InactivityClaimed(\n bytes32 indexed walletID,\n uint256 nonce,\n address notifier\n );\n\n modifier onlyStakingContract() {\n require(\n msg.sender == address(staking),\n \"Caller is not the staking contract\"\n );\n _;\n }\n\n /// @notice Reverts if called not by the Wallet Owner.\n modifier onlyWalletOwner() {\n require(\n msg.sender == address(walletOwner),\n \"Caller is not the Wallet Owner\"\n );\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(governance == msg.sender, \"Caller is not the governance\");\n _;\n }\n\n /// @dev Used to initialize immutable variables only, use `initialize` function\n /// for upgradable contract initialization on deployment.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(SortitionPool _sortitionPool, IStaking _staking) {\n sortitionPool = _sortitionPool;\n staking = _staking;\n\n _disableInitializers();\n }\n\n /// @dev Initializes upgradable contract on deployment.\n function initialize(\n DKGValidator _ecdsaDkgValidator,\n IRandomBeacon _randomBeacon,\n ReimbursementPool _reimbursementPool\n ) external initializer {\n randomBeacon = _randomBeacon;\n reimbursementPool = _reimbursementPool;\n\n _transferGovernance(msg.sender);\n\n //\n // All parameters set in the constructor are initial ones, used at the\n // moment contracts were deployed for the first time. Parameters are\n // governable and values assigned in the constructor do not need to\n // reflect the current ones.\n //\n\n // Minimum authorization is 40k T.\n //\n // Authorization decrease delay is 45 days.\n //\n // Authorization decrease change period is 45 days. It means pending\n // authorization decrease can be overwritten all the time.\n authorization.setMinimumAuthorization(40_000e18);\n authorization.setAuthorizationDecreaseDelay(3_888_000);\n authorization.setAuthorizationDecreaseChangePeriod(3_888_000);\n\n // Malicious DKG result slashing amount is set initially to 1% of the\n // minimum authorization (400 T). This values needs to be increased\n // significantly once the system is fully launched.\n //\n // Notifier of a malicious DKG result receives 100% of the notifier\n // reward from the staking contract.\n //\n // Inactive operators are set as ineligible for rewards for 2 weeks.\n _maliciousDkgResultSlashingAmount = 400e18;\n _maliciousDkgResultNotificationRewardMultiplier = 100;\n _sortitionPoolRewardsBanDuration = 2 weeks;\n\n // DKG seed timeout is set to 48h assuming 15s block time. The same\n // value is used by the Random Beacon as a relay entry hard timeout.\n //\n // DKG result challenge period length is set to 48h as well, assuming\n // 15s block time.\n //\n // DKG result submission timeout covers:\n // - 20 blocks required to confirm the DkgStarted event off-chain\n // - 5 retries of the off-chain protocol that takes 211 blocks at most\n // - 15 blocks to submit the result for each of the 100 members\n // That gives: 20 + (5 * 211) + (15 * 100) = 2575\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(2575);\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 // solhint-disable-next-line avoid-tx-origin\n require(msg.sender == tx.origin, \"Not EOA\");\n\n (\n bytes32 maliciousDkgResultHash,\n uint32 maliciousDkgResultSubmitterId\n ) = dkg.challengeResult(dkgResult);\n\n address maliciousDkgResultSubmitterAddress = sortitionPool\n .getIDOperator(maliciousDkgResultSubmitterId);\n\n address[] memory operatorWrapper = new address[](1);\n operatorWrapper[0] = operatorToStakingProvider(\n maliciousDkgResultSubmitterAddress\n );\n\n try\n staking.seize(\n _maliciousDkgResultSlashingAmount,\n _maliciousDkgResultNotificationRewardMultiplier,\n msg.sender,\n operatorWrapper\n )\n {\n // slither-disable-next-line reentrancy-events\n emit DkgMaliciousResultSlashed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n } catch {\n // Should never happen but we want to ensure a non-critical path\n // failure from an external contract does not stop the challenge\n // to complete.\n emit DkgMaliciousResultSlashingFailed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n }\n\n // Due to EIP-150, 1/64 of the gas is not forwarded to the call, and\n // will be kept to execute the remaining operations in the function\n // after the call inside the try-catch.\n //\n // To ensure there is no way for the caller to manipulate gas limit in\n // such a way that the call inside try-catch fails with out-of-gas and\n // the rest of the function is executed with the remaining 1/64 of gas,\n // we require an extra gas amount to be left at the end of the call to\n // `challengeDkgResult`.\n dkg.requireChallengeExtraGas();\n }\n\n /// @notice Notifies about operators who are inactive. Using this function,\n /// a majority of the wallet signing group can decide about\n /// punishing specific group members who constantly fail doing their\n /// job. If the provided claim is proved to be valid and signed by\n /// sufficient number of group members, operators of members deemed\n /// as inactive are banned from sortition pool rewards for the\n /// duration specified by `sortitionPoolRewardsBanDuration` parameter.\n /// The function allows to signal about single operators being\n /// inactive as well as to signal wallet-wide heartbeat failures\n /// that are propagated to the wallet owner who should begin the\n /// procedure of moving responsibilities to another wallet given\n /// that the wallet who failed the heartbeat may soon be not able to\n /// function and provide new signatures.\n /// The sender of the claim must be one of the claim signers. This\n /// function can be called only for registered wallets\n /// @param claim Operator inactivity claim.\n /// @param nonce Current inactivity claim nonce for the given wallet signing\n /// group. Must be the same as the stored one.\n /// @param groupMembers Identifiers of the wallet signing group members.\n function notifyOperatorInactivity(\n Inactivity.Claim calldata claim,\n uint256 nonce,\n uint32[] calldata groupMembers\n ) external {\n uint256 gasStart = gasleft();\n\n bytes32 walletID = claim.walletID;\n\n require(nonce == inactivityClaimNonce[walletID], \"Invalid nonce\");\n\n (bytes32 pubKeyX, bytes32 pubKeyY) = wallets\n .getWalletPublicKeyCoordinates(walletID);\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(groupMembers)),\n \"Invalid group members\"\n );\n\n uint32[] memory ineligibleOperators = Inactivity.verifyClaim(\n sortitionPool,\n claim,\n bytes.concat(pubKeyX, pubKeyY),\n nonce,\n groupMembers\n );\n\n inactivityClaimNonce[walletID]++;\n\n emit InactivityClaimed(walletID, nonce, msg.sender);\n\n sortitionPool.setRewardIneligibility(\n ineligibleOperators,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n\n if (claim.heartbeatFailed) {\n walletOwner.__ecdsaWalletHeartbeatFailedCallback(\n walletID,\n pubKeyX,\n pubKeyY\n );\n }\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifyOperatorInactivityGasOffset,\n msg.sender\n );\n }\n\n /// @notice Allows the wallet owner to add all signing group members of the\n /// wallet with the given ID to the slashing queue of the staking .\n /// contract. The notifier will receive reward per each group member\n /// from the staking contract notifiers treasury. The reward is\n /// scaled by the `rewardMultiplier` provided as a parameter.\n /// @param amount Amount of tokens to seize from each signing group member.\n /// @param rewardMultiplier Fraction of the staking contract notifiers\n /// reward the notifier should receive; should be between [0, 100].\n /// @param notifier Address of the misbehavior notifier.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - `rewardMultiplier` must be between [0, 100].\n /// - This function does revert if staking contract call reverts.\n /// The calling code needs to handle the potential revert.\n function seize(\n uint96 amount,\n uint256 rewardMultiplier,\n address notifier,\n bytes32 walletID,\n uint32[] calldata walletMembersIDs\n ) external onlyWalletOwner {\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n address[] memory groupMembersAddresses = sortitionPool.getIDOperators(\n walletMembersIDs\n );\n address[] memory stakingProvidersAddresses = new address[](\n walletMembersIDs.length\n );\n for (uint256 i = 0; i < groupMembersAddresses.length; i++) {\n stakingProvidersAddresses[i] = operatorToStakingProvider(\n groupMembersAddresses[i]\n );\n }\n\n staking.seize(\n amount,\n rewardMultiplier,\n notifier,\n stakingProvidersAddresses\n );\n }\n\n /// @notice Checks if DKG result is valid for the current DKG.\n /// @param result DKG result.\n /// @return True if the result is valid. If the result is invalid it returns\n /// false and an error message.\n function isDkgResultValid(DKG.Result calldata result)\n external\n view\n returns (bool, string memory)\n {\n return dkg.isResultValid(result);\n }\n\n /// @notice Check current wallet creation state.\n function getWalletCreationState() external view returns (DKG.State) {\n return dkg.currentState();\n }\n\n /// @notice Checks whether the given operator is a member of the given\n /// wallet signing group.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @param operator Address of the checked operator.\n /// @param walletMemberIndex Position of the operator in the wallet signing\n /// group members list.\n /// @return True - if the operator is a member of the given wallet signing\n /// group. False - otherwise.\n /// @dev Requirements:\n /// - The `operator` parameter must be an actual sortition pool operator.\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]\n function isWalletMember(\n bytes32 walletID,\n uint32[] calldata walletMembersIDs,\n address operator,\n uint256 walletMemberIndex\n ) external view returns (bool) {\n uint32 operatorID = sortitionPool.getOperatorID(operator);\n\n require(operatorID != 0, \"Not a sortition pool operator\");\n\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n require(\n 1 <= walletMemberIndex &&\n walletMemberIndex <= walletMembersIDs.length,\n \"Wallet member index is out of range\"\n );\n\n return walletMembersIDs[walletMemberIndex - 1] == operatorID;\n }\n\n /// @notice Checks if awaiting seed timed out.\n /// @return True if awaiting seed timed out, false otherwise.\n function hasSeedTimedOut() external view returns (bool) {\n return dkg.hasSeedTimedOut();\n }\n\n /// @notice Checks if DKG timed out. The DKG timeout period includes time required\n /// for off-chain protocol execution and time for the result publication\n /// for all group members. After this time result cannot be submitted\n /// and DKG can be notified about the timeout.\n /// @return True if DKG timed out, false otherwise.\n function hasDkgTimedOut() external view returns (bool) {\n return dkg.hasDkgTimedOut();\n }\n\n function getWallet(bytes32 walletID)\n external\n view\n returns (Wallets.Wallet memory)\n {\n return wallets.registry[walletID];\n }\n\n /// @notice Gets public key of a wallet with a given wallet ID.\n /// The public key is returned in an uncompressed format as a 64-byte\n /// concatenation of X and Y coordinates.\n /// @param walletID ID of the wallet.\n /// @return Uncompressed public key of the wallet.\n function getWalletPublicKey(bytes32 walletID)\n external\n view\n returns (bytes memory)\n {\n return wallets.getWalletPublicKey(walletID);\n }\n\n /// @notice Checks if a wallet with the given ID is registered.\n /// @param walletID Wallet's ID.\n /// @return True if wallet is registered, false otherwise.\n function isWalletRegistered(bytes32 walletID) external view returns (bool) {\n return wallets.isWalletRegistered(walletID);\n }\n\n /// @notice The minimum authorization amount required so that operator can\n /// participate in ECDSA Wallet operations.\n function minimumAuthorization() external view returns (uint96) {\n return authorization.parameters.minimumAuthorization;\n }\n\n /// @notice Returns the current value of the staking provider's eligible\n /// stake. Eligible stake is defined as the currently authorized\n /// stake minus the pending authorization decrease. Eligible stake\n /// is what is used for operator's weight in the sortition pool.\n /// If the authorized stake minus the pending authorization decrease\n /// is below the minimum authorization, eligible stake is 0.\n function eligibleStake(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.eligibleStake(staking, stakingProvider);\n }\n\n /// @notice Returns the amount of rewards available for withdrawal for the\n /// given staking provider. Reverts if staking provider has not\n /// registered the operator address.\n function availableRewards(address stakingProvider)\n external\n view\n returns (uint96)\n {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n return sortitionPool.getAvailableRewards(operator);\n }\n\n /// @notice Returns the amount of stake that is pending authorization\n /// decrease for the given staking provider. If no authorization\n /// decrease has been requested, returns zero.\n function pendingAuthorizationDecrease(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.pendingAuthorizationDecrease(stakingProvider);\n }\n\n /// @notice Returns the remaining time in seconds that needs to pass before\n /// the requested authorization decrease can be approved.\n /// If the sortition pool state was not updated yet by the operator\n /// after requesting the authorization decrease, returns\n /// `type(uint64).max`.\n function remainingAuthorizationDecreaseDelay(address stakingProvider)\n external\n view\n returns (uint64)\n {\n return\n authorization.remainingAuthorizationDecreaseDelay(stakingProvider);\n }\n\n /// @notice Returns operator registered for the given staking provider.\n function stakingProviderToOperator(address stakingProvider)\n public\n view\n returns (address)\n {\n return authorization.stakingProviderToOperator[stakingProvider];\n }\n\n /// @notice Returns staking provider of the given operator.\n function operatorToStakingProvider(address operator)\n public\n view\n returns (address)\n {\n return authorization.operatorToStakingProvider[operator];\n }\n\n /// @notice Checks if the operator's authorized stake is in sync with\n /// operator's weight in the sortition pool.\n /// If the operator is not in the sortition pool and their\n /// authorized stake is non-zero, function returns false.\n function isOperatorUpToDate(address operator) external view returns (bool) {\n return\n authorization.isOperatorUpToDate(staking, sortitionPool, operator);\n }\n\n /// @notice Returns true if the given operator is in the sortition pool.\n /// Otherwise, returns false.\n function isOperatorInPool(address operator) external view returns (bool) {\n return sortitionPool.isOperatorInPool(operator);\n }\n\n /// @notice Selects a new group of operators. Can only be called when DKG\n /// is in progress and the pool is locked.\n /// At least one operator has to be registered in the pool,\n /// otherwise the function fails reverting the transaction.\n /// @return IDs of selected group members.\n function selectGroup() external view returns (uint32[] memory) {\n return sortitionPool.selectGroup(DKG.groupSize, bytes32(dkg.seed));\n }\n\n /// @notice Retrieves dkg parameters that were set in DKG library.\n function dkgParameters() external view returns (DKG.Parameters memory) {\n return dkg.parameters;\n }\n\n /// @notice Returns authorization-related parameters.\n /// @dev The minimum authorization is also returned by `minimumAuthorization()`\n /// function, as a requirement of `IApplication` interface.\n /// @return minimumAuthorization The minimum authorization amount required\n /// so that operator can participate in the random beacon. This\n /// amount is required to execute slashing for providing a malicious\n /// DKG result or when a relay entry times out.\n /// @return authorizationDecreaseDelay Delay in seconds that needs to pass\n /// between the time authorization decrease is requested and the\n /// time that request gets approved. Protects against free-riders\n /// earning rewards and not being active in the network.\n /// @return authorizationDecreaseChangePeriod Authorization decrease change\n /// period in seconds. It is the time, before authorization decrease\n /// delay end, during which the pending authorization decrease\n /// request can be overwritten.\n /// If set to 0, pending authorization decrease request can not be\n /// overwritten until the entire `authorizationDecreaseDelay` ends.\n /// If set to value equal `authorizationDecreaseDelay`, request can\n /// always be overwritten.\n function authorizationParameters()\n external\n view\n returns (\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n )\n {\n return (\n authorization.parameters.minimumAuthorization,\n authorization.parameters.authorizationDecreaseDelay,\n authorization.parameters.authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Retrieves reward-related parameters.\n /// @return maliciousDkgResultNotificationRewardMultiplier Percentage of the\n /// staking contract malicious behavior notification reward which\n /// will be transferred to the notifier reporting about a malicious\n /// DKG result. Notifiers are rewarded from a notifiers treasury\n /// pool. For example, if notification reward is 1000 and the value\n /// of the multiplier is 5, the notifier will receive:\n /// 5% of 1000 = 50 per each operator affected.\n /// @return sortitionPoolRewardsBanDuration Duration of the sortition pool\n /// rewards ban imposed on operators who missed their turn for DKG\n /// result submission or who failed a heartbeat.\n function rewardParameters()\n external\n view\n returns (\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n )\n {\n return (\n _maliciousDkgResultNotificationRewardMultiplier,\n _sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Retrieves slashing-related parameters.\n /// @return maliciousDkgResultSlashingAmount Slashing amount for submitting\n /// a malicious DKG result. Every DKG result submitted can be\n /// challenged for the time of `dkg.resultChallengePeriodLength`.\n /// If the DKG result submitted is challenged and proven to be\n /// malicious, the operator who submitted the malicious result is\n /// slashed for `_maliciousDkgResultSlashingAmount`.\n function slashingParameters()\n external\n view\n returns (uint96 maliciousDkgResultSlashingAmount)\n {\n return _maliciousDkgResultSlashingAmount;\n }\n\n /// @notice Retrieves gas-related parameters.\n /// @return dkgResultSubmissionGas Calculated max gas cost for submitting\n /// a DKG result. This will be refunded as part of the DKG approval\n /// process. It is in the submitter's interest to not skip his\n /// priority turn on the approval, otherwise the refund of the DKG\n /// submission will be refunded to another group member that will\n /// call the DKG approve function.\n /// @return dkgResultApprovalGasOffset Gas that is meant to balance the DKG\n /// result approval's overall cost. It can be updated by the\n /// governance based on the current market conditions.\n /// @return notifyOperatorInactivityGasOffset Gas that is meant to balance\n /// the notification of an operator inactivity. It can be updated by\n /// the governance based on the current market conditions.\n /// @return notifySeedTimeoutGasOffset Gas that is meant to balance the\n /// notification of a seed for DKG delivery timeout. It can be updated\n /// by the governance based on the current market conditions.\n /// @return notifyDkgTimeoutNegativeGasOffset Gas that is meant to balance\n /// the notification of a DKG protocol execution timeout. It can be\n /// updated by the governance based on the current market conditions.\n function gasParameters()\n external\n view\n returns (\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n )\n {\n return (\n _dkgResultSubmissionGas,\n _dkgResultApprovalGasOffset,\n _notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n}\n"
|
|
41
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nimport \"./api/IWalletRegistry.sol\";\nimport \"./api/IWalletOwner.sol\";\nimport \"./libraries/Wallets.sol\";\nimport {EcdsaAuthorization as Authorization} from \"./libraries/EcdsaAuthorization.sol\";\nimport {EcdsaDkg as DKG} from \"./libraries/EcdsaDkg.sol\";\nimport {EcdsaInactivity as Inactivity} from \"./libraries/EcdsaInactivity.sol\";\nimport {EcdsaDkgValidator as DKGValidator} from \"./EcdsaDkgValidator.sol\";\n\nimport \"@keep-network/sortition-pools/contracts/SortitionPool.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeacon.sol\";\nimport \"@keep-network/random-beacon/contracts/api/IRandomBeaconConsumer.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\nimport \"@keep-network/random-beacon/contracts/Governable.sol\";\n\nimport \"@threshold-network/solidity-contracts/contracts/staking/IApplication.sol\";\nimport \"@threshold-network/solidity-contracts/contracts/staking/IStaking.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\n\ncontract WalletRegistry is\n IWalletRegistry,\n IRandomBeaconConsumer,\n IApplication,\n Governable,\n Reimbursable,\n Initializable\n{\n using Authorization for Authorization.Data;\n using DKG for DKG.Data;\n using Wallets for Wallets.Data;\n\n // Libraries data storages\n Authorization.Data internal authorization;\n DKG.Data internal dkg;\n Wallets.Data internal wallets;\n\n /// @notice Slashing amount for submitting a malicious DKG result. Every\n /// DKG result submitted can be challenged for the time of\n /// `dkg.resultChallengePeriodLength`. If the DKG result submitted\n /// is challenged and proven to be malicious, the operator who\n /// submitted the malicious result is slashed for\n /// `_maliciousDkgResultSlashingAmount`.\n uint96 internal _maliciousDkgResultSlashingAmount;\n\n /// @notice Percentage of the staking contract malicious behavior\n /// notification reward which will be transferred to the notifier\n /// reporting about a malicious DKG result. Notifiers are rewarded\n /// from a notifiers treasury pool. For example, if\n /// notification reward is 1000 and the value of the multiplier is\n /// 5, the notifier will receive: 5% of 1000 = 50 per each\n /// operator affected.\n uint256 internal _maliciousDkgResultNotificationRewardMultiplier;\n\n /// @notice Duration of the sortition pool rewards ban imposed on operators\n /// who missed their turn for DKG result submission or who failed\n /// a heartbeat.\n uint256 internal _sortitionPoolRewardsBanDuration;\n\n /// @notice Calculated max gas cost for submitting a DKG result. This will\n /// be refunded as part of the DKG approval process. It is in the\n /// submitter's interest to not skip his priority turn on the approval,\n /// otherwise the refund of the DKG submission will be refunded to\n /// another group member that will call the DKG approve function.\n uint256 internal _dkgResultSubmissionGas;\n\n /// @notice Gas that is meant to balance the DKG result approval's overall\n /// cost. It can be updated by the governance based on the current\n /// market conditions.\n uint256 internal _dkgResultApprovalGasOffset;\n\n /// @notice Gas that is meant to balance the notification of an operator\n /// inactivity. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifyOperatorInactivityGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a seed for DKG\n /// delivery timeout. It can be updated by the governance based on the\n /// current market conditions.\n uint256 internal _notifySeedTimeoutGasOffset;\n\n /// @notice Gas that is meant to balance the notification of a DKG protocol\n /// execution timeout. It can be updated by the governance based on the\n /// current market conditions.\n /// @dev The value is subtracted for the refundable gas calculation, as the\n /// DKG timeout notification transaction recovers some gas when cleaning\n /// up the storage.\n uint256 internal _notifyDkgTimeoutNegativeGasOffset;\n\n /// @notice Stores current operator inactivity claim nonce for the given\n /// wallet signing group. Each claim is made with a unique nonce\n /// which protects against claim replay.\n mapping(bytes32 => uint256) public inactivityClaimNonce; // walletID -> nonce\n\n // Address that is set as owner of all wallets. Only this address can request\n // new wallets creation and manage their state.\n IWalletOwner public walletOwner;\n\n // External dependencies\n\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n SortitionPool public immutable sortitionPool;\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStaking public immutable staking;\n IRandomBeacon public randomBeacon;\n\n // Events\n event DkgStarted(uint256 indexed seed);\n\n event DkgResultSubmitted(\n bytes32 indexed resultHash,\n uint256 indexed seed,\n DKG.Result result\n );\n\n event DkgTimedOut();\n\n event DkgResultApproved(\n bytes32 indexed resultHash,\n address indexed approver\n );\n\n event DkgResultChallenged(\n bytes32 indexed resultHash,\n address indexed challenger,\n string reason\n );\n\n event DkgStateLocked();\n\n event DkgSeedTimedOut();\n\n event WalletCreated(\n bytes32 indexed walletID,\n bytes32 indexed dkgResultHash\n );\n\n event WalletClosed(bytes32 indexed walletID);\n\n event DkgMaliciousResultSlashed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event DkgMaliciousResultSlashingFailed(\n bytes32 indexed resultHash,\n uint256 slashingAmount,\n address maliciousSubmitter\n );\n\n event AuthorizationParametersUpdated(\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n );\n\n event RewardParametersUpdated(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n );\n\n event SlashingParametersUpdated(uint256 maliciousDkgResultSlashingAmount);\n\n event DkgParametersUpdated(\n uint256 seedTimeout,\n uint256 resultChallengePeriodLength,\n uint256 resultChallengeExtraGas,\n uint256 resultSubmissionTimeout,\n uint256 resultSubmitterPrecedencePeriodLength\n );\n\n event GasParametersUpdated(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n );\n\n event RandomBeaconUpgraded(address randomBeacon);\n\n event WalletOwnerUpdated(address walletOwner);\n\n event OperatorRegistered(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event AuthorizationIncreased(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event AuthorizationDecreaseRequested(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount,\n uint64 decreasingAt\n );\n\n event AuthorizationDecreaseApproved(address indexed stakingProvider);\n\n event InvoluntaryAuthorizationDecreaseFailed(\n address indexed stakingProvider,\n address indexed operator,\n uint96 fromAmount,\n uint96 toAmount\n );\n\n event OperatorJoinedSortitionPool(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event OperatorStatusUpdated(\n address indexed stakingProvider,\n address indexed operator\n );\n\n event InactivityClaimed(\n bytes32 indexed walletID,\n uint256 nonce,\n address notifier\n );\n\n modifier onlyStakingContract() {\n require(\n msg.sender == address(staking),\n \"Caller is not the staking contract\"\n );\n _;\n }\n\n /// @notice Reverts if called not by the Wallet Owner.\n modifier onlyWalletOwner() {\n require(\n msg.sender == address(walletOwner),\n \"Caller is not the Wallet Owner\"\n );\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(governance == msg.sender, \"Caller is not the governance\");\n _;\n }\n\n /// @dev Used to initialize immutable variables only, use `initialize` function\n /// for upgradable contract initialization on deployment.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(SortitionPool _sortitionPool, IStaking _staking) {\n sortitionPool = _sortitionPool;\n staking = _staking;\n\n _disableInitializers();\n }\n\n /// @dev Initializes upgradable contract on deployment.\n function initialize(\n DKGValidator _ecdsaDkgValidator,\n IRandomBeacon _randomBeacon,\n ReimbursementPool _reimbursementPool\n ) external initializer {\n randomBeacon = _randomBeacon;\n reimbursementPool = _reimbursementPool;\n\n _transferGovernance(msg.sender);\n\n //\n // All parameters set in the constructor are initial ones, used at the\n // moment contracts were deployed for the first time. Parameters are\n // governable and values assigned in the constructor do not need to\n // reflect the current ones.\n //\n\n // Minimum authorization is 40k T.\n //\n // Authorization decrease delay is 45 days.\n //\n // Authorization decrease change period is 45 days. It means pending\n // authorization decrease can be overwritten all the time.\n authorization.setMinimumAuthorization(40_000e18);\n authorization.setAuthorizationDecreaseDelay(3_888_000);\n authorization.setAuthorizationDecreaseChangePeriod(3_888_000);\n\n // Malicious DKG result slashing amount is set initially to 1% of the\n // minimum authorization (400 T). This values needs to be increased\n // significantly once the system is fully launched.\n //\n // Notifier of a malicious DKG result receives 100% of the notifier\n // reward from the staking contract.\n //\n // Inactive operators are set as ineligible for rewards for 2 weeks.\n _maliciousDkgResultSlashingAmount = 400e18;\n _maliciousDkgResultNotificationRewardMultiplier = 100;\n _sortitionPoolRewardsBanDuration = 2 weeks;\n\n // DKG seed timeout is set to 48h assuming 15s block time. The same\n // value is used by the Random Beacon as a relay entry hard timeout.\n //\n // DKG result challenge period length is set to 48h as well, assuming\n // 15s block time.\n //\n // DKG result submission timeout covers:\n // - 20 blocks required to confirm the DkgStarted event off-chain\n // - 5 retries of the off-chain protocol that takes 211 blocks at most\n // - 15 blocks to submit the result for each of the 100 members\n // That gives: 20 + (5 * 211) + (15 * 100) = 2575\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(2575);\n dkg.setSubmitterPrecedencePeriodLength(20);\n\n // Gas parameters were adjusted based on Ethereum state in April 2022.\n // If the cost of EVM opcodes change over time, these parameters will\n // have to be updated.\n _dkgResultSubmissionGas = 290_000;\n _dkgResultApprovalGasOffset = 72_000;\n _notifyOperatorInactivityGasOffset = 93_000;\n _notifySeedTimeoutGasOffset = 7_250;\n _notifyDkgTimeoutNegativeGasOffset = 2_300;\n }\n\n /// @notice Withdraws application rewards for the given staking provider.\n /// Rewards are withdrawn to the staking provider's beneficiary\n /// address set in the staking contract. Reverts if staking provider\n /// has not registered the operator address.\n /// @dev Emits `RewardsWithdrawn` event.\n function withdrawRewards(address stakingProvider) external {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n (, address beneficiary, ) = staking.rolesOf(stakingProvider);\n uint96 amount = sortitionPool.withdrawRewards(operator, beneficiary);\n // slither-disable-next-line reentrancy-events\n emit RewardsWithdrawn(stakingProvider, amount);\n }\n\n /// @notice Withdraws rewards belonging to operators marked as ineligible\n /// for sortition pool rewards.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract.\n /// @param recipient Recipient of withdrawn rewards.\n function withdrawIneligibleRewards(address recipient)\n external\n onlyGovernance\n {\n sortitionPool.withdrawIneligible(recipient);\n }\n\n /// @notice Used by staking provider to set operator address that will\n /// operate ECDSA node. The given staking provider can set operator\n /// address only one time. The operator address can not be changed\n /// and must be unique. Reverts if the operator is already set for\n /// the staking provider or if the operator address is already in\n /// use. Reverts if there is a pending authorization decrease for\n /// the staking provider.\n function registerOperator(address operator) external {\n authorization.registerOperator(operator);\n }\n\n /// @notice Lets the operator join the sortition pool. The operator address\n /// must be known - before calling this function, it has to be\n /// appointed by the staking provider by calling `registerOperator`.\n /// Also, the operator must have the minimum authorization required\n /// by ECDSA. Function reverts if there is no minimum stake\n /// authorized or if the operator is not known. If there was an\n /// authorization decrease requested, it is activated by starting\n /// the authorization decrease delay.\n function joinSortitionPool() external {\n authorization.joinSortitionPool(staking, sortitionPool);\n }\n\n /// @notice Updates status of the operator in the sortition pool. If there\n /// was an authorization decrease requested, it is activated by\n /// starting the authorization decrease delay.\n /// Function reverts if the operator is not known.\n function updateOperatorStatus(address operator) external {\n authorization.updateOperatorStatus(staking, sortitionPool, operator);\n }\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorized stake amount for the given staking provider increased.\n ///\n /// Reverts if the authorization amount is below the minimum.\n ///\n /// The function is not updating the sortition pool. Sortition pool\n /// state needs to be updated by the operator with a call to\n /// `joinSortitionPool` or `updateOperatorStatus`.\n ///\n /// @dev Can only be called by T staking contract.\n function authorizationIncreased(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.authorizationIncreased(\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorization decrease for the given staking provider has been\n /// requested.\n ///\n /// Reverts if the amount after deauthorization would be non-zero\n /// and lower than the minimum authorization.\n ///\n /// If the operator is not known (`registerOperator` was not called)\n /// it lets to `approveAuthorizationDecrease` immediatelly. If the\n /// operator is known (`registerOperator` was called), the operator\n /// needs to update state of the sortition pool with a call to\n /// `joinSortitionPool` or `updateOperatorStatus`. After the\n /// sortition pool state is in sync, authorization decrease delay\n /// starts.\n ///\n /// After authorization decrease delay passes, authorization\n /// decrease request needs to be approved with a call to\n /// `approveAuthorizationDecrease` function.\n ///\n /// If there is a pending authorization decrease request, it is\n /// overwritten.\n ///\n /// @dev Can only be called by T staking contract.\n function authorizationDecreaseRequested(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.authorizationDecreaseRequested(\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Approves the previously registered authorization decrease\n /// request. Reverts if authorization decrease delay has not passed\n /// yet or if the authorization decrease was not requested for the\n /// given staking provider.\n function approveAuthorizationDecrease(address stakingProvider) external {\n authorization.approveAuthorizationDecrease(staking, stakingProvider);\n }\n\n /// @notice Used by T staking contract to inform the application the\n /// authorization has been decreased for the given staking provider\n /// involuntarily, as a result of slashing.\n ///\n /// If the operator is not known (`registerOperator` was not called)\n /// the function does nothing. The operator was never in a sortition\n /// pool so there is nothing to update.\n ///\n /// If the operator is known, sortition pool is unlocked, and the\n /// operator is in the sortition pool, the sortition pool state is\n /// updated. If the sortition pool is locked, update needs to be\n /// postponed. Every other staker is incentivized to call\n /// `updateOperatorStatus` for the problematic operator to increase\n /// their own rewards in the pool.\n function involuntaryAuthorizationDecrease(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external onlyStakingContract {\n authorization.involuntaryAuthorizationDecrease(\n staking,\n sortitionPool,\n stakingProvider,\n fromAmount,\n toAmount\n );\n }\n\n /// @notice Updates address of the Random Beacon.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _randomBeacon Random Beacon address.\n function upgradeRandomBeacon(IRandomBeacon _randomBeacon)\n external\n onlyGovernance\n {\n randomBeacon = _randomBeacon;\n emit RandomBeaconUpgraded(address(_randomBeacon));\n }\n\n /// @notice Updates the wallet owner.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters. The wallet owner has to implement `IWalletOwner`\n /// interface.\n /// @param _walletOwner New wallet owner address.\n function updateWalletOwner(IWalletOwner _walletOwner)\n external\n onlyGovernance\n {\n walletOwner = _walletOwner;\n emit WalletOwnerUpdated(address(_walletOwner));\n }\n\n /// @notice Updates the values of authorization parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _minimumAuthorization New minimum authorization amount.\n /// @param _authorizationDecreaseDelay New authorization decrease delay in\n /// seconds.\n /// @param _authorizationDecreaseChangePeriod New authorization decrease\n /// change period in seconds.\n function updateAuthorizationParameters(\n uint96 _minimumAuthorization,\n uint64 _authorizationDecreaseDelay,\n uint64 _authorizationDecreaseChangePeriod\n ) external onlyGovernance {\n authorization.setMinimumAuthorization(_minimumAuthorization);\n authorization.setAuthorizationDecreaseDelay(\n _authorizationDecreaseDelay\n );\n authorization.setAuthorizationDecreaseChangePeriod(\n _authorizationDecreaseChangePeriod\n );\n\n emit AuthorizationParametersUpdated(\n _minimumAuthorization,\n _authorizationDecreaseDelay,\n _authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Updates the values of DKG parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param _seedTimeout New seed timeout.\n /// @param _resultChallengePeriodLength New DKG result challenge period\n /// length.\n /// @param _resultChallengeExtraGas New extra gas value required to be left\n /// at the end of the DKG result challenge transaction.\n /// @param _resultSubmissionTimeout New DKG result submission timeout.\n /// @param _submitterPrecedencePeriodLength New submitter precedence period\n /// length.\n function updateDkgParameters(\n uint256 _seedTimeout,\n uint256 _resultChallengePeriodLength,\n uint256 _resultChallengeExtraGas,\n uint256 _resultSubmissionTimeout,\n uint256 _submitterPrecedencePeriodLength\n ) external onlyGovernance {\n dkg.setSeedTimeout(_seedTimeout);\n dkg.setResultChallengePeriodLength(_resultChallengePeriodLength);\n dkg.setResultChallengeExtraGas(_resultChallengeExtraGas);\n dkg.setResultSubmissionTimeout(_resultSubmissionTimeout);\n dkg.setSubmitterPrecedencePeriodLength(\n _submitterPrecedencePeriodLength\n );\n\n // slither-disable-next-line reentrancy-events\n emit DkgParametersUpdated(\n _seedTimeout,\n _resultChallengePeriodLength,\n _resultChallengeExtraGas,\n _resultSubmissionTimeout,\n _submitterPrecedencePeriodLength\n );\n }\n\n /// @notice Updates the values of reward parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param maliciousDkgResultNotificationRewardMultiplier New value of the\n /// DKG malicious result notification reward multiplier.\n /// @param sortitionPoolRewardsBanDuration New sortition pool rewards\n /// ban duration in seconds.\n function updateRewardParameters(\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n ) external onlyGovernance {\n _maliciousDkgResultNotificationRewardMultiplier = maliciousDkgResultNotificationRewardMultiplier;\n _sortitionPoolRewardsBanDuration = sortitionPoolRewardsBanDuration;\n emit RewardParametersUpdated(\n maliciousDkgResultNotificationRewardMultiplier,\n sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Updates the values of slashing parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param maliciousDkgResultSlashingAmount New malicious DKG result\n /// slashing amount.\n function updateSlashingParameters(uint96 maliciousDkgResultSlashingAmount)\n external\n onlyGovernance\n {\n _maliciousDkgResultSlashingAmount = maliciousDkgResultSlashingAmount;\n emit SlashingParametersUpdated(maliciousDkgResultSlashingAmount);\n }\n\n /// @notice Updates the values of gas-related parameters.\n /// @dev Can be called only by the contract guvnor, which should be the\n /// wallet registry governance contract. The caller is responsible for\n /// validating parameters.\n /// @param dkgResultSubmissionGas New DKG result submission gas.\n /// @param dkgResultApprovalGasOffset New DKG result approval gas offset.\n /// @param notifyOperatorInactivityGasOffset New operator inactivity\n /// notification gas offset.\n /// @param notifySeedTimeoutGasOffset New seed for DKG delivery timeout\n /// notification gas offset.\n /// @param notifyDkgTimeoutNegativeGasOffset New DKG timeout notification gas\n /// offset.\n function updateGasParameters(\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n ) external onlyGovernance {\n _dkgResultSubmissionGas = dkgResultSubmissionGas;\n _dkgResultApprovalGasOffset = dkgResultApprovalGasOffset;\n _notifyOperatorInactivityGasOffset = notifyOperatorInactivityGasOffset;\n _notifySeedTimeoutGasOffset = notifySeedTimeoutGasOffset;\n _notifyDkgTimeoutNegativeGasOffset = notifyDkgTimeoutNegativeGasOffset;\n\n emit GasParametersUpdated(\n dkgResultSubmissionGas,\n dkgResultApprovalGasOffset,\n notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n\n /// @notice Requests a new wallet creation.\n /// @dev Can be called only by the owner of wallets.\n /// It locks the DKG and request a new relay entry. It expects\n /// that the DKG process will be started once a new relay entry\n /// gets generated.\n function requestNewWallet() external onlyWalletOwner {\n dkg.lockState();\n\n randomBeacon.requestRelayEntry(this);\n }\n\n /// @notice Closes an existing wallet. Reverts if wallet with the given ID\n /// does not exist or if it has already been closed.\n /// @param walletID ID of the wallet.\n /// @dev Only a Wallet Owner can call this function.\n function closeWallet(bytes32 walletID) external onlyWalletOwner {\n wallets.deleteWallet(walletID);\n emit WalletClosed(walletID);\n }\n\n /// @notice A callback that is executed once a new relay entry gets\n /// generated. It starts the DKG process.\n /// @dev Can be called only by the random beacon contract.\n /// @param relayEntry Relay entry.\n function __beaconCallback(uint256 relayEntry, uint256) external {\n require(\n msg.sender == address(randomBeacon),\n \"Caller is not the Random Beacon\"\n );\n\n dkg.start(relayEntry);\n }\n\n /// @notice Submits result of DKG protocol.\n /// The DKG result consists of result submitting member index,\n /// calculated group public key, bytes array of misbehaved members,\n /// concatenation of signatures from group members, indices of members\n /// corresponding to each signature and the list of group members.\n /// The result is registered optimistically and waits for an approval.\n /// The result can be challenged when it is believed to be incorrect.\n /// The challenge verifies the registered result i.a. it checks if members\n /// list corresponds to the expected set of members determined\n /// by the sortition pool.\n /// @dev The message to be signed by each member is keccak256 hash of the\n /// chain ID, calculated group public key, misbehaved members indices\n /// and DKG start block. The calculated hash should be prefixed with\n /// `\\x19Ethereum signed message:\\n` before signing, so the message to\n /// sign is:\n /// `\\x19Ethereum signed message:\\n${keccak256(chainID,groupPubKey,misbehavedIndices,startBlock)}`\n /// @param dkgResult DKG result.\n function submitDkgResult(DKG.Result calldata dkgResult) external {\n wallets.validatePublicKey(dkgResult.groupPubKey);\n dkg.submitResult(dkgResult);\n }\n\n /// @notice Approves DKG result. Can be called when the challenge period for\n /// the submitted result is finished. Considers the submitted result\n /// as valid, bans misbehaved group members from the sortition pool\n /// rewards, and completes the group creation by activating the\n /// candidate group. For the first `resultSubmissionTimeout` blocks\n /// after the end of the challenge period can be called only by the\n /// DKG result submitter. After that time, can be called by anyone.\n /// A new wallet based on the DKG result details.\n /// @param dkgResult Result to approve. Must match the submitted result\n /// stored during `submitDkgResult`.\n function approveDkgResult(DKG.Result calldata dkgResult) external {\n uint256 gasStart = gasleft();\n uint32[] memory misbehavedMembers = dkg.approveResult(dkgResult);\n\n (bytes32 walletID, bytes32 publicKeyX, bytes32 publicKeyY) = wallets\n .addWallet(dkgResult.membersHash, dkgResult.groupPubKey);\n\n emit WalletCreated(walletID, keccak256(abi.encode(dkgResult)));\n\n if (misbehavedMembers.length > 0) {\n sortitionPool.setRewardIneligibility(\n misbehavedMembers,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n }\n\n walletOwner.__ecdsaWalletCreatedCallback(\n walletID,\n publicKeyX,\n publicKeyY\n );\n\n dkg.complete();\n\n // Refund msg.sender's ETH for DKG result submission and result approval\n reimbursementPool.refund(\n _dkgResultSubmissionGas +\n (gasStart - gasleft()) +\n _dkgResultApprovalGasOffset,\n msg.sender\n );\n }\n\n /// @notice Notifies about seed for DKG delivery timeout. It is expected\n /// that a seed is delivered by the Random Beacon as a relay entry in a\n /// callback function.\n function notifySeedTimeout() external {\n uint256 gasStart = gasleft();\n\n dkg.notifySeedTimeout();\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifySeedTimeoutGasOffset,\n msg.sender\n );\n }\n\n /// @notice Notifies about DKG timeout.\n function notifyDkgTimeout() external {\n uint256 gasStart = gasleft();\n\n dkg.notifyDkgTimeout();\n\n // Note that the offset is subtracted as it is expected that the cleanup\n // performed on DKG timeout notification removes data from the storage\n // which is recovering gas for the transaction.\n reimbursementPool.refund(\n (gasStart - gasleft()) - _notifyDkgTimeoutNegativeGasOffset,\n msg.sender\n );\n }\n\n /// @notice Challenges DKG result. If the submitted result is proved to be\n /// invalid it reverts the DKG back to the result submission phase.\n /// @param dkgResult Result to challenge. Must match the submitted result\n /// stored during `submitDkgResult`.\n /// @dev Due to EIP-150 1/64 of the gas is not forwarded to the call, and\n /// will be kept to execute the remaining operations in the function\n /// after the call inside the try-catch. To eliminate a class of\n /// attacks related to the gas limit manipulation, this function\n /// requires an extra amount of gas to be left at the end of the\n /// execution.\n function challengeDkgResult(DKG.Result calldata dkgResult) external {\n (\n bytes32 maliciousDkgResultHash,\n uint32 maliciousDkgResultSubmitterId\n ) = dkg.challengeResult(dkgResult);\n\n address maliciousDkgResultSubmitterAddress = sortitionPool\n .getIDOperator(maliciousDkgResultSubmitterId);\n\n address[] memory operatorWrapper = new address[](1);\n operatorWrapper[0] = operatorToStakingProvider(\n maliciousDkgResultSubmitterAddress\n );\n\n try\n staking.seize(\n _maliciousDkgResultSlashingAmount,\n _maliciousDkgResultNotificationRewardMultiplier,\n msg.sender,\n operatorWrapper\n )\n {\n // slither-disable-next-line reentrancy-events\n emit DkgMaliciousResultSlashed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n } catch {\n // Should never happen but we want to ensure a non-critical path\n // failure from an external contract does not stop the challenge\n // to complete.\n emit DkgMaliciousResultSlashingFailed(\n maliciousDkgResultHash,\n _maliciousDkgResultSlashingAmount,\n maliciousDkgResultSubmitterAddress\n );\n }\n\n // Due to EIP-150, 1/64 of the gas is not forwarded to the call, and\n // will be kept to execute the remaining operations in the function\n // after the call inside the try-catch.\n //\n // To ensure there is no way for the caller to manipulate gas limit in\n // such a way that the call inside try-catch fails with out-of-gas and\n // the rest of the function is executed with the remaining 1/64 of gas,\n // we require an extra gas amount to be left at the end of the call to\n // `challengeDkgResult`.\n dkg.requireChallengeExtraGas();\n }\n\n /// @notice Notifies about operators who are inactive. Using this function,\n /// a majority of the wallet signing group can decide about\n /// punishing specific group members who constantly fail doing their\n /// job. If the provided claim is proved to be valid and signed by\n /// sufficient number of group members, operators of members deemed\n /// as inactive are banned from sortition pool rewards for the\n /// duration specified by `sortitionPoolRewardsBanDuration` parameter.\n /// The function allows to signal about single operators being\n /// inactive as well as to signal wallet-wide heartbeat failures\n /// that are propagated to the wallet owner who should begin the\n /// procedure of moving responsibilities to another wallet given\n /// that the wallet who failed the heartbeat may soon be not able to\n /// function and provide new signatures.\n /// The sender of the claim must be one of the claim signers. This\n /// function can be called only for registered wallets\n /// @param claim Operator inactivity claim.\n /// @param nonce Current inactivity claim nonce for the given wallet signing\n /// group. Must be the same as the stored one.\n /// @param groupMembers Identifiers of the wallet signing group members.\n function notifyOperatorInactivity(\n Inactivity.Claim calldata claim,\n uint256 nonce,\n uint32[] calldata groupMembers\n ) external {\n uint256 gasStart = gasleft();\n\n bytes32 walletID = claim.walletID;\n\n require(nonce == inactivityClaimNonce[walletID], \"Invalid nonce\");\n\n (bytes32 pubKeyX, bytes32 pubKeyY) = wallets\n .getWalletPublicKeyCoordinates(walletID);\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(groupMembers)),\n \"Invalid group members\"\n );\n\n uint32[] memory ineligibleOperators = Inactivity.verifyClaim(\n sortitionPool,\n claim,\n bytes.concat(pubKeyX, pubKeyY),\n nonce,\n groupMembers\n );\n\n inactivityClaimNonce[walletID]++;\n\n emit InactivityClaimed(walletID, nonce, msg.sender);\n\n sortitionPool.setRewardIneligibility(\n ineligibleOperators,\n // solhint-disable-next-line not-rely-on-time\n block.timestamp + _sortitionPoolRewardsBanDuration\n );\n\n if (claim.heartbeatFailed) {\n walletOwner.__ecdsaWalletHeartbeatFailedCallback(\n walletID,\n pubKeyX,\n pubKeyY\n );\n }\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + _notifyOperatorInactivityGasOffset,\n msg.sender\n );\n }\n\n /// @notice Allows the wallet owner to add all signing group members of the\n /// wallet with the given ID to the slashing queue of the staking .\n /// contract. The notifier will receive reward per each group member\n /// from the staking contract notifiers treasury. The reward is\n /// scaled by the `rewardMultiplier` provided as a parameter.\n /// @param amount Amount of tokens to seize from each signing group member.\n /// @param rewardMultiplier Fraction of the staking contract notifiers\n /// reward the notifier should receive; should be between [0, 100].\n /// @param notifier Address of the misbehavior notifier.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @dev Requirements:\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - `rewardMultiplier` must be between [0, 100].\n /// - This function does revert if staking contract call reverts.\n /// The calling code needs to handle the potential revert.\n function seize(\n uint96 amount,\n uint256 rewardMultiplier,\n address notifier,\n bytes32 walletID,\n uint32[] calldata walletMembersIDs\n ) external onlyWalletOwner {\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n address[] memory groupMembersAddresses = sortitionPool.getIDOperators(\n walletMembersIDs\n );\n address[] memory stakingProvidersAddresses = new address[](\n walletMembersIDs.length\n );\n for (uint256 i = 0; i < groupMembersAddresses.length; i++) {\n stakingProvidersAddresses[i] = operatorToStakingProvider(\n groupMembersAddresses[i]\n );\n }\n\n staking.seize(\n amount,\n rewardMultiplier,\n notifier,\n stakingProvidersAddresses\n );\n }\n\n /// @notice Checks if DKG result is valid for the current DKG.\n /// @param result DKG result.\n /// @return True if the result is valid. If the result is invalid it returns\n /// false and an error message.\n function isDkgResultValid(DKG.Result calldata result)\n external\n view\n returns (bool, string memory)\n {\n return dkg.isResultValid(result);\n }\n\n /// @notice Check current wallet creation state.\n function getWalletCreationState() external view returns (DKG.State) {\n return dkg.currentState();\n }\n\n /// @notice Checks whether the given operator is a member of the given\n /// wallet signing group.\n /// @param walletID ID of the wallet.\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\n /// @param operator Address of the checked operator.\n /// @param walletMemberIndex Position of the operator in the wallet signing\n /// group members list.\n /// @return True - if the operator is a member of the given wallet signing\n /// group. False - otherwise.\n /// @dev Requirements:\n /// - The `operator` parameter must be an actual sortition pool operator.\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\n /// be exactly the same as the hash stored under `membersIdsHash`\n /// for the given `walletID`. Those IDs are not directly stored\n /// in the contract for gas efficiency purposes but they can be\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\n /// events.\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]\n function isWalletMember(\n bytes32 walletID,\n uint32[] calldata walletMembersIDs,\n address operator,\n uint256 walletMemberIndex\n ) external view returns (bool) {\n uint32 operatorID = sortitionPool.getOperatorID(operator);\n\n require(operatorID != 0, \"Not a sortition pool operator\");\n\n bytes32 memberIdsHash = wallets.getWalletMembersIdsHash(walletID);\n\n require(\n memberIdsHash == keccak256(abi.encode(walletMembersIDs)),\n \"Invalid wallet members identifiers\"\n );\n\n require(\n 1 <= walletMemberIndex &&\n walletMemberIndex <= walletMembersIDs.length,\n \"Wallet member index is out of range\"\n );\n\n return walletMembersIDs[walletMemberIndex - 1] == operatorID;\n }\n\n /// @notice Checks if awaiting seed timed out.\n /// @return True if awaiting seed timed out, false otherwise.\n function hasSeedTimedOut() external view returns (bool) {\n return dkg.hasSeedTimedOut();\n }\n\n /// @notice Checks if DKG timed out. The DKG timeout period includes time required\n /// for off-chain protocol execution and time for the result publication\n /// for all group members. After this time result cannot be submitted\n /// and DKG can be notified about the timeout.\n /// @return True if DKG timed out, false otherwise.\n function hasDkgTimedOut() external view returns (bool) {\n return dkg.hasDkgTimedOut();\n }\n\n function getWallet(bytes32 walletID)\n external\n view\n returns (Wallets.Wallet memory)\n {\n return wallets.registry[walletID];\n }\n\n /// @notice Gets public key of a wallet with a given wallet ID.\n /// The public key is returned in an uncompressed format as a 64-byte\n /// concatenation of X and Y coordinates.\n /// @param walletID ID of the wallet.\n /// @return Uncompressed public key of the wallet.\n function getWalletPublicKey(bytes32 walletID)\n external\n view\n returns (bytes memory)\n {\n return wallets.getWalletPublicKey(walletID);\n }\n\n /// @notice Checks if a wallet with the given ID is registered.\n /// @param walletID Wallet's ID.\n /// @return True if wallet is registered, false otherwise.\n function isWalletRegistered(bytes32 walletID) external view returns (bool) {\n return wallets.isWalletRegistered(walletID);\n }\n\n /// @notice The minimum authorization amount required so that operator can\n /// participate in ECDSA Wallet operations.\n function minimumAuthorization() external view returns (uint96) {\n return authorization.parameters.minimumAuthorization;\n }\n\n /// @notice Returns the current value of the staking provider's eligible\n /// stake. Eligible stake is defined as the currently authorized\n /// stake minus the pending authorization decrease. Eligible stake\n /// is what is used for operator's weight in the sortition pool.\n /// If the authorized stake minus the pending authorization decrease\n /// is below the minimum authorization, eligible stake is 0.\n function eligibleStake(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.eligibleStake(staking, stakingProvider);\n }\n\n /// @notice Returns the amount of rewards available for withdrawal for the\n /// given staking provider. Reverts if staking provider has not\n /// registered the operator address.\n function availableRewards(address stakingProvider)\n external\n view\n returns (uint96)\n {\n address operator = stakingProviderToOperator(stakingProvider);\n require(operator != address(0), \"Unknown operator\");\n return sortitionPool.getAvailableRewards(operator);\n }\n\n /// @notice Returns the amount of stake that is pending authorization\n /// decrease for the given staking provider. If no authorization\n /// decrease has been requested, returns zero.\n function pendingAuthorizationDecrease(address stakingProvider)\n external\n view\n returns (uint96)\n {\n return authorization.pendingAuthorizationDecrease(stakingProvider);\n }\n\n /// @notice Returns the remaining time in seconds that needs to pass before\n /// the requested authorization decrease can be approved.\n /// If the sortition pool state was not updated yet by the operator\n /// after requesting the authorization decrease, returns\n /// `type(uint64).max`.\n function remainingAuthorizationDecreaseDelay(address stakingProvider)\n external\n view\n returns (uint64)\n {\n return\n authorization.remainingAuthorizationDecreaseDelay(stakingProvider);\n }\n\n /// @notice Returns operator registered for the given staking provider.\n function stakingProviderToOperator(address stakingProvider)\n public\n view\n returns (address)\n {\n return authorization.stakingProviderToOperator[stakingProvider];\n }\n\n /// @notice Returns staking provider of the given operator.\n function operatorToStakingProvider(address operator)\n public\n view\n returns (address)\n {\n return authorization.operatorToStakingProvider[operator];\n }\n\n /// @notice Checks if the operator's authorized stake is in sync with\n /// operator's weight in the sortition pool.\n /// If the operator is not in the sortition pool and their\n /// authorized stake is non-zero, function returns false.\n function isOperatorUpToDate(address operator) external view returns (bool) {\n return\n authorization.isOperatorUpToDate(staking, sortitionPool, operator);\n }\n\n /// @notice Returns true if the given operator is in the sortition pool.\n /// Otherwise, returns false.\n function isOperatorInPool(address operator) external view returns (bool) {\n return sortitionPool.isOperatorInPool(operator);\n }\n\n /// @notice Selects a new group of operators. Can only be called when DKG\n /// is in progress and the pool is locked.\n /// At least one operator has to be registered in the pool,\n /// otherwise the function fails reverting the transaction.\n /// @return IDs of selected group members.\n function selectGroup() external view returns (uint32[] memory) {\n return sortitionPool.selectGroup(DKG.groupSize, bytes32(dkg.seed));\n }\n\n /// @notice Retrieves dkg parameters that were set in DKG library.\n function dkgParameters() external view returns (DKG.Parameters memory) {\n return dkg.parameters;\n }\n\n /// @notice Returns authorization-related parameters.\n /// @dev The minimum authorization is also returned by `minimumAuthorization()`\n /// function, as a requirement of `IApplication` interface.\n /// @return minimumAuthorization The minimum authorization amount required\n /// so that operator can participate in the random beacon. This\n /// amount is required to execute slashing for providing a malicious\n /// DKG result or when a relay entry times out.\n /// @return authorizationDecreaseDelay Delay in seconds that needs to pass\n /// between the time authorization decrease is requested and the\n /// time that request gets approved. Protects against free-riders\n /// earning rewards and not being active in the network.\n /// @return authorizationDecreaseChangePeriod Authorization decrease change\n /// period in seconds. It is the time, before authorization decrease\n /// delay end, during which the pending authorization decrease\n /// request can be overwritten.\n /// If set to 0, pending authorization decrease request can not be\n /// overwritten until the entire `authorizationDecreaseDelay` ends.\n /// If set to value equal `authorizationDecreaseDelay`, request can\n /// always be overwritten.\n function authorizationParameters()\n external\n view\n returns (\n uint96 minimumAuthorization,\n uint64 authorizationDecreaseDelay,\n uint64 authorizationDecreaseChangePeriod\n )\n {\n return (\n authorization.parameters.minimumAuthorization,\n authorization.parameters.authorizationDecreaseDelay,\n authorization.parameters.authorizationDecreaseChangePeriod\n );\n }\n\n /// @notice Retrieves reward-related parameters.\n /// @return maliciousDkgResultNotificationRewardMultiplier Percentage of the\n /// staking contract malicious behavior notification reward which\n /// will be transferred to the notifier reporting about a malicious\n /// DKG result. Notifiers are rewarded from a notifiers treasury\n /// pool. For example, if notification reward is 1000 and the value\n /// of the multiplier is 5, the notifier will receive:\n /// 5% of 1000 = 50 per each operator affected.\n /// @return sortitionPoolRewardsBanDuration Duration of the sortition pool\n /// rewards ban imposed on operators who missed their turn for DKG\n /// result submission or who failed a heartbeat.\n function rewardParameters()\n external\n view\n returns (\n uint256 maliciousDkgResultNotificationRewardMultiplier,\n uint256 sortitionPoolRewardsBanDuration\n )\n {\n return (\n _maliciousDkgResultNotificationRewardMultiplier,\n _sortitionPoolRewardsBanDuration\n );\n }\n\n /// @notice Retrieves slashing-related parameters.\n /// @return maliciousDkgResultSlashingAmount Slashing amount for submitting\n /// a malicious DKG result. Every DKG result submitted can be\n /// challenged for the time of `dkg.resultChallengePeriodLength`.\n /// If the DKG result submitted is challenged and proven to be\n /// malicious, the operator who submitted the malicious result is\n /// slashed for `_maliciousDkgResultSlashingAmount`.\n function slashingParameters()\n external\n view\n returns (uint96 maliciousDkgResultSlashingAmount)\n {\n return _maliciousDkgResultSlashingAmount;\n }\n\n /// @notice Retrieves gas-related parameters.\n /// @return dkgResultSubmissionGas Calculated max gas cost for submitting\n /// a DKG result. This will be refunded as part of the DKG approval\n /// process. It is in the submitter's interest to not skip his\n /// priority turn on the approval, otherwise the refund of the DKG\n /// submission will be refunded to another group member that will\n /// call the DKG approve function.\n /// @return dkgResultApprovalGasOffset Gas that is meant to balance the DKG\n /// result approval's overall cost. It can be updated by the\n /// governance based on the current market conditions.\n /// @return notifyOperatorInactivityGasOffset Gas that is meant to balance\n /// the notification of an operator inactivity. It can be updated by\n /// the governance based on the current market conditions.\n /// @return notifySeedTimeoutGasOffset Gas that is meant to balance the\n /// notification of a seed for DKG delivery timeout. It can be updated\n /// by the governance based on the current market conditions.\n /// @return notifyDkgTimeoutNegativeGasOffset Gas that is meant to balance\n /// the notification of a DKG protocol execution timeout. It can be\n /// updated by the governance based on the current market conditions.\n function gasParameters()\n external\n view\n returns (\n uint256 dkgResultSubmissionGas,\n uint256 dkgResultApprovalGasOffset,\n uint256 notifyOperatorInactivityGasOffset,\n uint256 notifySeedTimeoutGasOffset,\n uint256 notifyDkgTimeoutNegativeGasOffset\n )\n {\n return (\n _dkgResultSubmissionGas,\n _dkgResultApprovalGasOffset,\n _notifyOperatorInactivityGasOffset,\n _notifySeedTimeoutGasOffset,\n _notifyDkgTimeoutNegativeGasOffset\n );\n }\n}\n"
|
|
42
42
|
},
|
|
43
43
|
"@keep-network/random-beacon/contracts/api/IRandomBeacon.sol": {
|
|
44
44
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity 0.8.17;\n\nimport \"./IRandomBeaconConsumer.sol\";\n\n/// @title Random Beacon interface\ninterface IRandomBeacon {\n /// @notice Creates a request to generate a new relay entry. Requires a\n /// request fee denominated in T token.\n /// @param callbackContract Beacon consumer callback contract.\n function requestRelayEntry(IRandomBeaconConsumer callbackContract) external;\n}\n"
|
|
@@ -148,9 +148,18 @@
|
|
|
148
148
|
"@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol": {
|
|
149
149
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./math/MathUpgradeable.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary StringsUpgradeable {\n bytes16 private constant _SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = MathUpgradeable.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n /// @solidity memory-safe-assembly\n assembly {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n /// @solidity memory-safe-assembly\n assembly {\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, MathUpgradeable.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n"
|
|
150
150
|
},
|
|
151
|
+
"@openzeppelin/contracts/access/AccessControl.sol": {
|
|
152
|
+
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role);\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view virtual override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `_msgSender()` is missing `role`.\n * Overriding this function changes the behavior of the {onlyRole} modifier.\n *\n * Format of the revert message is described in {_checkRole}.\n *\n * _Available since v4.6._\n */\n function _checkRole(bytes32 role) internal view virtual {\n _checkRole(role, _msgSender());\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view virtual {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(account),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n *\n * May emit a {RoleGranted} event.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n *\n * May emit a {RoleRevoked} event.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n *\n * May emit a {RoleRevoked} event.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * May emit a {RoleGranted} event.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n *\n * May emit a {RoleGranted} event.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n *\n * May emit a {RoleRevoked} event.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n"
|
|
153
|
+
},
|
|
154
|
+
"@openzeppelin/contracts/access/IAccessControl.sol": {
|
|
155
|
+
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n"
|
|
156
|
+
},
|
|
151
157
|
"@openzeppelin/contracts/access/Ownable.sol": {
|
|
152
158
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
|
|
153
159
|
},
|
|
160
|
+
"@openzeppelin/contracts/governance/TimelockController.sol": {
|
|
161
|
+
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (governance/TimelockController.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../access/AccessControl.sol\";\nimport \"../token/ERC721/IERC721Receiver.sol\";\nimport \"../token/ERC1155/IERC1155Receiver.sol\";\nimport \"../utils/Address.sol\";\n\n/**\n * @dev Contract module which acts as a timelocked controller. When set as the\n * owner of an `Ownable` smart contract, it enforces a timelock on all\n * `onlyOwner` maintenance operations. This gives time for users of the\n * controlled contract to exit before a potentially dangerous maintenance\n * operation is applied.\n *\n * By default, this contract is self administered, meaning administration tasks\n * have to go through the timelock process. The proposer (resp executor) role\n * is in charge of proposing (resp executing) operations. A common use case is\n * to position this {TimelockController} as the owner of a smart contract, with\n * a multisig or a DAO as the sole proposer.\n *\n * _Available since v3.3._\n */\ncontract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver {\n bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256(\"TIMELOCK_ADMIN_ROLE\");\n bytes32 public constant PROPOSER_ROLE = keccak256(\"PROPOSER_ROLE\");\n bytes32 public constant EXECUTOR_ROLE = keccak256(\"EXECUTOR_ROLE\");\n bytes32 public constant CANCELLER_ROLE = keccak256(\"CANCELLER_ROLE\");\n uint256 internal constant _DONE_TIMESTAMP = uint256(1);\n\n mapping(bytes32 => uint256) private _timestamps;\n uint256 private _minDelay;\n\n /**\n * @dev Emitted when a call is scheduled as part of operation `id`.\n */\n event CallScheduled(\n bytes32 indexed id,\n uint256 indexed index,\n address target,\n uint256 value,\n bytes data,\n bytes32 predecessor,\n uint256 delay\n );\n\n /**\n * @dev Emitted when a call is performed as part of operation `id`.\n */\n event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);\n\n /**\n * @dev Emitted when operation `id` is cancelled.\n */\n event Cancelled(bytes32 indexed id);\n\n /**\n * @dev Emitted when the minimum delay for future operations is modified.\n */\n event MinDelayChange(uint256 oldDuration, uint256 newDuration);\n\n /**\n * @dev Initializes the contract with the following parameters:\n *\n * - `minDelay`: initial minimum delay for operations\n * - `proposers`: accounts to be granted proposer and canceller roles\n * - `executors`: accounts to be granted executor role\n * - `admin`: optional account to be granted admin role; disable with zero address\n *\n * IMPORTANT: The optional admin can aid with initial configuration of roles after deployment\n * without being subject to delay, but this role should be subsequently renounced in favor of\n * administration through timelocked proposals. Previous versions of this contract would assign\n * this admin to the deployer automatically and should be renounced as well.\n */\n constructor(\n uint256 minDelay,\n address[] memory proposers,\n address[] memory executors,\n address admin\n ) {\n _setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);\n _setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);\n _setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);\n _setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);\n\n // self administration\n _setupRole(TIMELOCK_ADMIN_ROLE, address(this));\n\n // optional admin\n if (admin != address(0)) {\n _setupRole(TIMELOCK_ADMIN_ROLE, admin);\n }\n\n // register proposers and cancellers\n for (uint256 i = 0; i < proposers.length; ++i) {\n _setupRole(PROPOSER_ROLE, proposers[i]);\n _setupRole(CANCELLER_ROLE, proposers[i]);\n }\n\n // register executors\n for (uint256 i = 0; i < executors.length; ++i) {\n _setupRole(EXECUTOR_ROLE, executors[i]);\n }\n\n _minDelay = minDelay;\n emit MinDelayChange(0, minDelay);\n }\n\n /**\n * @dev Modifier to make a function callable only by a certain role. In\n * addition to checking the sender's role, `address(0)` 's role is also\n * considered. Granting a role to `address(0)` is equivalent to enabling\n * this role for everyone.\n */\n modifier onlyRoleOrOpenRole(bytes32 role) {\n if (!hasRole(role, address(0))) {\n _checkRole(role, _msgSender());\n }\n _;\n }\n\n /**\n * @dev Contract might receive/hold ETH as part of the maintenance process.\n */\n receive() external payable {}\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AccessControl) returns (bool) {\n return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns whether an id correspond to a registered operation. This\n * includes both Pending, Ready and Done operations.\n */\n function isOperation(bytes32 id) public view virtual returns (bool registered) {\n return getTimestamp(id) > 0;\n }\n\n /**\n * @dev Returns whether an operation is pending or not.\n */\n function isOperationPending(bytes32 id) public view virtual returns (bool pending) {\n return getTimestamp(id) > _DONE_TIMESTAMP;\n }\n\n /**\n * @dev Returns whether an operation is ready or not.\n */\n function isOperationReady(bytes32 id) public view virtual returns (bool ready) {\n uint256 timestamp = getTimestamp(id);\n return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;\n }\n\n /**\n * @dev Returns whether an operation is done or not.\n */\n function isOperationDone(bytes32 id) public view virtual returns (bool done) {\n return getTimestamp(id) == _DONE_TIMESTAMP;\n }\n\n /**\n * @dev Returns the timestamp at with an operation becomes ready (0 for\n * unset operations, 1 for done operations).\n */\n function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) {\n return _timestamps[id];\n }\n\n /**\n * @dev Returns the minimum delay for an operation to become valid.\n *\n * This value can be changed by executing an operation that calls `updateDelay`.\n */\n function getMinDelay() public view virtual returns (uint256 duration) {\n return _minDelay;\n }\n\n /**\n * @dev Returns the identifier of an operation containing a single\n * transaction.\n */\n function hashOperation(\n address target,\n uint256 value,\n bytes calldata data,\n bytes32 predecessor,\n bytes32 salt\n ) public pure virtual returns (bytes32 hash) {\n return keccak256(abi.encode(target, value, data, predecessor, salt));\n }\n\n /**\n * @dev Returns the identifier of an operation containing a batch of\n * transactions.\n */\n function hashOperationBatch(\n address[] calldata targets,\n uint256[] calldata values,\n bytes[] calldata payloads,\n bytes32 predecessor,\n bytes32 salt\n ) public pure virtual returns (bytes32 hash) {\n return keccak256(abi.encode(targets, values, payloads, predecessor, salt));\n }\n\n /**\n * @dev Schedule an operation containing a single transaction.\n *\n * Emits a {CallScheduled} event.\n *\n * Requirements:\n *\n * - the caller must have the 'proposer' role.\n */\n function schedule(\n address target,\n uint256 value,\n bytes calldata data,\n bytes32 predecessor,\n bytes32 salt,\n uint256 delay\n ) public virtual onlyRole(PROPOSER_ROLE) {\n bytes32 id = hashOperation(target, value, data, predecessor, salt);\n _schedule(id, delay);\n emit CallScheduled(id, 0, target, value, data, predecessor, delay);\n }\n\n /**\n * @dev Schedule an operation containing a batch of transactions.\n *\n * Emits one {CallScheduled} event per transaction in the batch.\n *\n * Requirements:\n *\n * - the caller must have the 'proposer' role.\n */\n function scheduleBatch(\n address[] calldata targets,\n uint256[] calldata values,\n bytes[] calldata payloads,\n bytes32 predecessor,\n bytes32 salt,\n uint256 delay\n ) public virtual onlyRole(PROPOSER_ROLE) {\n require(targets.length == values.length, \"TimelockController: length mismatch\");\n require(targets.length == payloads.length, \"TimelockController: length mismatch\");\n\n bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);\n _schedule(id, delay);\n for (uint256 i = 0; i < targets.length; ++i) {\n emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay);\n }\n }\n\n /**\n * @dev Schedule an operation that is to becomes valid after a given delay.\n */\n function _schedule(bytes32 id, uint256 delay) private {\n require(!isOperation(id), \"TimelockController: operation already scheduled\");\n require(delay >= getMinDelay(), \"TimelockController: insufficient delay\");\n _timestamps[id] = block.timestamp + delay;\n }\n\n /**\n * @dev Cancel an operation.\n *\n * Requirements:\n *\n * - the caller must have the 'canceller' role.\n */\n function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {\n require(isOperationPending(id), \"TimelockController: operation cannot be cancelled\");\n delete _timestamps[id];\n\n emit Cancelled(id);\n }\n\n /**\n * @dev Execute an (ready) operation containing a single transaction.\n *\n * Emits a {CallExecuted} event.\n *\n * Requirements:\n *\n * - the caller must have the 'executor' role.\n */\n // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,\n // thus any modifications to the operation during reentrancy should be caught.\n // slither-disable-next-line reentrancy-eth\n function execute(\n address target,\n uint256 value,\n bytes calldata payload,\n bytes32 predecessor,\n bytes32 salt\n ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {\n bytes32 id = hashOperation(target, value, payload, predecessor, salt);\n\n _beforeCall(id, predecessor);\n _execute(target, value, payload);\n emit CallExecuted(id, 0, target, value, payload);\n _afterCall(id);\n }\n\n /**\n * @dev Execute an (ready) operation containing a batch of transactions.\n *\n * Emits one {CallExecuted} event per transaction in the batch.\n *\n * Requirements:\n *\n * - the caller must have the 'executor' role.\n */\n function executeBatch(\n address[] calldata targets,\n uint256[] calldata values,\n bytes[] calldata payloads,\n bytes32 predecessor,\n bytes32 salt\n ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {\n require(targets.length == values.length, \"TimelockController: length mismatch\");\n require(targets.length == payloads.length, \"TimelockController: length mismatch\");\n\n bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);\n\n _beforeCall(id, predecessor);\n for (uint256 i = 0; i < targets.length; ++i) {\n address target = targets[i];\n uint256 value = values[i];\n bytes calldata payload = payloads[i];\n _execute(target, value, payload);\n emit CallExecuted(id, i, target, value, payload);\n }\n _afterCall(id);\n }\n\n /**\n * @dev Execute an operation's call.\n */\n function _execute(\n address target,\n uint256 value,\n bytes calldata data\n ) internal virtual {\n (bool success, ) = target.call{value: value}(data);\n require(success, \"TimelockController: underlying transaction reverted\");\n }\n\n /**\n * @dev Checks before execution of an operation's calls.\n */\n function _beforeCall(bytes32 id, bytes32 predecessor) private view {\n require(isOperationReady(id), \"TimelockController: operation is not ready\");\n require(predecessor == bytes32(0) || isOperationDone(predecessor), \"TimelockController: missing dependency\");\n }\n\n /**\n * @dev Checks after execution of an operation's calls.\n */\n function _afterCall(bytes32 id) private {\n require(isOperationReady(id), \"TimelockController: operation is not ready\");\n _timestamps[id] = _DONE_TIMESTAMP;\n }\n\n /**\n * @dev Changes the minimum timelock duration for future operations.\n *\n * Emits a {MinDelayChange} event.\n *\n * Requirements:\n *\n * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing\n * an operation where the timelock is the target and the data is the ABI-encoded call to this function.\n */\n function updateDelay(uint256 newDelay) external virtual {\n require(msg.sender == address(this), \"TimelockController: caller must be timelock\");\n emit MinDelayChange(_minDelay, newDelay);\n _minDelay = newDelay;\n }\n\n /**\n * @dev See {IERC721Receiver-onERC721Received}.\n */\n function onERC721Received(\n address,\n address,\n uint256,\n bytes memory\n ) public virtual override returns (bytes4) {\n return this.onERC721Received.selector;\n }\n\n /**\n * @dev See {IERC1155Receiver-onERC1155Received}.\n */\n function onERC1155Received(\n address,\n address,\n uint256,\n uint256,\n bytes memory\n ) public virtual override returns (bytes4) {\n return this.onERC1155Received.selector;\n }\n\n /**\n * @dev See {IERC1155Receiver-onERC1155BatchReceived}.\n */\n function onERC1155BatchReceived(\n address,\n address,\n uint256[] memory,\n uint256[] memory,\n bytes memory\n ) public virtual override returns (bytes4) {\n return this.onERC1155BatchReceived.selector;\n }\n}\n"
|
|
162
|
+
},
|
|
154
163
|
"@openzeppelin/contracts/interfaces/draft-IERC1822.sol": {
|
|
155
164
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\n * proxy whose upgrades are fully controlled by the current implementation.\n */\ninterface IERC1822Proxiable {\n /**\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\n * address.\n *\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n * function revert if invoked through a proxy.\n */\n function proxiableUUID() external view returns (bytes32);\n}\n"
|
|
156
165
|
},
|
|
@@ -175,6 +184,9 @@
|
|
|
175
184
|
"@openzeppelin/contracts/security/ReentrancyGuard.sol": {
|
|
176
185
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n}\n"
|
|
177
186
|
},
|
|
187
|
+
"@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol": {
|
|
188
|
+
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev _Available since v3.1._\n */\ninterface IERC1155Receiver is IERC165 {\n /**\n * @dev Handles the receipt of a single ERC1155 token type. This function is\n * called at the end of a `safeTransferFrom` after the balance has been updated.\n *\n * NOTE: To accept the transfer, this must return\n * `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n * (i.e. 0xf23a6e61, or its own function selector).\n *\n * @param operator The address which initiated the transfer (i.e. msg.sender)\n * @param from The address which previously owned the token\n * @param id The ID of the token being transferred\n * @param value The amount of tokens being transferred\n * @param data Additional data with no specified format\n * @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` if transfer is allowed\n */\n function onERC1155Received(\n address operator,\n address from,\n uint256 id,\n uint256 value,\n bytes calldata data\n ) external returns (bytes4);\n\n /**\n * @dev Handles the receipt of a multiple ERC1155 token types. This function\n * is called at the end of a `safeBatchTransferFrom` after the balances have\n * been updated.\n *\n * NOTE: To accept the transfer(s), this must return\n * `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n * (i.e. 0xbc197c81, or its own function selector).\n *\n * @param operator The address which initiated the batch transfer (i.e. msg.sender)\n * @param from The address which previously owned the token\n * @param ids An array containing ids of each token being transferred (order and length must match values array)\n * @param values An array containing amounts of each token being transferred (order and length must match ids array)\n * @param data Additional data with no specified format\n * @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` if transfer is allowed\n */\n function onERC1155BatchReceived(\n address operator,\n address from,\n uint256[] calldata ids,\n uint256[] calldata values,\n bytes calldata data\n ) external returns (bytes4);\n}\n"
|
|
189
|
+
},
|
|
178
190
|
"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol": {
|
|
179
191
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n"
|
|
180
192
|
},
|
|
@@ -242,7 +254,7 @@
|
|
|
242
254
|
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\n/// @title Application interface for Threshold Network applications\n/// @notice Generic interface for an application. Application is an external\n/// smart contract or a set of smart contracts utilizing functionalities\n/// offered by Threshold Network. Applications authorized for the given\n/// staking provider are eligible to slash the stake delegated to that\n/// staking provider.\ninterface IApplication {\n /// @dev Event emitted by `withdrawRewards` function.\n event RewardsWithdrawn(address indexed stakingProvider, uint96 amount);\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.\n /// @dev Emits `RewardsWithdrawn` event.\n function withdrawRewards(address stakingProvider) external;\n\n /// @notice Used by T staking contract to inform the application that the\n /// authorized amount for the given staking provider increased.\n /// The application may do any necessary housekeeping. The\n /// application must revert the transaction in case the\n /// authorization is below the minimum required.\n function authorizationIncreased(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external;\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. The application should mark the authorization as\n /// pending decrease and respond to the staking contract with\n /// `approveAuthorizationDecrease` at its discretion. It may\n /// happen right away but it also may happen several months later.\n /// If there is already a pending authorization decrease request\n /// for the application, and the application does not agree for\n /// overwriting it, the function should revert.\n function authorizationDecreaseRequested(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external;\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. Lets the application to\n /// do any housekeeping neccessary. Called with 250k gas limit and\n /// does not revert the transaction if\n /// `involuntaryAuthorizationDecrease` call failed.\n function involuntaryAuthorizationDecrease(\n address stakingProvider,\n uint96 fromAmount,\n uint96 toAmount\n ) external;\n\n /// @notice Returns the amount of application rewards available for\n /// withdrawal for the given staking provider.\n function availableRewards(address stakingProvider)\n external\n view\n returns (uint96);\n\n /// @notice The minimum authorization amount required for the staking\n /// provider so that they can participate in the application.\n function minimumAuthorization() external view returns (uint96);\n}\n"
|
|
243
255
|
},
|
|
244
256
|
"@threshold-network/solidity-contracts/contracts/staking/IStaking.sol": {
|
|
245
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\n/// @title Interface of Threshold Network staking contract\n/// @notice The staking contract enables T owners to have their wallets offline\n/// and their stake managed by staking providers on their behalf.\n/// The staking contract does not define operator role. The operator\n/// responsible for running off-chain client software is appointed by\n/// the staking provider in the particular application utilizing the\n/// staking contract. All off-chain client software should be able\n/// to run without exposing operator's or staking provider’s private\n/// key and should not require any owner’s keys at all. The stake\n/// delegation optimizes the network throughput without compromising the\n/// security of the owners’ stake.\ninterface IStaking {\n enum StakeType {\n NU,\n KEEP,\n T\n }\n\n //\n //\n // Delegating a stake\n //\n //\n\n /// @notice Creates a delegation with `msg.sender` owner with the given\n /// staking provider, beneficiary, and authorizer. Transfers the\n /// given amount of T to the staking contract.\n /// @dev The owner of the delegation needs to have the amount approved to\n /// transfer to the staking contract.\n function stake(\n address stakingProvider,\n address payable beneficiary,\n address authorizer,\n uint96 amount\n ) external;\n\n /// @notice Copies delegation from the legacy KEEP staking contract to T\n /// staking contract. No tokens are transferred. Caches the active\n /// stake amount from KEEP staking contract. Can be called by\n /// anyone.\n /// @dev The staking provider in T staking contract is the legacy KEEP\n /// staking contract operator.\n function stakeKeep(address stakingProvider) external;\n\n /// @notice Copies delegation from the legacy NU staking contract to T\n /// staking contract, additionally appointing staking provider,\n /// beneficiary and authorizer roles. Caches the amount staked in NU\n /// staking contract. Can be called only by the original delegation\n /// owner.\n function stakeNu(\n address stakingProvider,\n address payable beneficiary,\n address authorizer\n ) external;\n\n /// @notice Refresh Keep stake owner. Can be called only by the old owner\n /// or their staking provider.\n /// @dev The staking provider in T staking contract is the legacy KEEP\n /// staking contract operator.\n function refreshKeepStakeOwner(address stakingProvider) external;\n\n /// @notice Allows the Governance to set the minimum required stake amount.\n /// This amount is required to protect against griefing the staking\n /// contract and individual applications are allowed to require\n /// higher minimum stakes if necessary.\n function setMinimumStakeAmount(uint96 amount) external;\n\n //\n //\n // Authorizing an application\n //\n //\n\n /// @notice Allows the Governance to approve the particular application\n /// before individual stake authorizers are able to authorize it.\n function approveApplication(address application) external;\n\n /// @notice Increases the authorization of the given staking provider for\n /// the given application by the given amount. Can only be called by\n /// the authorizer for that staking provider.\n /// @dev Calls `authorizationIncreased(address stakingProvider, uint256 amount)`\n /// on the given application to notify the application about\n /// authorization change. See `IApplication`.\n function increaseAuthorization(\n address stakingProvider,\n address application,\n uint96 amount\n ) external;\n\n /// @notice Requests decrease of the authorization for the given staking\n /// provider on the given application by the provided amount.\n /// It may not change the authorized amount immediatelly. When\n /// it happens depends on the application. Can only be called by the\n /// given staking provider’s authorizer. Overwrites pending\n /// authorization decrease for the given staking provider and\n /// application if the application agrees for that. If the\n /// application does not agree for overwriting, the function\n /// reverts.\n /// @dev Calls `authorizationDecreaseRequested(address stakingProvider, uint256 amount)`\n /// on the given application. See `IApplication`.\n function requestAuthorizationDecrease(\n address stakingProvider,\n address application,\n uint96 amount\n ) external;\n\n /// @notice Requests decrease of all authorizations for the given staking\n /// provider on all applications by all authorized amount.\n /// It may not change the authorized amount immediatelly. When\n /// it happens depends on the application. Can only be called by the\n /// given staking provider’s authorizer. Overwrites pending\n /// authorization decrease for the given staking provider and\n /// application.\n /// @dev Calls `authorizationDecreaseRequested(address stakingProvider, uint256 amount)`\n /// for each authorized application. See `IApplication`.\n function requestAuthorizationDecrease(address stakingProvider) external;\n\n /// @notice Called by the application at its discretion to approve the\n /// previously requested authorization decrease request. Can only be\n /// called by the application that was previously requested to\n /// decrease the authorization for that staking provider.\n /// Returns resulting authorized amount for the application.\n function approveAuthorizationDecrease(address stakingProvider)\n external\n returns (uint96);\n\n /// @notice Decreases the authorization for the given `stakingProvider` on\n /// the given disabled `application`, for all authorized amount.\n /// Can be called by anyone.\n function forceDecreaseAuthorization(\n address stakingProvider,\n address application\n ) external;\n\n /// @notice Pauses the given application’s eligibility to slash stakes.\n /// Besides that stakers can't change authorization to the application.\n /// Can be called only by the Panic Button of the particular\n /// application. The paused application can not slash stakes until\n /// it is approved again by the Governance using `approveApplication`\n /// function. Should be used only in case of an emergency.\n function pauseApplication(address application) external;\n\n /// @notice Disables the given application. The disabled application can't\n /// slash stakers. Also stakers can't increase authorization to that\n /// application but can decrease without waiting by calling\n /// `requestAuthorizationDecrease` at any moment. Can be called only\n /// by the governance. The disabled application can't be approved\n /// again. Should be used only in case of an emergency.\n function disableApplication(address application) external;\n\n /// @notice Sets the Panic Button role for the given application to the\n /// provided address. Can only be called by the Governance. If the\n /// Panic Button for the given application should be disabled, the\n /// role address should be set to 0x0 address.\n function setPanicButton(address application, address panicButton) external;\n\n /// @notice Sets the maximum number of applications one staking provider can\n /// have authorized. Used to protect against DoSing slashing queue.\n /// Can only be called by the Governance.\n function setAuthorizationCeiling(uint256 ceiling) external;\n\n //\n //\n // Stake top-up\n //\n //\n\n /// @notice Increases the amount of the stake for the given staking provider.\n /// @dev The sender of this transaction needs to have the amount approved to\n /// transfer to the staking contract.\n function topUp(address stakingProvider, uint96 amount) external;\n\n /// @notice Propagates information about stake top-up from the legacy KEEP\n /// staking contract to T staking contract. Can be called only by\n /// the owner or the staking provider.\n function topUpKeep(address stakingProvider) external;\n\n /// @notice Propagates information about stake top-up from the legacy NU\n /// staking contract to T staking contract. Can be called only by\n /// the owner or the staking provider.\n function topUpNu(address stakingProvider) external;\n\n //\n //\n // Undelegating a stake (unstaking)\n //\n //\n\n /// @notice Reduces the liquid T stake amount by the provided amount and\n /// withdraws T to the owner. Reverts if there is at least one\n /// authorization higher than the sum of the legacy stake and\n /// remaining liquid T stake or if the unstake amount is higher than\n /// the liquid T stake amount. Can be called only by the delegation\n /// owner or the staking provider.\n function unstakeT(address stakingProvider, uint96 amount) external;\n\n /// @notice Sets the legacy KEEP staking contract active stake amount cached\n /// in T staking contract to 0. Reverts if the amount of liquid T\n /// staked in T staking contract is lower than the highest\n /// application authorization. This function allows to unstake from\n /// KEEP staking contract and still being able to operate in T\n /// network and earning rewards based on the liquid T staked. Can be\n /// called only by the delegation owner or the staking provider.\n function unstakeKeep(address stakingProvider) external;\n\n /// @notice Reduces cached legacy NU stake amount by the provided amount.\n /// Reverts if there is at least one authorization higher than the\n /// sum of remaining legacy NU stake and liquid T stake for that\n /// staking provider or if the untaked amount is higher than the\n /// cached legacy stake amount. If succeeded, the legacy NU stake\n /// can be partially or fully undelegated on the legacy staking\n /// contract. This function allows to unstake from NU staking\n /// contract and still being able to operate in T network and\n /// earning rewards based on the liquid T staked. Can be called only\n /// by the delegation owner or the staking provider.\n function unstakeNu(address stakingProvider, uint96 amount) external;\n\n /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake\n /// amount to 0 and withdraws all liquid T from the stake to the\n /// owner. Reverts if there is at least one non-zero authorization.\n /// Can be called only by the delegation owner or the staking\n /// provider.\n function unstakeAll(address stakingProvider) external;\n\n //\n //\n // Keeping information in sync\n //\n //\n\n /// @notice Notifies about the discrepancy between legacy KEEP active stake\n /// and the amount cached in T staking contract. Slashes the staking\n /// provider in case the amount cached is higher than the actual\n /// active stake amount in KEEP staking contract. Needs to update\n /// authorizations of all affected applications and execute an\n /// involuntary allocation decrease on all affected applications.\n /// Can be called by anyone, notifier receives a reward.\n function notifyKeepStakeDiscrepancy(address stakingProvider) external;\n\n /// @notice Notifies about the discrepancy between legacy NU active stake\n /// and the amount cached in T staking contract. Slashes the\n /// staking provider in case the amount cached is higher than the\n /// actual active stake amount in NU staking contract. Needs to\n /// update authorizations of all affected applications and execute\n /// an involuntary allocation decrease on all affected applications.\n /// Can be called by anyone, notifier receives a reward.\n function notifyNuStakeDiscrepancy(address stakingProvider) external;\n\n /// @notice Sets the penalty amount for stake discrepancy and reward\n /// multiplier for reporting it. The penalty is seized from the\n /// delegated stake, and 5% of the penalty, scaled by the\n /// multiplier, is given to the notifier. The rest of the tokens are\n /// burned. Can only be called by the Governance. See `seize` function.\n function setStakeDiscrepancyPenalty(\n uint96 penalty,\n uint256 rewardMultiplier\n ) external;\n\n /// @notice Sets reward in T tokens for notification of misbehaviour\n /// of one staking provider. Can only be called by the governance.\n function setNotificationReward(uint96 reward) external;\n\n /// @notice Transfer some amount of T tokens as reward for notifications\n /// of misbehaviour\n function pushNotificationReward(uint96 reward) external;\n\n /// @notice Withdraw some amount of T tokens from notifiers treasury.\n /// Can only be called by the governance.\n function withdrawNotificationReward(address recipient, uint96 amount)\n external;\n\n /// @notice Adds staking providers to the slashing queue along with the\n /// amount that should be slashed from each one of them. Can only be\n /// called by application authorized for all staking providers in\n /// the array.\n function slash(uint96 amount, address[] memory stakingProviders) external;\n\n /// @notice Adds staking providers to the slashing queue along with the\n /// amount. The notifier will receive reward per each staking\n /// provider from notifiers treasury. Can only be called by\n /// application authorized for all staking providers in the array.\n function seize(\n uint96 amount,\n uint256 rewardMultipier,\n address notifier,\n address[] memory stakingProviders\n ) external;\n\n /// @notice Takes the given number of queued slashing operations and\n /// processes them. Receives 5% of the slashed amount.\n /// Executes `involuntaryAllocationDecrease` function on each\n /// affected application.\n function processSlashing(uint256 count) external;\n\n //\n //\n // Auxiliary functions\n //\n //\n\n /// @notice Returns the authorized stake amount of the staking provider for\n /// the application.\n function authorizedStake(address stakingProvider, address application)\n external\n view\n returns (uint96);\n\n /// @notice Returns staked amount of T, Keep and Nu for the specified\n /// staking provider.\n /// @dev All values are in T denomination\n function stakes(address stakingProvider)\n external\n view\n returns (\n uint96 tStake,\n uint96 keepInTStake,\n uint96 nuInTStake\n );\n\n /// @notice Returns start staking timestamp.\n /// @dev This value is set at most once.\n function getStartStakingTimestamp(address stakingProvider)\n external\n view\n returns (uint256);\n\n /// @notice Returns staked amount of NU for the specified staking provider.\n function stakedNu(address stakingProvider) external view returns (uint256);\n\n /// @notice Gets the stake owner, the beneficiary and the authorizer\n /// for the specified staking provider address.\n /// @return owner Stake owner address.\n /// @return beneficiary Beneficiary address.\n /// @return authorizer Authorizer address.\n function rolesOf(address stakingProvider)\n external\n view\n returns (\n address owner,\n address payable beneficiary,\n address authorizer\n );\n\n /// @notice Returns length of application array\n function getApplicationsLength() external view returns (uint256);\n\n /// @notice Returns length of slashing queue\n function getSlashingQueueLength() external view returns (uint256);\n\n /// @notice Returns minimum possible stake for T, KEEP or NU in T\n /// denomination.\n /// @dev For example, suppose the given staking provider has 10 T, 20 T\n /// worth of KEEP, and 30 T worth of NU all staked, and the maximum\n /// application authorization is 40 T, then `getMinStaked` for\n /// that staking provider returns:\n /// * 0 T if KEEP stake type specified i.e.\n /// min = 40 T max - (10 T + 30 T worth of NU) = 0 T\n /// * 10 T if NU stake type specified i.e.\n /// min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T\n /// * 0 T if T stake type specified i.e.\n /// min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T\n /// In other words, the minimum stake amount for the specified\n /// stake type is the minimum amount of stake of the given type\n /// needed to satisfy the maximum application authorization given the\n /// staked amounts of the other stake types for that staking provider.\n function getMinStaked(address stakingProvider, StakeType stakeTypes)\n external\n view\n returns (uint96);\n\n /// @notice Returns available amount to authorize for the specified application\n function getAvailableToAuthorize(\n address stakingProvider,\n address application\n ) external view returns (uint96);\n}\n"
|
|
257
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\n/// @title Interface of Threshold Network staking contract\n/// @notice The staking contract enables T owners to have their wallets offline\n/// and their stake managed by staking providers on their behalf.\n/// The staking contract does not define operator role. The operator\n/// responsible for running off-chain client software is appointed by\n/// the staking provider in the particular application utilizing the\n/// staking contract. All off-chain client software should be able\n/// to run without exposing operator's or staking provider’s private\n/// key and should not require any owner’s keys at all. The stake\n/// delegation optimizes the network throughput without compromising the\n/// security of the owners’ stake.\ninterface IStaking {\n enum StakeType {\n NU,\n KEEP,\n T\n }\n\n //\n //\n // Delegating a stake\n //\n //\n\n /// @notice Creates a delegation with `msg.sender` owner with the given\n /// staking provider, beneficiary, and authorizer. Transfers the\n /// given amount of T to the staking contract.\n /// @dev The owner of the delegation needs to have the amount approved to\n /// transfer to the staking contract.\n function stake(\n address stakingProvider,\n address payable beneficiary,\n address authorizer,\n uint96 amount\n ) external;\n\n /// @notice Copies delegation from the legacy KEEP staking contract to T\n /// staking contract. No tokens are transferred. Caches the active\n /// stake amount from KEEP staking contract. Can be called by\n /// anyone.\n /// @dev The staking provider in T staking contract is the legacy KEEP\n /// staking contract operator.\n function stakeKeep(address stakingProvider) external;\n\n /// @notice Copies delegation from the legacy NU staking contract to T\n /// staking contract, additionally appointing staking provider,\n /// beneficiary and authorizer roles. Caches the amount staked in NU\n /// staking contract. Can be called only by the original delegation\n /// owner.\n function stakeNu(\n address stakingProvider,\n address payable beneficiary,\n address authorizer\n ) external;\n\n /// @notice Allows the Governance to set the minimum required stake amount.\n /// This amount is required to protect against griefing the staking\n /// contract and individual applications are allowed to require\n /// higher minimum stakes if necessary.\n function setMinimumStakeAmount(uint96 amount) external;\n\n //\n //\n // Authorizing an application\n //\n //\n\n /// @notice Allows the Governance to approve the particular application\n /// before individual stake authorizers are able to authorize it.\n function approveApplication(address application) external;\n\n /// @notice Increases the authorization of the given staking provider for\n /// the given application by the given amount. Can only be called by\n /// the authorizer for that staking provider.\n /// @dev Calls `authorizationIncreased(address stakingProvider, uint256 amount)`\n /// on the given application to notify the application about\n /// authorization change. See `IApplication`.\n function increaseAuthorization(\n address stakingProvider,\n address application,\n uint96 amount\n ) external;\n\n /// @notice Requests decrease of the authorization for the given staking\n /// provider on the given application by the provided amount.\n /// It may not change the authorized amount immediatelly. When\n /// it happens depends on the application. Can only be called by the\n /// given staking provider’s authorizer. Overwrites pending\n /// authorization decrease for the given staking provider and\n /// application if the application agrees for that. If the\n /// application does not agree for overwriting, the function\n /// reverts.\n /// @dev Calls `authorizationDecreaseRequested(address stakingProvider, uint256 amount)`\n /// on the given application. See `IApplication`.\n function requestAuthorizationDecrease(\n address stakingProvider,\n address application,\n uint96 amount\n ) external;\n\n /// @notice Requests decrease of all authorizations for the given staking\n /// provider on all applications by all authorized amount.\n /// It may not change the authorized amount immediatelly. When\n /// it happens depends on the application. Can only be called by the\n /// given staking provider’s authorizer. Overwrites pending\n /// authorization decrease for the given staking provider and\n /// application.\n /// @dev Calls `authorizationDecreaseRequested(address stakingProvider, uint256 amount)`\n /// for each authorized application. See `IApplication`.\n function requestAuthorizationDecrease(address stakingProvider) external;\n\n /// @notice Called by the application at its discretion to approve the\n /// previously requested authorization decrease request. Can only be\n /// called by the application that was previously requested to\n /// decrease the authorization for that staking provider.\n /// Returns resulting authorized amount for the application.\n function approveAuthorizationDecrease(address stakingProvider)\n external\n returns (uint96);\n\n /// @notice Decreases the authorization for the given `stakingProvider` on\n /// the given disabled `application`, for all authorized amount.\n /// Can be called by anyone.\n function forceDecreaseAuthorization(\n address stakingProvider,\n address application\n ) external;\n\n /// @notice Pauses the given application’s eligibility to slash stakes.\n /// Besides that stakers can't change authorization to the application.\n /// Can be called only by the Panic Button of the particular\n /// application. The paused application can not slash stakes until\n /// it is approved again by the Governance using `approveApplication`\n /// function. Should be used only in case of an emergency.\n function pauseApplication(address application) external;\n\n /// @notice Disables the given application. The disabled application can't\n /// slash stakers. Also stakers can't increase authorization to that\n /// application but can decrease without waiting by calling\n /// `requestAuthorizationDecrease` at any moment. Can be called only\n /// by the governance. The disabled application can't be approved\n /// again. Should be used only in case of an emergency.\n function disableApplication(address application) external;\n\n /// @notice Sets the Panic Button role for the given application to the\n /// provided address. Can only be called by the Governance. If the\n /// Panic Button for the given application should be disabled, the\n /// role address should be set to 0x0 address.\n function setPanicButton(address application, address panicButton) external;\n\n /// @notice Sets the maximum number of applications one staking provider can\n /// have authorized. Used to protect against DoSing slashing queue.\n /// Can only be called by the Governance.\n function setAuthorizationCeiling(uint256 ceiling) external;\n\n //\n //\n // Stake top-up\n //\n //\n\n /// @notice Increases the amount of the stake for the given staking provider.\n /// @dev The sender of this transaction needs to have the amount approved to\n /// transfer to the staking contract.\n function topUp(address stakingProvider, uint96 amount) external;\n\n /// @notice Propagates information about stake top-up from the legacy KEEP\n /// staking contract to T staking contract. Can be called only by\n /// the owner or the staking provider.\n function topUpKeep(address stakingProvider) external;\n\n /// @notice Propagates information about stake top-up from the legacy NU\n /// staking contract to T staking contract. Can be called only by\n /// the owner or the staking provider.\n function topUpNu(address stakingProvider) external;\n\n //\n //\n // Undelegating a stake (unstaking)\n //\n //\n\n /// @notice Reduces the liquid T stake amount by the provided amount and\n /// withdraws T to the owner. Reverts if there is at least one\n /// authorization higher than the sum of the legacy stake and\n /// remaining liquid T stake or if the unstake amount is higher than\n /// the liquid T stake amount. Can be called only by the delegation\n /// owner or the staking provider.\n function unstakeT(address stakingProvider, uint96 amount) external;\n\n /// @notice Sets the legacy KEEP staking contract active stake amount cached\n /// in T staking contract to 0. Reverts if the amount of liquid T\n /// staked in T staking contract is lower than the highest\n /// application authorization. This function allows to unstake from\n /// KEEP staking contract and still being able to operate in T\n /// network and earning rewards based on the liquid T staked. Can be\n /// called only by the delegation owner or the staking provider.\n function unstakeKeep(address stakingProvider) external;\n\n /// @notice Reduces cached legacy NU stake amount by the provided amount.\n /// Reverts if there is at least one authorization higher than the\n /// sum of remaining legacy NU stake and liquid T stake for that\n /// staking provider or if the untaked amount is higher than the\n /// cached legacy stake amount. If succeeded, the legacy NU stake\n /// can be partially or fully undelegated on the legacy staking\n /// contract. This function allows to unstake from NU staking\n /// contract and still being able to operate in T network and\n /// earning rewards based on the liquid T staked. Can be called only\n /// by the delegation owner or the staking provider.\n function unstakeNu(address stakingProvider, uint96 amount) external;\n\n /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake\n /// amount to 0 and withdraws all liquid T from the stake to the\n /// owner. Reverts if there is at least one non-zero authorization.\n /// Can be called only by the delegation owner or the staking\n /// provider.\n function unstakeAll(address stakingProvider) external;\n\n //\n //\n // Keeping information in sync\n //\n //\n\n /// @notice Notifies about the discrepancy between legacy KEEP active stake\n /// and the amount cached in T staking contract. Slashes the staking\n /// provider in case the amount cached is higher than the actual\n /// active stake amount in KEEP staking contract. Needs to update\n /// authorizations of all affected applications and execute an\n /// involuntary allocation decrease on all affected applications.\n /// Can be called by anyone, notifier receives a reward.\n function notifyKeepStakeDiscrepancy(address stakingProvider) external;\n\n /// @notice Notifies about the discrepancy between legacy NU active stake\n /// and the amount cached in T staking contract. Slashes the\n /// staking provider in case the amount cached is higher than the\n /// actual active stake amount in NU staking contract. Needs to\n /// update authorizations of all affected applications and execute\n /// an involuntary allocation decrease on all affected applications.\n /// Can be called by anyone, notifier receives a reward.\n function notifyNuStakeDiscrepancy(address stakingProvider) external;\n\n /// @notice Sets the penalty amount for stake discrepancy and reward\n /// multiplier for reporting it. The penalty is seized from the\n /// delegated stake, and 5% of the penalty, scaled by the\n /// multiplier, is given to the notifier. The rest of the tokens are\n /// burned. Can only be called by the Governance. See `seize` function.\n function setStakeDiscrepancyPenalty(\n uint96 penalty,\n uint256 rewardMultiplier\n ) external;\n\n /// @notice Sets reward in T tokens for notification of misbehaviour\n /// of one staking provider. Can only be called by the governance.\n function setNotificationReward(uint96 reward) external;\n\n /// @notice Transfer some amount of T tokens as reward for notifications\n /// of misbehaviour\n function pushNotificationReward(uint96 reward) external;\n\n /// @notice Withdraw some amount of T tokens from notifiers treasury.\n /// Can only be called by the governance.\n function withdrawNotificationReward(address recipient, uint96 amount)\n external;\n\n /// @notice Adds staking providers to the slashing queue along with the\n /// amount that should be slashed from each one of them. Can only be\n /// called by application authorized for all staking providers in\n /// the array.\n function slash(uint96 amount, address[] memory stakingProviders) external;\n\n /// @notice Adds staking providers to the slashing queue along with the\n /// amount. The notifier will receive reward per each staking\n /// provider from notifiers treasury. Can only be called by\n /// application authorized for all staking providers in the array.\n function seize(\n uint96 amount,\n uint256 rewardMultipier,\n address notifier,\n address[] memory stakingProviders\n ) external;\n\n /// @notice Takes the given number of queued slashing operations and\n /// processes them. Receives 5% of the slashed amount.\n /// Executes `involuntaryAllocationDecrease` function on each\n /// affected application.\n function processSlashing(uint256 count) external;\n\n //\n //\n // Auxiliary functions\n //\n //\n\n /// @notice Returns the authorized stake amount of the staking provider for\n /// the application.\n function authorizedStake(address stakingProvider, address application)\n external\n view\n returns (uint96);\n\n /// @notice Returns staked amount of T, Keep and Nu for the specified\n /// staking provider.\n /// @dev All values are in T denomination\n function stakes(address stakingProvider)\n external\n view\n returns (\n uint96 tStake,\n uint96 keepInTStake,\n uint96 nuInTStake\n );\n\n /// @notice Returns start staking timestamp.\n /// @dev This value is set at most once.\n function getStartStakingTimestamp(address stakingProvider)\n external\n view\n returns (uint256);\n\n /// @notice Returns staked amount of NU for the specified staking provider.\n function stakedNu(address stakingProvider) external view returns (uint256);\n\n /// @notice Gets the stake owner, the beneficiary and the authorizer\n /// for the specified staking provider address.\n /// @return owner Stake owner address.\n /// @return beneficiary Beneficiary address.\n /// @return authorizer Authorizer address.\n function rolesOf(address stakingProvider)\n external\n view\n returns (\n address owner,\n address payable beneficiary,\n address authorizer\n );\n\n /// @notice Returns length of application array\n function getApplicationsLength() external view returns (uint256);\n\n /// @notice Returns length of slashing queue\n function getSlashingQueueLength() external view returns (uint256);\n\n /// @notice Returns minimum possible stake for T, KEEP or NU in T\n /// denomination.\n /// @dev For example, suppose the given staking provider has 10 T, 20 T\n /// worth of KEEP, and 30 T worth of NU all staked, and the maximum\n /// application authorization is 40 T, then `getMinStaked` for\n /// that staking provider returns:\n /// * 0 T if KEEP stake type specified i.e.\n /// min = 40 T max - (10 T + 30 T worth of NU) = 0 T\n /// * 10 T if NU stake type specified i.e.\n /// min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T\n /// * 0 T if T stake type specified i.e.\n /// min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T\n /// In other words, the minimum stake amount for the specified\n /// stake type is the minimum amount of stake of the given type\n /// needed to satisfy the maximum application authorization given the\n /// staked amounts of the other stake types for that staking provider.\n function getMinStaked(address stakingProvider, StakeType stakeTypes)\n external\n view\n returns (uint96);\n\n /// @notice Returns available amount to authorize for the specified application\n function getAvailableToAuthorize(\n address stakingProvider,\n address application\n ) external view returns (uint96);\n}\n"
|
|
246
258
|
},
|
|
247
259
|
"contracts/bank/Bank.sol": {
|
|
248
260
|
"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 \"./IReceiveBalanceApproval.sol\";\nimport \"../vault/IVault.sol\";\n\n/// @title Bitcoin Bank\n/// @notice Bank is a central component tracking Bitcoin balances. Balances can\n/// be transferred between balance owners, and balance owners can\n/// approve their balances to be spent by others. Balances in the Bank\n/// are updated for depositors who deposited their Bitcoin into the\n/// Bridge and only the Bridge can increase balances.\n/// @dev Bank is a governable contract and the Governance can upgrade the Bridge\n/// address.\ncontract Bank is Ownable {\n address public bridge;\n\n /// @notice The balance of the given account in the Bank. Zero by default.\n mapping(address => uint256) public balanceOf;\n\n /// @notice The remaining amount of balance a spender will be\n /// allowed to transfer on behalf of an owner using\n /// `transferBalanceFrom`. Zero by default.\n mapping(address => mapping(address => uint256)) public allowance;\n\n /// @notice Returns the current nonce for an EIP2612 permission for the\n /// provided balance owner to protect against replay attacks. Used\n /// to construct an EIP2612 signature provided to the `permit`\n /// function.\n mapping(address => uint256) public nonces;\n\n uint256 public immutable cachedChainId;\n bytes32 public immutable cachedDomainSeparator;\n\n /// @notice Returns an EIP2612 Permit message hash. Used to construct\n /// an EIP2612 signature provided to the `permit` function.\n bytes32 public constant PERMIT_TYPEHASH =\n keccak256(\n \"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\"\n );\n\n event BalanceTransferred(\n address indexed from,\n address indexed to,\n uint256 amount\n );\n\n event BalanceApproved(\n address indexed owner,\n address indexed spender,\n uint256 amount\n );\n\n event BalanceIncreased(address indexed owner, uint256 amount);\n\n event BalanceDecreased(address indexed owner, uint256 amount);\n\n event BridgeUpdated(address newBridge);\n\n modifier onlyBridge() {\n require(msg.sender == address(bridge), \"Caller is not the bridge\");\n _;\n }\n\n constructor() {\n cachedChainId = block.chainid;\n cachedDomainSeparator = buildDomainSeparator();\n }\n\n /// @notice Allows the Governance to upgrade the Bridge address.\n /// @dev The function does not implement any governance delay and does not\n /// check the status of the Bridge. The Governance implementation needs\n /// to ensure all requirements for the upgrade are satisfied before\n /// executing this function.\n /// Requirements:\n /// - The new Bridge address must not be zero.\n /// @param _bridge The new Bridge address.\n function updateBridge(address _bridge) external onlyOwner {\n require(_bridge != address(0), \"Bridge address must not be 0x0\");\n bridge = _bridge;\n emit BridgeUpdated(_bridge);\n }\n\n /// @notice Moves the given `amount` of balance from the caller to\n /// `recipient`.\n /// @dev Requirements:\n /// - `recipient` cannot be the zero address,\n /// - the caller must have a balance of at least `amount`.\n /// @param recipient The recipient of the balance.\n /// @param amount The amount of the balance transferred.\n function transferBalance(address recipient, uint256 amount) external {\n _transferBalance(msg.sender, recipient, amount);\n }\n\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\n /// balance. Does not allow updating an existing allowance to\n /// a value that is non-zero to avoid someone using both the old and\n /// the new allowance by unfortunate transaction ordering. To update\n /// an allowance to a non-zero value please set it to zero first or\n /// use `increaseBalanceAllowance` or `decreaseBalanceAllowance` for\n /// an atomic update.\n /// @dev If the `amount` is set to `type(uint256).max`,\n /// `transferBalanceFrom` will not reduce an allowance.\n /// @param spender The address that will be allowed to spend the balance.\n /// @param amount The amount the spender is allowed to spend.\n function approveBalance(address spender, uint256 amount) external {\n require(\n amount == 0 || allowance[msg.sender][spender] == 0,\n \"Non-atomic allowance change not allowed\"\n );\n _approveBalance(msg.sender, spender, amount);\n }\n\n /// @notice Sets the `amount` as an allowance of a smart contract `spender`\n /// over the caller's balance and calls the `spender` via\n /// `receiveBalanceApproval`.\n /// @dev If the `amount` is set to `type(uint256).max`, the potential\n /// `transferBalanceFrom` executed in `receiveBalanceApproval` of\n /// `spender` will not reduce an allowance. Beware that changing an\n /// allowance with this function brings the risk that `spender` may use\n /// both the old and the new allowance by unfortunate transaction\n /// ordering. Please use `increaseBalanceAllowance` and\n /// `decreaseBalanceAllowance` to eliminate the risk.\n /// @param spender The smart contract that will be allowed to spend the\n /// balance.\n /// @param amount The amount the spender contract is allowed to spend.\n /// @param extraData Extra data passed to the `spender` contract via\n /// `receiveBalanceApproval` call.\n function approveBalanceAndCall(\n address spender,\n uint256 amount,\n bytes calldata extraData\n ) external {\n _approveBalance(msg.sender, spender, amount);\n IReceiveBalanceApproval(spender).receiveBalanceApproval(\n msg.sender,\n amount,\n extraData\n );\n }\n\n /// @notice Atomically increases the caller's balance allowance granted to\n /// `spender` by the given `addedValue`.\n /// @param spender The spender address for which the allowance is increased.\n /// @param addedValue The amount by which the allowance is increased.\n function increaseBalanceAllowance(address spender, uint256 addedValue)\n external\n {\n _approveBalance(\n msg.sender,\n spender,\n allowance[msg.sender][spender] + addedValue\n );\n }\n\n /// @notice Atomically decreases the caller's balance allowance granted to\n /// `spender` by the given `subtractedValue`.\n /// @dev Requirements:\n /// - `spender` must not be the zero address,\n /// - the current allowance for `spender` must not be lower than\n /// the `subtractedValue`.\n /// @param spender The spender address for which the allowance is decreased.\n /// @param subtractedValue The amount by which the allowance is decreased.\n function decreaseBalanceAllowance(address spender, uint256 subtractedValue)\n external\n {\n uint256 currentAllowance = allowance[msg.sender][spender];\n require(\n currentAllowance >= subtractedValue,\n \"Can not decrease balance allowance below zero\"\n );\n unchecked {\n _approveBalance(\n msg.sender,\n spender,\n currentAllowance - subtractedValue\n );\n }\n }\n\n /// @notice Moves `amount` of balance from `spender` to `recipient` using the\n /// allowance mechanism. `amount` is then deducted from the caller's\n /// allowance unless the allowance was made for `type(uint256).max`.\n /// @dev Requirements:\n /// - `recipient` cannot be the zero address,\n /// - `spender` must have a balance of at least `amount`,\n /// - the caller must have an allowance for `spender`'s balance of at\n /// least `amount`.\n /// @param spender The address from which the balance is transferred.\n /// @param recipient The address to which the balance is transferred.\n /// @param amount The amount of balance that is transferred.\n function transferBalanceFrom(\n address spender,\n address recipient,\n uint256 amount\n ) external {\n uint256 currentAllowance = allowance[spender][msg.sender];\n if (currentAllowance != type(uint256).max) {\n require(\n currentAllowance >= amount,\n \"Transfer amount exceeds allowance\"\n );\n unchecked {\n _approveBalance(spender, msg.sender, currentAllowance - amount);\n }\n }\n _transferBalance(spender, recipient, amount);\n }\n\n /// @notice An EIP2612 approval made with secp256k1 signature. Users can\n /// authorize a transfer of their balance with a signature\n /// conforming to the EIP712 standard, rather than an on-chain\n /// transaction from their address. Anyone can submit this signature\n /// on the user's behalf by calling the `permit` function, paying\n /// gas fees, and possibly performing other actions in the same\n /// transaction.\n /// @dev The deadline argument can be set to `type(uint256).max to create\n /// permits that effectively never expire. If the `amount` is set\n /// to `type(uint256).max` then `transferBalanceFrom` will not\n /// reduce an allowance. Beware that changing an allowance with this\n /// function brings the risk that someone may use both the old and the\n /// new allowance by unfortunate transaction ordering. Please use\n /// `increaseBalanceAllowance` and `decreaseBalanceAllowance` to\n /// eliminate the risk.\n /// @param owner The balance owner who signed the permission.\n /// @param spender The address that will be allowed to spend the balance.\n /// @param amount The amount the spender is allowed to spend.\n /// @param deadline The UNIX time until which the permit is valid.\n /// @param v V part of the permit signature.\n /// @param r R part of the permit signature.\n /// @param s S part of the permit signature.\n function permit(\n address owner,\n address spender,\n uint256 amount,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external {\n /* solhint-disable-next-line not-rely-on-time */\n require(deadline >= block.timestamp, \"Permission expired\");\n\n // Validate `s` and `v` values for a malleability concern described in EIP2.\n // Only signatures with `s` value in the lower half of the secp256k1\n // curve's order and `v` value of 27 or 28 are considered valid.\n require(\n uint256(s) <=\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\n \"Invalid signature 's' value\"\n );\n require(v == 27 || v == 28, \"Invalid signature 'v' value\");\n\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n DOMAIN_SEPARATOR(),\n keccak256(\n abi.encode(\n PERMIT_TYPEHASH,\n owner,\n spender,\n amount,\n nonces[owner]++,\n deadline\n )\n )\n )\n );\n address recoveredAddress = ecrecover(digest, v, r, s);\n require(\n recoveredAddress != address(0) && recoveredAddress == owner,\n \"Invalid signature\"\n );\n _approveBalance(owner, spender, amount);\n }\n\n /// @notice Increases balances of the provided `recipients` by the provided\n /// `amounts`. Can only be called by the Bridge.\n /// @dev Requirements:\n /// - length of `recipients` and `amounts` must be the same,\n /// - none of `recipients` addresses must point to the Bank.\n /// @param recipients Balance increase recipients.\n /// @param amounts Amounts by which balances are increased.\n function increaseBalances(\n address[] calldata recipients,\n uint256[] calldata amounts\n ) external onlyBridge {\n require(\n recipients.length == amounts.length,\n \"Arrays must have the same length\"\n );\n for (uint256 i = 0; i < recipients.length; i++) {\n _increaseBalance(recipients[i], amounts[i]);\n }\n }\n\n /// @notice Increases balance of the provided `recipient` by the provided\n /// `amount`. Can only be called by the Bridge.\n /// @dev Requirements:\n /// - `recipient` address must not point to the Bank.\n /// @param recipient Balance increase recipient.\n /// @param amount Amount by which the balance is increased.\n function increaseBalance(address recipient, uint256 amount)\n external\n onlyBridge\n {\n _increaseBalance(recipient, amount);\n }\n\n /// @notice Increases the given smart contract `vault`'s balance and\n /// notifies the `vault` contract about it.\n /// Can be called only by the Bridge.\n /// @dev Requirements:\n /// - `vault` must implement `IVault` interface,\n /// - length of `recipients` and `amounts` must be the same.\n /// @param vault Address of `IVault` recipient contract.\n /// @param recipients Balance increase recipients.\n /// @param amounts Amounts by which balances are increased.\n function increaseBalanceAndCall(\n address vault,\n address[] calldata recipients,\n uint256[] calldata amounts\n ) external onlyBridge {\n require(\n recipients.length == amounts.length,\n \"Arrays must have the same length\"\n );\n uint256 totalAmount = 0;\n for (uint256 i = 0; i < amounts.length; i++) {\n totalAmount += amounts[i];\n }\n _increaseBalance(vault, totalAmount);\n IVault(vault).receiveBalanceIncrease(recipients, amounts);\n }\n\n /// @notice Decreases caller's balance by the provided `amount`. There is no\n /// way to restore the balance so do not call this function unless\n /// you really know what you are doing!\n /// @dev Requirements:\n /// - The caller must have a balance of at least `amount`.\n /// @param amount The amount by which the balance is decreased.\n function decreaseBalance(uint256 amount) external {\n balanceOf[msg.sender] -= amount;\n emit BalanceDecreased(msg.sender, amount);\n }\n\n /// @notice Returns hash of EIP712 Domain struct with `TBTC Bank` as\n /// a signing domain and Bank contract as a verifying contract.\n /// Used to construct an EIP2612 signature provided to the `permit`\n /// function.\n /* solhint-disable-next-line func-name-mixedcase */\n function DOMAIN_SEPARATOR() public view returns (bytes32) {\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\n // chainId and is defined at contract deployment instead of\n // reconstructed for every signature, there is a risk of possible replay\n // attacks between chains in the event of a future chain split.\n // To address this issue, we check the cached chain ID against the\n // current one and in case they are different, we build domain separator\n // from scratch.\n if (block.chainid == cachedChainId) {\n return cachedDomainSeparator;\n } else {\n return buildDomainSeparator();\n }\n }\n\n function _increaseBalance(address recipient, uint256 amount) internal {\n require(\n recipient != address(this),\n \"Can not increase balance for Bank\"\n );\n balanceOf[recipient] += amount;\n emit BalanceIncreased(recipient, amount);\n }\n\n function _transferBalance(\n address spender,\n address recipient,\n uint256 amount\n ) private {\n require(\n recipient != address(0),\n \"Can not transfer to the zero address\"\n );\n require(\n recipient != address(this),\n \"Can not transfer to the Bank address\"\n );\n\n uint256 spenderBalance = balanceOf[spender];\n require(spenderBalance >= amount, \"Transfer amount exceeds balance\");\n unchecked {\n balanceOf[spender] = spenderBalance - amount;\n }\n balanceOf[recipient] += amount;\n emit BalanceTransferred(spender, recipient, amount);\n }\n\n function _approveBalance(\n address owner,\n address spender,\n uint256 amount\n ) private {\n require(spender != address(0), \"Can not approve to the zero address\");\n allowance[owner][spender] = amount;\n emit BalanceApproved(owner, spender, amount);\n }\n\n function buildDomainSeparator() private view returns (bytes32) {\n return\n keccak256(\n abi.encode(\n keccak256(\n \"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"\n ),\n keccak256(bytes(\"TBTC Bank\")),\n keccak256(bytes(\"1\")),\n block.chainid,\n address(this)\n )\n );\n }\n}\n"
|
|
@@ -317,19 +329,28 @@
|
|
|
317
329
|
"content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';\n"
|
|
318
330
|
},
|
|
319
331
|
"contracts/integrator/AbstractTBTCDepositor.sol": {
|
|
320
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.0;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\n\nimport \"./IBridge.sol\";\nimport \"./ITBTCVault.sol\";\n\n/// @title Abstract AbstractTBTCDepositor contract.\n/// @notice This abstract contract is meant to facilitate integration of protocols\n/// aiming to use tBTC as an underlying Bitcoin bridge.\n///\n/// Such an integrator is supposed to:\n/// - Create a child contract inheriting from this abstract contract\n/// - Call the `__AbstractTBTCDepositor_initialize` initializer function\n/// - Use the `_initializeDeposit` and `_finalizeDeposit` as part of their\n/// business logic in order to initialize and finalize deposits.\n///\n/// @dev Example usage:\n/// ```\n/// // Example upgradeable integrator contract.\n/// contract ExampleTBTCIntegrator is AbstractTBTCDepositor, Initializable {\n/// /// @custom:oz-upgrades-unsafe-allow constructor\n/// constructor() {\n/// // Prevents the contract from being initialized again.\n/// _disableInitializers();\n/// }\n///\n/// function initialize(\n/// address _bridge,\n/// address _tbtcVault\n/// ) external initializer {\n/// __AbstractTBTCDepositor_initialize(_bridge, _tbtcVault);\n/// }\n///\n/// function startProcess(\n/// IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n/// IBridgeTypes.DepositRevealInfo calldata reveal\n/// ) external {\n/// // Embed necessary context as extra data.\n/// bytes32 extraData = ...;\n///\n/// (uint256 depositKey, uint256 initialDepositAmount) = _initializeDeposit(\n/// fundingTx,\n/// reveal,\n/// extraData\n/// );\n///\n/// // Use the depositKey to track the process.\n/// }\n///\n/// function finalizeProcess(uint256 depositKey) external {\n/// // Ensure the function cannot be called for the same deposit\n/// // twice.\n///\n/// (\n/// uint256 initialDepositAmount,\n/// uint256 tbtcAmount,\n/// bytes32 extraData\n/// ) = _finalizeDeposit(depositKey);\n///\n/// // Do something with the minted TBTC using context\n/// // embedded in the extraData.\n/// }\n/// }\nabstract contract AbstractTBTCDepositor {\n using BTCUtils for bytes;\n\n /// @notice Multiplier to convert satoshi to TBTC token units.\n uint256 public constant SATOSHI_MULTIPLIER = 10**10;\n\n /// @notice Bridge contract address.\n IBridge public bridge;\n /// @notice TBTCVault contract address.\n ITBTCVault public tbtcVault;\n\n // Reserved storage space that allows adding more variables without affecting\n // the storage layout of the child contracts. The convention from OpenZeppelin\n // suggests the storage space should add up to 50 slots. If more variables are\n // added in the upcoming versions one need to reduce the array size accordingly.\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // slither-disable-next-line unused-state\n uint256[47] private __gap;\n\n /// @notice Initializes the contract. MUST BE CALLED from the child\n /// contract initializer.\n // slither-disable-next-line dead-code\n function __AbstractTBTCDepositor_initialize(\n address _bridge,\n address _tbtcVault\n ) internal {\n require(\n address(bridge) == address(0) && address(tbtcVault) == address(0),\n \"AbstractTBTCDepositor already initialized\"\n );\n\n require(_bridge != address(0), \"Bridge address cannot be zero\");\n require(_tbtcVault != address(0), \"TBTCVault address cannot be zero\");\n\n bridge = IBridge(_bridge);\n tbtcVault = ITBTCVault(_tbtcVault);\n }\n\n /// @notice Initializes a deposit by revealing it to the Bridge.\n /// @param fundingTx Bitcoin funding transaction data, see `IBridgeTypes.BitcoinTxInfo`.\n /// @param reveal Deposit reveal data, see `IBridgeTypes.DepositRevealInfo` struct.\n /// @param extraData 32-byte deposit extra data.\n /// @return depositKey Deposit key computed as\n /// `keccak256(fundingTxHash | reveal.fundingOutputIndex)`. This\n /// key can be used to refer to the deposit in the Bridge and\n /// TBTCVault contracts.\n /// @return initialDepositAmount Amount of funding transaction deposit. In\n /// TBTC token decimals precision.\n /// @dev Requirements:\n /// - The revealed vault address must match the TBTCVault address,\n /// - All requirements from {Bridge#revealDepositWithExtraData}\n /// function must be met.\n /// @dev This function doesn't validate if a deposit has been initialized before,\n /// as the Bridge won't allow the same deposit to be revealed twice.\n // slither-disable-next-line dead-code\n function _initializeDeposit(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n bytes32 extraData\n ) internal returns (uint256 depositKey, uint256 initialDepositAmount) {\n require(reveal.vault == address(tbtcVault), \"Vault address mismatch\");\n\n depositKey = _calculateDepositKey(\n _calculateBitcoinTxHash(fundingTx),\n reveal.fundingOutputIndex\n );\n\n // The Bridge does not allow to reveal the same deposit twice and\n // revealed deposits stay there forever. The transaction will revert\n // if the deposit has already been revealed so, there is no need to do\n // an explicit check here.\n bridge.revealDepositWithExtraData(fundingTx, reveal, extraData);\n\n initialDepositAmount =\n bridge.deposits(depositKey).amount *\n SATOSHI_MULTIPLIER;\n }\n\n /// @notice Finalizes a deposit by calculating the amount of TBTC minted\n /// for the deposit.\n /// @param depositKey Deposit key identifying the deposit.\n /// @return initialDepositAmount Amount of funding transaction deposit. In\n /// TBTC token decimals precision.\n /// @return tbtcAmount Approximate amount of TBTC minted for the deposit. In\n /// TBTC token decimals precision.\n /// @return extraData 32-byte deposit extra data.\n /// @dev Requirements:\n /// - The deposit must be initialized but not finalized\n /// (in the context of this contract) yet.\n /// - The deposit must be finalized on the Bridge side. That means the\n /// deposit must be either swept or optimistically minted.\n /// @dev THIS FUNCTION DOESN'T VALIDATE IF A DEPOSIT HAS BEEN FINALIZED BEFORE,\n /// IT IS A RESPONSIBILITY OF THE IMPLEMENTING CONTRACT TO ENSURE THIS\n /// FUNCTION WON'T BE CALLED TWICE FOR THE SAME DEPOSIT.\n /// @dev IMPORTANT NOTE: The tbtcAmount returned by this function is an\n /// approximation. See documentation of the `calculateTbtcAmount`\n /// responsible for calculating this value for more details.\n // slither-disable-next-line dead-code\n function _finalizeDeposit(uint256 depositKey)\n internal\n returns (\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n bytes32 extraData\n )\n {\n IBridgeTypes.DepositRequest memory deposit = bridge.deposits(\n depositKey\n );\n require(deposit.revealedAt != 0, \"Deposit not initialized\");\n\n (, uint64 finalizedAt) = tbtcVault.optimisticMintingRequests(\n depositKey\n );\n\n require(\n deposit.sweptAt != 0 || finalizedAt != 0,\n \"Deposit not finalized by the bridge\"\n );\n\n initialDepositAmount = deposit.amount * SATOSHI_MULTIPLIER;\n\n tbtcAmount = _calculateTbtcAmount(deposit.amount, deposit.treasuryFee);\n\n extraData = deposit.extraData;\n }\n\n /// @notice Calculates the amount of TBTC minted for the deposit.\n /// @param depositAmountSat Deposit amount in satoshi (1e8 precision).\n /// This is the actual amount deposited by the deposit creator, i.e.\n /// the gross amount the Bridge's fees are cut from.\n /// @param depositTreasuryFeeSat Deposit treasury fee in satoshi (1e8 precision).\n /// This is an accurate value of the treasury fee that was actually\n /// cut upon minting.\n /// @return tbtcAmount Approximate amount of TBTC minted for the deposit.\n /// @dev IMPORTANT NOTE: The tbtcAmount returned by this function may\n /// not correspond to the actual amount of TBTC minted for the deposit.\n /// Although the treasury fee cut upon minting is known precisely,\n /// this is not the case for the optimistic minting fee and the Bitcoin\n /// transaction fee. To overcome that problem, this function just takes\n /// the current maximum allowed values of both fees, at the moment of deposit\n /// finalization. For the great majority of the deposits, such an\n /// algorithm will return a tbtcAmount slightly lesser than the\n /// actual amount of TBTC minted for the deposit. This will cause\n /// some TBTC to be left in the contract and ensure there is enough\n /// liquidity to finalize the deposit. However, in some rare cases,\n /// where the actual values of those fees change between the deposit\n /// minting and finalization, the tbtcAmount returned by this function\n /// may be greater than the actual amount of TBTC minted for the deposit.\n /// If this happens and the reserve coming from previous deposits\n /// leftovers does not provide enough liquidity, the deposit will have\n /// to wait for finalization until the reserve is refilled by subsequent\n /// deposits or a manual top-up. The integrator is responsible for\n /// handling such cases.\n // slither-disable-next-line dead-code\n function _calculateTbtcAmount(\n uint64 depositAmountSat,\n uint64 depositTreasuryFeeSat\n ) internal view virtual returns (uint256) {\n // Both deposit amount and treasury fee are in the 1e8 satoshi precision.\n // We need to convert them to the 1e18 TBTC precision.\n uint256 amountSubTreasury = (depositAmountSat - depositTreasuryFeeSat) *\n SATOSHI_MULTIPLIER;\n\n uint256 omFeeDivisor = tbtcVault.optimisticMintingFeeDivisor();\n uint256 omFee = omFeeDivisor > 0\n ? (amountSubTreasury / omFeeDivisor)\n : 0;\n\n // The deposit transaction max fee is in the 1e8 satoshi precision.\n // We need to convert them to the 1e18 TBTC precision.\n (, , uint64 depositTxMaxFee, ) = bridge.depositParameters();\n uint256 txMaxFee = depositTxMaxFee * SATOSHI_MULTIPLIER;\n\n return amountSubTreasury - omFee - txMaxFee;\n }\n\n /// @notice Calculates the deposit key for the given funding transaction\n /// hash and funding output index.\n /// @param fundingTxHash Funding transaction hash.\n /// @param fundingOutputIndex Funding output index.\n /// @return depositKey Deposit key computed as\n /// `keccak256(fundingTxHash | reveal.fundingOutputIndex)`. This\n /// key can be used to refer to the deposit in the Bridge and\n /// TBTCVault contracts.\n // slither-disable-next-line dead-code\n function _calculateDepositKey(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) internal pure returns (uint256) {\n return\n uint256(\n keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex))\n );\n }\n\n /// @notice Calculates the Bitcoin transaction hash for the given Bitcoin\n /// transaction data.\n /// @param txInfo Bitcoin transaction data, see `IBridgeTypes.BitcoinTxInfo` struct.\n /// @return txHash Bitcoin transaction hash.\n // slither-disable-next-line dead-code\n function _calculateBitcoinTxHash(IBridgeTypes.BitcoinTxInfo calldata txInfo)\n internal\n view\n returns (bytes32)\n {\n return\n abi\n .encodePacked(\n txInfo.version,\n txInfo.inputVector,\n txInfo.outputVector,\n txInfo.locktime\n )\n .hash256View();\n }\n\n /// @notice Returns minimum deposit amount.\n /// @return Minimum deposit amount. In TBTC token decimals precision.\n // slither-disable-next-line dead-code\n function _minDepositAmount() internal view returns (uint256) {\n // Read tBTC Bridge Deposit Dust Threshold in satoshi precision.\n (uint64 bridgeDepositDustThresholdSat, , , ) = bridge\n .depositParameters();\n\n // Convert tBTC Bridge Deposit Dust Threshold to TBTC token precision.\n return bridgeDepositDustThresholdSat * SATOSHI_MULTIPLIER;\n }\n}\n"
|
|
332
|
+
"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.0;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\n\nimport \"./IBridge.sol\";\nimport \"./ITBTCVault.sol\";\n\n/// @title Abstract AbstractTBTCDepositor contract.\n/// @notice This abstract contract is meant to facilitate integration of protocols\n/// aiming to use tBTC as an underlying Bitcoin bridge.\n///\n/// Such an integrator is supposed to:\n/// - Create a child contract inheriting from this abstract contract\n/// - Call the `__AbstractTBTCDepositor_initialize` initializer function\n/// - Use the `_initializeDeposit` and `_finalizeDeposit` as part of their\n/// business logic in order to initialize and finalize deposits.\n///\n/// @dev Example usage:\n/// ```\n/// // Example upgradeable integrator contract.\n/// contract ExampleTBTCIntegrator is AbstractTBTCDepositor, Initializable {\n/// /// @custom:oz-upgrades-unsafe-allow constructor\n/// constructor() {\n/// // Prevents the contract from being initialized again.\n/// _disableInitializers();\n/// }\n///\n/// function initialize(\n/// address _bridge,\n/// address _tbtcVault\n/// ) external initializer {\n/// __AbstractTBTCDepositor_initialize(_bridge, _tbtcVault);\n/// }\n///\n/// function startProcess(\n/// IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n/// IBridgeTypes.DepositRevealInfo calldata reveal\n/// ) external {\n/// // Embed necessary context as extra data.\n/// bytes32 extraData = ...;\n///\n/// (uint256 depositKey, uint256 initialDepositAmount) = _initializeDeposit(\n/// fundingTx,\n/// reveal,\n/// extraData\n/// );\n///\n/// // Use the depositKey to track the process.\n/// }\n///\n/// function finalizeProcess(uint256 depositKey) external {\n/// // Ensure the function cannot be called for the same deposit\n/// // twice.\n///\n/// (\n/// uint256 initialDepositAmount,\n/// uint256 tbtcAmount,\n/// bytes32 extraData\n/// ) = _finalizeDeposit(depositKey);\n///\n/// // Do something with the minted TBTC using context\n/// // embedded in the extraData.\n/// }\n/// }\nabstract contract AbstractTBTCDepositor {\n using BTCUtils for bytes;\n\n /// @notice Multiplier to convert satoshi to TBTC token units.\n uint256 public constant SATOSHI_MULTIPLIER = 10**10;\n\n /// @notice Bridge contract address.\n IBridge public bridge;\n /// @notice TBTCVault contract address.\n ITBTCVault public tbtcVault;\n\n // Reserved storage space that allows adding more variables without affecting\n // the storage layout of the child contracts. The convention from OpenZeppelin\n // suggests the storage space should add up to 50 slots. If more variables are\n // added in the upcoming versions one need to reduce the array size accordingly.\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // slither-disable-next-line unused-state\n uint256[47] private __gap;\n\n /// @notice Initializes the contract. MUST BE CALLED from the child\n /// contract initializer.\n // slither-disable-next-line dead-code\n function __AbstractTBTCDepositor_initialize(\n address _bridge,\n address _tbtcVault\n ) internal {\n require(\n address(bridge) == address(0) && address(tbtcVault) == address(0),\n \"AbstractTBTCDepositor already initialized\"\n );\n\n require(_bridge != address(0), \"Bridge address cannot be zero\");\n require(_tbtcVault != address(0), \"TBTCVault address cannot be zero\");\n\n bridge = IBridge(_bridge);\n tbtcVault = ITBTCVault(_tbtcVault);\n }\n\n /// @notice Initializes a deposit by revealing it to the Bridge.\n /// @param fundingTx Bitcoin funding transaction data, see `IBridgeTypes.BitcoinTxInfo`.\n /// @param reveal Deposit reveal data, see `IBridgeTypes.DepositRevealInfo` struct.\n /// @param extraData 32-byte deposit extra data.\n /// @return depositKey Deposit key computed as\n /// `keccak256(fundingTxHash | reveal.fundingOutputIndex)`. This\n /// key can be used to refer to the deposit in the Bridge and\n /// TBTCVault contracts.\n /// @return initialDepositAmount Amount of funding transaction deposit. In\n /// TBTC token decimals precision.\n /// @dev Requirements:\n /// - The revealed vault address must match the TBTCVault address,\n /// - All requirements from {Bridge#revealDepositWithExtraData}\n /// function must be met.\n /// @dev This function doesn't validate if a deposit has been initialized before,\n /// as the Bridge won't allow the same deposit to be revealed twice.\n // slither-disable-next-line dead-code\n function _initializeDeposit(\n IBridgeTypes.BitcoinTxInfo memory fundingTx,\n IBridgeTypes.DepositRevealInfo memory reveal,\n bytes32 extraData\n ) internal returns (uint256 depositKey, uint256 initialDepositAmount) {\n require(reveal.vault == address(tbtcVault), \"Vault address mismatch\");\n\n depositKey = _calculateDepositKey(\n _calculateBitcoinTxHash(fundingTx),\n reveal.fundingOutputIndex\n );\n\n // The Bridge does not allow to reveal the same deposit twice and\n // revealed deposits stay there forever. The transaction will revert\n // if the deposit has already been revealed so, there is no need to do\n // an explicit check here.\n bridge.revealDepositWithExtraData(fundingTx, reveal, extraData);\n\n initialDepositAmount =\n bridge.deposits(depositKey).amount *\n SATOSHI_MULTIPLIER;\n }\n\n /// @notice Finalizes a deposit by calculating the amount of TBTC minted\n /// for the deposit.\n /// @param depositKey Deposit key identifying the deposit.\n /// @return initialDepositAmount Amount of funding transaction deposit. In\n /// TBTC token decimals precision.\n /// @return tbtcAmount Approximate amount of TBTC minted for the deposit. In\n /// TBTC token decimals precision.\n /// @return extraData 32-byte deposit extra data.\n /// @dev Requirements:\n /// - The deposit must be initialized but not finalized\n /// (in the context of this contract) yet.\n /// - The deposit must be finalized on the Bridge side. That means the\n /// deposit must be either swept or optimistically minted.\n /// @dev THIS FUNCTION DOESN'T VALIDATE IF A DEPOSIT HAS BEEN FINALIZED BEFORE,\n /// IT IS A RESPONSIBILITY OF THE IMPLEMENTING CONTRACT TO ENSURE THIS\n /// FUNCTION WON'T BE CALLED TWICE FOR THE SAME DEPOSIT.\n /// @dev IMPORTANT NOTE: The tbtcAmount returned by this function is an\n /// approximation. See documentation of the `calculateTbtcAmount`\n /// responsible for calculating this value for more details.\n // slither-disable-next-line dead-code\n function _finalizeDeposit(uint256 depositKey)\n internal\n returns (\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n bytes32 extraData\n )\n {\n IBridgeTypes.DepositRequest memory deposit = bridge.deposits(\n depositKey\n );\n require(deposit.revealedAt != 0, \"Deposit not initialized\");\n\n (, uint64 finalizedAt) = tbtcVault.optimisticMintingRequests(\n depositKey\n );\n\n require(\n deposit.sweptAt != 0 || finalizedAt != 0,\n \"Deposit not finalized by the bridge\"\n );\n\n initialDepositAmount = deposit.amount * SATOSHI_MULTIPLIER;\n\n tbtcAmount = _calculateTbtcAmount(deposit.amount, deposit.treasuryFee);\n\n extraData = deposit.extraData;\n }\n\n /// @notice Calculates the amount of TBTC minted for the deposit.\n /// @param depositAmountSat Deposit amount in satoshi (1e8 precision).\n /// This is the actual amount deposited by the deposit creator, i.e.\n /// the gross amount the Bridge's fees are cut from.\n /// @param depositTreasuryFeeSat Deposit treasury fee in satoshi (1e8 precision).\n /// This is an accurate value of the treasury fee that was actually\n /// cut upon minting.\n /// @return tbtcAmount Approximate amount of TBTC minted for the deposit.\n /// @dev IMPORTANT NOTE: The tbtcAmount returned by this function may\n /// not correspond to the actual amount of TBTC minted for the deposit.\n /// Although the treasury fee cut upon minting is known precisely,\n /// this is not the case for the optimistic minting fee and the Bitcoin\n /// transaction fee. To overcome that problem, this function just takes\n /// the current maximum allowed values of both fees, at the moment of deposit\n /// finalization. For the great majority of the deposits, such an\n /// algorithm will return a tbtcAmount slightly lesser than the\n /// actual amount of TBTC minted for the deposit. This will cause\n /// some TBTC to be left in the contract and ensure there is enough\n /// liquidity to finalize the deposit. However, in some rare cases,\n /// where the actual values of those fees change between the deposit\n /// minting and finalization, the tbtcAmount returned by this function\n /// may be greater than the actual amount of TBTC minted for the deposit.\n /// If this happens and the reserve coming from previous deposits\n /// leftovers does not provide enough liquidity, the deposit will have\n /// to wait for finalization until the reserve is refilled by subsequent\n /// deposits or a manual top-up. The integrator is responsible for\n /// handling such cases.\n // slither-disable-next-line dead-code\n function _calculateTbtcAmount(\n uint64 depositAmountSat,\n uint64 depositTreasuryFeeSat\n ) internal view virtual returns (uint256) {\n // Both deposit amount and treasury fee are in the 1e8 satoshi precision.\n // We need to convert them to the 1e18 TBTC precision.\n uint256 amountSubTreasury = (depositAmountSat - depositTreasuryFeeSat) *\n SATOSHI_MULTIPLIER;\n\n uint256 omFeeDivisor = tbtcVault.optimisticMintingFeeDivisor();\n uint256 omFee = omFeeDivisor > 0\n ? (amountSubTreasury / omFeeDivisor)\n : 0;\n\n // The deposit transaction max fee is in the 1e8 satoshi precision.\n // We need to convert them to the 1e18 TBTC precision.\n (, , uint64 depositTxMaxFee, ) = bridge.depositParameters();\n uint256 txMaxFee = depositTxMaxFee * SATOSHI_MULTIPLIER;\n\n return amountSubTreasury - omFee - txMaxFee;\n }\n\n /// @notice Calculates the deposit key for the given funding transaction\n /// hash and funding output index.\n /// @param fundingTxHash Funding transaction hash.\n /// @param fundingOutputIndex Funding output index.\n /// @return depositKey Deposit key computed as\n /// `keccak256(fundingTxHash | reveal.fundingOutputIndex)`. This\n /// key can be used to refer to the deposit in the Bridge and\n /// TBTCVault contracts.\n // slither-disable-next-line dead-code\n function _calculateDepositKey(\n bytes32 fundingTxHash,\n uint32 fundingOutputIndex\n ) internal pure returns (uint256) {\n return\n uint256(\n keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex))\n );\n }\n\n /// @notice Calculates the Bitcoin transaction hash for the given Bitcoin\n /// transaction data.\n /// @param txInfo Bitcoin transaction data, see `IBridgeTypes.BitcoinTxInfo` struct.\n /// @return txHash Bitcoin transaction hash.\n // slither-disable-next-line dead-code\n function _calculateBitcoinTxHash(IBridgeTypes.BitcoinTxInfo memory txInfo)\n internal\n view\n returns (bytes32)\n {\n return\n abi\n .encodePacked(\n txInfo.version,\n txInfo.inputVector,\n txInfo.outputVector,\n txInfo.locktime\n )\n .hash256View();\n }\n\n /// @notice Returns minimum deposit amount.\n /// @return Minimum deposit amount. In TBTC token decimals precision.\n // slither-disable-next-line dead-code\n function _minDepositAmount() internal view returns (uint256) {\n // Read tBTC Bridge Deposit Dust Threshold in satoshi precision.\n (uint64 bridgeDepositDustThresholdSat, , , ) = bridge\n .depositParameters();\n\n // Convert tBTC Bridge Deposit Dust Threshold to TBTC token precision.\n return bridgeDepositDustThresholdSat * SATOSHI_MULTIPLIER;\n }\n}\n"
|
|
321
333
|
},
|
|
322
334
|
"contracts/integrator/IBridge.sol": {
|
|
323
335
|
"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.0;\n\n/// @notice Namespace which groups all types relevant to the IBridge interface.\n/// @dev This is a mirror of the real types used in the Bridge contract.\n/// This way, the `integrator` subpackage does not need to import\n/// anything from the `bridge` subpackage and explicitly depend on it.\n/// This simplifies the dependency graph for integrators.\nlibrary IBridgeTypes {\n /// @dev See bridge/BitcoinTx.sol#Info\n struct BitcoinTxInfo {\n bytes4 version;\n bytes inputVector;\n bytes outputVector;\n bytes4 locktime;\n }\n\n /// @dev See bridge/Deposit.sol#DepositRevealInfo\n struct DepositRevealInfo {\n uint32 fundingOutputIndex;\n bytes8 blindingFactor;\n bytes20 walletPubKeyHash;\n bytes20 refundPubKeyHash;\n bytes4 refundLocktime;\n address vault;\n }\n\n /// @dev See bridge/Deposit.sol#DepositRequest\n struct DepositRequest {\n address depositor;\n uint64 amount;\n uint32 revealedAt;\n address vault;\n uint64 treasuryFee;\n uint32 sweptAt;\n bytes32 extraData;\n }\n}\n\n/// @notice Interface of the Bridge contract.\n/// @dev See bridge/Bridge.sol\ninterface IBridge {\n /// @dev See {Bridge#revealDepositWithExtraData}\n function revealDepositWithExtraData(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n bytes32 extraData\n ) external;\n\n /// @dev See {Bridge#deposits}\n function deposits(uint256 depositKey)\n external\n view\n returns (IBridgeTypes.DepositRequest memory);\n\n /// @dev See {Bridge#depositParameters}\n function depositParameters()\n external\n view\n returns (\n uint64 depositDustThreshold,\n uint64 depositTreasuryFeeDivisor,\n uint64 depositTxMaxFee,\n uint32 depositRevealAheadPeriod\n );\n}\n"
|
|
324
336
|
},
|
|
325
337
|
"contracts/integrator/ITBTCVault.sol": {
|
|
326
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.0;\n\n/// @notice Interface of the TBTCVault contract.\n/// @dev See vault/TBTCVault.sol\ninterface ITBTCVault {\n /// @dev See {TBTCVault#optimisticMintingRequests}\n function optimisticMintingRequests(uint256 depositKey)\n external\n returns (uint64 requestedAt, uint64 finalizedAt);\n\n /// @dev See {TBTCVault#optimisticMintingFeeDivisor}\n function optimisticMintingFeeDivisor() external view returns (uint32);\n}\n"
|
|
338
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.0;\n\n/// @notice Interface of the TBTCVault contract.\n/// @dev See vault/TBTCVault.sol\ninterface ITBTCVault {\n /// @dev See {TBTCVault#optimisticMintingRequests}\n function optimisticMintingRequests(uint256 depositKey)\n external\n returns (uint64 requestedAt, uint64 finalizedAt);\n\n /// @dev See {TBTCVault#optimisticMintingFeeDivisor}\n function optimisticMintingFeeDivisor() external view returns (uint32);\n\n /// @dev See {TBTCVault#tbtcToken}\n function tbtcToken() external view returns (address);\n}\n"
|
|
339
|
+
},
|
|
340
|
+
"contracts/l2/L1BitcoinDepositor.sol": {
|
|
341
|
+
"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 \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\n\nimport \"../integrator/AbstractTBTCDepositor.sol\";\nimport \"../integrator/IBridge.sol\";\nimport \"../integrator/ITBTCVault.sol\";\nimport \"./Wormhole.sol\";\n\n/// @title L1BitcoinDepositor\n/// @notice This contract is part of the direct bridging mechanism allowing\n/// users to obtain ERC20 TBTC on supported L2 chains, without the need\n/// to interact with the L1 tBTC ledger chain where minting occurs.\n///\n/// `L1BitcoinDepositor` is deployed on the L1 chain and interacts with\n/// their L2 counterpart, the `L2BitcoinDepositor`, deployed on the given\n/// L2 chain. Each `L1BitcoinDepositor` & `L2BitcoinDepositor` pair is\n/// responsible for a specific L2 chain.\n///\n/// The outline of the direct bridging mechanism is as follows:\n/// 1. An L2 user issues a Bitcoin funding transaction to a P2(W)SH\n/// deposit address that embeds the `L1BitcoinDepositor` contract\n/// and L2 user addresses. The `L1BitcoinDepositor` contract serves\n/// as the actual depositor on the L1 chain while the L2 user\n/// address is set as the deposit owner who will receive the\n/// minted ERC20 TBTC.\n/// 2. The data about the Bitcoin funding transaction and deposit\n/// address are passed to the relayer. In the first iteration of\n/// the direct bridging mechanism, this is achieved using an\n/// on-chain event emitted by the `L2BitcoinDepositor` contract.\n/// Further iterations assumes those data are passed off-chain, e.g.\n/// through a REST API exposed by the relayer.\n/// 3. The relayer uses the data to initialize a deposit on the L1\n/// chain by calling the `initializeDeposit` function of the\n/// `L1BitcoinDepositor` contract. The `initializeDeposit` function\n/// reveals the deposit to the tBTC Bridge so minting of ERC20 L1 TBTC\n/// can occur.\n/// 4. Once minting is complete, the `L1BitcoinDepositor` contract\n/// receives minted ERC20 L1 TBTC. The relayer then calls the\n/// `finalizeDeposit` function of the `L1BitcoinDepositor` contract\n/// to transfer the minted ERC20 L1 TBTC to the L2 user address. This\n/// is achieved using the Wormhole protocol. First, the `finalizeDeposit`\n/// function initiates a Wormhole token transfer that locks the ERC20\n/// L1 TBTC within the Wormhole Token Bridge contract and assigns\n/// Wormhole-wrapped L2 TBTC to the corresponding `L2WormholeGateway`\n/// contract. Then, `finalizeDeposit` notifies the `L2BitcoinDepositor`\n/// contract by sending a Wormhole message containing the VAA\n/// of the Wormhole token transfer. The `L2BitcoinDepositor` contract\n/// receives the Wormhole message, and calls the `L2WormholeGateway`\n/// contract that redeems Wormhole-wrapped L2 TBTC from the Wormhole\n/// Token Bridge and uses it to mint canonical L2 TBTC to the L2 user\n/// address.\ncontract L1BitcoinDepositor is\n AbstractTBTCDepositor,\n OwnableUpgradeable,\n Reimbursable\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Reflects the deposit state:\n /// - Unknown deposit has not been initialized yet.\n /// - Initialized deposit has been initialized with a call to\n /// `initializeDeposit` function and is known to this contract.\n /// - Finalized deposit led to TBTC ERC20 minting and was finalized\n /// with a call to `finalizeDeposit` function that transferred\n /// TBTC ERC20 to the L2 deposit owner.\n enum DepositState {\n Unknown,\n Initialized,\n Finalized\n }\n\n /// @notice Holds information about a deferred gas reimbursement.\n struct GasReimbursement {\n /// @notice Receiver that is supposed to receive the reimbursement.\n address receiver;\n /// @notice Gas expenditure that is meant to be reimbursed.\n uint96 gasSpent;\n }\n\n /// @notice Holds the deposit state, keyed by the deposit key calculated for\n /// the individual deposit during the call to `initializeDeposit`\n /// function.\n mapping(uint256 => DepositState) public deposits;\n /// @notice ERC20 L1 TBTC token contract.\n IERC20Upgradeable public tbtcToken;\n /// @notice `Wormhole` core contract on L1.\n IWormhole public wormhole;\n /// @notice `WormholeRelayer` contract on L1.\n IWormholeRelayer public wormholeRelayer;\n /// @notice Wormhole `TokenBridge` contract on L1.\n IWormholeTokenBridge public wormholeTokenBridge;\n /// @notice tBTC `L2WormholeGateway` contract on the corresponding L2 chain.\n address public l2WormholeGateway;\n /// @notice Wormhole chain ID of the corresponding L2 chain.\n uint16 public l2ChainId;\n /// @notice tBTC `L2BitcoinDepositor` contract on the corresponding L2 chain.\n address public l2BitcoinDepositor;\n /// @notice Gas limit necessary to execute the L2 part of the deposit\n /// finalization. This value is used to calculate the payment for\n /// the Wormhole Relayer that is responsible to execute the\n /// deposit finalization on the corresponding L2 chain. Can be\n /// updated by the owner.\n uint256 public l2FinalizeDepositGasLimit;\n /// @notice Holds deferred gas reimbursements for deposit initialization\n /// (indexed by deposit key). Reimbursement for deposit\n /// initialization is paid out upon deposit finalization. This is\n /// because the tBTC Bridge accepts all (even invalid) deposits but\n /// mints ERC20 TBTC only for the valid ones. Paying out the\n /// reimbursement directly upon initialization would make the\n /// reimbursement pool vulnerable to malicious actors that could\n /// drain it by initializing invalid deposits.\n mapping(uint256 => GasReimbursement) public gasReimbursements;\n /// @notice Gas that is meant to balance the overall cost of deposit initialization.\n /// Can be updated by the owner based on the current market conditions.\n uint256 public initializeDepositGasOffset;\n /// @notice Gas that is meant to balance the overall cost of deposit finalization.\n /// Can be updated by the owner based on the current market conditions.\n uint256 public finalizeDepositGasOffset;\n /// @notice Set of addresses that are authorized to receive gas reimbursements\n /// for deposit initialization and finalization. The authorization is\n /// granted by the contract owner.\n mapping(address => bool) public reimbursementAuthorizations;\n\n /// @notice **Feature Flag** controlling whether the deposit transaction max fee\n /// is **reimbursed** (added to the user’s TBTC) or **deducted**.\n /// - `true` => Add `txMaxFee` to the minted TBTC amount\n /// - `false` => Subtract `txMaxFee` from the minted TBTC amount\n bool public reimburseTxMaxFee;\n\n event DepositInitialized(\n uint256 indexed depositKey,\n address indexed l2DepositOwner,\n address indexed l1Sender\n );\n\n event DepositFinalized(\n uint256 indexed depositKey,\n address indexed l2DepositOwner,\n address indexed l1Sender,\n uint256 initialAmount,\n uint256 tbtcAmount\n );\n\n event L2FinalizeDepositGasLimitUpdated(uint256 l2FinalizeDepositGasLimit);\n\n event GasOffsetParametersUpdated(\n uint256 initializeDepositGasOffset,\n uint256 finalizeDepositGasOffset\n );\n\n event ReimbursementAuthorizationUpdated(\n address indexed _address,\n bool authorization\n );\n\n /// @notice Emitted whenever the owner toggles the reimbursement of the deposit\n /// transaction max fee.\n event ReimburseTxMaxFeeUpdated(bool reimburseTxMaxFee);\n\n /// @dev This modifier comes from the `Reimbursable` base contract and\n /// must be overridden to protect the `updateReimbursementPool` call.\n modifier onlyReimbursableAdmin() override {\n require(msg.sender == owner(), \"Caller is not the owner\");\n _;\n }\n\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n function initialize(\n address _tbtcBridge,\n address _tbtcVault,\n address _wormhole,\n address _wormholeRelayer,\n address _wormholeTokenBridge,\n address _l2WormholeGateway,\n uint16 _l2ChainId\n ) external initializer {\n __AbstractTBTCDepositor_initialize(_tbtcBridge, _tbtcVault);\n __Ownable_init();\n\n require(_wormhole != address(0), \"Wormhole address cannot be zero\");\n require(\n _wormholeRelayer != address(0),\n \"WormholeRelayer address cannot be zero\"\n );\n require(\n _wormholeTokenBridge != address(0),\n \"WormholeTokenBridge address cannot be zero\"\n );\n require(\n _l2WormholeGateway != address(0),\n \"L2WormholeGateway address cannot be zero\"\n );\n\n tbtcToken = IERC20Upgradeable(ITBTCVault(_tbtcVault).tbtcToken());\n wormhole = IWormhole(_wormhole);\n wormholeRelayer = IWormholeRelayer(_wormholeRelayer);\n wormholeTokenBridge = IWormholeTokenBridge(_wormholeTokenBridge);\n // slither-disable-next-line missing-zero-check\n l2WormholeGateway = _l2WormholeGateway;\n l2ChainId = _l2ChainId;\n l2FinalizeDepositGasLimit = 500_000;\n initializeDepositGasOffset = 60_000;\n finalizeDepositGasOffset = 20_000;\n reimburseTxMaxFee = false;\n }\n\n /// @notice Sets the address of the `L2BitcoinDepositor` contract on the\n /// corresponding L2 chain. This function solves the chicken-and-egg\n /// problem of setting the `L2BitcoinDepositor` contract address\n /// on the `L1BitcoinDepositor` contract and vice versa.\n /// @param _l2BitcoinDepositor Address of the `L2BitcoinDepositor` contract.\n /// @dev Requirements:\n /// - Can be called only by the contract owner,\n /// - The address must not be set yet,\n /// - The new address must not be 0x0.\n function attachL2BitcoinDepositor(address _l2BitcoinDepositor)\n external\n onlyOwner\n {\n require(\n l2BitcoinDepositor == address(0),\n \"L2 Bitcoin Depositor already set\"\n );\n require(\n _l2BitcoinDepositor != address(0),\n \"L2 Bitcoin Depositor must not be 0x0\"\n );\n l2BitcoinDepositor = _l2BitcoinDepositor;\n }\n\n /// @notice Updates the gas limit necessary to execute the L2 part of the\n /// deposit finalization.\n /// @param _l2FinalizeDepositGasLimit New gas limit.\n /// @dev Requirements:\n /// - Can be called only by the contract owner.\n function updateL2FinalizeDepositGasLimit(uint256 _l2FinalizeDepositGasLimit)\n external\n onlyOwner\n {\n l2FinalizeDepositGasLimit = _l2FinalizeDepositGasLimit;\n emit L2FinalizeDepositGasLimitUpdated(_l2FinalizeDepositGasLimit);\n }\n\n /// @notice Updates the values of gas offset parameters.\n /// @dev Can be called only by the contract owner. The caller is responsible\n /// for validating parameters.\n /// @param _initializeDepositGasOffset New initialize deposit gas offset.\n /// @param _finalizeDepositGasOffset New finalize deposit gas offset.\n function updateGasOffsetParameters(\n uint256 _initializeDepositGasOffset,\n uint256 _finalizeDepositGasOffset\n ) external onlyOwner {\n initializeDepositGasOffset = _initializeDepositGasOffset;\n finalizeDepositGasOffset = _finalizeDepositGasOffset;\n\n emit GasOffsetParametersUpdated(\n _initializeDepositGasOffset,\n _finalizeDepositGasOffset\n );\n }\n\n /// @notice Updates the reimbursement authorization for the given address.\n /// @param _address Address to update the authorization for.\n /// @param authorization New authorization status.\n /// @dev Requirements:\n /// - Can be called only by the contract owner.\n function updateReimbursementAuthorization(\n address _address,\n bool authorization\n ) external onlyOwner {\n emit ReimbursementAuthorizationUpdated(_address, authorization);\n reimbursementAuthorizations[_address] = authorization;\n }\n\n /// @notice Toggles whether the deposit transaction max fee is reimbursed\n /// or deducted. Only callable by the contract owner.\n /// @param _reimburseTxMaxFee `true` => reimburse (add) the deposit tx max fee,\n /// `false` => deduct the deposit tx max fee.\n function setReimburseTxMaxFee(bool _reimburseTxMaxFee) external onlyOwner {\n reimburseTxMaxFee = _reimburseTxMaxFee;\n emit ReimburseTxMaxFeeUpdated(_reimburseTxMaxFee);\n }\n\n /// @notice Initializes the deposit process on L1 by revealing the deposit\n /// data (funding transaction and components of the P2(W)SH deposit\n /// address) to the tBTC Bridge. Once tBTC minting is completed,\n /// this call should be followed by a call to `finalizeDeposit`.\n /// Callers of `initializeDeposit` are eligible for a gas refund\n /// that is paid out upon deposit finalization (only if the\n /// reimbursement pool is attached and the given caller is\n /// authorized for refunds).\n ///\n /// The Bitcoin funding transaction must transfer funds to a P2(W)SH\n /// deposit address whose underlying script is built from the\n /// following components:\n ///\n /// <depositor-address> DROP\n /// <depositor-extra-data> DROP\n /// <blinding-factor> DROP\n /// DUP HASH160 <signingGroupPubkeyHash> EQUAL\n /// IF\n /// CHECKSIG\n /// ELSE\n /// DUP HASH160 <refundPubkeyHash> EQUALVERIFY\n /// <locktime> CHECKLOCKTIMEVERIFY DROP\n /// CHECKSIG\n /// ENDIF\n ///\n /// Where:\n ///\n /// <depositor-address> 20-byte L1 address of the\n /// `L1BitcoinDepositor` contract.\n ///\n /// <depositor-extra-data> L2 deposit owner address in the Wormhole\n /// format, i.e. 32-byte value left-padded with 0.\n ///\n /// <blinding-factor> 8-byte deposit blinding factor, as used in the\n /// tBTC bridge.\n ///\n /// <signingGroupPubkeyHash> The compressed Bitcoin public key (33\n /// bytes and 02 or 03 prefix) of the deposit's wallet hashed in the\n /// HASH160 Bitcoin opcode style. This must point to the active tBTC\n /// bridge wallet.\n ///\n /// <refundPubkeyHash> The compressed Bitcoin public key (33 bytes\n /// and 02 or 03 prefix) that can be used to make the deposit refund\n /// after the tBTC bridge refund locktime passed. Hashed in the\n /// HASH160 Bitcoin opcode style. This is needed only as a security\n /// measure protecting the user in case tBTC bridge completely stops\n /// functioning.\n ///\n /// <locktime> The Bitcoin script refund locktime (4-byte LE),\n /// according to tBTC bridge rules.\n ///\n /// Please consult tBTC `Bridge.revealDepositWithExtraData` function\n /// documentation for more information.\n /// @param fundingTx Bitcoin funding transaction data.\n /// @param reveal Deposit reveal data.\n /// @param l2DepositOwner Address of the L2 deposit owner.\n /// @dev Requirements:\n /// - The L2 deposit owner address must not be 0x0,\n /// - The function can be called only one time for the given Bitcoin\n /// funding transaction,\n /// - The L2 deposit owner must be embedded in the Bitcoin P2(W)SH\n /// deposit script as the <depositor-extra-data> field. The 20-byte\n /// address must be expressed as a 32-byte value left-padded with 0.\n /// If the value in the Bitcoin script and the value passed as\n /// parameter do not match, the function will revert,\n /// - All the requirements of tBTC Bridge.revealDepositWithExtraData\n /// must be met.\n function initializeDeposit(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n address l2DepositOwner\n ) external {\n uint256 gasStart = gasleft();\n\n require(\n l2DepositOwner != address(0),\n \"L2 deposit owner must not be 0x0\"\n );\n\n // Convert the L2 deposit owner address into the Wormhole format and\n // encode it as deposit extra data.\n bytes32 extraData = WormholeUtils.toWormholeAddress(l2DepositOwner);\n\n // Input parameters do not have to be validated in any way.\n // The tBTC Bridge is responsible for validating whether the provided\n // Bitcoin funding transaction transfers funds to the P2(W)SH deposit\n // address built from the reveal data. Despite the tBTC Bridge accepts\n // all transactions that meet the format requirements, it mints ERC20\n // L1 TBTC only for the ones that actually occurred on the Bitcoin\n // network and gathered enough confirmations.\n (uint256 depositKey, ) = _initializeDeposit(\n fundingTx,\n reveal,\n extraData\n );\n\n require(\n deposits[depositKey] == DepositState.Unknown,\n \"Wrong deposit state\"\n );\n\n // slither-disable-next-line reentrancy-benign\n deposits[depositKey] = DepositState.Initialized;\n\n // slither-disable-next-line reentrancy-events\n emit DepositInitialized(depositKey, l2DepositOwner, msg.sender);\n\n // Record a deferred gas reimbursement if the reimbursement pool is\n // attached and the caller is authorized to receive reimbursements.\n if (\n address(reimbursementPool) != address(0) &&\n reimbursementAuthorizations[msg.sender]\n ) {\n uint256 gasSpent = (gasStart - gasleft()) +\n initializeDepositGasOffset;\n\n // Should not happen as long as initializeDepositGasOffset is\n // set to a reasonable value. If it happens, it's better to\n // omit the reimbursement than to revert the transaction.\n if (gasSpent > type(uint96).max) {\n return;\n }\n\n // Do not issue a reimbursement immediately. Record\n // a deferred reimbursement that will be paid out upon deposit\n // finalization. This is because the tBTC Bridge accepts all\n // (even invalid) deposits but mints ERC20 TBTC only for the valid\n // ones. Paying out the reimbursement directly upon initialization\n // would make the reimbursement pool vulnerable to malicious actors\n // that could drain it by initializing invalid deposits.\n // slither-disable-next-line reentrancy-benign\n gasReimbursements[depositKey] = GasReimbursement({\n receiver: msg.sender,\n gasSpent: uint96(gasSpent)\n });\n }\n }\n\n /// @notice Finalizes the deposit process by transferring ERC20 L1 TBTC\n /// to the L2 deposit owner. This function should be called after\n /// the deposit was initialized with a call to `initializeDeposit`\n /// function and after ERC20 L1 TBTC was minted by the tBTC Bridge\n /// to the `L1BitcoinDepositor` contract. Please note several hours\n /// may pass between `initializeDeposit`and `finalizeDeposit`.\n /// If the reimbursement pool is attached, the function pays out\n /// a gas and call's value refund to the caller (if the given\n /// caller is authorized for refunds) as well as the deferred gas\n /// refund to the caller of `initializeDeposit` corresponding to\n /// the finalized deposit.\n /// @param depositKey The deposit key, as emitted in the `DepositInitialized`\n /// event emitted by the `initializeDeposit` function for the deposit.\n /// @dev Requirements:\n /// - `initializeDeposit` was called for the given deposit before,\n /// - ERC20 L1 TBTC was minted by tBTC Bridge to this contract,\n /// - The function was not called for the given deposit before,\n /// - The call must carry a payment for the Wormhole Relayer that\n /// is responsible for executing the deposit finalization on the\n /// corresponding L2 chain. The payment must be equal to the\n /// value returned by the `quoteFinalizeDeposit` function.\n function finalizeDeposit(uint256 depositKey) external payable {\n uint256 gasStart = gasleft();\n\n require(\n deposits[depositKey] == DepositState.Initialized,\n \"Wrong deposit state\"\n );\n\n deposits[depositKey] = DepositState.Finalized;\n\n (\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n // Deposit extra data is actually the L2 deposit owner\n // address in Wormhole format.\n bytes32 l2DepositOwner\n ) = _finalizeDeposit(depositKey);\n\n // ----------------------------\n // Reimburse or Deduct Max Fee\n // ----------------------------\n if (reimburseTxMaxFee) {\n // Retrieve deposit tx max fee in 1e8 sat precision -> scale it to 1e18.\n (, , uint64 depositTxMaxFee, ) = bridge.depositParameters();\n uint256 txMaxFee = depositTxMaxFee * SATOSHI_MULTIPLIER;\n // The DAO is \"refunding\" it by adding it to the TBTC minted.\n tbtcAmount += txMaxFee;\n }\n\n // slither-disable-next-line reentrancy-events\n emit DepositFinalized(\n depositKey,\n WormholeUtils.fromWormholeAddress(l2DepositOwner),\n msg.sender,\n initialDepositAmount,\n tbtcAmount\n );\n\n _transferTbtc(tbtcAmount, l2DepositOwner);\n\n // `ReimbursementPool` calls the untrusted receiver address using a\n // low-level call. Reentrancy risk is mitigated by making sure that\n // `ReimbursementPool.refund` is a non-reentrant function and executing\n // reimbursements as the last step of the deposit finalization.\n if (address(reimbursementPool) != address(0)) {\n // If there is a deferred reimbursement for this deposit\n // initialization, pay it out now. No need to check reimbursement\n // authorization for the initialization caller. If the deferred\n // reimbursement is here, that implies the caller was authorized\n // to receive it.\n GasReimbursement memory reimbursement = gasReimbursements[\n depositKey\n ];\n if (reimbursement.receiver != address(0)) {\n delete gasReimbursements[depositKey];\n\n reimbursementPool.refund(\n reimbursement.gasSpent,\n reimbursement.receiver\n );\n }\n\n // Pay out the reimbursement for deposit finalization if the caller\n // is authorized to receive reimbursements.\n if (reimbursementAuthorizations[msg.sender]) {\n // As this call is payable and this transaction carries out a\n // msg.value that covers Wormhole cost, we need to reimburse\n // that as well. However, the `ReimbursementPool` issues refunds\n // based on gas spent. We need to convert msg.value accordingly\n // using the `_refundToGasSpent` function.\n uint256 msgValueOffset = _refundToGasSpent(msg.value);\n reimbursementPool.refund(\n (gasStart - gasleft()) +\n msgValueOffset +\n finalizeDepositGasOffset,\n msg.sender\n );\n }\n }\n }\n\n /// @notice The `ReimbursementPool` contract issues refunds based on\n /// gas spent. If there is a need to get a specific refund based\n /// on WEI value, such a value must be first converted to gas spent.\n /// This function does such a conversion.\n /// @param refund Refund value in WEI.\n /// @return Refund value as gas spent.\n /// @dev This function is the reverse of the logic used\n /// within `ReimbursementPool.refund`.\n function _refundToGasSpent(uint256 refund) internal returns (uint256) {\n uint256 maxGasPrice = reimbursementPool.maxGasPrice();\n uint256 staticGas = reimbursementPool.staticGas();\n\n uint256 gasPrice = tx.gasprice < maxGasPrice\n ? tx.gasprice\n : maxGasPrice;\n\n // Should not happen but check just in case of weird ReimbursementPool\n // configuration.\n if (gasPrice == 0) {\n return 0;\n }\n\n uint256 gasSpent = (refund / gasPrice);\n\n // Should not happen but check just in case of weird ReimbursementPool\n // configuration.\n if (staticGas > gasSpent) {\n return 0;\n }\n\n return gasSpent - staticGas;\n }\n\n /// @notice Quotes the payment that must be attached to the `finalizeDeposit`\n /// function call. The payment is necessary to cover the cost of\n /// the Wormhole Relayer that is responsible for executing the\n /// deposit finalization on the corresponding L2 chain.\n /// @return cost The cost of the `finalizeDeposit` function call in WEI.\n function quoteFinalizeDeposit() external view returns (uint256 cost) {\n cost = _quoteFinalizeDeposit(wormhole.messageFee());\n }\n\n /// @notice Internal version of the `quoteFinalizeDeposit` function that\n /// works with a custom Wormhole message fee.\n /// @param messageFee Custom Wormhole message fee.\n /// @return cost The cost of the `finalizeDeposit` function call in WEI.\n /// @dev Implemented based on examples presented as part of the Wormhole SDK:\n /// https://github.com/wormhole-foundation/hello-token/blob/8ec757248788dc12183f13627633e1d6fd1001bb/src/example-extensions/HelloTokenWithoutSDK.sol#L23\n function _quoteFinalizeDeposit(uint256 messageFee)\n internal\n view\n returns (uint256 cost)\n {\n // Cost of delivering token and payload to `l2ChainId`.\n (uint256 deliveryCost, ) = wormholeRelayer.quoteEVMDeliveryPrice(\n l2ChainId,\n 0,\n l2FinalizeDepositGasLimit\n );\n\n // Total cost = delivery cost + cost of publishing the `sending token`\n // Wormhole message.\n cost = deliveryCost + messageFee;\n }\n\n /// @notice Transfers ERC20 L1 TBTC to the L2 deposit owner using the Wormhole\n /// protocol. The function initiates a Wormhole token transfer that\n /// locks the ERC20 L1 TBTC within the Wormhole Token Bridge contract\n /// and assigns Wormhole-wrapped L2 TBTC to the corresponding\n /// `L2WormholeGateway` contract. Then, the function notifies the\n /// `L2BitcoinDepositor` contract by sending a Wormhole message\n /// containing the VAA of the Wormhole token transfer. The\n /// `L2BitcoinDepositor` contract receives the Wormhole message,\n /// and calls the `L2WormholeGateway` contract that redeems\n /// Wormhole-wrapped L2 TBTC from the Wormhole Token Bridge and\n /// uses it to mint canonical L2 TBTC to the L2 deposit owner address.\n /// @param amount Amount of TBTC L1 ERC20 to transfer (1e18 precision).\n /// @param l2Receiver Address of the L2 deposit owner.\n /// @dev Requirements:\n /// - The normalized amount (1e8 precision) must be greater than 0,\n /// - The appropriate payment for the Wormhole Relayer must be\n /// attached to the call (as calculated by `quoteFinalizeDeposit`).\n /// @dev Implemented based on examples presented as part of the Wormhole SDK:\n /// https://github.com/wormhole-foundation/hello-token/blob/8ec757248788dc12183f13627633e1d6fd1001bb/src/example-extensions/HelloTokenWithoutSDK.sol#L29\n function _transferTbtc(uint256 amount, bytes32 l2Receiver) internal {\n // Wormhole supports the 1e8 precision at most. TBTC is 1e18 so\n // the amount needs to be normalized.\n amount = WormholeUtils.normalize(amount);\n\n require(amount > 0, \"Amount too low to bridge\");\n\n // Cost of requesting a `finalizeDeposit` message to be sent to\n // `l2ChainId` with a gasLimit of `l2FinalizeDepositGasLimit`.\n uint256 wormholeMessageFee = wormhole.messageFee();\n uint256 cost = _quoteFinalizeDeposit(wormholeMessageFee);\n\n require(msg.value == cost, \"Payment for Wormhole Relayer is too low\");\n\n // The Wormhole Token Bridge will pull the TBTC amount\n // from this contract. We need to approve the transfer first.\n tbtcToken.safeIncreaseAllowance(address(wormholeTokenBridge), amount);\n\n // Initiate a Wormhole token transfer that will lock L1 TBTC within\n // the Wormhole Token Bridge contract and assign Wormhole-wrapped\n // L2 TBTC to the corresponding `L2WormholeGateway` contract.\n // slither-disable-next-line arbitrary-send-eth\n uint64 transferSequence = wormholeTokenBridge.transferTokensWithPayload{\n value: wormholeMessageFee\n }(\n address(tbtcToken),\n amount,\n l2ChainId,\n WormholeUtils.toWormholeAddress(l2WormholeGateway),\n 0, // Nonce is a free field that is not relevant in this context.\n abi.encode(l2Receiver) // Set the L2 receiver address as the transfer payload.\n );\n\n // Construct the VAA key corresponding to the above Wormhole token transfer.\n WormholeTypes.VaaKey[]\n memory additionalVaas = new WormholeTypes.VaaKey[](1);\n additionalVaas[0] = WormholeTypes.VaaKey({\n chainId: wormhole.chainId(),\n emitterAddress: WormholeUtils.toWormholeAddress(\n address(wormholeTokenBridge)\n ),\n sequence: transferSequence\n });\n\n // The Wormhole token transfer initiated above must be finalized on\n // the L2 chain. We achieve that by sending the transfer's VAA to the\n // `L2BitcoinDepositor` contract. Once, the `L2BitcoinDepositor`\n // contract receives it, it calls the `L2WormholeGateway` contract\n // that redeems Wormhole-wrapped L2 TBTC from the Wormhole Token\n // Bridge and use it to mint canonical L2 TBTC to the receiver address.\n // slither-disable-next-line arbitrary-send-eth,unused-return\n wormholeRelayer.sendVaasToEvm{value: cost - wormholeMessageFee}(\n l2ChainId,\n l2BitcoinDepositor,\n bytes(\"\"), // No payload needed. The L2 receiver address is already encoded in the Wormhole token transfer payload.\n 0, // No receiver value needed.\n l2FinalizeDepositGasLimit,\n additionalVaas,\n l2ChainId, // Set the L2 chain as the refund chain to avoid cross-chain refunds.\n msg.sender // Set the caller as the refund receiver.\n );\n }\n}\n"
|
|
342
|
+
},
|
|
343
|
+
"contracts/l2/L2BitcoinDepositor.sol": {
|
|
344
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\n\nimport \"../integrator/IBridge.sol\";\nimport \"./Wormhole.sol\";\n\n/// @title IL2WormholeGateway\n/// @notice Interface to the `L2WormholeGateway` contract.\ninterface IL2WormholeGateway {\n /// @dev See ./L2WormholeGateway.sol#receiveTbtc\n function receiveTbtc(bytes memory vaa) external;\n}\n\n/// @title L2BitcoinDepositor\n/// @notice This contract is part of the direct bridging mechanism allowing\n/// users to obtain ERC20 TBTC on supported L2 chains, without the need\n/// to interact with the L1 tBTC ledger chain where minting occurs.\n///\n/// `L2BitcoinDepositor` is deployed on the L2 chain and interacts with\n/// their L1 counterpart, the `L1BitcoinDepositor`, deployed on the\n/// L1 tBTC ledger chain. Each `L1BitcoinDepositor` & `L2BitcoinDepositor`\n/// pair is responsible for a specific L2 chain.\n///\n/// Please consult the `L1BitcoinDepositor` docstring for an\n/// outline of the direct bridging mechanism\n// slither-disable-next-line locked-ether\ncontract L2BitcoinDepositor is IWormholeReceiver, OwnableUpgradeable {\n /// @notice `WormholeRelayer` contract on L2.\n IWormholeRelayer public wormholeRelayer;\n /// @notice tBTC `L2WormholeGateway` contract on L2.\n IL2WormholeGateway public l2WormholeGateway;\n /// @notice Wormhole chain ID of the corresponding L1 chain.\n uint16 public l1ChainId;\n /// @notice tBTC `L1BitcoinDepositor` contract on the corresponding L1 chain.\n address public l1BitcoinDepositor;\n\n event DepositInitialized(\n IBridgeTypes.BitcoinTxInfo fundingTx,\n IBridgeTypes.DepositRevealInfo reveal,\n address indexed l2DepositOwner,\n address indexed l2Sender\n );\n\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n function initialize(\n address _wormholeRelayer,\n address _l2WormholeGateway,\n uint16 _l1ChainId\n ) external initializer {\n __Ownable_init();\n\n require(\n _wormholeRelayer != address(0),\n \"WormholeRelayer address cannot be zero\"\n );\n require(\n _l2WormholeGateway != address(0),\n \"L2WormholeGateway address cannot be zero\"\n );\n\n wormholeRelayer = IWormholeRelayer(_wormholeRelayer);\n l2WormholeGateway = IL2WormholeGateway(_l2WormholeGateway);\n l1ChainId = _l1ChainId;\n }\n\n /// @notice Sets the address of the `L1BitcoinDepositor` contract on the\n /// corresponding L1 chain. This function solves the chicken-and-egg\n /// problem of setting the `L1BitcoinDepositor` contract address\n /// on the `L2BitcoinDepositor` contract and vice versa.\n /// @param _l1BitcoinDepositor Address of the `L1BitcoinDepositor` contract.\n /// @dev Requirements:\n /// - Can be called only by the contract owner,\n /// - The address must not be set yet,\n /// - The new address must not be 0x0.\n function attachL1BitcoinDepositor(address _l1BitcoinDepositor)\n external\n onlyOwner\n {\n require(\n l1BitcoinDepositor == address(0),\n \"L1 Bitcoin Depositor already set\"\n );\n require(\n _l1BitcoinDepositor != address(0),\n \"L1 Bitcoin Depositor must not be 0x0\"\n );\n l1BitcoinDepositor = _l1BitcoinDepositor;\n }\n\n /// @notice Initializes the deposit process on L2 by emitting an event\n /// containing the deposit data (funding transaction and\n /// components of the P2(W)SH deposit address). The event is\n /// supposed to be picked up by the relayer and used to initialize\n /// the deposit on L1 through the `L1BitcoinDepositor` contract.\n /// @param fundingTx Bitcoin funding transaction data.\n /// @param reveal Deposit reveal data.\n /// @param l2DepositOwner Address of the L2 deposit owner.\n /// @dev The alternative approach of using Wormhole Relayer to send the\n /// deposit data to L1 was considered. However, it turned out to be\n /// too expensive. For example, relying deposit data from Base L2 to\n /// Ethereum L1 costs around ~0.045 ETH (~170 USD at the moment of writing).\n /// Moreover, the next iteration of the direct bridging mechanism\n /// assumes that no L2 transaction will be required to initialize the\n /// deposit and the relayer should obtain the deposit data off-chain.\n /// There is a high chance this function will be removed then.\n /// That said, there was no sense to explore another cross-chain\n /// messaging solutions. Relying on simple on-chain event and custom\n /// off-chain relayer seems to be the most reasonable way to go. It\n /// also aligns with the future direction of the direct bridging mechanism.\n function initializeDeposit(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n address l2DepositOwner\n ) external {\n emit DepositInitialized(fundingTx, reveal, l2DepositOwner, msg.sender);\n }\n\n /// @notice Receives Wormhole messages originating from the corresponding\n /// `L1BitcoinDepositor` contract that lives on the L1 chain.\n /// Messages are issued upon deposit finalization on L1 and\n /// are supposed to carry the VAA of the Wormhole token transfer of\n /// ERC20 L1 TBTC to the L2 chain. This contract performs some basic\n /// checks and forwards the VAA to the `L2WormholeGateway` contract\n /// that is authorized to withdraw the Wormhole-wrapped L2 TBTC\n /// from the Wormhole Token Bridge (representing the ERC20 TBTC\n /// locked on L1) and use it to mint the canonical L2 TBTC for the\n /// deposit owner.\n /// @param additionalVaas Additional VAAs that are part of the Wormhole message.\n /// @param sourceAddress Address of the source of the message (in Wormhole format).\n /// @param sourceChain Wormhole chain ID of the source chain.\n /// @dev Requirements:\n /// - Can be called only by the Wormhole Relayer contract,\n /// - The source chain must be the expected L1 chain,\n /// - The source address must be the corresponding\n /// `L1BitcoinDepositor` contract,\n /// - The message must carry exactly 1 additional VAA key representing\n /// the token transfer.\n function receiveWormholeMessages(\n bytes memory,\n bytes[] memory additionalVaas,\n bytes32 sourceAddress,\n uint16 sourceChain,\n bytes32\n ) external payable {\n require(\n msg.sender == address(wormholeRelayer),\n \"Caller is not Wormhole Relayer\"\n );\n\n require(\n sourceChain == l1ChainId,\n \"Source chain is not the expected L1 chain\"\n );\n\n require(\n WormholeUtils.fromWormholeAddress(sourceAddress) ==\n l1BitcoinDepositor,\n \"Source address is not the expected L1 Bitcoin depositor\"\n );\n\n require(\n additionalVaas.length == 1,\n \"Expected 1 additional VAA key for token transfer\"\n );\n\n l2WormholeGateway.receiveTbtc(additionalVaas[0]);\n }\n}\n"
|
|
327
345
|
},
|
|
328
346
|
"contracts/l2/L2TBTC.sol": {
|
|
329
347
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol\";\n\n/// @title L2TBTC\n/// @notice Canonical L2/sidechain token implementation. tBTC token is minted on\n/// L1 and locked there to be moved to L2/sidechain. By deploying\n/// a canonical token on each L2/sidechain, we can ensure the supply of\n/// tBTC remains sacrosanct, while enabling quick, interoperable\n/// cross-chain bridges and localizing ecosystem risk.\n///\n/// This contract is flexible enough to:\n/// - Delegate minting authority to a native bridge on the chain, if\n/// present.\n/// - Delegate minting authority to a short list of ecosystem bridges.\n/// - Have mints and burns paused by any one of n guardians, allowing\n/// avoidance of contagion in case of a chain- or bridge-specific\n/// incident.\n/// - Be governed and upgradeable.\n///\n/// The token is burnable by the token holder and supports EIP2612\n/// permits. Token holder can authorize a transfer of their token with\n/// a signature conforming EIP712 standard instead of an on-chain\n/// transaction from their address. Anyone can submit this signature on\n/// the user's behalf by calling the permit function, paying gas fees,\n/// and possibly performing other actions in the same transaction.\n/// The governance can recover ERC20 and ERC721 tokens sent mistakenly\n/// to L2TBTC token contract.\ncontract L2TBTC is\n ERC20Upgradeable,\n ERC20BurnableUpgradeable,\n ERC20PermitUpgradeable,\n OwnableUpgradeable,\n PausableUpgradeable\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Indicates if the given address is a minter. Only minters can\n /// mint the token.\n mapping(address => bool) public isMinter;\n\n /// @notice List of all minters.\n address[] public minters;\n\n /// @notice Indicates if the given address is a guardian. Only guardians can\n /// pause token mints and burns.\n mapping(address => bool) public isGuardian;\n\n /// @notice List of all guardians.\n address[] public guardians;\n\n event MinterAdded(address indexed minter);\n event MinterRemoved(address indexed minter);\n\n event GuardianAdded(address indexed guardian);\n event GuardianRemoved(address indexed guardian);\n\n modifier onlyMinter() {\n require(isMinter[msg.sender], \"Caller is not a minter\");\n _;\n }\n\n modifier onlyGuardian() {\n require(isGuardian[msg.sender], \"Caller is not a guardian\");\n _;\n }\n\n /// @notice Initializes the token contract.\n /// @param _name The name of the token.\n /// @param _symbol The symbol of the token, usually a shorter version of the\n /// name.\n function initialize(string memory _name, string memory _symbol)\n external\n initializer\n {\n // OpenZeppelin upgradeable contracts documentation says:\n //\n // \"Use with multiple inheritance requires special care. Initializer\n // functions are not linearized by the compiler like constructors.\n // Because of this, each __{ContractName}_init function embeds the\n // linearized calls to all parent initializers. As a consequence,\n // calling two of these init functions can potentially initialize the\n // same contract twice.\"\n //\n // Note that ERC20 extensions do not linearize calls to ERC20Upgradeable\n // initializer so we call all extension initializers individually. At\n // the same time, ERC20PermitUpgradeable does linearize the call to\n // EIP712Upgradeable so we are not using the unchained initializer\n // versions.\n __ERC20_init(_name, _symbol);\n __ERC20Burnable_init();\n __ERC20Permit_init(_name);\n __Ownable_init();\n __Pausable_init();\n }\n\n /// @notice Adds the address to the minters list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `minter` must not be a minter address already.\n /// @param minter The address to be added as a minter.\n function addMinter(address minter) external onlyOwner {\n require(!isMinter[minter], \"This address is already a minter\");\n isMinter[minter] = true;\n minters.push(minter);\n emit MinterAdded(minter);\n }\n\n /// @notice Removes the address from the minters list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `minter` must be a minter address.\n /// @param minter The address to be removed from the minters list.\n function removeMinter(address minter) external onlyOwner {\n require(isMinter[minter], \"This address is not a minter\");\n delete isMinter[minter];\n\n // We do not expect too many minters so a simple loop is safe.\n for (uint256 i = 0; i < minters.length; i++) {\n if (minters[i] == minter) {\n minters[i] = minters[minters.length - 1];\n // slither-disable-next-line costly-loop\n minters.pop();\n break;\n }\n }\n\n emit MinterRemoved(minter);\n }\n\n /// @notice Adds the address to the guardians list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `guardian` must not be a guardian address already.\n /// @param guardian The address to be added as a guardian.\n function addGuardian(address guardian) external onlyOwner {\n require(!isGuardian[guardian], \"This address is already a guardian\");\n isGuardian[guardian] = true;\n guardians.push(guardian);\n emit GuardianAdded(guardian);\n }\n\n /// @notice Removes the address from the guardians list.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - `guardian` must be a guardian address.\n /// @param guardian The address to be removed from the guardians list.\n function removeGuardian(address guardian) external onlyOwner {\n require(isGuardian[guardian], \"This address is not a guardian\");\n delete isGuardian[guardian];\n\n // We do not expect too many guardians so a simple loop is safe.\n for (uint256 i = 0; i < guardians.length; i++) {\n if (guardians[i] == guardian) {\n guardians[i] = guardians[guardians.length - 1];\n // slither-disable-next-line costly-loop\n guardians.pop();\n break;\n }\n }\n\n emit GuardianRemoved(guardian);\n }\n\n /// @notice Allows the governance of the token contract to recover any ERC20\n /// sent mistakenly to the token contract address.\n /// @param token The address of the token to be recovered.\n /// @param recipient The token recipient address that will receive recovered\n /// tokens.\n /// @param amount The amount to be recovered.\n function recoverERC20(\n IERC20Upgradeable token,\n address recipient,\n uint256 amount\n ) external onlyOwner {\n token.safeTransfer(recipient, amount);\n }\n\n /// @notice Allows the governance of the token contract to recover any\n /// ERC721 sent mistakenly to the token contract address.\n /// @param token The address of the token to be recovered.\n /// @param recipient The token recipient address that will receive the\n /// recovered token.\n /// @param tokenId The ID of the ERC721 token to be recovered.\n function recoverERC721(\n IERC721Upgradeable token,\n address recipient,\n uint256 tokenId,\n bytes calldata data\n ) external onlyOwner {\n token.safeTransferFrom(address(this), recipient, tokenId, data);\n }\n\n /// @notice Allows one of the guardians to pause mints and burns allowing\n /// avoidance of contagion in case of a chain- or bridge-specific\n /// incident.\n /// @dev Requirements:\n /// - The caller must be a guardian.\n /// - The contract must not be already paused.\n function pause() external onlyGuardian {\n _pause();\n }\n\n /// @notice Allows the governance to unpause mints and burns previously\n /// paused by one of the guardians.\n /// @dev Requirements:\n /// - The caller must be the contract owner.\n /// - The contract must be paused.\n function unpause() external onlyOwner {\n _unpause();\n }\n\n /// @notice Allows one of the minters to mint `amount` tokens and assign\n /// them to `account`, increasing the total supply. Emits\n /// a `Transfer` event with `from` set to the zero address.\n /// @dev Requirements:\n /// - The caller must be a minter.\n /// - `account` must not be the zero address.\n /// @param account The address to receive tokens.\n /// @param amount The amount of token to be minted.\n function mint(address account, uint256 amount)\n external\n whenNotPaused\n onlyMinter\n {\n _mint(account, amount);\n }\n\n /// @notice Destroys `amount` tokens from the caller. Emits a `Transfer`\n /// event with `to` set to the zero address.\n /// @dev Requirements:\n /// - The caller must have at least `amount` tokens.\n /// @param amount The amount of token to be burned.\n function burn(uint256 amount) public override whenNotPaused {\n super.burn(amount);\n }\n\n /// @notice Destroys `amount` tokens from `account`, deducting from the\n /// caller's allowance. Emits a `Transfer` event with `to` set to\n /// the zero address.\n /// @dev Requirements:\n /// - The che caller must have allowance for `accounts`'s tokens of at\n /// least `amount`.\n /// - `account` must not be the zero address.\n /// - `account` must have at least `amount` tokens.\n /// @param account The address owning tokens to be burned.\n /// @param amount The amount of token to be burned.\n function burnFrom(address account, uint256 amount)\n public\n override\n whenNotPaused\n {\n super.burnFrom(account, amount);\n }\n\n /// @notice Allows to fetch a list of all minters.\n function getMinters() external view returns (address[] memory) {\n return minters;\n }\n\n /// @notice Allows to fetch a list of all guardians.\n function getGuardians() external view returns (address[] memory) {\n return guardians;\n }\n}\n"
|
|
330
348
|
},
|
|
331
349
|
"contracts/l2/L2WormholeGateway.sol": {
|
|
332
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\n\nimport \"./L2TBTC.sol\";\n\n/// @title IWormholeTokenBridge\n/// @notice Wormhole Token Bridge interface. Contains only selected functions\n/// used by L2WormholeGateway.\ninterface IWormholeTokenBridge {\n function completeTransferWithPayload(bytes memory encodedVm)\n external\n returns (bytes memory);\n\n function parseTransferWithPayload(bytes memory encoded)\n external\n pure\n returns (TransferWithPayload memory transfer);\n\n function transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable returns (uint64 sequence);\n\n function transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes memory payload\n ) external payable returns (uint64 sequence);\n\n struct TransferWithPayload {\n uint8 payloadID;\n uint256 amount;\n bytes32 tokenAddress;\n uint16 tokenChain;\n bytes32 to;\n uint16 toChain;\n bytes32 fromAddress;\n bytes payload;\n }\n}\n\n/// @title L2WormholeGateway\n/// @notice Selected cross-ecosystem bridges are given the minting authority for\n/// tBTC token on L2 and sidechains. This contract gives a minting\n/// authority to the Wormhole Bridge.\n///\n/// The process of bridging from L1 to L2 (or sidechain) looks as\n/// follows:\n/// 1. There is a tBTC holder on L1. The holder goes to the Wormhole\n/// Portal and selects the chain they want to bridge to.\n/// 2. The holder submits one transaction to L1 locking their tBTC\n/// tokens in the bridge’s smart contract. After the transaction is\n/// mined, they wait about 15 minutes for the Ethereum block\n/// finality.\n/// 3. The holder submits one transaction to L2 that is minting tokens.\n/// After that transaction is mined, they have their tBTC on L2.\n///\n/// The process of bridging from L2 (or sidechain) to L1 looks as\n/// follows:\n/// 1. There is a tBTC holder on L2. That holder goes to the Wormhole\n/// Portal and selects one of the L2 chains they want to bridge from.\n/// 2. The holder submits one transaction to L2 that is burning the\n/// token. After the transaction is mined, they wait about 15 minutes\n/// for the L2 block finality.\n/// 3. The holder submits one transaction to L1 unlocking their tBTC\n/// tokens from the bridge’s smart contract. After that transaction\n/// is mined, they have their tBTC on L1.\n///\n/// This smart contract is integrated with step 3 of L1->L2 bridging and\n/// step 1 of L2->L1 or L2->L2 bridging. When the user redeems token on\n/// L2, this contract receives the Wormhole tBTC representation and\n/// mints the canonical tBTC in an equal amount. When user sends their\n/// token from L1, this contract burns the canonical tBTC and sends\n/// Wormhole tBTC representation through the bridge in an equal amount.\n/// @dev This contract is supposed to be deployed behind a transparent\n/// upgradeable proxy.\ncontract L2WormholeGateway is\n Initializable,\n OwnableUpgradeable,\n ReentrancyGuardUpgradeable\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Reference to the Wormhole Token Bridge contract.\n IWormholeTokenBridge public bridge;\n\n /// @notice Wormhole tBTC token representation.\n IERC20Upgradeable public bridgeToken;\n\n /// @notice Canonical tBTC token.\n L2TBTC public tbtc;\n\n /// @notice Maps Wormhole chain ID to the Wormhole tBTC gateway address on\n /// that chain. For example, this chain's ID should be mapped to\n /// this contract's address. If there is no Wormhole tBTC gateway\n /// address on the given chain, there is no entry in this mapping.\n /// The mapping holds addresses in a Wormhole-specific format, where\n /// Ethereum address is left-padded with zeros.\n mapping(uint16 => bytes32) public gateways;\n\n /// @notice Minting limit for this gateway. Useful for early days of testing\n /// the system. The gateway can not mint more canonical tBTC than\n /// this limit.\n uint256 public mintingLimit;\n\n /// @notice The amount of tBTC minted by this contract. tBTC burned by this\n /// contract decreases this amount.\n uint256 public mintedAmount;\n\n event WormholeTbtcReceived(address receiver, uint256 amount);\n\n event WormholeTbtcSent(\n uint256 amount,\n uint16 recipientChain,\n bytes32 gateway,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n );\n\n event WormholeTbtcDeposited(address depositor, uint256 amount);\n\n event GatewayAddressUpdated(uint16 chainId, bytes32 gateway);\n\n event MintingLimitUpdated(uint256 mintingLimit);\n\n function initialize(\n IWormholeTokenBridge _bridge,\n IERC20Upgradeable _bridgeToken,\n L2TBTC _tbtc\n ) external initializer {\n __Ownable_init();\n __ReentrancyGuard_init();\n\n require(\n address(_bridge) != address(0),\n \"Wormhole bridge address must not be 0x0\"\n );\n require(\n address(_bridgeToken) != address(0),\n \"Bridge token address must not be 0x0\"\n );\n require(\n address(_tbtc) != address(0),\n \"L2TBTC token address must not be 0x0\"\n );\n\n bridge = _bridge;\n bridgeToken = _bridgeToken;\n tbtc = _tbtc;\n mintingLimit = type(uint256).max;\n }\n\n /// @notice This function is called when the user sends their token from L2.\n /// The contract burns the canonical tBTC from the user and sends\n /// wormhole tBTC representation over the bridge.\n /// Keep in mind that when multiple bridges receive a minting\n /// authority on the canonical tBTC, this function may not be able\n /// to send all amounts of tBTC through the Wormhole bridge. The\n /// capability of Wormhole Bridge to send tBTC from the chain is\n /// limited to the amount of tBTC bridged through Wormhole to that\n /// chain.\n /// @dev Requirements:\n /// - The sender must have at least `amount` of the canonical tBTC and\n /// it has to be approved for L2WormholeGateway.\n /// - The L2WormholeGateway must have at least `amount` of the wormhole\n /// tBTC.\n /// - The recipient must not be 0x0.\n /// - The amount to transfer must not be 0,\n /// - The amount to transfer must be >= 10^10 (1e18 precision).\n /// Depending if Wormhole tBTC gateway is registered on the target\n /// chain, this function uses transfer or transfer with payload over\n /// the Wormhole bridge.\n /// @param amount The amount of tBTC to be sent.\n /// @param recipientChain The Wormhole recipient chain ID.\n /// @param recipient The address of the recipient in the Wormhole format.\n /// @param arbiterFee The Wormhole arbiter fee. Ignored if sending\n /// tBTC to chain with Wormhole tBTC gateway.\n /// @param nonce The Wormhole nonce used to batch messages together.\n /// @return The Wormhole sequence number.\n function sendTbtc(\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable nonReentrant returns (uint64) {\n require(recipient != bytes32(0), \"0x0 recipient not allowed\");\n require(amount != 0, \"Amount must not be 0\");\n\n // Normalize the amount to bridge. The dust can not be bridged due to\n // the decimal shift in the Wormhole Bridge contract.\n amount = normalize(amount);\n\n // Check again after dropping the dust.\n require(amount != 0, \"Amount too low to bridge\");\n\n require(\n bridgeToken.balanceOf(address(this)) >= amount,\n \"Not enough wormhole tBTC in the gateway to bridge\"\n );\n\n bytes32 gateway = gateways[recipientChain];\n\n emit WormholeTbtcSent(\n amount,\n recipientChain,\n gateway,\n recipient,\n arbiterFee,\n nonce\n );\n\n mintedAmount -= amount;\n tbtc.burnFrom(msg.sender, amount);\n bridgeToken.safeApprove(address(bridge), amount);\n\n if (gateway == bytes32(0)) {\n // No Wormhole tBTC gateway on the target chain. The token minted\n // by Wormhole should be considered canonical.\n return\n bridge.transferTokens{value: msg.value}(\n address(bridgeToken),\n amount,\n recipientChain,\n recipient,\n arbiterFee,\n nonce\n );\n } else {\n // There is a Wormhole tBTC gateway on the target chain.\n // The gateway needs to mint canonical tBTC for the recipient\n // encoded in the payload.\n return\n bridge.transferTokensWithPayload{value: msg.value}(\n address(bridgeToken),\n amount,\n recipientChain,\n gateway,\n nonce,\n abi.encode(recipient)\n );\n }\n }\n\n /// @notice This function is called when the user redeems their token on L2.\n /// The contract receives Wormhole tBTC representation and mints the\n /// canonical tBTC for the user.\n /// If the tBTC minting limit has been reached by this contract,\n /// instead of minting tBTC the receiver address receives Wormhole\n /// tBTC representation.\n /// @dev Requirements:\n /// - The receiver of Wormhole tBTC should be the L2WormholeGateway\n /// contract.\n /// - The receiver of the canonical tBTC should be abi-encoded in the\n /// payload.\n /// - The receiver of the canonical tBTC must not be the zero address.\n ///\n /// The Wormhole Token Bridge contract has protection against redeeming\n /// the same VAA again. When a Token Bridge VAA is redeemed, its\n /// message body hash is stored in a map. This map is used to check\n /// whether the hash has already been set in this map. For this reason,\n /// this function does not have to be nonReentrant in theory. However,\n /// to make this function non-dependent on Wormhole Bridge implementation,\n /// we are making it nonReentrant anyway.\n /// @param encodedVm A byte array containing a Wormhole VAA signed by the\n /// guardians.\n function receiveTbtc(bytes calldata encodedVm) external nonReentrant {\n // ITokenBridge.completeTransferWithPayload completes a contract-controlled\n // transfer of an ERC20 token. Calling this function is not enough to\n // ensure L2WormholeGateway received Wormhole tBTC representation.\n // Instead of going too deep into the ITokenBridge implementation,\n // asserting who is the receiver of the token, and which token it is,\n // we check the balance before the ITokenBridge call and the balance\n // after ITokenBridge call. This way, we are sure this contract received\n // Wormhole tBTC token in the given amount. This is transparent to\n // all potential upgrades of ITokenBridge implementation and no other\n // validations are needed.\n uint256 balanceBefore = bridgeToken.balanceOf(address(this));\n bytes memory encoded = bridge.completeTransferWithPayload(encodedVm);\n uint256 balanceAfter = bridgeToken.balanceOf(address(this));\n\n uint256 amount = balanceAfter - balanceBefore;\n // Protect against the custody of irrelevant tokens.\n require(amount > 0, \"No tBTC transferred\");\n\n address receiver = fromWormholeAddress(\n bytes32(bridge.parseTransferWithPayload(encoded).payload)\n );\n require(receiver != address(0), \"0x0 receiver not allowed\");\n\n // We send wormhole tBTC OR mint canonical tBTC. We do not want to send\n // dust. Sending wormhole tBTC is an exceptional situation and we want\n // to keep it simple.\n if (mintedAmount + amount > mintingLimit) {\n bridgeToken.safeTransfer(receiver, amount);\n } else {\n // The function is non-reentrant.\n // slither-disable-next-line reentrancy-benign\n mintedAmount += amount;\n tbtc.mint(receiver, amount);\n }\n\n // The function is non-reentrant.\n // slither-disable-next-line reentrancy-events\n emit WormholeTbtcReceived(receiver, amount);\n }\n\n /// @notice Lets the governance to update the tBTC gateway address on the\n /// chain with the given Wormhole ID.\n /// @dev Use toWormholeAddress function to convert between Ethereum and\n /// Wormhole address formats.\n /// @param chainId Wormhole ID of the chain.\n /// @param gateway Address of tBTC gateway on the given chain in a Wormhole\n /// format.\n function updateGatewayAddress(uint16 chainId, bytes32 gateway)\n external\n onlyOwner\n {\n gateways[chainId] = gateway;\n emit GatewayAddressUpdated(chainId, gateway);\n }\n\n /// @notice Lets the governance to update the tBTC minting limit for this\n /// contract.\n /// @param _mintingLimit The new minting limit.\n function updateMintingLimit(uint256 _mintingLimit) external onlyOwner {\n mintingLimit = _mintingLimit;\n emit MintingLimitUpdated(_mintingLimit);\n }\n\n /// @notice Converts Ethereum address into Wormhole format.\n /// @param _address The address to convert.\n function toWormholeAddress(address _address)\n external\n pure\n returns (bytes32)\n {\n return bytes32(uint256(uint160(_address)));\n }\n\n /// @notice Converts Wormhole address into Ethereum format.\n /// @param _address The address to convert.\n function fromWormholeAddress(bytes32 _address)\n public\n pure\n returns (address)\n {\n return address(uint160(uint256(_address)));\n }\n\n /// @dev Eliminates the dust that cannot be bridged with Wormhole\n /// due to the decimal shift in the Wormhole Bridge contract.\n /// See https://github.com/wormhole-foundation/wormhole/blob/96682bdbeb7c87bfa110eade0554b3d8cbf788d2/ethereum/contracts/bridge/Bridge.sol#L276-L288\n function normalize(uint256 amount) internal pure returns (uint256) {\n // slither-disable-next-line divide-before-multiply\n amount /= 10**10;\n amount *= 10**10;\n return amount;\n }\n}\n"
|
|
350
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\n\nimport \"./Wormhole.sol\";\nimport \"./L2TBTC.sol\";\n\n/// @title L2WormholeGateway\n/// @notice Selected cross-ecosystem bridges are given the minting authority for\n/// tBTC token on L2 and sidechains. This contract gives a minting\n/// authority to the Wormhole Bridge.\n///\n/// The process of bridging from L1 to L2 (or sidechain) looks as\n/// follows:\n/// 1. There is a tBTC holder on L1. The holder goes to the Wormhole\n/// Portal and selects the chain they want to bridge to.\n/// 2. The holder submits one transaction to L1 locking their tBTC\n/// tokens in the bridge’s smart contract. After the transaction is\n/// mined, they wait about 15 minutes for the Ethereum block\n/// finality.\n/// 3. The holder submits one transaction to L2 that is minting tokens.\n/// After that transaction is mined, they have their tBTC on L2.\n///\n/// The process of bridging from L2 (or sidechain) to L1 looks as\n/// follows:\n/// 1. There is a tBTC holder on L2. That holder goes to the Wormhole\n/// Portal and selects one of the L2 chains they want to bridge from.\n/// 2. The holder submits one transaction to L2 that is burning the\n/// token. After the transaction is mined, they wait about 15 minutes\n/// for the L2 block finality.\n/// 3. The holder submits one transaction to L1 unlocking their tBTC\n/// tokens from the bridge’s smart contract. After that transaction\n/// is mined, they have their tBTC on L1.\n///\n/// This smart contract is integrated with step 3 of L1->L2 bridging and\n/// step 1 of L2->L1 or L2->L2 bridging. When the user redeems token on\n/// L2, this contract receives the Wormhole tBTC representation and\n/// mints the canonical tBTC in an equal amount. When user sends their\n/// token from L1, this contract burns the canonical tBTC and sends\n/// Wormhole tBTC representation through the bridge in an equal amount.\n/// @dev This contract is supposed to be deployed behind a transparent\n/// upgradeable proxy.\n// slither-disable-next-line missing-inheritance\ncontract L2WormholeGateway is\n Initializable,\n OwnableUpgradeable,\n ReentrancyGuardUpgradeable\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Reference to the Wormhole Token Bridge contract.\n IWormholeTokenBridge public bridge;\n\n /// @notice Wormhole tBTC token representation.\n IERC20Upgradeable public bridgeToken;\n\n /// @notice Canonical tBTC token.\n L2TBTC public tbtc;\n\n /// @notice Maps Wormhole chain ID to the Wormhole tBTC gateway address on\n /// that chain. For example, this chain's ID should be mapped to\n /// this contract's address. If there is no Wormhole tBTC gateway\n /// address on the given chain, there is no entry in this mapping.\n /// The mapping holds addresses in a Wormhole-specific format, where\n /// Ethereum address is left-padded with zeros.\n mapping(uint16 => bytes32) public gateways;\n\n /// @notice Minting limit for this gateway. Useful for early days of testing\n /// the system. The gateway can not mint more canonical tBTC than\n /// this limit.\n uint256 public mintingLimit;\n\n /// @notice The amount of tBTC minted by this contract. tBTC burned by this\n /// contract decreases this amount.\n uint256 public mintedAmount;\n\n event WormholeTbtcReceived(address receiver, uint256 amount);\n\n event WormholeTbtcSent(\n uint256 amount,\n uint16 recipientChain,\n bytes32 gateway,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n );\n\n event WormholeTbtcDeposited(address depositor, uint256 amount);\n\n event GatewayAddressUpdated(uint16 chainId, bytes32 gateway);\n\n event MintingLimitUpdated(uint256 mintingLimit);\n\n function initialize(\n IWormholeTokenBridge _bridge,\n IERC20Upgradeable _bridgeToken,\n L2TBTC _tbtc\n ) external initializer {\n __Ownable_init();\n __ReentrancyGuard_init();\n\n require(\n address(_bridge) != address(0),\n \"Wormhole bridge address must not be 0x0\"\n );\n require(\n address(_bridgeToken) != address(0),\n \"Bridge token address must not be 0x0\"\n );\n require(\n address(_tbtc) != address(0),\n \"L2TBTC token address must not be 0x0\"\n );\n\n bridge = _bridge;\n bridgeToken = _bridgeToken;\n tbtc = _tbtc;\n mintingLimit = type(uint256).max;\n }\n\n /// @notice This function is called when the user sends their token from L2.\n /// The contract burns the canonical tBTC from the user and sends\n /// wormhole tBTC representation over the bridge.\n /// Keep in mind that when multiple bridges receive a minting\n /// authority on the canonical tBTC, this function may not be able\n /// to send all amounts of tBTC through the Wormhole bridge. The\n /// capability of Wormhole Bridge to send tBTC from the chain is\n /// limited to the amount of tBTC bridged through Wormhole to that\n /// chain.\n /// @dev Requirements:\n /// - The sender must have at least `amount` of the canonical tBTC and\n /// it has to be approved for L2WormholeGateway.\n /// - The L2WormholeGateway must have at least `amount` of the wormhole\n /// tBTC.\n /// - The recipient must not be 0x0.\n /// - The amount to transfer must not be 0,\n /// - The amount to transfer must be >= 10^10 (1e18 precision).\n /// Depending if Wormhole tBTC gateway is registered on the target\n /// chain, this function uses transfer or transfer with payload over\n /// the Wormhole bridge.\n /// @param amount The amount of tBTC to be sent.\n /// @param recipientChain The Wormhole recipient chain ID.\n /// @param recipient The address of the recipient in the Wormhole format.\n /// @param arbiterFee The Wormhole arbiter fee. Ignored if sending\n /// tBTC to chain with Wormhole tBTC gateway.\n /// @param nonce The Wormhole nonce used to batch messages together.\n /// @return The Wormhole sequence number.\n function sendTbtc(\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable nonReentrant returns (uint64) {\n require(recipient != bytes32(0), \"0x0 recipient not allowed\");\n require(amount != 0, \"Amount must not be 0\");\n\n // Normalize the amount to bridge. The dust can not be bridged due to\n // the decimal shift in the Wormhole Bridge contract.\n amount = WormholeUtils.normalize(amount);\n\n // Check again after dropping the dust.\n require(amount != 0, \"Amount too low to bridge\");\n\n require(\n bridgeToken.balanceOf(address(this)) >= amount,\n \"Not enough wormhole tBTC in the gateway to bridge\"\n );\n\n bytes32 gateway = gateways[recipientChain];\n\n emit WormholeTbtcSent(\n amount,\n recipientChain,\n gateway,\n recipient,\n arbiterFee,\n nonce\n );\n\n mintedAmount -= amount;\n tbtc.burnFrom(msg.sender, amount);\n bridgeToken.safeApprove(address(bridge), amount);\n\n if (gateway == bytes32(0)) {\n // No Wormhole tBTC gateway on the target chain. The token minted\n // by Wormhole should be considered canonical.\n return\n bridge.transferTokens{value: msg.value}(\n address(bridgeToken),\n amount,\n recipientChain,\n recipient,\n arbiterFee,\n nonce\n );\n } else {\n // There is a Wormhole tBTC gateway on the target chain.\n // The gateway needs to mint canonical tBTC for the recipient\n // encoded in the payload.\n return\n bridge.transferTokensWithPayload{value: msg.value}(\n address(bridgeToken),\n amount,\n recipientChain,\n gateway,\n nonce,\n abi.encode(recipient)\n );\n }\n }\n\n /// @notice This function is called when the user redeems their token on L2.\n /// The contract receives Wormhole tBTC representation and mints the\n /// canonical tBTC for the user.\n /// If the tBTC minting limit has been reached by this contract,\n /// instead of minting tBTC the receiver address receives Wormhole\n /// tBTC representation.\n /// @dev Requirements:\n /// - The receiver of Wormhole tBTC should be the L2WormholeGateway\n /// contract.\n /// - The receiver of the canonical tBTC should be abi-encoded in the\n /// payload.\n /// - The receiver of the canonical tBTC must not be the zero address.\n ///\n /// The Wormhole Token Bridge contract has protection against redeeming\n /// the same VAA again. When a Token Bridge VAA is redeemed, its\n /// message body hash is stored in a map. This map is used to check\n /// whether the hash has already been set in this map. For this reason,\n /// this function does not have to be nonReentrant in theory. However,\n /// to make this function non-dependent on Wormhole Bridge implementation,\n /// we are making it nonReentrant anyway.\n /// @param encodedVm A byte array containing a Wormhole VAA signed by the\n /// guardians.\n function receiveTbtc(bytes calldata encodedVm) external nonReentrant {\n // ITokenBridge.completeTransferWithPayload completes a contract-controlled\n // transfer of an ERC20 token. Calling this function is not enough to\n // ensure L2WormholeGateway received Wormhole tBTC representation.\n // Instead of going too deep into the ITokenBridge implementation,\n // asserting who is the receiver of the token, and which token it is,\n // we check the balance before the ITokenBridge call and the balance\n // after ITokenBridge call. This way, we are sure this contract received\n // Wormhole tBTC token in the given amount. This is transparent to\n // all potential upgrades of ITokenBridge implementation and no other\n // validations are needed.\n uint256 balanceBefore = bridgeToken.balanceOf(address(this));\n bytes memory encoded = bridge.completeTransferWithPayload(encodedVm);\n uint256 balanceAfter = bridgeToken.balanceOf(address(this));\n\n uint256 amount = balanceAfter - balanceBefore;\n // Protect against the custody of irrelevant tokens.\n require(amount > 0, \"No tBTC transferred\");\n\n address receiver = fromWormholeAddress(\n bytes32(bridge.parseTransferWithPayload(encoded).payload)\n );\n require(receiver != address(0), \"0x0 receiver not allowed\");\n\n // We send wormhole tBTC OR mint canonical tBTC. We do not want to send\n // dust. Sending wormhole tBTC is an exceptional situation and we want\n // to keep it simple.\n if (mintedAmount + amount > mintingLimit) {\n bridgeToken.safeTransfer(receiver, amount);\n } else {\n // The function is non-reentrant.\n // slither-disable-next-line reentrancy-benign\n mintedAmount += amount;\n tbtc.mint(receiver, amount);\n }\n\n // The function is non-reentrant.\n // slither-disable-next-line reentrancy-events\n emit WormholeTbtcReceived(receiver, amount);\n }\n\n /// @notice Lets the governance to update the tBTC gateway address on the\n /// chain with the given Wormhole ID.\n /// @dev Use toWormholeAddress function to convert between Ethereum and\n /// Wormhole address formats.\n /// @param chainId Wormhole ID of the chain.\n /// @param gateway Address of tBTC gateway on the given chain in a Wormhole\n /// format.\n function updateGatewayAddress(uint16 chainId, bytes32 gateway)\n external\n onlyOwner\n {\n gateways[chainId] = gateway;\n emit GatewayAddressUpdated(chainId, gateway);\n }\n\n /// @notice Lets the governance to update the tBTC minting limit for this\n /// contract.\n /// @param _mintingLimit The new minting limit.\n function updateMintingLimit(uint256 _mintingLimit) external onlyOwner {\n mintingLimit = _mintingLimit;\n emit MintingLimitUpdated(_mintingLimit);\n }\n\n /// @notice Converts Ethereum address into Wormhole format.\n /// @param _address The address to convert.\n function toWormholeAddress(address _address)\n external\n pure\n returns (bytes32)\n {\n return WormholeUtils.toWormholeAddress(_address);\n }\n\n /// @notice Converts Wormhole address into Ethereum format.\n /// @param _address The address to convert.\n function fromWormholeAddress(bytes32 _address)\n public\n pure\n returns (address)\n {\n return WormholeUtils.fromWormholeAddress(_address);\n }\n}\n"
|
|
351
|
+
},
|
|
352
|
+
"contracts/l2/Wormhole.sol": {
|
|
353
|
+
"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\n/// @title WormholeTypes\n/// @notice Namespace which groups all types relevant to Wormhole interfaces.\nlibrary WormholeTypes {\n /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L22\n struct VaaKey {\n uint16 chainId;\n bytes32 emitterAddress;\n uint64 sequence;\n }\n}\n\n/// @title IWormhole\n/// @notice Wormhole interface.\n/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormhole.sol#L6\ninterface IWormhole {\n /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormhole.sol#L109\n function chainId() external view returns (uint16);\n\n /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormhole.sol#L117\n function messageFee() external view returns (uint256);\n}\n\n/// @title IWormholeRelayer\n/// @notice Wormhole Relayer interface.\n/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L74\ninterface IWormholeRelayer {\n /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L442\n function quoteEVMDeliveryPrice(\n uint16 targetChain,\n uint256 receiverValue,\n uint256 gasLimit\n )\n external\n view\n returns (\n uint256 nativePriceQuote,\n uint256 targetChainRefundPerGasUnused\n );\n\n /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L182\n function sendVaasToEvm(\n uint16 targetChain,\n address targetAddress,\n bytes memory payload,\n uint256 receiverValue,\n uint256 gasLimit,\n WormholeTypes.VaaKey[] memory vaaKeys,\n uint16 refundChain,\n address refundAddress\n ) external payable returns (uint64 sequence);\n}\n\n/// @title IWormholeReceiver\n/// @notice Wormhole Receiver interface.\n/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeReceiver.sol#L8\ninterface IWormholeReceiver {\n /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeReceiver.sol#L44\n function receiveWormholeMessages(\n bytes memory payload,\n bytes[] memory additionalVaas,\n bytes32 sourceAddress,\n uint16 sourceChain,\n bytes32 deliveryHash\n ) external payable;\n}\n\n/// @title IWormholeTokenBridge\n/// @notice Wormhole Token Bridge interface.\n/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/ITokenBridge.sol#L9\ninterface IWormholeTokenBridge {\n function completeTransferWithPayload(bytes memory encodedVm)\n external\n returns (bytes memory);\n\n function parseTransferWithPayload(bytes memory encoded)\n external\n pure\n returns (TransferWithPayload memory transfer);\n\n function transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable returns (uint64 sequence);\n\n function transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes memory payload\n ) external payable returns (uint64 sequence);\n\n struct TransferWithPayload {\n uint8 payloadID;\n uint256 amount;\n bytes32 tokenAddress;\n uint16 tokenChain;\n bytes32 to;\n uint16 toChain;\n bytes32 fromAddress;\n bytes payload;\n }\n}\n\n/// @title WormholeUtils\n/// @notice Library for Wormhole utilities.\nlibrary WormholeUtils {\n /// @notice Converts Ethereum address into Wormhole format.\n /// @param _address The address to convert.\n function toWormholeAddress(address _address)\n internal\n pure\n returns (bytes32)\n {\n return bytes32(uint256(uint160(_address)));\n }\n\n /// @notice Converts Wormhole address into Ethereum format.\n /// @param _address The address to convert.\n function fromWormholeAddress(bytes32 _address)\n internal\n pure\n returns (address)\n {\n return address(uint160(uint256(_address)));\n }\n\n /// @dev Eliminates the dust that cannot be bridged with Wormhole\n /// due to the decimal shift in the Wormhole Bridge contract.\n /// See https://github.com/wormhole-foundation/wormhole/blob/96682bdbeb7c87bfa110eade0554b3d8cbf788d2/ethereum/contracts/bridge/Bridge.sol#L276-L288\n function normalize(uint256 amount) internal pure returns (uint256) {\n // slither-disable-next-line divide-before-multiply\n amount /= 10**10;\n amount *= 10**10;\n return amount;\n }\n}\n"
|
|
333
354
|
},
|
|
334
355
|
"contracts/maintainer/MaintainerProxy.sol": {
|
|
335
356
|
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@keep-network/random-beacon/contracts/Reimbursable.sol\";\nimport \"@keep-network/random-beacon/contracts/ReimbursementPool.sol\";\n\nimport \"../bridge/BitcoinTx.sol\";\nimport \"../bridge/Bridge.sol\";\n\n/// @title Maintainer Proxy\n/// @notice Maintainers are the willing off-chain clients approved by the governance.\n/// Maintainers proxy calls to the `Bridge` contract via 'MaintainerProxy'\n/// and are refunded for the spent gas from the `ReimbursementPool`.\n/// There are two types of maintainers: wallet maintainers and SPV\n/// maintainers.\ncontract MaintainerProxy is Ownable, Reimbursable {\n Bridge public bridge;\n\n /// @notice Authorized wallet maintainers that can interact with the set of\n /// functions for wallet maintainers only. Authorization can be\n /// granted and removed by the governance.\n /// @dev 'Key' is the address of the maintainer. 'Value' represents an index+1\n /// in the 'maintainers' array. 1 was added so the maintainer index can\n /// never be 0 which is a reserved index for a non-existent maintainer\n /// in this map.\n mapping(address => uint256) public isWalletMaintainer;\n\n /// @notice This list of wallet maintainers keeps the order of which wallet\n /// maintainer should be submitting a next transaction. It does not\n /// enforce the order but only tracks who should be next in line.\n address[] public walletMaintainers;\n\n /// @notice Authorized SPV maintainers that can interact with the set of\n /// functions for SPV maintainers only. Authorization can be\n /// granted and removed by the governance.\n /// @dev 'Key' is the address of the maintainer. 'Value' represents an index+1\n /// in the 'maintainers' array. 1 was added so the maintainer index can\n /// never be 0 which is a reserved index for a non-existent maintainer\n /// in this map.\n mapping(address => uint256) public isSpvMaintainer;\n\n /// @notice This list of SPV maintainers keeps the order of which SPV\n /// maintainer should be submitting a next transaction. It does not\n /// enforce the order but only tracks who should be next in line.\n address[] public spvMaintainers;\n\n /// @notice Gas that is meant to balance the submission of deposit sweep proof\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public submitDepositSweepProofGasOffset;\n\n /// @notice Gas that is meant to balance the submission of redemption proof\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public submitRedemptionProofGasOffset;\n\n /// @notice Gas that is meant to balance the reset of moving funds timeout\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public resetMovingFundsTimeoutGasOffset;\n\n /// @notice Gas that is meant to balance the submission of moving funds proof\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public submitMovingFundsProofGasOffset;\n\n /// @notice Gas that is meant to balance the notification of moving funds below\n /// dust overall cost. Can be updated by the governance based on the\n /// current market conditions.\n uint256 public notifyMovingFundsBelowDustGasOffset;\n\n /// @notice Gas that is meant to balance the submission of moved funds sweep\n /// proof overall cost. Can be updated by the governance based on the\n /// current market conditions.\n uint256 public submitMovedFundsSweepProofGasOffset;\n\n /// @notice Gas that is meant to balance the request of a new wallet overall\n /// cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public requestNewWalletGasOffset;\n\n /// @notice Gas that is meant to balance the notification of closeable wallet\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public notifyWalletCloseableGasOffset;\n\n /// @notice Gas that is meant to balance the notification of wallet closing\n /// period elapsed overall cost. Can be updated by the governance\n /// based on the current market conditions.\n uint256 public notifyWalletClosingPeriodElapsedGasOffset;\n\n /// @notice Gas that is meant to balance the defeat fraud challenge\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public defeatFraudChallengeGasOffset;\n\n /// @notice Gas that is meant to balance the defeat fraud challenge with heartbeat\n /// overall cost. Can be updated by the governance based on the current\n /// market conditions.\n uint256 public defeatFraudChallengeWithHeartbeatGasOffset;\n\n event WalletMaintainerAuthorized(address indexed maintainer);\n\n event WalletMaintainerUnauthorized(address indexed maintainer);\n\n event SpvMaintainerAuthorized(address indexed maintainer);\n\n event SpvMaintainerUnauthorized(address indexed maintainer);\n\n event BridgeUpdated(address newBridge);\n\n event GasOffsetParametersUpdated(\n uint256 submitDepositSweepProofGasOffset,\n uint256 submitRedemptionProofGasOffset,\n uint256 resetMovingFundsTimeoutGasOffset,\n uint256 submitMovingFundsProofGasOffset,\n uint256 notifyMovingFundsBelowDustGasOffset,\n uint256 submitMovedFundsSweepProofGasOffset,\n uint256 requestNewWalletGasOffset,\n uint256 notifyWalletCloseableGasOffset,\n uint256 notifyWalletClosingPeriodElapsedGasOffset,\n uint256 defeatFraudChallengeGasOffset,\n uint256 defeatFraudChallengeWithHeartbeatGasOffset\n );\n\n modifier onlyWalletMaintainer() {\n require(\n isWalletMaintainer[msg.sender] != 0,\n \"Caller is not authorized\"\n );\n _;\n }\n\n modifier onlySpvMaintainer() {\n require(isSpvMaintainer[msg.sender] != 0, \"Caller is not authorized\");\n _;\n }\n\n modifier onlyReimbursableAdmin() override {\n require(owner() == msg.sender, \"Caller is not the owner\");\n _;\n }\n\n constructor(Bridge _bridge, ReimbursementPool _reimbursementPool) {\n bridge = _bridge;\n reimbursementPool = _reimbursementPool;\n submitDepositSweepProofGasOffset = 30000;\n submitRedemptionProofGasOffset = 4000;\n resetMovingFundsTimeoutGasOffset = 1000;\n submitMovingFundsProofGasOffset = 20000;\n notifyMovingFundsBelowDustGasOffset = 3500;\n submitMovedFundsSweepProofGasOffset = 26000;\n requestNewWalletGasOffset = 3500;\n notifyWalletCloseableGasOffset = 4000;\n notifyWalletClosingPeriodElapsedGasOffset = 3000;\n defeatFraudChallengeGasOffset = 10000;\n defeatFraudChallengeWithHeartbeatGasOffset = 5000;\n }\n\n /// @notice Wraps `Bridge.submitDepositSweepProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitDepositSweepProof` function documentation.\n function submitDepositSweepProof(\n BitcoinTx.Info calldata sweepTx,\n BitcoinTx.Proof calldata sweepProof,\n BitcoinTx.UTXO calldata mainUtxo,\n address vault\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitDepositSweepProof(sweepTx, sweepProof, mainUtxo, vault);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitDepositSweepProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.submitRedemptionProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitRedemptionProof` function documentation.\n function submitRedemptionProof(\n BitcoinTx.Info calldata redemptionTx,\n BitcoinTx.Proof calldata redemptionProof,\n BitcoinTx.UTXO calldata mainUtxo,\n bytes20 walletPubKeyHash\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitRedemptionProof(\n redemptionTx,\n redemptionProof,\n mainUtxo,\n walletPubKeyHash\n );\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitRedemptionProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.resetMovingFundsTimeout` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.resetMovingFundsTimeout` function documentation.\n function resetMovingFundsTimeout(bytes20 walletPubKeyHash) external {\n uint256 gasStart = gasleft();\n\n bridge.resetMovingFundsTimeout(walletPubKeyHash);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + resetMovingFundsTimeoutGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.submitMovingFundsProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitMovingFundsProof` function documentation.\n function submitMovingFundsProof(\n BitcoinTx.Info calldata movingFundsTx,\n BitcoinTx.Proof calldata movingFundsProof,\n BitcoinTx.UTXO calldata mainUtxo,\n bytes20 walletPubKeyHash\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitMovingFundsProof(\n movingFundsTx,\n movingFundsProof,\n mainUtxo,\n walletPubKeyHash\n );\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitMovingFundsProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.notifyMovingFundsBelowDust` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.notifyMovingFundsBelowDust` function documentation.\n function notifyMovingFundsBelowDust(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata mainUtxo\n ) external onlyWalletMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.notifyMovingFundsBelowDust(walletPubKeyHash, mainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + notifyMovingFundsBelowDustGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.submitMovedFundsSweepProof` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.submitMovedFundsSweepProof` function documentation.\n function submitMovedFundsSweepProof(\n BitcoinTx.Info calldata sweepTx,\n BitcoinTx.Proof calldata sweepProof,\n BitcoinTx.UTXO calldata mainUtxo\n ) external onlySpvMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.submitMovedFundsSweepProof(sweepTx, sweepProof, mainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + submitMovedFundsSweepProofGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.requestNewWallet` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.requestNewWallet` function documentation.\n function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)\n external\n onlyWalletMaintainer\n {\n uint256 gasStart = gasleft();\n\n bridge.requestNewWallet(activeWalletMainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + requestNewWalletGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.notifyWalletCloseable` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.notifyWalletCloseable` function documentation.\n function notifyWalletCloseable(\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata walletMainUtxo\n ) external onlyWalletMaintainer {\n uint256 gasStart = gasleft();\n\n bridge.notifyWalletCloseable(walletPubKeyHash, walletMainUtxo);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + notifyWalletCloseableGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.notifyWalletClosingPeriodElapsed` call and reimburses\n /// the caller's transaction cost.\n /// @dev See `Bridge.notifyWalletClosingPeriodElapsed` function documentation.\n function notifyWalletClosingPeriodElapsed(bytes20 walletPubKeyHash)\n external\n onlyWalletMaintainer\n {\n uint256 gasStart = gasleft();\n\n bridge.notifyWalletClosingPeriodElapsed(walletPubKeyHash);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + notifyWalletClosingPeriodElapsedGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.defeatFraudChallenge` call and reimburses the\n /// caller's transaction cost.\n /// @dev See `Bridge.defeatFraudChallenge` function documentation.\n function defeatFraudChallenge(\n bytes calldata walletPublicKey,\n bytes calldata preimage,\n bool witness\n ) external {\n uint256 gasStart = gasleft();\n\n bridge.defeatFraudChallenge(walletPublicKey, preimage, witness);\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + defeatFraudChallengeGasOffset,\n msg.sender\n );\n }\n\n /// @notice Wraps `Bridge.defeatFraudChallengeWithHeartbeat` call and\n /// reimburses the caller's transaction cost.\n /// @dev See `Bridge.defeatFraudChallengeWithHeartbeat` function documentation.\n function defeatFraudChallengeWithHeartbeat(\n bytes calldata walletPublicKey,\n bytes calldata heartbeatMessage\n ) external {\n uint256 gasStart = gasleft();\n\n bridge.defeatFraudChallengeWithHeartbeat(\n walletPublicKey,\n heartbeatMessage\n );\n\n reimbursementPool.refund(\n (gasStart - gasleft()) + defeatFraudChallengeWithHeartbeatGasOffset,\n msg.sender\n );\n }\n\n /// @notice Authorize a wallet maintainer that can interact with this\n /// reimbursement pool. Can be authorized by the owner only.\n /// @param maintainer Wallet maintainer to authorize.\n function authorizeWalletMaintainer(address maintainer) external onlyOwner {\n walletMaintainers.push(maintainer);\n isWalletMaintainer[maintainer] = walletMaintainers.length;\n\n emit WalletMaintainerAuthorized(maintainer);\n }\n\n /// @notice Authorize an SPV maintainer that can interact with this\n /// reimbursement pool. Can be authorized by the owner only.\n /// @param maintainer SPV maintainer to authorize.\n function authorizeSpvMaintainer(address maintainer) external onlyOwner {\n spvMaintainers.push(maintainer);\n isSpvMaintainer[maintainer] = spvMaintainers.length;\n\n emit SpvMaintainerAuthorized(maintainer);\n }\n\n /// @notice Unauthorize a wallet maintainer that was previously authorized to\n /// interact with the Maintainer Proxy contract. Can be unauthorized\n /// by the owner only.\n /// @dev The last maintainer is swapped with the one to be unauthorized.\n /// The unauthorized maintainer is then removed from the list. An index\n /// of the last maintainer is changed with the removed maintainer.\n /// Ex.\n /// 'walletMaintainers' list: [0x1, 0x2, 0x3, 0x4, 0x5]\n /// 'isWalletMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x3 -> 3, 0x4 -> 4, 0x5 -> 5]\n /// unauthorize: 0x3\n /// new 'walletMaintainers' list: [0x1, 0x2, 0x5, 0x4]\n /// new 'isWalletMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x4 -> 4, 0x5 -> 3]\n /// @param maintainerToUnauthorize Maintainer to unauthorize.\n function unauthorizeWalletMaintainer(address maintainerToUnauthorize)\n external\n onlyOwner\n {\n uint256 maintainerIdToUnauthorize = isWalletMaintainer[\n maintainerToUnauthorize\n ];\n\n require(maintainerIdToUnauthorize != 0, \"No maintainer to unauthorize\");\n\n address lastMaintainerAddress = walletMaintainers[\n walletMaintainers.length - 1\n ];\n\n walletMaintainers[\n maintainerIdToUnauthorize - 1\n ] = lastMaintainerAddress;\n walletMaintainers.pop();\n\n isWalletMaintainer[lastMaintainerAddress] = maintainerIdToUnauthorize;\n\n delete isWalletMaintainer[maintainerToUnauthorize];\n\n emit WalletMaintainerUnauthorized(maintainerToUnauthorize);\n }\n\n /// @notice Unauthorize an SPV maintainer that was previously authorized to\n /// interact with the Maintainer Proxy contract. Can be unauthorized\n /// by the owner only.\n /// @dev The last maintainer is swapped with the one to be unauthorized.\n /// The unauthorized maintainer is then removed from the list. An index\n /// of the last maintainer is changed with the removed maintainer.\n /// Ex.\n /// 'spvMaintainers' list: [0x1, 0x2, 0x3, 0x4, 0x5]\n /// 'isSpvMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x3 -> 3, 0x4 -> 4, 0x5 -> 5]\n /// unauthorize: 0x3\n /// new 'spvMaintainers' list: [0x1, 0x2, 0x5, 0x4]\n /// new 'isSpvMaintainer' map: [0x1 -> 1, 0x2 -> 2, 0x4 -> 4, 0x5 -> 3]\n /// @param maintainerToUnauthorize Maintainer to unauthorize.\n function unauthorizeSpvMaintainer(address maintainerToUnauthorize)\n external\n onlyOwner\n {\n uint256 maintainerIdToUnauthorize = isSpvMaintainer[\n maintainerToUnauthorize\n ];\n\n require(maintainerIdToUnauthorize != 0, \"No maintainer to unauthorize\");\n\n address lastMaintainerAddress = spvMaintainers[\n spvMaintainers.length - 1\n ];\n\n spvMaintainers[maintainerIdToUnauthorize - 1] = lastMaintainerAddress;\n spvMaintainers.pop();\n\n isSpvMaintainer[lastMaintainerAddress] = maintainerIdToUnauthorize;\n\n delete isSpvMaintainer[maintainerToUnauthorize];\n\n emit SpvMaintainerUnauthorized(maintainerToUnauthorize);\n }\n\n /// @notice Allows the Governance to upgrade the Bridge address.\n /// @dev The function does not implement any governance delay and does not\n /// check the status of the Bridge. The Governance implementation needs\n /// to ensure all requirements for the upgrade are satisfied before\n /// executing this function.\n function updateBridge(Bridge _bridge) external onlyOwner {\n bridge = _bridge;\n\n emit BridgeUpdated(address(_bridge));\n }\n\n /// @notice Updates the values of gas offset parameters.\n /// @dev Can be called only by the contract owner. The caller is responsible\n /// for validating parameters.\n /// @param newSubmitDepositSweepProofGasOffset New submit deposit sweep\n /// proof gas offset.\n /// @param newSubmitRedemptionProofGasOffset New submit redemption proof gas\n /// offset.\n /// @param newResetMovingFundsTimeoutGasOffset New reset moving funds\n /// timeout gas offset.\n /// @param newSubmitMovingFundsProofGasOffset New submit moving funds proof\n /// gas offset.\n /// @param newNotifyMovingFundsBelowDustGasOffset New notify moving funds\n /// below dust gas offset.\n /// @param newSubmitMovedFundsSweepProofGasOffset New submit moved funds\n /// sweep proof gas offset.\n /// @param newRequestNewWalletGasOffset New request new wallet gas offset.\n /// @param newNotifyWalletCloseableGasOffset New notify closeable wallet gas\n /// offset.\n /// @param newNotifyWalletClosingPeriodElapsedGasOffset New notify wallet\n /// closing period elapsed gas offset.\n /// @param newDefeatFraudChallengeGasOffset New defeat fraud challenge gas\n /// offset.\n /// @param newDefeatFraudChallengeWithHeartbeatGasOffset New defeat fraud\n /// challenge with heartbeat gas offset.\n function updateGasOffsetParameters(\n uint256 newSubmitDepositSweepProofGasOffset,\n uint256 newSubmitRedemptionProofGasOffset,\n uint256 newResetMovingFundsTimeoutGasOffset,\n uint256 newSubmitMovingFundsProofGasOffset,\n uint256 newNotifyMovingFundsBelowDustGasOffset,\n uint256 newSubmitMovedFundsSweepProofGasOffset,\n uint256 newRequestNewWalletGasOffset,\n uint256 newNotifyWalletCloseableGasOffset,\n uint256 newNotifyWalletClosingPeriodElapsedGasOffset,\n uint256 newDefeatFraudChallengeGasOffset,\n uint256 newDefeatFraudChallengeWithHeartbeatGasOffset\n ) external onlyOwner {\n submitDepositSweepProofGasOffset = newSubmitDepositSweepProofGasOffset;\n submitRedemptionProofGasOffset = newSubmitRedemptionProofGasOffset;\n resetMovingFundsTimeoutGasOffset = newResetMovingFundsTimeoutGasOffset;\n submitMovingFundsProofGasOffset = newSubmitMovingFundsProofGasOffset;\n notifyMovingFundsBelowDustGasOffset = newNotifyMovingFundsBelowDustGasOffset;\n submitMovedFundsSweepProofGasOffset = newSubmitMovedFundsSweepProofGasOffset;\n requestNewWalletGasOffset = newRequestNewWalletGasOffset;\n notifyWalletCloseableGasOffset = newNotifyWalletCloseableGasOffset;\n notifyWalletClosingPeriodElapsedGasOffset = newNotifyWalletClosingPeriodElapsedGasOffset;\n defeatFraudChallengeGasOffset = newDefeatFraudChallengeGasOffset;\n defeatFraudChallengeWithHeartbeatGasOffset = newDefeatFraudChallengeWithHeartbeatGasOffset;\n\n emit GasOffsetParametersUpdated(\n submitDepositSweepProofGasOffset,\n submitRedemptionProofGasOffset,\n resetMovingFundsTimeoutGasOffset,\n submitMovingFundsProofGasOffset,\n notifyMovingFundsBelowDustGasOffset,\n submitMovedFundsSweepProofGasOffset,\n requestNewWalletGasOffset,\n notifyWalletCloseableGasOffset,\n notifyWalletClosingPeriodElapsedGasOffset,\n defeatFraudChallengeGasOffset,\n defeatFraudChallengeWithHeartbeatGasOffset\n );\n }\n\n /// @notice Gets an entire array of wallet maintainer addresses.\n function allWalletMaintainers() external view returns (address[] memory) {\n return walletMaintainers;\n }\n\n /// @notice Gets an entire array of SPV maintainer addresses.\n function allSpvMaintainers() external view returns (address[] memory) {\n return spvMaintainers;\n }\n}\n"
|
|
@@ -374,10 +395,13 @@
|
|
|
374
395
|
"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"
|
|
375
396
|
},
|
|
376
397
|
"contracts/test/TestTBTCDepositor.sol": {
|
|
377
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.0;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\n\nimport \"../integrator/AbstractTBTCDepositor.sol\";\nimport \"../integrator/IBridge.sol\";\nimport \"../integrator/ITBTCVault.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract TestTBTCDepositor is AbstractTBTCDepositor {\n event InitializeDepositReturned(\n uint256 depositKey,\n uint256 initialDepositAmount\n );\n\n event FinalizeDepositReturned(\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n bytes32 extraData\n );\n\n function initialize(address _bridge, address _tbtcVault) external {\n __AbstractTBTCDepositor_initialize(_bridge, _tbtcVault);\n }\n\n function initializeDepositPublic(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n bytes32 extraData\n ) external {\n (uint256 depositKey, uint256 initialDepositAmount) = _initializeDeposit(\n fundingTx,\n reveal,\n extraData\n );\n emit InitializeDepositReturned(depositKey, initialDepositAmount);\n }\n\n function finalizeDepositPublic(uint256 depositKey) external {\n (\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n bytes32 extraData\n ) = _finalizeDeposit(depositKey);\n emit FinalizeDepositReturned(\n initialDepositAmount,\n tbtcAmount,\n extraData\n );\n }\n\n function calculateTbtcAmountPublic(\n uint64 depositAmountSat,\n uint64 depositTreasuryFeeSat\n ) external view returns (uint256) {\n return _calculateTbtcAmount(depositAmountSat, depositTreasuryFeeSat);\n }\n\n function minDepositAmountPublic() external view returns (uint256) {\n return _minDepositAmount();\n }\n}\n\ncontract MockBridge is IBridge {\n using BTCUtils for bytes;\n\n mapping(uint256 => IBridgeTypes.DepositRequest) internal _deposits;\n\n uint64 internal _depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC\n uint64 internal _depositTreasuryFeeDivisor = 50; // 1/50 == 100 bps == 2% == 0.02\n uint64 internal _depositTxMaxFee = 1000; // 1000 satoshi = 0.00001 BTC\n\n event DepositRevealed(uint256 depositKey);\n\n function revealDepositWithExtraData(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n bytes32 extraData\n ) external {\n bytes32 fundingTxHash = abi\n .encodePacked(\n fundingTx.version,\n fundingTx.inputVector,\n fundingTx.outputVector,\n fundingTx.locktime\n )\n .hash256View();\n\n uint256 depositKey = uint256(\n keccak256(\n abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)\n )\n );\n\n require(\n _deposits[depositKey].revealedAt == 0,\n \"Deposit already revealed\"\n );\n\n bytes memory fundingOutput = fundingTx\n .outputVector\n .extractOutputAtIndex(reveal.fundingOutputIndex);\n\n uint64 fundingOutputAmount = fundingOutput.extractValue();\n\n IBridgeTypes.DepositRequest memory request;\n\n request.depositor = msg.sender;\n request.amount = fundingOutputAmount;\n /* solhint-disable-next-line not-rely-on-time */\n request.revealedAt = uint32(block.timestamp);\n request.vault = reveal.vault;\n request.treasuryFee = _depositTreasuryFeeDivisor > 0\n ? fundingOutputAmount / _depositTreasuryFeeDivisor\n : 0;\n request.sweptAt = 0;\n request.extraData = extraData;\n\n _deposits[depositKey] = request;\n\n emit DepositRevealed(depositKey);\n }\n\n function sweepDeposit(uint256 depositKey) public {\n require(_deposits[depositKey].revealedAt != 0, \"Deposit not revealed\");\n require(_deposits[depositKey].sweptAt == 0, \"Deposit already swept\");\n /* solhint-disable-next-line not-rely-on-time */\n _deposits[depositKey].sweptAt = uint32(block.timestamp);\n }\n\n function deposits(uint256 depositKey)\n external\n view\n returns (IBridgeTypes.DepositRequest memory)\n {\n return _deposits[depositKey];\n }\n\n function depositParameters()\n external\n view\n returns (\n uint64 depositDustThreshold,\n uint64 depositTreasuryFeeDivisor,\n uint64 depositTxMaxFee,\n uint32 depositRevealAheadPeriod\n )\n {\n depositDustThreshold = _depositDustThreshold;\n depositTreasuryFeeDivisor = _depositTreasuryFeeDivisor;\n depositTxMaxFee = _depositTxMaxFee;\n depositRevealAheadPeriod = 0;\n }\n\n function setDepositDustThreshold(uint64 value) external {\n _depositDustThreshold = value;\n }\n\n function setDepositTreasuryFeeDivisor(uint64 value) external {\n _depositTreasuryFeeDivisor = value;\n }\n\n function setDepositTxMaxFee(uint64 value) external {\n _depositTxMaxFee = value;\n }\n}\n\ncontract MockTBTCVault is ITBTCVault {\n struct Request {\n uint64 requestedAt;\n uint64 finalizedAt;\n }\n\n mapping(uint256 => Request) internal _requests;\n\n uint32 public optimisticMintingFeeDivisor = 100; // 1%\n\n function optimisticMintingRequests(uint256 depositKey)\n external\n returns (uint64 requestedAt, uint64 finalizedAt)\n {\n Request memory request = _requests[depositKey];\n return (request.requestedAt, request.finalizedAt);\n }\n\n /// @dev The function is virtual to allow other projects using this mock\n /// for AbtractTBTCDepositor-based contract tests to add any custom\n /// logic needed.\n function createOptimisticMintingRequest(uint256 depositKey) public virtual {\n require(\n _requests[depositKey].requestedAt == 0,\n \"Request already exists\"\n );\n /* solhint-disable-next-line not-rely-on-time */\n _requests[depositKey].requestedAt = uint64(block.timestamp);\n }\n\n /// @dev The function is virtual to allow other projects using this mock\n /// for AbtractTBTCDepositor-based contract tests to add any custom\n /// logic needed.\n function finalizeOptimisticMintingRequest(uint256 depositKey)\n public\n virtual\n {\n require(\n _requests[depositKey].requestedAt != 0,\n \"Request does not exist\"\n );\n require(\n _requests[depositKey].finalizedAt == 0,\n \"Request already finalized\"\n );\n /* solhint-disable-next-line not-rely-on-time */\n _requests[depositKey].finalizedAt = uint64(block.timestamp);\n }\n\n function setOptimisticMintingFeeDivisor(uint32 value) external {\n optimisticMintingFeeDivisor = value;\n }\n}\n"
|
|
398
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.0;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\n\nimport \"../integrator/AbstractTBTCDepositor.sol\";\nimport \"../integrator/IBridge.sol\";\nimport \"../integrator/ITBTCVault.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract TestTBTCDepositor is AbstractTBTCDepositor {\n event InitializeDepositReturned(\n uint256 depositKey,\n uint256 initialDepositAmount\n );\n\n event FinalizeDepositReturned(\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n bytes32 extraData\n );\n\n function initialize(address _bridge, address _tbtcVault) external {\n __AbstractTBTCDepositor_initialize(_bridge, _tbtcVault);\n }\n\n function initializeDepositPublic(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n bytes32 extraData\n ) external {\n (uint256 depositKey, uint256 initialDepositAmount) = _initializeDeposit(\n fundingTx,\n reveal,\n extraData\n );\n emit InitializeDepositReturned(depositKey, initialDepositAmount);\n }\n\n function finalizeDepositPublic(uint256 depositKey) external {\n (\n uint256 initialDepositAmount,\n uint256 tbtcAmount,\n bytes32 extraData\n ) = _finalizeDeposit(depositKey);\n emit FinalizeDepositReturned(\n initialDepositAmount,\n tbtcAmount,\n extraData\n );\n }\n\n function calculateTbtcAmountPublic(\n uint64 depositAmountSat,\n uint64 depositTreasuryFeeSat\n ) external view returns (uint256) {\n return _calculateTbtcAmount(depositAmountSat, depositTreasuryFeeSat);\n }\n\n function minDepositAmountPublic() external view returns (uint256) {\n return _minDepositAmount();\n }\n}\n\ncontract MockBridge is IBridge {\n using BTCUtils for bytes;\n\n mapping(uint256 => IBridgeTypes.DepositRequest) internal _deposits;\n\n uint64 internal _depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC\n uint64 internal _depositTreasuryFeeDivisor = 50; // 1/50 == 100 bps == 2% == 0.02\n uint64 internal _depositTxMaxFee = 1000; // 1000 satoshi = 0.00001 BTC\n\n event DepositRevealed(uint256 depositKey);\n\n function revealDepositWithExtraData(\n IBridgeTypes.BitcoinTxInfo calldata fundingTx,\n IBridgeTypes.DepositRevealInfo calldata reveal,\n bytes32 extraData\n ) external {\n bytes32 fundingTxHash = abi\n .encodePacked(\n fundingTx.version,\n fundingTx.inputVector,\n fundingTx.outputVector,\n fundingTx.locktime\n )\n .hash256View();\n\n uint256 depositKey = uint256(\n keccak256(\n abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)\n )\n );\n\n require(\n _deposits[depositKey].revealedAt == 0,\n \"Deposit already revealed\"\n );\n\n bytes memory fundingOutput = fundingTx\n .outputVector\n .extractOutputAtIndex(reveal.fundingOutputIndex);\n\n uint64 fundingOutputAmount = fundingOutput.extractValue();\n\n IBridgeTypes.DepositRequest memory request;\n\n request.depositor = msg.sender;\n request.amount = fundingOutputAmount;\n /* solhint-disable-next-line not-rely-on-time */\n request.revealedAt = uint32(block.timestamp);\n request.vault = reveal.vault;\n request.treasuryFee = _depositTreasuryFeeDivisor > 0\n ? fundingOutputAmount / _depositTreasuryFeeDivisor\n : 0;\n request.sweptAt = 0;\n request.extraData = extraData;\n\n _deposits[depositKey] = request;\n\n emit DepositRevealed(depositKey);\n }\n\n function sweepDeposit(uint256 depositKey) public {\n require(_deposits[depositKey].revealedAt != 0, \"Deposit not revealed\");\n require(_deposits[depositKey].sweptAt == 0, \"Deposit already swept\");\n /* solhint-disable-next-line not-rely-on-time */\n _deposits[depositKey].sweptAt = uint32(block.timestamp);\n }\n\n function deposits(uint256 depositKey)\n external\n view\n returns (IBridgeTypes.DepositRequest memory)\n {\n return _deposits[depositKey];\n }\n\n function depositParameters()\n external\n view\n returns (\n uint64 depositDustThreshold,\n uint64 depositTreasuryFeeDivisor,\n uint64 depositTxMaxFee,\n uint32 depositRevealAheadPeriod\n )\n {\n depositDustThreshold = _depositDustThreshold;\n depositTreasuryFeeDivisor = _depositTreasuryFeeDivisor;\n depositTxMaxFee = _depositTxMaxFee;\n depositRevealAheadPeriod = 0;\n }\n\n function setDepositDustThreshold(uint64 value) external {\n _depositDustThreshold = value;\n }\n\n function setDepositTreasuryFeeDivisor(uint64 value) external {\n _depositTreasuryFeeDivisor = value;\n }\n\n function setDepositTxMaxFee(uint64 value) external {\n _depositTxMaxFee = value;\n }\n}\n\ncontract MockTBTCVault is ITBTCVault {\n struct Request {\n uint64 requestedAt;\n uint64 finalizedAt;\n }\n\n mapping(uint256 => Request) internal _requests;\n\n uint32 public optimisticMintingFeeDivisor = 100; // 1%\n\n function optimisticMintingRequests(uint256 depositKey)\n external\n returns (uint64 requestedAt, uint64 finalizedAt)\n {\n Request memory request = _requests[depositKey];\n return (request.requestedAt, request.finalizedAt);\n }\n\n /// @dev The function is virtual to allow other projects using this mock\n /// for AbtractTBTCDepositor-based contract tests to add any custom\n /// logic needed.\n function createOptimisticMintingRequest(uint256 depositKey) public virtual {\n require(\n _requests[depositKey].requestedAt == 0,\n \"Request already exists\"\n );\n /* solhint-disable-next-line not-rely-on-time */\n _requests[depositKey].requestedAt = uint64(block.timestamp);\n }\n\n /// @dev The function is virtual to allow other projects using this mock\n /// for AbtractTBTCDepositor-based contract tests to add any custom\n /// logic needed.\n function finalizeOptimisticMintingRequest(uint256 depositKey)\n public\n virtual\n {\n require(\n _requests[depositKey].requestedAt != 0,\n \"Request does not exist\"\n );\n require(\n _requests[depositKey].finalizedAt == 0,\n \"Request already finalized\"\n );\n /* solhint-disable-next-line not-rely-on-time */\n _requests[depositKey].finalizedAt = uint64(block.timestamp);\n }\n\n function setOptimisticMintingFeeDivisor(uint32 value) external {\n optimisticMintingFeeDivisor = value;\n }\n\n function tbtcToken() external view returns (address) {\n revert(\"Not implemented\");\n }\n}\n"
|
|
378
399
|
},
|
|
379
400
|
"contracts/test/WormholeBridgeStub.sol": {
|
|
380
|
-
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"./TestERC20.sol\";\nimport \"../l2/
|
|
401
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity 0.8.17;\n\nimport \"./TestERC20.sol\";\nimport \"../l2/Wormhole.sol\";\n\n/// @dev Stub contract used in L2WormholeGateway unit tests.\n/// Stub contract is used instead of a smock because of the token transfer\n/// that needs to happen in completeTransferWithPayload function.\ncontract WormholeBridgeStub is IWormholeTokenBridge {\n TestERC20 public wormholeToken;\n\n uint256 public transferAmount;\n bytes32 public receiverAddress;\n\n // Two simple events allowing to assert Wormhole bridge functions are\n // called.\n event WormholeBridgeStub_completeTransferWithPayload(bytes encodedVm);\n event WormholeBridgeStub_transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n );\n event WormholeBridgeStub_transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes payload\n );\n\n constructor(TestERC20 _wormholeToken) {\n wormholeToken = _wormholeToken;\n }\n\n function completeTransferWithPayload(bytes memory encodedVm)\n external\n returns (bytes memory)\n {\n emit WormholeBridgeStub_completeTransferWithPayload(encodedVm);\n wormholeToken.mint(msg.sender, transferAmount);\n\n // In a real implementation, encodedVm is parsed. To avoid copy-pasting\n // Wormhole code to this contract and then encoding parmaters in unit\n // tests, we allow to set the receiver address on the stub contract and\n // we return it here. The rest of the parameters does not matter.\n IWormholeTokenBridge.TransferWithPayload memory transfer = IWormholeTokenBridge\n .TransferWithPayload(\n 1, // payloadID\n 2, // amount\n 0x3000000000000000000000000000000000000000000000000000000000000000, // tokenAddress\n 4, // tokenChain\n 0x5000000000000000000000000000000000000000000000000000000000000000, // to\n 6, // toChain\n 0x7000000000000000000000000000000000000000000000000000000000000000, // fromAddress\n abi.encode(receiverAddress) // payload\n );\n\n return abi.encode(transfer);\n }\n\n function transferTokens(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint256 arbiterFee,\n uint32 nonce\n ) external payable returns (uint64 sequence) {\n emit WormholeBridgeStub_transferTokens(\n token,\n amount,\n recipientChain,\n recipient,\n arbiterFee,\n nonce\n );\n return 777;\n }\n\n function transferTokensWithPayload(\n address token,\n uint256 amount,\n uint16 recipientChain,\n bytes32 recipient,\n uint32 nonce,\n bytes memory payload\n ) external payable returns (uint64 sequence) {\n emit WormholeBridgeStub_transferTokensWithPayload(\n token,\n amount,\n recipientChain,\n recipient,\n nonce,\n payload\n );\n return 888;\n }\n\n function parseTransferWithPayload(bytes memory encoded)\n external\n pure\n returns (IWormholeTokenBridge.TransferWithPayload memory transfer)\n {\n return abi.decode(encoded, (IWormholeTokenBridge.TransferWithPayload));\n }\n\n function setTransferAmount(uint256 _transferAmount) external {\n transferAmount = _transferAmount;\n }\n\n function setReceiverAddress(bytes32 _receiverAddress) external {\n receiverAddress = _receiverAddress;\n }\n\n // Allows to mint Wormhole tBTC for depositWormholeTbtc unit tests.\n function mintWormholeToken(address to, uint256 amount) external {\n wormholeToken.mint(to, amount);\n }\n}\n"
|
|
402
|
+
},
|
|
403
|
+
"contracts/Timelock.sol": {
|
|
404
|
+
"content": "// SPDX-License-Identifier: GPL-3.0-only\npragma solidity 0.8.17;\n\nimport \"@openzeppelin/contracts/governance/TimelockController.sol\";\n\ncontract Timelock is TimelockController {\n constructor(\n uint256 minDelay,\n address[] memory proposers,\n address[] memory executors\n ) TimelockController(minDelay, proposers, executors, address(0)) {}\n}\n"
|
|
381
405
|
},
|
|
382
406
|
"contracts/token/TBTC.sol": {
|
|
383
407
|
"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"
|