@keep-network/tbtc-v2 1.7.0-dev.3 → 1.7.0-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/artifacts/BLS.json +1 -1
  2. package/artifacts/Bank.json +3 -3
  3. package/artifacts/BeaconAuthorization.json +1 -1
  4. package/artifacts/BeaconDkg.json +1 -1
  5. package/artifacts/BeaconDkgValidator.json +1 -1
  6. package/artifacts/BeaconInactivity.json +1 -1
  7. package/artifacts/BeaconSortitionPool.json +3 -3
  8. package/artifacts/Bridge.json +5 -5
  9. package/artifacts/BridgeGovernance.json +2 -2
  10. package/artifacts/BridgeGovernanceParameters.json +2 -2
  11. package/artifacts/Deposit.json +2 -2
  12. package/artifacts/DepositSweep.json +2 -2
  13. package/artifacts/DonationVault.json +2 -2
  14. package/artifacts/EcdsaDkgValidator.json +1 -1
  15. package/artifacts/EcdsaInactivity.json +1 -1
  16. package/artifacts/EcdsaSortitionPool.json +3 -3
  17. package/artifacts/Fraud.json +2 -2
  18. package/artifacts/LightRelay.json +3 -3
  19. package/artifacts/LightRelayMaintainerProxy.json +3 -3
  20. package/artifacts/MaintainerProxy.json +3 -3
  21. package/artifacts/MovingFunds.json +2 -2
  22. package/artifacts/NuCypherToken.json +2 -2
  23. package/artifacts/RandomBeacon.json +2 -2
  24. package/artifacts/RandomBeaconChaosnet.json +2 -2
  25. package/artifacts/RandomBeaconGovernance.json +2 -2
  26. package/artifacts/Redemption.json +2 -2
  27. package/artifacts/RedemptionWatchtower.json +5 -5
  28. package/artifacts/ReimbursementPool.json +2 -2
  29. package/artifacts/T.json +2 -2
  30. package/artifacts/TBTC.json +3 -3
  31. package/artifacts/TBTCToken.json +3 -3
  32. package/artifacts/TBTCVault.json +3 -3
  33. package/artifacts/TokenStaking.json +1 -1
  34. package/artifacts/TokenholderGovernor.json +9 -9
  35. package/artifacts/TokenholderTimelock.json +8 -8
  36. package/artifacts/VendingMachine.json +3 -3
  37. package/artifacts/VendingMachineNuCypher.json +1 -1
  38. package/artifacts/VendingMachineV2.json +3 -3
  39. package/artifacts/VendingMachineV3.json +3 -3
  40. package/artifacts/WalletProposalValidator.json +2 -2
  41. package/artifacts/WalletRegistry.json +5 -5
  42. package/artifacts/WalletRegistryGovernance.json +2 -2
  43. package/artifacts/Wallets.json +2 -2
  44. package/artifacts/solcInputs/{9e25453061017eb37854db4f2196e831.json → 7feaa969ec563d78b5d1a7cbf222210c.json} +2 -2
  45. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  46. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  47. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
  48. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  49. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  50. package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
  51. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  52. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  53. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  54. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  55. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  56. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  57. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  58. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  59. package/build/contracts/bridge/Redemption.sol/IRedemptionWatchtower.dbg.json +1 -1
  60. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  61. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  62. package/build/contracts/bridge/RedemptionWatchtower.sol/RedemptionWatchtower.dbg.json +1 -1
  63. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  64. package/build/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.dbg.json +1 -1
  65. package/build/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.dbg.json +1 -1
  66. package/build/contracts/bridge/WalletProposalValidator.sol/WalletProposalValidator.dbg.json +1 -1
  67. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  68. package/build/contracts/integrator/AbstractTBTCDepositor.sol/AbstractTBTCDepositor.dbg.json +1 -1
  69. package/build/contracts/integrator/IBridge.sol/IBridge.dbg.json +1 -1
  70. package/build/contracts/integrator/IBridge.sol/IBridgeTypes.dbg.json +1 -1
  71. package/build/contracts/integrator/ITBTCVault.sol/ITBTCVault.dbg.json +1 -1
  72. package/build/contracts/l2/L1BitcoinDepositor.sol/L1BitcoinDepositor.dbg.json +1 -1
  73. package/build/contracts/l2/L1BitcoinDepositor.sol/L1BitcoinDepositor.json +2 -2
  74. package/build/contracts/l2/L2BitcoinDepositor.sol/IL2WormholeGateway.dbg.json +1 -1
  75. package/build/contracts/l2/L2BitcoinDepositor.sol/L2BitcoinDepositor.dbg.json +1 -1
  76. package/build/contracts/l2/L2BitcoinDepositor.sol/L2BitcoinDepositor.json +2 -2
  77. package/build/contracts/l2/L2TBTC.sol/L2TBTC.dbg.json +1 -1
  78. package/build/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.dbg.json +1 -1
  79. package/build/contracts/l2/Wormhole.sol/IWormhole.dbg.json +1 -1
  80. package/build/contracts/l2/Wormhole.sol/IWormholeReceiver.dbg.json +1 -1
  81. package/build/contracts/l2/Wormhole.sol/IWormholeRelayer.dbg.json +1 -1
  82. package/build/contracts/l2/Wormhole.sol/IWormholeTokenBridge.dbg.json +1 -1
  83. package/build/contracts/l2/Wormhole.sol/WormholeTypes.dbg.json +1 -1
  84. package/build/contracts/l2/Wormhole.sol/WormholeUtils.dbg.json +1 -1
  85. package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
  86. package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +1 -1
  87. package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +1 -1
  88. package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +1 -1
  89. package/build/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.dbg.json +1 -1
  90. package/build/contracts/test/BankStub.sol/BankStub.dbg.json +1 -1
  91. package/build/contracts/test/BridgeStub.sol/BridgeStub.dbg.json +1 -1
  92. package/build/contracts/test/HeartbeatStub.sol/HeartbeatStub.dbg.json +1 -1
  93. package/build/contracts/test/LightRelayStub.sol/LightRelayStub.dbg.json +1 -1
  94. package/build/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.dbg.json +1 -1
  95. package/build/contracts/test/SepoliaLightRelay.sol/SepoliaLightRelay.dbg.json +1 -1
  96. package/build/contracts/test/SystemTestRelay.sol/SystemTestRelay.dbg.json +1 -1
  97. package/build/contracts/test/TestBitcoinTx.sol/TestBitcoinTx.dbg.json +1 -1
  98. package/build/contracts/test/TestERC20.sol/TestERC20.dbg.json +1 -1
  99. package/build/contracts/test/TestERC721.sol/TestERC721.dbg.json +1 -1
  100. package/build/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.dbg.json +1 -1
  101. package/build/contracts/test/TestTBTCDepositor.sol/MockBridge.dbg.json +1 -1
  102. package/build/contracts/test/TestTBTCDepositor.sol/MockTBTCVault.dbg.json +1 -1
  103. package/build/contracts/test/TestTBTCDepositor.sol/TestTBTCDepositor.dbg.json +1 -1
  104. package/build/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.dbg.json +1 -1
  105. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  106. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
  107. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  108. package/build/contracts/vault/TBTCOptimisticMinting.sol/TBTCOptimisticMinting.dbg.json +1 -1
  109. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  110. package/contracts/l2/L1BitcoinDepositor.sol +1 -1
  111. package/contracts/l2/L2BitcoinDepositor.sol +1 -1
  112. package/export/artifacts/contracts/l2/L1BitcoinDepositor.sol/L1BitcoinDepositor.json +7 -7
  113. package/export/artifacts/contracts/l2/L2BitcoinDepositor.sol/L2BitcoinDepositor.json +7 -7
  114. package/export/typechain/factories/L1BitcoinDepositor__factory.js +1 -1
  115. package/export/typechain/factories/L2BitcoinDepositor__factory.js +1 -1
  116. package/package.json +1 -1
@@ -326,10 +326,10 @@
326
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 /// @dev See {TBTCVault#tbtcToken}\n function tbtcToken() external view returns (address);\n}\n"
327
327
  },
328
328
  "contracts/l2/L1BitcoinDepositor.sol": {
329
- "content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.17;\n\nimport \"@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 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 /// @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 }\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 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 // 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"
329
+ "content": "// SPDX-License-Identifier: GPL-3.0-only\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity 0.8.17;\n\nimport \"@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 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 /// @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 }\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 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 // 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"
330
330
  },
331
331
  "contracts/l2/L2BitcoinDepositor.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/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"
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/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"
333
333
  },
334
334
  "contracts/l2/L2TBTC.sol": {
335
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.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"
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/49a76df3263c976e2f2346b8674103a4.json"
3
+ "buildInfo": "../../../build-info/a18cbdac96c217ea121b5c05923ceb21.json"
4
4
  }