@keep-network/tbtc-v2 0.1.1-dev.96 → 0.1.1-dev.99

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 (73) hide show
  1. package/artifacts/Bank.json +8 -8
  2. package/artifacts/Bridge.json +67 -67
  3. package/artifacts/Deposit.json +9 -9
  4. package/artifacts/DepositSweep.json +9 -9
  5. package/artifacts/EcdsaDkgValidator.json +1 -1
  6. package/artifacts/EcdsaInactivity.json +1 -1
  7. package/artifacts/EcdsaSortitionPool.json +2 -2
  8. package/artifacts/Fraud.json +9 -9
  9. package/artifacts/KeepRegistry.json +1 -1
  10. package/artifacts/KeepStake.json +2 -2
  11. package/artifacts/KeepToken.json +2 -2
  12. package/artifacts/KeepTokenStaking.json +1 -1
  13. package/artifacts/MovingFunds.json +9 -9
  14. package/artifacts/NuCypherStakingEscrow.json +1 -1
  15. package/artifacts/NuCypherToken.json +2 -2
  16. package/artifacts/RandomBeaconStub.json +1 -1
  17. package/artifacts/Redemption.json +10 -10
  18. package/artifacts/ReimbursementPool.json +2 -2
  19. package/artifacts/Relay.json +9 -9
  20. package/artifacts/T.json +2 -2
  21. package/artifacts/TBTC.json +10 -10
  22. package/artifacts/TBTCToken.json +10 -10
  23. package/artifacts/TBTCVault.json +15 -15
  24. package/artifacts/TokenStaking.json +1 -1
  25. package/artifacts/TokenholderGovernor.json +9 -9
  26. package/artifacts/TokenholderTimelock.json +8 -8
  27. package/artifacts/VendingMachine.json +11 -11
  28. package/artifacts/VendingMachineKeep.json +1 -1
  29. package/artifacts/VendingMachineNuCypher.json +1 -1
  30. package/artifacts/WalletRegistry.json +5 -5
  31. package/artifacts/WalletRegistryGovernance.json +2 -2
  32. package/artifacts/Wallets.json +11 -11
  33. package/artifacts/solcInputs/{3cf46a7694ce157f71d9dbf4db692b09.json → 5334fac3c976c2a5cd9bc3d6190e35c4.json} +9 -9
  34. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  35. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  36. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
  37. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  38. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  39. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  40. package/build/contracts/bridge/Bridge.sol/Bridge.json +123 -123
  41. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  42. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +16 -16
  43. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  44. package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
  45. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  46. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +2 -2
  47. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  48. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  49. package/build/contracts/bridge/Fraud.sol/Fraud.json +2 -2
  50. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  51. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  52. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  53. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +2 -2
  54. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  55. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +2 -2
  56. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  57. package/build/contracts/bridge/Redemption.sol/Redemption.json +2 -2
  58. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  59. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  60. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  61. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  62. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
  63. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  64. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  65. package/contracts/bridge/BitcoinTx.sol +74 -39
  66. package/contracts/bridge/Bridge.sol +31 -31
  67. package/contracts/bridge/BridgeState.sol +100 -84
  68. package/contracts/bridge/Fraud.sol +7 -32
  69. package/contracts/bridge/MovingFunds.sol +18 -51
  70. package/contracts/bridge/Redemption.sol +95 -76
  71. package/contracts/bridge/Wallets.sol +309 -143
  72. package/export.json +54 -54
  73. package/package.json +2 -2
@@ -424,16 +424,14 @@ library Redemption {
424
424
  );
425
425
 
426
426
  // Validate if redeemer output script is a correct standard type
