Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/verifier/UserOpMultiSigVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {OnlyKeystore} from "../lib/OnlyKeystore.sol";

contract UserOpMultiSigVerifier is IVerifier, OnlyKeystore {
error ZeroThresholdNotAllowed();
error MaxOwnersLimitExceeded();

bytes1 public constant SIGNATURES_ONLY_TAG = 0xff;

Expand Down Expand Up @@ -47,6 +48,7 @@ contract UserOpMultiSigVerifier is IVerifier, OnlyKeystore {
{
(uint8 threshold, address[] memory owners) = abi.decode(config, (uint8, address[]));
require(threshold > 0, ZeroThresholdNotAllowed());
require(owners.length <= type(uint8).max, MaxOwnersLimitExceeded());

SignerData[] memory signatures;
if (bytes1(data[0]) == SIGNATURES_ONLY_TAG) {
Expand Down
44 changes: 40 additions & 4 deletions test/verifier/UserOpMultiSigVerifier.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ contract UserOpMultiSigVerifierTest is Test {
verifier.validateData(message, data, config);
}

function testFuzz_validateDataMaxOwners(bool withUserOp, uint8 threshold, uint8 offset, uint8 excess) public {
uint16 size = _getSizeAndAssumeMaxOwnerLimitExceeded(threshold, offset, excess);
Signer[] memory signers = _createSigners(size);

bytes32 message = keccak256("Signed by signer");
bytes memory data = _createData(message, threshold, offset, signers);
if (withUserOp) {
PackedUserOperation memory userOp;
userOp.signature = data;
data = abi.encode(userOp);
} else {
data = abi.encodePacked(verifier.SIGNATURES_ONLY_TAG(), data);
}

bytes memory config = _createConfig(threshold, signers);

vm.expectRevert(UserOpMultiSigVerifier.MaxOwnersLimitExceeded.selector);
verifier.validateData(message, data, config);
}

function testFuzz_validateDataInvalidCaller(address keystore) public {
vm.assume(keystore != address(this));
vm.prank(keystore);
Expand All @@ -103,9 +123,19 @@ contract UserOpMultiSigVerifierTest is Test {
vm.assume(uint16(threshold) + uint16(offset) <= size);
}

function _createSigners(uint8 size) internal returns (Signer[] memory) {
function _getSizeAndAssumeMaxOwnerLimitExceeded(uint8 threshold, uint8 offset, uint8 excess)
internal
pure
returns (uint16 size)
{
size = uint16(type(uint8).max) + excess;
vm.assume(threshold > 0 && excess > 0);
vm.assume(uint16(threshold) + uint16(offset) <= size);
}

function _createSigners(uint16 size) internal returns (Signer[] memory) {
Signer[] memory signers = new Signer[](size);
for (uint8 i = 0; i < size; i++) {
for (uint16 i = 0; i < size; i++) {
(address addr, uint256 pk) = makeAddrAndKey(LibString.toString(i));
signers[i] = Signer({addr: addr, pk: pk});
}
Expand All @@ -128,8 +158,14 @@ contract UserOpMultiSigVerifierTest is Test {
{
UserOpMultiSigVerifier.SignerData[] memory sd = new UserOpMultiSigVerifier.SignerData[](threshold);
for (uint8 i = 0; i < threshold; i++) {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[i + offset].pk, message);
sd[i] = UserOpMultiSigVerifier.SignerData({index: i + offset, signature: abi.encodePacked(r, s, v)});
uint16 index = uint16(i) + offset;
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[index].pk, message);
sd[i] = UserOpMultiSigVerifier.SignerData({
// Note: index will overflow back to 0 after max uint8.
// This is ok since a MaxOwnersLimitExceeded() error is expected.
index: uint8(index),
signature: abi.encodePacked(r, s, v)
});
}

return abi.encode(sd);
Expand Down