Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
f5544fd
Add Account framework
ernestognw May 2, 2025
40dfd20
Add missing mocks
ernestognw May 2, 2025
0bcc521
Adding missing hardhat config
ernestognw May 2, 2025
7e75026
up
ernestognw May 2, 2025
5fb074c
Remove unnecessary files for mocks
ernestognw May 2, 2025
e5df541
Remove more unnecessary mock files
ernestognw May 2, 2025
5ad9788
replace hardcoded links
ernestognw May 2, 2025
7090f67
lockfile
ernestognw May 2, 2025
c4af1d7
update ethers
ernestognw May 2, 2025
aa8f29b
add missing interface
ernestognw May 2, 2025
415c00d
Add changesets
ernestognw May 2, 2025
f60aa3a
up
ernestognw May 2, 2025
087a844
up
ernestognw May 2, 2025
79629b7
up
ernestognw May 2, 2025
f47cab7
up
ernestognw May 2, 2025
8f58197
up
ernestognw May 2, 2025
ecede7f
up
ernestognw May 2, 2025
68bd96a
up
ernestognw May 2, 2025
d5cb119
chore: empty commit
ernestognw May 2, 2025
6a0ae8a
change read permissions
ernestognw May 2, 2025
a49a157
Update lucky-donuts-scream.md
ernestognw May 2, 2025
a634278
Update clean-ways-push.md
ernestognw May 2, 2025
8b6501a
Update tame-bears-mix.md
ernestognw May 2, 2025
39a1026
reset package-lock.json
ernestognw May 2, 2025
71a6b25
up
ernestognw May 2, 2025
35d4a12
up
ernestognw May 2, 2025
a95705b
reset dependencies
ernestognw May 2, 2025
90509bd
reset dependencies
ernestognw May 2, 2025
10f40d7
reset dependencies
ernestognw May 2, 2025
36fb044
lint
ernestognw May 2, 2025
7e10f80
Attempt to fix tests
ernestognw May 2, 2025
c6ed868
up
ernestognw May 2, 2025
b87c8e2
Merge branch 'master' into feature/account-abstraction
ernestognw May 2, 2025
f6d07c2
adjust action.yml
ernestognw May 2, 2025
cfa2392
up
ernestognw May 2, 2025
9a8e63f
Merge branch 'master' into feature/account-abstraction
ernestognw May 2, 2025
3a90091
lint
ernestognw May 2, 2025
bdec803
lint
ernestognw May 2, 2025
73c12c7
Merge branch 'master' into feature/account-abstraction
ernestognw May 2, 2025
1c97739
Test ethers 6.13.6-beta.1
ernestognw May 2, 2025
6b1bbd8
up
ernestognw May 2, 2025
7764515
up
ernestognw May 2, 2025
19fe4c5
up
ernestognw May 2, 2025
11c42c3
checks
ernestognw May 2, 2025
be68753
up
ernestognw May 2, 2025
f0a1155
build in slither
ernestognw May 2, 2025
c42a7fd
Update build command
ernestognw May 2, 2025
6e576ca
compile hardhat too
ernestognw May 2, 2025
593e879
revert slither changes
ernestognw May 2, 2025
c3f39a1
Remove package-lock.json to skip installing dependencies
ernestognw May 2, 2025
db76c3b
up
ernestognw May 2, 2025
8eebff0
Add @custom:stateless tag
ernestognw May 3, 2025
65fa7de
update upgradeable.patch
ernestognw May 3, 2025
abac3bd
fix conflicts
ernestognw May 3, 2025
7d120b9
rollback <package-version>
ernestognw May 3, 2025
02eccc1
update upgradeable.patch
ernestognw May 3, 2025
c39d5f5
Tweak workflows
ernestognw May 3, 2025
54f632a
Use Solidity 0.8.27 as default and set default EVM to prague
ernestognw May 3, 2025
58c794e
Adjust ERC2771Forwarder gas to avoid GasFloorMoreThanGasLimit
ernestognw May 3, 2025
6a60523
Remove console.log
ernestognw May 3, 2025
80edba8
Add EnumerableSetExtended and EnumerableMapExtended
ernestognw May 2, 2025
29c48d9
Fix lint and enable formatting after generation
ernestognw May 3, 2025
fae0a67
Add ERC7913 signers and utilities
ernestognw May 2, 2025
e412bd9
Add EnumerableSetExtended and EnumerableMapExtended
ernestognw May 2, 2025
f7f64ee
Add changeset and fix linting
ernestognw May 3, 2025
b0b70eb
Merge branch 'master' into feature/enumerable-extended
ernestognw Jun 2, 2025
b695659
Remove TODOs
ernestognw Jun 2, 2025
5c4fb88
Reset package-lock
ernestognw Jun 2, 2025
8f32638
Revert run.js
ernestognw Jun 2, 2025
b12ca61
Merge Enumerable{Set,Map}Extended into Enumerable{Set,Map}
Amxx Jun 2, 2025
45fa35f
Update scripts/generate/templates/Enumerable.opts.js
Amxx Jun 2, 2025
bec8059
clarification
Amxx Jun 2, 2025
0d7f9d0
speedup generation with selective linter
Amxx Jun 2, 2025
2376a45
update documentation
Amxx Jun 2, 2025
1e35eab
Merge master
ernestognw Jun 2, 2025
c275c03
Remove unnecesary code
ernestognw Jun 2, 2025
5975d79
Remove Bytes32x2
ernestognw Jun 2, 2025
9356599
Update .changeset/pink-dolls-shop.md
ernestognw Jun 2, 2025
86c2cb8
Remove unnecessary _hashes
ernestognw Jun 2, 2025
6898230
Simplify
ernestognw Jun 2, 2025
4e2bc70
Improve changesets
ernestognw Jun 2, 2025
2a1f503
remove unecessary import
Amxx Jun 2, 2025
326c466
Use Arrays.sol
ernestognw Jun 2, 2025
52ac908
Merge branch 'feature/enumerable-extended' into feature/erc7913
ernestognw Jun 3, 2025
6725618
up
ernestognw Jun 3, 2025
0cce4d5
up
ernestognw Jun 3, 2025
5c45ff0
Merge branch 'master' into feature/erc7913
Amxx Jun 3, 2025
80d8bd5
Merge branch 'master' into feature/erc7913
ernestognw Jun 3, 2025
30f3bfa
cleanup
ernestognw Jun 3, 2025
eab64f8
Remove EnumerableSetExtended usage
ernestognw Jun 3, 2025
e3dcc2b
Add tests
ernestognw Jun 3, 2025
3ac675d
Add changesets
ernestognw Jun 3, 2025
b20b017
Merge branch 'master' into feature/erc7913
ernestognw Jun 4, 2025
804ceea
Organize
ernestognw Jun 4, 2025
d0f5961
Review
ernestognw Jun 4, 2025
98108d9
Increase SignatureChecker's pragma
ernestognw Jun 4, 2025
e1ffe05
Pragma consistency
ernestognw Jun 4, 2025
9a32135
Increase SignatureChecker minimum pragma to 0.8.24
ernestognw Jun 4, 2025
d8e132a
Update mocks
ernestognw Jun 4, 2025
f2406ca
Merge branch 'master' into feature/erc7913
Amxx Jun 4, 2025
2dd9f27
paginated getSigner()
Amxx Jun 4, 2025
77587ad
simplify mock
Amxx Jun 4, 2025
b7ddad9
minor signer helper refactor
Amxx Jun 4, 2025
ea7ffc3
simplify helper
Amxx Jun 4, 2025
2f3f054
Merge branch 'master' into feature/erc7913
ernestognw Jun 4, 2025
6619a53
Add fallback to check uniqueness if signers are not ordered (#56)
Amxx Jun 4, 2025
cefe101
Update contracts/utils/cryptography/SignatureChecker.sol
Amxx Jun 4, 2025
b1753d1
Fix tests
ernestognw Jun 4, 2025
cd2399b
Remove unnecessary mock
ernestognw Jun 4, 2025
0b1b8eb
Docs nits
ernestognw Jun 4, 2025
6ecd5d7
Use uint64 as threshold
ernestognw Jun 5, 2025
f6ea7dd
Use uint64 as threshold
ernestognw Jun 5, 2025
0c233e0
Add totalSigners function
ernestognw Jun 5, 2025
015d857
Add totalSigners function
ernestognw Jun 5, 2025
fd94425
Remove cached totalWeight
ernestognw Jun 5, 2025
79e9879
Merge branch 'master' into feature/multisig-weighted
ernestognw Jun 5, 2025
a23ea6a
up
ernestognw Jun 5, 2025
08fb689
Update multisig natspec docs
ernestognw Jun 5, 2025
f66dd98
remove unused import
Amxx Jun 10, 2025
126f9c2
use an extraWeigth mapping
Amxx Jun 10, 2025
357aece
Update MultiSignerERC7913Weighted.sol
Amxx Jun 10, 2025
7b2661a
up
ernestognw Jun 11, 2025
18a197a
partially fix tests?
ernestognw Jun 11, 2025
e70e79e
Revert commits
Amxx Jun 11, 2025
438af46
remove unecessary event
Amxx Jun 11, 2025
9214c55
fix tests
ernestognw Jun 11, 2025
0a20256
Merge branch 'master' into feature/multisig-weighted
ernestognw Jun 11, 2025
f470216
Merge remote-tracking branch 'ernestognw/feature/multisig-weighted' i…
Amxx Jun 11, 2025
61bff1f
docs nits
ernestognw Jun 11, 2025
cd310ac
Apply suggestions from code review
Amxx Jun 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/public-crabs-heal.md
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.
31 changes: 28 additions & 3 deletions contracts/mocks/account/AccountMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {SignerRSA} from "../../utils/cryptography/signers/SignerRSA.sol";
import {SignerERC7702} from "../../utils/cryptography/signers/SignerERC7702.sol";
import {SignerERC7913} from "../../utils/cryptography/signers/SignerERC7913.sol";
import {MultiSignerERC7913} from "../../utils/cryptography/signers/MultiSignerERC7913.sol";
import {MultiSignerERC7913Weighted} from "../../utils/cryptography/signers/MultiSignerERC7913Weighted.sol";

abstract contract AccountMock is Account, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
/// Validates a user operation with a boolean signature.
Expand Down Expand Up @@ -139,6 +140,21 @@ abstract contract AccountERC7579HookedMock is AccountERC7579Hooked {
}
}

