MultiSignerERC7913Weighted.sol 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.27;
  3. import {SafeCast} from "../../math/SafeCast.sol";
  4. import {MultiSignerERC7913} from "./MultiSignerERC7913.sol";
  5. /**
  6. * @dev Extension of {MultiSignerERC7913} that supports weighted signatures.
  7. *
  8. * This contract allows assigning different weights to each signer, enabling more
  9. * flexible governance schemes. For example, some signers could have higher weight
  10. * than others, allowing for weighted voting or prioritized authorization.
  11. *
  12. * Example of usage:
  13. *
  14. * ```solidity
  15. * contract MyWeightedMultiSignerAccount is Account, MultiSignerERC7913Weighted, Initializable {
  16. * function initialize(bytes[] memory signers, uint64[] memory weights, uint64 threshold) public initializer {
  17. * _addSigners(signers);
  18. * _setSignerWeights(signers, weights);
  19. * _setThreshold(threshold);
  20. * }
  21. *
  22. * function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
  23. * _addSigners(signers);
  24. * }
  25. *
  26. * function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
  27. * _removeSigners(signers);
  28. * }
  29. *
  30. * function setThreshold(uint64 threshold) public onlyEntryPointOrSelf {
  31. * _setThreshold(threshold);
  32. * }
  33. *
  34. * function setSignerWeights(bytes[] memory signers, uint64[] memory weights) public onlyEntryPointOrSelf {
  35. * _setSignerWeights(signers, weights);
  36. * }
  37. * }
  38. * ```
  39. *
  40. * IMPORTANT: When setting a threshold value, ensure it matches the scale used for signer weights.
  41. * For example, if signers have weights like 1, 2, or 3, then a threshold of 4 would require at
  42. * least two signers (e.g., one with weight 1 and one with weight 3). See {signerWeight}.
  43. */
  44. abstract contract MultiSignerERC7913Weighted is MultiSignerERC7913 {
  45. using SafeCast for *;
  46. // Sum of all the extra weights of all signers. Storage packed with `MultiSignerERC7913._threshold`
  47. uint64 private _totalExtraWeight;
  48. // Mapping from signer to extraWeight (in addition to all authorized signers having weight 1)
  49. mapping(bytes signer => uint64) private _extraWeights;
  50. /**
  51. * @dev Emitted when a signer's weight is changed.
  52. *
  53. * NOTE: Not emitted in {_addSigners} or {_removeSigners}. Indexers must rely on {ERC7913SignerAdded}
  54. * and {ERC7913SignerRemoved} to index a default weight of 1. See {signerWeight}.
  55. */
  56. event ERC7913SignerWeightChanged(bytes indexed signer, uint64 weight);
  57. /// @dev Thrown when a signer's weight is invalid.
  58. error MultiSignerERC7913WeightedInvalidWeight(bytes signer, uint64 weight);
  59. /// @dev Thrown when the arrays lengths don't match. See {_setSignerWeights}.
  60. error MultiSignerERC7913WeightedMismatchedLength();
  61. constructor(bytes[] memory signers_, uint64[] memory weights_, uint64 threshold_) MultiSignerERC7913(signers_, 1) {
  62. _setSignerWeights(signers_, weights_);
  63. _setThreshold(threshold_);
  64. }
  65. /// @dev Gets the weight of a signer. Returns 0 if the signer is not authorized.
  66. function signerWeight(bytes memory signer) public view virtual returns (uint64) {
  67. unchecked {
  68. // Safe cast, _setSignerWeights guarantees 1+_extraWeights is a uint64
  69. return uint64(isSigner(signer).toUint() * (1 + _extraWeights[signer]));
  70. }
  71. }
  72. /// @dev Gets the total weight of all signers.
  73. function totalWeight() public view virtual returns (uint64) {
  74. return (getSignerCount() + _totalExtraWeight).toUint64();
  75. }
  76. /**
  77. * @dev Sets weights for multiple signers at once. Internal version without access control.
  78. *
  79. * Requirements:
  80. *
  81. * * `signers` and `weights` arrays must have the same length. Reverts with {MultiSignerERC7913WeightedMismatchedLength} on mismatch.
  82. * * Each signer must exist in the set of authorized signers. Otherwise reverts with {MultiSignerERC7913NonexistentSigner}
  83. * * Each weight must be greater than 0. Otherwise reverts with {MultiSignerERC7913WeightedInvalidWeight}
  84. * * See {_validateReachableThreshold} for the threshold validation.
  85. *
  86. * Emits {ERC7913SignerWeightChanged} for each signer.
  87. */
  88. function _setSignerWeights(bytes[] memory signers, uint64[] memory weights) internal virtual {
  89. require(signers.length == weights.length, MultiSignerERC7913WeightedMismatchedLength());
  90. uint256 extraWeightAdded = 0;
  91. uint256 extraWeightRemoved = 0;
  92. for (uint256 i = 0; i < signers.length; ++i) {
  93. bytes memory signer = signers[i];
  94. uint64 weight = weights[i];
  95. require(isSigner(signer), MultiSignerERC7913NonexistentSigner(signer));
  96. require(weight > 0, MultiSignerERC7913WeightedInvalidWeight(signer, weight));
  97. unchecked {
  98. // Overflow impossible: weight values are bounded by uint64 and economic constraints
  99. extraWeightRemoved += _extraWeights[signer];
  100. extraWeightAdded += _extraWeights[signer] = weight - 1;
  101. }
  102. emit ERC7913SignerWeightChanged(signer, weight);
  103. }
  104. unchecked {
  105. // Safe from underflow: `extraWeightRemoved` is bounded by `_totalExtraWeight` by construction
  106. // and weight values are bounded by uint64 and economic constraints
  107. _totalExtraWeight = (uint256(_totalExtraWeight) + extraWeightAdded - extraWeightRemoved).toUint64();
  108. }
  109. _validateReachableThreshold();
  110. }
  111. /**
  112. * @dev See {MultiSignerERC7913-_removeSigners}.
  113. *
  114. * Just like {_addSigners}, this function does not emit {ERC7913SignerWeightChanged} events. The
  115. * {ERC7913SignerRemoved} event emitted by {MultiSignerERC7913-_removeSigners} is enough to track weights here.
  116. */
  117. function _removeSigners(bytes[] memory signers) internal virtual override {
  118. // Clean up weights for removed signers
  119. //
  120. // The `extraWeightRemoved` is bounded by `_totalExtraWeight`. The `super._removeSigners` function will revert
  121. // if the signers array contains any duplicates, ensuring each signer's weight is only counted once. Since
  122. // `_totalExtraWeight` is stored as a `uint64`, the final subtraction operation is also safe.
  123. unchecked {
  124. uint64 extraWeightRemoved = 0;
  125. for (uint256 i = 0; i < signers.length; ++i) {
  126. bytes memory signer = signers[i];
  127. extraWeightRemoved += _extraWeights[signer];
  128. delete _extraWeights[signer];
  129. }
  130. _totalExtraWeight -= extraWeightRemoved;
  131. }
  132. super._removeSigners(signers);
  133. }
  134. /**
  135. * @dev Sets the threshold for the multisignature operation. Internal version without access control.
  136. *
  137. * Requirements:
  138. *
  139. * * The {totalWeight} must be `>=` the {threshold}. Otherwise reverts with {MultiSignerERC7913UnreachableThreshold}
  140. *
  141. * NOTE: This function intentionally does not call `super._validateReachableThreshold` because the base implementation
  142. * assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple
  143. * implementations of this function may exist in the contract, so important side effects may be missed
  144. * depending on the linearization order.
  145. */
  146. function _validateReachableThreshold() internal view virtual override {
  147. uint64 weight = totalWeight();
  148. uint64 currentThreshold = threshold();
  149. require(weight >= currentThreshold, MultiSignerERC7913UnreachableThreshold(weight, currentThreshold));
  150. }
  151. /**
  152. * @dev Validates that the total weight of signers meets the threshold requirement.
  153. *
  154. * NOTE: This function intentionally does not call `super._validateThreshold` because the base implementation
  155. * assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple
  156. * implementations of this function may exist in the contract, so important side effects may be missed
  157. * depending on the linearization order.
  158. */
  159. function _validateThreshold(bytes[] memory signers) internal view virtual override returns (bool) {
  160. unchecked {
  161. uint64 weight = 0;
  162. for (uint256 i = 0; i < signers.length; ++i) {
  163. // Overflow impossible: weight values are bounded by uint64 and economic constraints
  164. weight += signerWeight(signers[i]);
  165. }
  166. return weight >= threshold();
  167. }
  168. }
  169. }