Skip to content

Commit 57367c8

Browse files
authored
Merge branch 'master' into feat/memory-fixed-size-stack
2 parents d63bcab + 0aaa23e commit 57367c8

File tree

4 files changed

+89
-81
lines changed

4 files changed

+89
-81
lines changed

contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT
22

3-
pragma solidity ^0.8.27;
3+
pragma solidity ^0.8.26;
44

55
import {SafeCast} from "../../math/SafeCast.sol";
66
import {MultiSignerERC7913} from "./MultiSignerERC7913.sol";
@@ -104,18 +104,22 @@ abstract contract MultiSignerERC7913Weighted is MultiSignerERC7913 {
104104
uint256 extraWeightRemoved = 0;
105105
for (uint256 i = 0; i < signers.length; ++i) {
106106
bytes memory signer = signers[i];
107-
uint64 weight = weights[i];
108-
109107
require(isSigner(signer), MultiSignerERC7913NonexistentSigner(signer));
108+
109+
uint64 weight = weights[i];
110110
require(weight > 0, MultiSignerERC7913WeightedInvalidWeight(signer, weight));
111111

112112
unchecked {
113-
// Overflow impossible: weight values are bounded by uint64 and economic constraints
114-
extraWeightRemoved += _extraWeights[signer];
115-
extraWeightAdded += _extraWeights[signer] = weight - 1;
113+
uint64 oldExtraWeight = _extraWeights[signer];
114+
uint64 newExtraWeight = weight - 1;
115+
116+
if (oldExtraWeight != newExtraWeight) {
117+
// Overflow impossible: weight values are bounded by uint64 and economic constraints
118+
extraWeightRemoved += oldExtraWeight;
119+
extraWeightAdded += _extraWeights[signer] = newExtraWeight;
120+
emit ERC7913SignerWeightChanged(signer, weight);
121+
}
116122
}
117-
118-
emit ERC7913SignerWeightChanged(signer, weight);
119123
}
120124
unchecked {
121125
// Safe from underflow: `extraWeightRemoved` is bounded by `_totalExtraWeight` by construction

contracts/utils/structs/EnumerableSet.sol

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -519,12 +519,12 @@ library EnumerableSet {
519519
* Returns true if the value was added to the set, that is if it was not
520520
* already present.
521521
*/
522-
function add(StringSet storage self, string memory value) internal returns (bool) {
523-
if (!contains(self, value)) {
524-
self._values.push(value);
522+
function add(StringSet storage set, string memory value) internal returns (bool) {
523+
if (!contains(set, value)) {
524+
set._values.push(value);
525525
// The value is stored at length-1, but we add 1 to all indexes
526526
// and use 0 as a sentinel value
527-
self._positions[value] = self._values.length;
527+
set._positions[value] = set._values.length;
528528
return true;
529529
} else {
530530
return false;
@@ -537,33 +537,33 @@ library EnumerableSet {
537537
* Returns true if the value was removed from the set, that is if it was
538538
* present.
539539
*/
540-
function remove(StringSet storage self, string memory value) internal returns (bool) {
540+
function remove(StringSet storage set, string memory value) internal returns (bool) {
541541
// We cache the value's position to prevent multiple reads from the same storage slot
542-
uint256 position = self._positions[value];
542+
uint256 position = set._positions[value];
543543

544544
if (position != 0) {
545-
// Equivalent to contains(self, value)
545+
// Equivalent to contains(set, value)
546546
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
547547
// the array, and then remove the last element (sometimes called as 'swap and pop').
548548
// This modifies the order of the array, as noted in {at}.
549549

550550
uint256 valueIndex = position - 1;
551-
uint256 lastIndex = self._values.length - 1;
551+
uint256 lastIndex = set._values.length - 1;
552552

553553
if (valueIndex != lastIndex) {
554-
string memory lastValue = self._values[lastIndex];
554+
string memory lastValue = set._values[lastIndex];
555555

556556
// Move the lastValue to the index where the value to delete is
557-
self._values[valueIndex] = lastValue;
557+
set._values[valueIndex] = lastValue;
558558
// Update the tracked position of the lastValue (that was just moved)
559-
self._positions[lastValue] = position;
559+
set._positions[lastValue] = position;
560560
}
561561

562562
// Delete the slot where the moved value was stored
563-
self._values.pop();
563+
set._values.pop();
564564

565565
// Delete the tracked position for the deleted slot
566-
delete self._positions[value];
566+
delete set._positions[value];
567567

568568
return true;
569569
} else {
@@ -588,15 +588,15 @@ library EnumerableSet {
588588
/**
589589
* @dev Returns true if the value is in the set. O(1).
590590
*/
591-
function contains(StringSet storage self, string memory value) internal view returns (bool) {
592-
return self._positions[value] != 0;
591+
function contains(StringSet storage set, string memory value) internal view returns (bool) {
592+
return set._positions[value] != 0;
593593
}
594594

595595
/**
596596
* @dev Returns the number of values on the set. O(1).
597597
*/
598-
function length(StringSet storage self) internal view returns (uint256) {
599-
return self._values.length;
598+
function length(StringSet storage set) internal view returns (uint256) {
599+
return set._values.length;
600600
}
601601

602602
/**
@@ -609,8 +609,8 @@ library EnumerableSet {
609609
*
610610
* - `index` must be strictly less than {length}.
611611
*/
612-
function at(StringSet storage self, uint256 index) internal view returns (string memory) {
613-
return self._values[index];
612+
function at(StringSet storage set, uint256 index) internal view returns (string memory) {
613+
return set._values[index];
614614
}
615615

616616
/**
@@ -621,8 +621,8 @@ library EnumerableSet {
621621
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
622622
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
623623
*/
624-
function values(StringSet storage self) internal view returns (string[] memory) {
625-
return self._values;
624+
function values(StringSet storage set) internal view returns (string[] memory) {
625+
return set._values;
626626
}
627627

628628
/**
@@ -661,12 +661,12 @@ library EnumerableSet {
661661
* Returns true if the value was added to the set, that is if it was not
662662
* already present.
663663
*/
664-
function add(BytesSet storage self, bytes memory value) internal returns (bool) {
665-
if (!contains(self, value)) {
666-
self._values.push(value);
664+
function add(BytesSet storage set, bytes memory value) internal returns (bool) {
665+
if (!contains(set, value)) {
666+
set._values.push(value);
667667
// The value is stored at length-1, but we add 1 to all indexes
668668
// and use 0 as a sentinel value
669-
self._positions[value] = self._values.length;
669+
set._positions[value] = set._values.length;
670670
return true;
671671
} else {
672672
return false;
@@ -679,33 +679,33 @@ library EnumerableSet {
679679
* Returns true if the value was removed from the set, that is if it was
680680
* present.
681681
*/
682-
function remove(BytesSet storage self, bytes memory value) internal returns (bool) {
682+
function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
683683
// We cache the value's position to prevent multiple reads from the same storage slot
684-
uint256 position = self._positions[value];
684+
uint256 position = set._positions[value];
685685

686686
if (position != 0) {
687-
// Equivalent to contains(self, value)
687+
// Equivalent to contains(set, value)
688688
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
689689
// the array, and then remove the last element (sometimes called as 'swap and pop').
690690
// This modifies the order of the array, as noted in {at}.
691691

692692
uint256 valueIndex = position - 1;
693-
uint256 lastIndex = self._values.length - 1;
693+
uint256 lastIndex = set._values.length - 1;
694694

695695
if (valueIndex != lastIndex) {
696-
bytes memory lastValue = self._values[lastIndex];
696+
bytes memory lastValue = set._values[lastIndex];
697697

698698
// Move the lastValue to the index where the value to delete is
699-
self._values[valueIndex] = lastValue;
699+
set._values[valueIndex] = lastValue;
700700
// Update the tracked position of the lastValue (that was just moved)
701-
self._positions[lastValue] = position;
701+
set._positions[lastValue] = position;
702702
}
703703

704704
// Delete the slot where the moved value was stored
705-
self._values.pop();
705+
set._values.pop();
706706

707707
// Delete the tracked position for the deleted slot
708-
delete self._positions[value];
708+
delete set._positions[value];
709709

710710
return true;
711711
} else {
@@ -730,15 +730,15 @@ library EnumerableSet {
730730
/**
731731
* @dev Returns true if the value is in the set. O(1).
732732
*/
733-
function contains(BytesSet storage self, bytes memory value) internal view returns (bool) {
734-
return self._positions[value] != 0;
733+
function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
734+
return set._positions[value] != 0;
735735
}
736736

737737
/**
738738
* @dev Returns the number of values on the set. O(1).
739739
*/
740-
function length(BytesSet storage self) internal view returns (uint256) {
741-
return self._values.length;
740+
function length(BytesSet storage set) internal view returns (uint256) {
741+
return set._values.length;
742742
}
743743

744744
/**
@@ -751,8 +751,8 @@ library EnumerableSet {
751751
*
752752
* - `index` must be strictly less than {length}.
753753
*/
754-
function at(BytesSet storage self, uint256 index) internal view returns (bytes memory) {
755-
return self._values[index];
754+
function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
755+
return set._values[index];
756756
}
757757

758758
/**
@@ -763,8 +763,8 @@ library EnumerableSet {
763763
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
764764
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
765765
*/
766-
function values(BytesSet storage self) internal view returns (bytes[] memory) {
767-
return self._values;
766+
function values(BytesSet storage set) internal view returns (bytes[] memory) {
767+
return set._values;
768768
}
769769

770770
/**

scripts/generate/templates/EnumerableSet.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,12 @@ struct ${name} {
324324
* Returns true if the value was added to the set, that is if it was not
325325
* already present.
326326
*/
327-
function add(${name} storage self, ${value.type} memory value) internal returns (bool) {
328-
if (!contains(self, value)) {
329-
self._values.push(value);
327+
function add(${name} storage set, ${value.type} memory value) internal returns (bool) {
328+
if (!contains(set, value)) {
329+
set._values.push(value);
330330
// The value is stored at length-1, but we add 1 to all indexes
331331
// and use 0 as a sentinel value
332-
self._positions[value] = self._values.length;
332+
set._positions[value] = set._values.length;
333333
return true;
334334
} else {
335335
return false;
@@ -342,33 +342,33 @@ function add(${name} storage self, ${value.type} memory value) internal returns
342342
* Returns true if the value was removed from the set, that is if it was
343343
* present.
344344
*/
345-
function remove(${name} storage self, ${value.type} memory value) internal returns (bool) {
345+
function remove(${name} storage set, ${value.type} memory value) internal returns (bool) {
346346
// We cache the value's position to prevent multiple reads from the same storage slot
347-
uint256 position = self._positions[value];
347+
uint256 position = set._positions[value];
348348
349349
if (position != 0) {
350-
// Equivalent to contains(self, value)
350+
// Equivalent to contains(set, value)
351351
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
352352
// the array, and then remove the last element (sometimes called as 'swap and pop').
353353
// This modifies the order of the array, as noted in {at}.
354354
355355
uint256 valueIndex = position - 1;
356-
uint256 lastIndex = self._values.length - 1;
356+
uint256 lastIndex = set._values.length - 1;
357357
358358
if (valueIndex != lastIndex) {
359-
${value.type} memory lastValue = self._values[lastIndex];
359+
${value.type} memory lastValue = set._values[lastIndex];
360360
361361
// Move the lastValue to the index where the value to delete is
362-
self._values[valueIndex] = lastValue;
362+
set._values[valueIndex] = lastValue;
363363
// Update the tracked position of the lastValue (that was just moved)
364-
self._positions[lastValue] = position;
364+
set._positions[lastValue] = position;
365365
}
366366
367367
// Delete the slot where the moved value was stored
368-
self._values.pop();
368+
set._values.pop();
369369
370370
// Delete the tracked position for the deleted slot
371-
delete self._positions[value];
371+
delete set._positions[value];
372372
373373
return true;
374374
} else {
@@ -393,15 +393,15 @@ function clear(${name} storage set) internal {
393393
/**
394394
* @dev Returns true if the value is in the set. O(1).
395395
*/
396-
function contains(${name} storage self, ${value.type} memory value) internal view returns (bool) {
397-
return self._positions[value] != 0;
396+
function contains(${name} storage set, ${value.type} memory value) internal view returns (bool) {
397+
return set._positions[value] != 0;
398398
}
399399
400400
/**
401401
* @dev Returns the number of values on the set. O(1).
402402
*/
403-
function length(${name} storage self) internal view returns (uint256) {
404-
return self._values.length;
403+
function length(${name} storage set) internal view returns (uint256) {
404+
return set._values.length;
405405
}
406406
407407
/**
@@ -414,8 +414,8 @@ function length(${name} storage self) internal view returns (uint256) {
414414
*
415415
* - \`index\` must be strictly less than {length}.
416416
*/
417-
function at(${name} storage self, uint256 index) internal view returns (${value.type} memory) {
418-
return self._values[index];
417+
function at(${name} storage set, uint256 index) internal view returns (${value.type} memory) {
418+
return set._values[index];
419419
}
420420
421421
/**
@@ -426,8 +426,8 @@ function at(${name} storage self, uint256 index) internal view returns (${value.
426426
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
427427
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
428428
*/
429-
function values(${name} storage self) internal view returns (${value.type}[] memory) {
430-
return self._values;
429+
function values(${name} storage set) internal view returns (${value.type}[] memory) {
430+
return set._values;
431431
}
432432
433433
/**

test/account/AccountMultiSignerWeighted.test.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ describe('AccountMultiSignerWeighted', function () {
158158
await expect(this.mock.signerWeight(signer3)).to.eventually.equal(3); // unchanged
159159
});
160160

161+
it("no-op doesn't emit an event", async function () {
162+
await expect(this.mock.$_setSignerWeights([signer1], [1])).to.not.emit(this.mock, 'ERC7913SignerWeightChanged');
163+
});
164+
161165
it('cannot set weight to non-existent signer', async function () {
162166
// Reverts when setting weight for non-existent signer
163167
await expect(this.mock.$_setSignerWeights([signer4], [1]))
@@ -186,28 +190,28 @@ describe('AccountMultiSignerWeighted', function () {
186190
});
187191

188192
it('validates threshold is reachable when updating weights', async function () {
189-
// First, lower the weights so the sum is exactly 6 (just enough for threshold=6)
190-
await expect(this.mock.$_setSignerWeights([signer1, signer2, signer3], [1, 2, 3]))
193+
// First, lower the weights so the sum is exactly 9 (just enough for threshold=9)
194+
await expect(this.mock.$_setSignerWeights([signer1, signer2, signer3], [2, 3, 4]))
191195
.to.emit(this.mock, 'ERC7913SignerWeightChanged')
192-
.withArgs(signer1, 1)
196+
.withArgs(signer1, 2)
193197
.to.emit(this.mock, 'ERC7913SignerWeightChanged')
194-
.withArgs(signer2, 2)
198+
.withArgs(signer2, 3)
195199
.to.emit(this.mock, 'ERC7913SignerWeightChanged')
196-
.withArgs(signer3, 3);
200+
.withArgs(signer3, 4);
197201

198-
// Increase threshold to 6
199-
await expect(this.mock.$_setThreshold(6)).to.emit(this.mock, 'ERC7913ThresholdSet').withArgs(6);
202+
// Increase threshold to 9
203+
await expect(this.mock.$_setThreshold(9)).to.emit(this.mock, 'ERC7913ThresholdSet').withArgs(9);
200204

201205
// Now try to lower weights so their sum is less than the threshold
202-
await expect(this.mock.$_setSignerWeights([signer1, signer2, signer3], [1, 1, 1])).to.be.revertedWithCustomError(
206+
await expect(this.mock.$_setSignerWeights([signer1, signer2, signer3], [2, 2, 2])).to.be.revertedWithCustomError(
203207
this.mock,
204208
'MultiSignerERC7913UnreachableThreshold',
205209
);
206210

207211
// Try to increase threshold to be larger than the total weight
208-
await expect(this.mock.$_setThreshold(7))
212+
await expect(this.mock.$_setThreshold(10))
209213
.to.be.revertedWithCustomError(this.mock, 'MultiSignerERC7913UnreachableThreshold')
210-
.withArgs(6, 7);
214+
.withArgs(9, 10);
211215
});
212216

213217
it('reports default weight of 1 for signers without explicit weight', async function () {

0 commit comments

Comments
 (0)