-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Add MultiSignerERC7913Weighted #5718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Amxx
merged 130 commits into
OpenZeppelin:master
from
ernestognw:feature/multisig-weighted
Jun 12, 2025
Merged
Changes from all commits
Commits
Show all changes
130 commits
Select commit
Hold shift + click to select a range
f5544fd
Add Account framework
ernestognw 40dfd20
Add missing mocks
ernestognw 0bcc521
Adding missing hardhat config
ernestognw 7e75026
up
ernestognw 5fb074c
Remove unnecessary files for mocks
ernestognw e5df541
Remove more unnecessary mock files
ernestognw 5ad9788
replace hardcoded links
ernestognw 7090f67
lockfile
ernestognw c4af1d7
update ethers
ernestognw aa8f29b
add missing interface
ernestognw 415c00d
Add changesets
ernestognw f60aa3a
up
ernestognw 087a844
up
ernestognw 79629b7
up
ernestognw f47cab7
up
ernestognw 8f58197
up
ernestognw ecede7f
up
ernestognw 68bd96a
up
ernestognw d5cb119
chore: empty commit
ernestognw 6a0ae8a
change read permissions
ernestognw a49a157
Update lucky-donuts-scream.md
ernestognw a634278
Update clean-ways-push.md
ernestognw 8b6501a
Update tame-bears-mix.md
ernestognw 39a1026
reset package-lock.json
ernestognw 71a6b25
up
ernestognw 35d4a12
up
ernestognw a95705b
reset dependencies
ernestognw 90509bd
reset dependencies
ernestognw 10f40d7
reset dependencies
ernestognw 36fb044
lint
ernestognw 7e10f80
Attempt to fix tests
ernestognw c6ed868
up
ernestognw b87c8e2
Merge branch 'master' into feature/account-abstraction
ernestognw f6d07c2
adjust action.yml
ernestognw cfa2392
up
ernestognw 9a8e63f
Merge branch 'master' into feature/account-abstraction
ernestognw 3a90091
lint
ernestognw bdec803
lint
ernestognw 73c12c7
Merge branch 'master' into feature/account-abstraction
ernestognw 1c97739
Test ethers 6.13.6-beta.1
ernestognw 6b1bbd8
up
ernestognw 7764515
up
ernestognw 19fe4c5
up
ernestognw 11c42c3
checks
ernestognw be68753
up
ernestognw f0a1155
build in slither
ernestognw c42a7fd
Update build command
ernestognw 6e576ca
compile hardhat too
ernestognw 593e879
revert slither changes
ernestognw c3f39a1
Remove package-lock.json to skip installing dependencies
ernestognw db76c3b
up
ernestognw 8eebff0
Add @custom:stateless tag
ernestognw 65fa7de
update upgradeable.patch
ernestognw abac3bd
fix conflicts
ernestognw 7d120b9
rollback <package-version>
ernestognw 02eccc1
update upgradeable.patch
ernestognw c39d5f5
Tweak workflows
ernestognw 54f632a
Use Solidity 0.8.27 as default and set default EVM to prague
ernestognw 58c794e
Adjust ERC2771Forwarder gas to avoid GasFloorMoreThanGasLimit
ernestognw 6a60523
Remove console.log
ernestognw 80edba8
Add EnumerableSetExtended and EnumerableMapExtended
ernestognw 29c48d9
Fix lint and enable formatting after generation
ernestognw fae0a67
Add ERC7913 signers and utilities
ernestognw e412bd9
Add EnumerableSetExtended and EnumerableMapExtended
ernestognw f7f64ee
Add changeset and fix linting
ernestognw b0b70eb
Merge branch 'master' into feature/enumerable-extended
ernestognw b695659
Remove TODOs
ernestognw 5c4fb88
Reset package-lock
ernestognw 8f32638
Revert run.js
ernestognw b12ca61
Merge Enumerable{Set,Map}Extended into Enumerable{Set,Map}
Amxx 45fa35f
Update scripts/generate/templates/Enumerable.opts.js
Amxx bec8059
clarification
Amxx 0d7f9d0
speedup generation with selective linter
Amxx 2376a45
update documentation
Amxx 1e35eab
Merge master
ernestognw c275c03
Remove unnecesary code
ernestognw 5975d79
Remove Bytes32x2
ernestognw 9356599
Update .changeset/pink-dolls-shop.md
ernestognw 86c2cb8
Remove unnecessary _hashes
ernestognw 6898230
Simplify
ernestognw 4e2bc70
Improve changesets
ernestognw 2a1f503
remove unecessary import
Amxx 326c466
Use Arrays.sol
ernestognw 52ac908
Merge branch 'feature/enumerable-extended' into feature/erc7913
ernestognw 6725618
up
ernestognw 0cce4d5
up
ernestognw 5c45ff0
Merge branch 'master' into feature/erc7913
Amxx 80d8bd5
Merge branch 'master' into feature/erc7913
ernestognw 30f3bfa
cleanup
ernestognw eab64f8
Remove EnumerableSetExtended usage
ernestognw e3dcc2b
Add tests
ernestognw 3ac675d
Add changesets
ernestognw b20b017
Merge branch 'master' into feature/erc7913
ernestognw 804ceea
Organize
ernestognw d0f5961
Review
ernestognw 98108d9
Increase SignatureChecker's pragma
ernestognw e1ffe05
Pragma consistency
ernestognw 9a32135
Increase SignatureChecker minimum pragma to 0.8.24
ernestognw d8e132a
Update mocks
ernestognw f2406ca
Merge branch 'master' into feature/erc7913
Amxx 2dd9f27
paginated getSigner()
Amxx 77587ad
simplify mock
Amxx b7ddad9
minor signer helper refactor
Amxx ea7ffc3
simplify helper
Amxx 2f3f054
Merge branch 'master' into feature/erc7913
ernestognw 6619a53
Add fallback to check uniqueness if signers are not ordered (#56)
Amxx cefe101
Update contracts/utils/cryptography/SignatureChecker.sol
Amxx b1753d1
Fix tests
ernestognw cd2399b
Remove unnecessary mock
ernestognw 0b1b8eb
Docs nits
ernestognw 6ecd5d7
Use uint64 as threshold
ernestognw f6ea7dd
Use uint64 as threshold
ernestognw 0c233e0
Add totalSigners function
ernestognw 015d857
Add totalSigners function
ernestognw fd94425
Remove cached totalWeight
ernestognw 79e9879
Merge branch 'master' into feature/multisig-weighted
ernestognw a23ea6a
up
ernestognw 08fb689
Update multisig natspec docs
ernestognw f66dd98
remove unused import
Amxx 126f9c2
use an extraWeigth mapping
Amxx 357aece
Update MultiSignerERC7913Weighted.sol
Amxx 7b2661a
up
ernestognw 18a197a
partially fix tests?
ernestognw e70e79e
Revert commits
Amxx 438af46
remove unecessary event
Amxx 9214c55
fix tests
ernestognw 0a20256
Merge branch 'master' into feature/multisig-weighted
ernestognw f470216
Merge remote-tracking branch 'ernestognw/feature/multisig-weighted' i…
Amxx 61bff1f
docs nits
ernestognw cd310ac
Apply suggestions from code review
Amxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-solidity': minor | ||
| --- | ||
|
|
||
| `MultiSignerERC7913Weighted`: Extension of `MultiSignerERC7913` that supports assigning different weights to each signer, enabling more flexible governance schemes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.27; | ||
|
|
||
| import {SafeCast} from "../../math/SafeCast.sol"; | ||
| import {MultiSignerERC7913} from "./MultiSignerERC7913.sol"; | ||
|
|
||
| /** | ||
| * @dev Extension of {MultiSignerERC7913} that supports weighted signatures. | ||
| * | ||
| * This contract allows assigning different weights to each signer, enabling more | ||
| * flexible governance schemes. For example, some signers could have higher weight | ||
| * than others, allowing for weighted voting or prioritized authorization. | ||
| * | ||
| * Example of usage: | ||
| * | ||
| * ```solidity | ||
| * contract MyWeightedMultiSignerAccount is Account, MultiSignerERC7913Weighted, Initializable { | ||
| * function initialize(bytes[] memory signers, uint64[] memory weights, uint64 threshold) public initializer { | ||
| * _addSigners(signers); | ||
| * _setSignerWeights(signers, weights); | ||
| * _setThreshold(threshold); | ||
| * } | ||
| * | ||
| * function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf { | ||
| * _addSigners(signers); | ||
| * } | ||
| * | ||
| * function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf { | ||
| * _removeSigners(signers); | ||
| * } | ||
| * | ||
| * function setThreshold(uint64 threshold) public onlyEntryPointOrSelf { | ||
| * _setThreshold(threshold); | ||
| * } | ||
| * | ||
| * function setSignerWeights(bytes[] memory signers, uint64[] memory weights) public onlyEntryPointOrSelf { | ||
| * _setSignerWeights(signers, weights); | ||
| * } | ||
| * } | ||
| * ``` | ||
| * | ||
| * IMPORTANT: When setting a threshold value, ensure it matches the scale used for signer weights. | ||
| * For example, if signers have weights like 1, 2, or 3, then a threshold of 4 would require at | ||
| * least two signers (e.g., one with weight 1 and one with weight 3). See {signerWeight}. | ||
| */ | ||
| abstract contract MultiSignerERC7913Weighted is MultiSignerERC7913 { | ||
| using SafeCast for *; | ||
|
|
||
| // Sum of all the extra weights of all signers. Storage packed with `MultiSignerERC7913._threshold` | ||
| uint64 private _totalExtraWeight; | ||
|
|
||
| // Mapping from signer to extraWeight (in addition to all authorized signers having weight 1) | ||
| mapping(bytes signer => uint64) private _extraWeights; | ||
|
|
||
| /** | ||
| * @dev Emitted when a signer's weight is changed. | ||
| * | ||
| * NOTE: Not emitted in {_addSigners} or {_removeSigners}. Indexers must rely on {ERC7913SignerAdded} | ||
| * and {ERC7913SignerRemoved} to index a default weight of 1. See {signerWeight}. | ||
| */ | ||
| event ERC7913SignerWeightChanged(bytes indexed signer, uint64 weight); | ||
|
|
||
| /// @dev Thrown when a signer's weight is invalid. | ||
| error MultiSignerERC7913WeightedInvalidWeight(bytes signer, uint64 weight); | ||
|
|
||
| /// @dev Thrown when the arrays lengths don't match. See {_setSignerWeights}. | ||
| error MultiSignerERC7913WeightedMismatchedLength(); | ||
|
|
||
| /// @dev Gets the weight of a signer. Returns 0 if the signer is not authorized. | ||
| function signerWeight(bytes memory signer) public view virtual returns (uint64) { | ||
| unchecked { | ||
| // Safe cast, _setSignerWeights guarantees 1+_extraWeights is a uint64 | ||
| return uint64(isSigner(signer).toUint() * (1 + _extraWeights[signer])); | ||
| } | ||
| } | ||
|
|
||
| /// @dev Gets the total weight of all signers. | ||
| function totalWeight() public view virtual returns (uint64) { | ||
| return (getSignerCount() + _totalExtraWeight).toUint64(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Sets weights for multiple signers at once. Internal version without access control. | ||
| * | ||
| * Requirements: | ||
| * | ||
| * * `signers` and `weights` arrays must have the same length. Reverts with {MultiSignerERC7913WeightedMismatchedLength} on mismatch. | ||
| * * Each signer must exist in the set of authorized signers. Otherwise reverts with {MultiSignerERC7913NonexistentSigner} | ||
| * * Each weight must be greater than 0. Otherwise reverts with {MultiSignerERC7913WeightedInvalidWeight} | ||
| * * See {_validateReachableThreshold} for the threshold validation. | ||
| * | ||
| * Emits {ERC7913SignerWeightChanged} for each signer. | ||
| */ | ||
| function _setSignerWeights(bytes[] memory signers, uint64[] memory weights) internal virtual { | ||
| require(signers.length == weights.length, MultiSignerERC7913WeightedMismatchedLength()); | ||
|
|
||
| uint256 extraWeightAdded = 0; | ||
| uint256 extraWeightRemoved = 0; | ||
| for (uint256 i = 0; i < signers.length; ++i) { | ||
| bytes memory signer = signers[i]; | ||
| uint64 weight = weights[i]; | ||
|
|
||
| require(isSigner(signer), MultiSignerERC7913NonexistentSigner(signer)); | ||
| require(weight > 0, MultiSignerERC7913WeightedInvalidWeight(signer, weight)); | ||
|
|
||
| unchecked { | ||
| // Overflow impossible: weight values are bounded by uint64 and economic constraints | ||
| extraWeightRemoved += _extraWeights[signer]; | ||
| extraWeightAdded += _extraWeights[signer] = weight - 1; | ||
| } | ||
|
|
||
| emit ERC7913SignerWeightChanged(signer, weight); | ||
| } | ||
| unchecked { | ||
| // Safe from underflow: `extraWeightRemoved` is bounded by `_totalExtraWeight` by construction | ||
| // and weight values are bounded by uint64 and economic constraints | ||
| _totalExtraWeight = (uint256(_totalExtraWeight) + extraWeightAdded - extraWeightRemoved).toUint64(); | ||
| } | ||
| _validateReachableThreshold(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev See {MultiSignerERC7913-_removeSigners}. | ||
| * | ||
| * Just like {_addSigners}, this function does not emit {ERC7913SignerWeightChanged} events. The | ||
| * {ERC7913SignerRemoved} event emitted by {MultiSignerERC7913-_removeSigners} is enough to track weights here. | ||
| */ | ||
| function _removeSigners(bytes[] memory signers) internal virtual override { | ||
| // Clean up weights for removed signers | ||
| // | ||
| // The `extraWeightRemoved` is bounded by `_totalExtraWeight`. The `super._removeSigners` function will revert | ||
| // if the signers array contains any duplicates, ensuring each signer's weight is only counted once. Since | ||
| // `_totalExtraWeight` is stored as a `uint64`, the final subtraction operation is also safe. | ||
| unchecked { | ||
| uint64 extraWeightRemoved = 0; | ||
| for (uint256 i = 0; i < signers.length; ++i) { | ||
| bytes memory signer = signers[i]; | ||
|
|
||
| extraWeightRemoved += _extraWeights[signer]; | ||
| delete _extraWeights[signer]; | ||
| } | ||
| _totalExtraWeight -= extraWeightRemoved; | ||
| } | ||
| super._removeSigners(signers); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Sets the threshold for the multisignature operation. Internal version without access control. | ||
| * | ||
| * Requirements: | ||
| * | ||
| * * The {totalWeight} must be `>=` the {threshold}. Otherwise reverts with {MultiSignerERC7913UnreachableThreshold} | ||
| * | ||
| * NOTE: This function intentionally does not call `super._validateReachableThreshold` because the base implementation | ||
| * assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple | ||
| * implementations of this function may exist in the contract, so important side effects may be missed | ||
| * depending on the linearization order. | ||
| */ | ||
| function _validateReachableThreshold() internal view virtual override { | ||
| uint64 weight = totalWeight(); | ||
| uint64 currentThreshold = threshold(); | ||
| require(weight >= currentThreshold, MultiSignerERC7913UnreachableThreshold(weight, currentThreshold)); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Validates that the total weight of signers meets the threshold requirement. | ||
| * | ||
| * NOTE: This function intentionally does not call `super._validateThreshold` because the base implementation | ||
| * assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple | ||
| * implementations of this function may exist in the contract, so important side effects may be missed | ||
| * depending on the linearization order. | ||
| */ | ||
| function _validateThreshold(bytes[] memory signers) internal view virtual override returns (bool) { | ||
| unchecked { | ||
| uint64 weight = 0; | ||
| for (uint256 i = 0; i < signers.length; ++i) { | ||
| // Overflow impossible: weight values are bounded by uint64 and economic constraints | ||
| weight += signerWeight(signers[i]); | ||
| } | ||
| return weight >= threshold(); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.