427
- // (P2PKH, P2WPKH, P2SH or P2WSH). This is done by building a stub
428
- // output with 0 as value and using `BTCUtils.extractHash` on it. Such
429
- // a function extracts the payload properly only from standard outputs
430
- // so if it succeeds, we have a guarantee the redeemer output script
431
- // is proper. Worth to note `extractHash` ignores the value at all
432
- // so this is why we can use 0 safely. This way of validation is the
433
- // same as in tBTC v1.
434
- bytes memory redeemerOutputScriptPayload = abi
435
- .encodePacked(bytes8(0), redeemerOutputScript)
436
- .extractHash();
427
+ // (P2PKH, P2WPKH, P2SH or P2WSH). This is done by using
428
+ // `BTCUtils.extractHashAt` on it. Such a function extracts the payload
429
+ // properly only from standard outputs so if it succeeds, we have a
430
+ // guarantee the redeemer output script is proper. The underlying way
431
+ // of validation is the same as in tBTC v1.
432
+ bytes memory redeemerOutputScriptPayload = redeemerOutputScript
433
+ .extractHashAt(0, redeemerOutputScript.length);
434
+
437
435
  require(
438
436
  redeemerOutputScriptPayload.length > 0,
439
437
  "Redeemer output script must be a standard type"
@@ -441,8 +439,8 @@ library Redemption {
441
439
  // Check if the redeemer output script payload does not point to the
442
440
  // wallet public key hash.
443
441
  require(
444
- keccak256(abi.encodePacked(walletPubKeyHash)) !=
445
- keccak256(redeemerOutputScriptPayload),
442
+ redeemerOutputScriptPayload.length != 20 ||
443
+ walletPubKeyHash != redeemerOutputScriptPayload.slice20(0),
446
444
  "Redeemer output script must not point to the wallet PKH"
447
445
  );
448
446
 
@@ -455,8 +453,9 @@ library Redemption {
455
453
  // and redeemer output script pair. That means there can be only one
456
454
  // request asking for redemption from the given wallet to the given
457
455
  // BTC script at the same time.
458
- uint256 redemptionKey = uint256(
459
- keccak256(abi.encodePacked(walletPubKeyHash, redeemerOutputScript))
456
+ uint256 redemptionKey = getRedemptionKey(
457
+ walletPubKeyHash,
458
+ redeemerOutputScript
460
459
  );
461
460
 
462
461
  // Check if given redemption key is not used by a pending redemption.
@@ -497,6 +496,7 @@ library Redemption {
497
496
  uint32(block.timestamp)
498
497
  );
499
498
 
499
+ // slither-disable-next-line reentrancy-events
500
500
  emit RedemptionRequested(
501
501
  walletPubKeyHash,
502
502
  redeemerOutputScript,
@@ -674,7 +674,7 @@ library Redemption {
674
674
  /// must be validated using e.g. `BTCUtils.validateVout` function
675
675
  /// before it is passed here.
676
676
  /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
677
- // HASH160 over the compressed ECDSA public key) of the wallet which
677
+ /// HASH160 over the compressed ECDSA public key) of the wallet which
678
678
  /// performed the redemption transaction.
679
679
  /// @return info Outcomes of the processing.
680
680
  function processRedemptionTxOutputs(
@@ -722,7 +722,7 @@ library Redemption {
722
722
  // which matches the P2PKH structure as per:
723
723
  // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
724
724
  bytes32 walletP2PKHScriptKeccak = keccak256(
725
- abi.encodePacked(hex"1976a914", walletPubKeyHash, hex"88ac")
725
+ abi.encodePacked(BitcoinTx.makeP2PKHScript(walletPubKeyHash))
726
726
  );
727
727
  // The P2WPKH script has the byte format: <0x160014> <20-byte PKH>.
728
728
  // According to https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
@@ -732,7 +732,7 @@ library Redemption {
732
732
  // which matches the P2WPKH structure as per:
733
733
  // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
734
734
  bytes32 walletP2WPKHScriptKeccak = keccak256(
735
- abi.encodePacked(hex"160014", walletPubKeyHash)
735
+ abi.encodePacked(BitcoinTx.makeP2WPKHScript(walletPubKeyHash))
736
736
  );
737
737
 
738
738
  return
@@ -751,7 +751,7 @@ library Redemption {
751
751
 
752
752
  /// @notice Processes all outputs from the redemption transaction. Tries to
753
753
  /// identify output as a change output, pending redemption request
754
- // or reported redemption. Reverts if one of the outputs cannot be
754
+ /// or reported redemption. Reverts if one of the outputs cannot be
755
755
  /// recognized properly. Marks each request as processed by removing
756
756
  /// them from `pendingRedemptions` mapping.
757
757
  /// @param redemptionTxOutputVector Bitcoin redemption transaction output
@@ -759,7 +759,7 @@ library Redemption {
759
759
  /// must be validated using e.g. `BTCUtils.validateVout` function
760
760
  /// before it is passed here.
761
761
  /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
762
- // HASH160 over the compressed ECDSA public key) of the wallet which
762
+ /// HASH160 over the compressed ECDSA public key) of the wallet which
763
763
  /// performed the redemption transaction.
764
764
  /// @param processInfo RedemptionTxOutputsProcessingInfo identifying output
765
765
  /// starting index, the number of outputs and possible wallet change
@@ -777,30 +777,38 @@ library Redemption {
777
777
 
778
778
  // Outputs processing loop.
779
779
  for (uint256 i = 0; i < processInfo.outputsCount; i++) {
780
- // TODO: Check if we can optimize gas costs by adding
781
- // `extractValueAt` and `extractHashAt` in `bitcoin-spv-sol`
782
- // in order to avoid allocating bytes in memory.
783
- // https://github.com/keep-network/tbtc-v2/issues/257
784
780
  uint256 outputLength = redemptionTxOutputVector
785
781
  .determineOutputLengthAt(processInfo.outputStartingIndex);
786
- bytes memory output = redemptionTxOutputVector.slice(
787
- processInfo.outputStartingIndex,
788
- outputLength
789
- );
790
782
 
791
783
  // Extract the value from given output.
792
- uint64 outputValue = output.extractValue();
784
+ uint64 outputValue = redemptionTxOutputVector.extractValueAt(
785
+ processInfo.outputStartingIndex
786
+ );
787
+
793
788
  // The output consists of an 8-byte value and a variable length
794
- // script. To extract that script we slice the output starting from
789
+ // script. To hash that script we slice the output starting from
795
790
  // 9th byte until the end.
796
- bytes memory outputScript = output.slice(8, output.length - 8);
791
+ uint256 scriptLength = outputLength - 8;
792
+ uint256 outputScriptStart = processInfo.outputStartingIndex + 8;
793
+
794
+ bytes32 outputScriptHash;
795
+ /* solhint-disable-next-line no-inline-assembly */
796
+ assembly {
797
+ // The first argument to assembly keccak256 is the pointer.
798
+ // We point to `redemptionTxOutputVector` but at the position
799
+ // indicated by `outputScriptStart`. To load that position, we
800
+ // need to call `add(outputScriptStart, 32)` because
801
+ // `outputScriptStart` has 32 bytes.
802
+ outputScriptHash := keccak256(
803
+ add(redemptionTxOutputVector, add(outputScriptStart, 32)),
804
+ scriptLength
805
+ )
806
+ }
797
807
 
798
808
  if (
799
809
  resultInfo.changeValue == 0 &&
800
- (keccak256(outputScript) ==
801
- processInfo.walletP2PKHScriptKeccak ||
802
- keccak256(outputScript) ==
803
- processInfo.walletP2WPKHScriptKeccak) &&
810
+ (outputScriptHash == processInfo.walletP2PKHScriptKeccak ||
811
+ outputScriptHash == processInfo.walletP2WPKHScriptKeccak) &&
804
812
  outputValue > 0
805
813
  ) {
806
814
  // If we entered here, that means the change output with a
@@ -815,8 +823,7 @@ library Redemption {
815
823
  uint64 treasuryFee
816
824
  ) = processNonChangeRedemptionTxOutput(
817
825
  self,
818
- walletPubKeyHash,
819
- outputScript,
826
+ _getRedemptionKey(walletPubKeyHash, outputScriptHash),
820
827
  outputValue
821
828
  );
822
829
  resultInfo.totalBurnableValue += burnableValue;
@@ -847,10 +854,7 @@ library Redemption {
847
854
  /// requested and reported timed-out redemption.
848
855
  /// This function also marks each pending request as processed by
849
856
  /// removing them from `pendingRedemptions` mapping.
850
- /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
851
- // HASH160 over the compressed ECDSA public key) of the wallet which
852
- /// performed the redemption transaction.
853
- /// @param outputScript Non-change output script to be processed.
857
+ /// @param redemptionKey Redemption key of the output being processed.
854
858
  /// @param outputValue Value of the output being processed.
855
859
  /// @return burnableValue The value burnable as a result of processing this
856
860
  /// single redemption output. This value needs to be summed up with
@@ -862,19 +866,14 @@ library Redemption {
862
866
  /// outputs to evaluate the total treasury fee for the entire
863
867
  /// redemption transaction. This value is 0 for a timed-out
864
868
  /// redemption request.
869
+ /// @dev Requirements:
870
+ /// - This function should be called only if the given output
871
+ /// represents redemption. It must not be the change output.
865
872
  function processNonChangeRedemptionTxOutput(
866
873
  BridgeState.Storage storage self,
867
- bytes20 walletPubKeyHash,
868
- bytes memory outputScript,
874
+ uint256 redemptionKey,
869
875
  uint64 outputValue
870
876
  ) internal returns (uint64 burnableValue, uint64 treasuryFee) {
871
- // This function should be called only if the given output is
872
- // supposed to represent a redemption. Build the redemption key
873
- // to perform that check.
874
- uint256 redemptionKey = uint256(
875
- keccak256(abi.encodePacked(walletPubKeyHash, outputScript))
876
- );
877
-
878
877
  if (self.pendingRedemptions[redemptionKey].requestedAt != 0) {
879
878
  // If we entered here, that means the output was identified
880
879
  // as a pending redemption request.
@@ -936,7 +935,7 @@ library Redemption {
936
935
  /// @notice Notifies that there is a pending redemption request associated
937
936
  /// with the given wallet, that has timed out. The redemption
938
937
  /// request is identified by the key built as
939
- /// `keccak256(walletPubKeyHash | redeemerOutputScript)`.
938
+ /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.
940
939
  /// The results of calling this function:
941
940
  /// - the pending redemptions value for the wallet will be decreased
942
941
  /// by the requested amount (minus treasury fee),
@@ -974,8 +973,10 @@ library Redemption {
974
973
  uint32[] calldata walletMembersIDs,
975
974
  bytes calldata redeemerOutputScript
976
975
  ) external {
977
- uint256 redemptionKey = uint256(
978
- keccak256(abi.encodePacked(walletPubKeyHash, redeemerOutputScript))
976
+ // Wallet state is validated in `notifyWalletRedemptionTimeout`.
977
+ uint256 redemptionKey = getRedemptionKey(
978
+ walletPubKeyHash,
979
+ redeemerOutputScript
979
980
  );
980
981
  Redemption.RedemptionRequest memory request = self.pendingRedemptions[
981
982
  redemptionKey
@@ -996,44 +997,62 @@ library Redemption {
996
997
  request.requestedAmount -
997
998
  request.treasuryFee;
998
999
 
999
- require(
1000
- wallet.state == Wallets.WalletState.Live ||
1001
- wallet.state == Wallets.WalletState.MovingFunds ||
1002
- wallet.state == Wallets.WalletState.Terminated,
1003
- "Wallet must be in Live, MovingFunds or Terminated state"
1004
- );
1005
-
1006
1000
  // It is worth noting that there is no need to check if
1007
1001
  // `timedOutRedemption` mapping already contains the given redemption
1008
1002
  // key. There is no possibility to re-use a key of a reported timed-out
1009
1003
  // redemption because the wallet responsible for causing the timeout is
1010
1004
  // moved to a state that prevents it to receive new redemption requests.
1011
1005
 
1006
+ // Propagate timeout consequences to the wallet
1007
+ self.notifyWalletRedemptionTimeout(walletPubKeyHash, walletMembersIDs);
1008
+
1012
1009
  // Move the redemption from pending redemptions to timed-out redemptions
1013
1010
  self.timedOutRedemptions[redemptionKey] = request;
1014
1011
  delete self.pendingRedemptions[redemptionKey];
1015
1012
 
1016
- if (
1017
- wallet.state == Wallets.WalletState.Live ||
1018
- wallet.state == Wallets.WalletState.MovingFunds
1019
- ) {
1020
- // Slash the wallet operators and reward the notifier
1021
- self.ecdsaWalletRegistry.seize(
1022
- self.redemptionTimeoutSlashingAmount,
1023
- self.redemptionTimeoutNotifierRewardMultiplier,
1024
- msg.sender,
1025
- wallet.ecdsaWalletID,
1026
- walletMembersIDs
1027
- );
1028
-
1029
- // Propagate timeout consequences to the wallet
1030
- self.notifyWalletTimedOutRedemption(walletPubKeyHash);
1031
- }
1032
-
1033
1013
  // slither-disable-next-line reentrancy-events
1034
1014
  emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);
1035
1015
 
1036
1016
  // Return the requested amount of tokens to the redeemer
1037
1017
  self.bank.transferBalance(request.redeemer, request.requestedAmount);
1038
1018
  }
1019
+
1020
+ /// @notice Calculate redemption key without allocations.
1021
+ /// @param walletPubKeyHash the pubkey hash of the wallet.
1022
+ /// @param script the output script of the redemption.
1023
+ /// @return The key = keccak256(keccak256(script) | walletPubKeyHash).
1024
+ function getRedemptionKey(bytes20 walletPubKeyHash, bytes memory script)
1025
+ internal
1026
+ pure
1027
+ returns (uint256)
1028
+ {
1029
+ bytes32 scriptHash = keccak256(script);
1030
+ uint256 key;
1031
+ /* solhint-disable-next-line no-inline-assembly */
1032
+ assembly {
1033
+ mstore(0, scriptHash)
1034
+ mstore(32, walletPubKeyHash)
1035
+ key := keccak256(0, 52)
1036
+ }
1037
+ return key;
1038
+ }
1039
+
1040
+ /// @notice Finish calculating redemption key without allocations.
1041
+ /// @param walletPubKeyHash the pubkey hash of the wallet.
1042
+ /// @param scriptHash the output script hash of the redemption.
1043
+ /// @return The key = keccak256(scriptHash | walletPubKeyHash).
1044
+ function _getRedemptionKey(bytes20 walletPubKeyHash, bytes32 scriptHash)
1045
+ internal
1046
+ pure
1047
+ returns (uint256)
1048
+ {
1049
+ uint256 key;
1050
+ /* solhint-disable-next-line no-inline-assembly */
1051
+ assembly {
1052
+ mstore(0, scriptHash)
1053
+ mstore(32, walletPubKeyHash)
1054
+ key := keccak256(0, 52)
1055
+ }
1056
+ return key;
1057
+ }
1039
1058
  }