abstract contract AccountERC7913Mock is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
constructor(bytes memory _signer) {
_setSigner(_signer);
}

/// @inheritdoc ERC7821
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
}

abstract contract AccountMultiSignerMock is Account, MultiSignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
constructor(bytes[] memory signers, uint64 threshold) {
_addSigners(signers);
Expand All @@ -155,9 +171,18 @@ abstract contract AccountMultiSignerMock is Account, MultiSignerERC7913, ERC7739
}
}

abstract contract AccountERC7913Mock is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
constructor(bytes memory _signer) {
_setSigner(_signer);
abstract contract AccountMultiSignerWeightedMock is
Account,
MultiSignerERC7913Weighted,
ERC7739,
ERC7821,
ERC721Holder,
ERC1155Holder
{
constructor(bytes[] memory signers, uint64[] memory weights, uint64 threshold) {
_addSigners(signers);
_setSignerWeights(signers, weights);
_setThreshold(threshold);
}

/// @inheritdoc ERC7821
Expand Down
4 changes: 3 additions & 1 deletion contracts/utils/cryptography/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A collection of contracts and libraries that implement various signature validat
* {ERC7739}: An abstract contract to validate signatures following the rehashing scheme from {ERC7739Utils}.
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
* {SignerERC7702}: Implementation of {AbstractSigner} that validates signatures using the contract's own address as the signer, useful for delegated accounts following EIP-7702.
* {SignerERC7913}, {MultiSignerERC7913}: Implementations of {AbstractSigner} that validate signatures based on ERC-7913. Including a simple multisignature scheme.
* {SignerERC7913}, {MultiSignerERC7913}, {MultiSignerERC7913Weighted}: Implementations of {AbstractSigner} that validate signatures based on ERC-7913. Including a simple and weighted multisignature scheme.
* {ERC7913P256Verifier}, {ERC7913RSAVerifier}: Ready to use ERC-7913 signature verifiers for P256 and RSA keys.

== Utils
Expand Down Expand Up @@ -58,6 +58,8 @@ A collection of contracts and libraries that implement various signature validat

{{MultiSignerERC7913}}

{{MultiSignerERC7913Weighted}}

== Verifiers

{{ERC7913P256Verifier}}
Expand Down
7 changes: 5 additions & 2 deletions contracts/utils/cryptography/signers/MultiSignerERC7913.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import {EnumerableSet} from "../../structs/EnumerableSet.sol";
*
* ```solidity
* contract MyMultiSignerAccount is Account, MultiSignerERC7913, Initializable {
* constructor() EIP712("MyMultiSignerAccount", "1") {}
*
* function initialize(bytes[] memory signers, uint64 threshold) public initializer {
* _addSigners(signers);
* _setThreshold(threshold);
Expand Down Expand Up @@ -84,6 +82,11 @@ abstract contract MultiSignerERC7913 is AbstractSigner {
return _signers.values(start, end);
}

/// @dev Returns the number of authorized signers
function getSignerCount() public view virtual returns (uint256) {
return _signers.length();
}

/// @dev Returns whether the `signer` is an authorized signer.
function isSigner(bytes memory signer) public view virtual returns (bool) {
return _signers.contains(signer);
Expand Down
184 changes: 184 additions & 0 deletions contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol
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();
}
}
}
Loading