Skip to content

Commit

Permalink
Add EIP-2098 support
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized committed Nov 2, 2023
1 parent 069434b commit 3ada077
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 62 deletions.
28 changes: 14 additions & 14 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
MulticallerTest:testMultiCallerWithSignerIncrementNonceSalt(uint256) (runs: 256, μ: 80930, ~: 79981)
MulticallerTest:testMultiCallerWithSignerIncrementNonceSaltWithERC1271(uint256) (runs: 256, μ: 580421, ~: 580326)
MulticallerTest:testMultiCallerWithSignerIncrementNonceSalt(uint256) (runs: 256, μ: 83578, ~: 83625)
MulticallerTest:testMultiCallerWithSignerIncrementNonceSaltWithERC1271(uint256) (runs: 256, μ: 615181, ~: 615198)
MulticallerTest:testMulticallerCdFallback(string) (runs: 256, μ: 295907, ~: 281868)
MulticallerTest:testMulticallerForwardsMessageValue() (gas: 214013)
MulticallerTest:testMulticallerGetNames() (gas: 147637)
MulticallerTest:testMulticallerReentrancyGuard() (gas: 19980)
MulticallerTest:testMulticallerRefund(uint256) (runs: 256, μ: 169867, ~: 172026)
MulticallerTest:testMulticallerReturnDataIsProperlyEncoded() (gas: 122173)
MulticallerTest:testMulticallerReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 497463, ~: 510689)
MulticallerTest:testMulticallerReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 466635, ~: 447957)
MulticallerTest:testMulticallerReturnDataIsProperlyEncoded(uint256,uint256,uint256,uint256) (runs: 256, μ: 122252, ~: 122252)
MulticallerTest:testMulticallerRevertWithCustomError() (gas: 35485)
MulticallerTest:testMulticallerRevertWithMessage() (gas: 38244)
Expand All @@ -15,17 +15,17 @@ MulticallerTest:testMulticallerRevertWithNothing() (gas: 35306)
MulticallerTest:testMulticallerSenderDoesNotRevertWithoutMulticallerDeployed() (gas: 3423)
MulticallerTest:testMulticallerTargetGetMulticallerSender() (gas: 27448)
MulticallerTest:testMulticallerWithNoData() (gas: 16213)
MulticallerTest:testMulticallerWithSigner(uint256) (runs: 256, μ: 129666, ~: 118547)
MulticallerTest:testMulticallerWithSignerEIP712Domain() (gas: 12375)
MulticallerTest:testMulticallerWithSignerGetMulticallerSigner() (gas: 135812)
MulticallerTest:testMulticallerWithSignerInvalidateNonces(uint256) (runs: 256, μ: 78926, ~: 76336)
MulticallerTest:testMulticallerWithSignerInvalidateNoncesWithERC1271(uint256) (runs: 256, μ: 586347, ~: 584887)
MulticallerTest:testMulticallerWithSignerNonPayableFunctions() (gas: 48884)
MulticallerTest:testMulticallerWithSignerReentrancyGuard() (gas: 124205)
MulticallerTest:testMulticallerWithSignerRevert() (gas: 204187)
MulticallerTest:testMulticallerWithSignerWithERC1271(uint256) (runs: 256, μ: 630979, ~: 621234)
MulticallerTest:testMulticallerWithSignerWithNoData() (gas: 128730)
MulticallerTest:testMulticallerWithSigner(uint256) (runs: 256, μ: 132933, ~: 119957)
MulticallerTest:testMulticallerWithSignerEIP712Domain() (gas: 12295)
MulticallerTest:testMulticallerWithSignerGetMulticallerSigner() (gas: 136616)
MulticallerTest:testMulticallerWithSignerInvalidateNonces(uint256) (runs: 256, μ: 82117, ~: 82343)
MulticallerTest:testMulticallerWithSignerInvalidateNoncesWithERC1271(uint256) (runs: 256, μ: 617658, ~: 616689)
MulticallerTest:testMulticallerWithSignerNonPayableFunctions() (gas: 48775)
MulticallerTest:testMulticallerWithSignerReentrancyGuard() (gas: 125306)
MulticallerTest:testMulticallerWithSignerRevert() (gas: 206828)
MulticallerTest:testMulticallerWithSignerWithERC1271(uint256) (runs: 256, μ: 661518, ~: 651795)
MulticallerTest:testMulticallerWithSignerWithNoData() (gas: 130757)
MulticallerTest:testNastyCalldataRevert() (gas: 3420)
MulticallerTest:testOffsetTrick(uint256,uint256,uint256) (runs: 256, μ: 571, ~: 571)
MulticallerTest:test__codesize() (gas: 49617)
MulticallerTest:test__codesize() (gas: 54219)
TestPlus:test__codesize() (gas: 1102)
27 changes: 27 additions & 0 deletions src/MulticallerWithSigner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,15 @@ contract MulticallerWithSigner {
mstore(returndatasize(), 0x1901) // Store "\x19\x01".
let digest := keccak256(0x1e, 0x42) // Compute the digest.
let signatureIsValid := 0
if eq(signature.length, 64) {
mstore(returndatasize(), digest) // Store the digest.
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
signatureIsValid := mul(returndatasize(), eq(signer, mload(t)))
}
if eq(signature.length, 65) {
mstore(returndatasize(), digest) // Store the digest.
calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`.
Expand Down Expand Up @@ -380,6 +389,15 @@ contract MulticallerWithSigner {
mstore(returndatasize(), 0x1901) // Store "\x19\x01".
let digest := keccak256(0x1e, 0x42) // Compute the digest.
let signatureIsValid := 0
if eq(signature.length, 64) {
mstore(returndatasize(), digest) // Store the digest.
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
signatureIsValid := mul(returndatasize(), eq(signer, mload(t)))
}
if eq(signature.length, 65) {
mstore(returndatasize(), digest) // Store the digest.
calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`.
Expand Down Expand Up @@ -499,6 +517,15 @@ contract MulticallerWithSigner {
mstore(returndatasize(), 0x1901) // Store "\x19\x01".
let digest := keccak256(0x1e, 0x42) // Compute the digest.
let signatureIsValid := 0
if eq(signature.length, 64) {
mstore(returndatasize(), digest) // Store the digest.
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
signatureIsValid := mul(returndatasize(), eq(signer, mload(t)))
}
if eq(signature.length, 65) {
mstore(returndatasize(), digest) // Store the digest.
calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`.
Expand Down
54 changes: 43 additions & 11 deletions test/Multicaller.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ contract MulticallerTest is TestPlus {
// Uncomment as needed to test the source files directly:
// _etchMulticaller();
// _etchMulticallerWithSender();
// _etchMulticallerWithSigner();
_etchMulticallerWithSigner();

_deployTargets();
}
Expand Down Expand Up @@ -754,7 +754,7 @@ contract MulticallerTest is TestPlus {
returns (bytes[] memory results)
{
results = multicallerWithSigner.aggregateWithSigner{value: value}(
t.targets, t.data, t.values, t.nonce, t.signer, t.signature
t.targets, t.data, t.values, t.nonce, t.signer, _maybeMake2098(t.signature)
);
}

Expand Down Expand Up @@ -817,7 +817,9 @@ contract MulticallerTest is TestPlus {
_generateInvalidateNoncesSignature(nonces, signer, privateKey);
vm.expectEmit(true, true, true, true);
emit NoncesInvalidated(signer, nonces);
multicallerWithSigner.invalidateNoncesForSigner(nonces, signer, signature);
multicallerWithSigner.invalidateNoncesForSigner(
nonces, signer, _maybeMake2098(signature)
);
}

invalidated = multicallerWithSigner.noncesInvalidated(signer, nonces);
Expand Down Expand Up @@ -862,14 +864,20 @@ contract MulticallerTest is TestPlus {
_generateInvalidateNoncesSignature(nonces, erc1271Wallet, erc721SignerPrivateKey);

vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector);
multicallerWithSigner.invalidateNoncesForSigner(nonces, erc1271Malicious, signature);
multicallerWithSigner.invalidateNoncesForSigner(
nonces, erc1271Malicious, _maybeMake2098(signature)
);

vm.expectEmit(true, true, true, true);
emit NoncesInvalidated(erc1271Wallet, nonces);
multicallerWithSigner.invalidateNoncesForSigner(nonces, erc1271Wallet, signature);
multicallerWithSigner.invalidateNoncesForSigner(
nonces, erc1271Wallet, _maybeMake2098(signature)
);

vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector);
multicallerWithSigner.invalidateNoncesForSigner(nonces, erc1271Malicious, signature);
multicallerWithSigner.invalidateNoncesForSigner(
nonces, erc1271Malicious, _maybeMake2098(signature)
);
}
}

Expand All @@ -888,10 +896,10 @@ contract MulticallerTest is TestPlus {
bytes memory signature = _generateIncrementNonceSaltSignature(signer, privateKey);
vm.expectEmit(true, true, true, true);
emit NonceSaltIncremented(signer, nextNonceSalt);
multicallerWithSigner.incrementNonceSaltForSigner(signer, signature);
multicallerWithSigner.incrementNonceSaltForSigner(signer, _maybeMake2098(signature));

vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector);
multicallerWithSigner.incrementNonceSaltForSigner(signer, signature);
multicallerWithSigner.incrementNonceSaltForSigner(signer, _maybeMake2098(signature));
}
uint256 nonceSaltAfter = multicallerWithSigner.nonceSaltOf(signer);
assertEq(nextNonceSalt, nonceSaltAfter);
Expand All @@ -906,13 +914,15 @@ contract MulticallerTest is TestPlus {
_generateIncrementNonceSaltSignature(erc1271Wallet, erc721SignerPrivateKey);

vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector);
multicallerWithSigner.incrementNonceSaltForSigner(erc1271Malicious, signature);
multicallerWithSigner.incrementNonceSaltForSigner(
erc1271Malicious, _maybeMake2098(signature)
);

emit NonceSaltIncremented(erc1271Wallet, nextNonceSalt);
multicallerWithSigner.incrementNonceSaltForSigner(erc1271Wallet, signature);
multicallerWithSigner.incrementNonceSaltForSigner(erc1271Wallet, _maybeMake2098(signature));

vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector);
multicallerWithSigner.incrementNonceSaltForSigner(erc1271Wallet, signature);
multicallerWithSigner.incrementNonceSaltForSigner(erc1271Wallet, _maybeMake2098(signature));
}

function _generateInvalidateNoncesSignature(
Expand Down Expand Up @@ -975,6 +985,27 @@ contract MulticallerTest is TestPlus {
);
}

function _maybeMake2098(bytes memory signature)
internal
returns (bytes memory shortSignature)
{
if (_random() % 2 == 0 || signature.length != 65) {
shortSignature = signature;
} else {
/// @solidity memory-safe-assembly
assembly {
let r := mload(add(signature, 0x20))
let s := mload(add(signature, 0x40))
let v := byte(0, mload(add(signature, 0x60)))
shortSignature := mload(0x40)
mstore(shortSignature, 0x40)
mstore(add(shortSignature, 0x20), r)
mstore(add(shortSignature, 0x40), or(shl(255, sub(v, 27)), s))
mstore(0x40, add(shortSignature, 0x60))
}
}
}

function testOffsetTrick(uint256 a, uint256 b, uint256 c) public {
unchecked {
uint256 aDiff = a - c;
Expand All @@ -990,6 +1021,7 @@ contract MulticallerTest is TestPlus {
}

function testNastyCalldataRevert() public {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)

Expand Down
127 changes: 90 additions & 37 deletions test/utils/mocks/MockERC1271Wallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,93 @@ abstract contract ERC1155TokenReceiver {
}
}

library SignatureCheckerLib {
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.

let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
}

/// @dev WARNING! This mock is strictly intended for testing purposes only.
/// Do NOT copy anything here into production code unless you really know what you are doing.
contract MockERC1271Wallet is ERC721TokenReceiver, ERC1155TokenReceiver {
Expand All @@ -52,42 +139,8 @@ contract MockERC1271Wallet is ERC721TokenReceiver, ERC1155TokenReceiver {
view
returns (bytes4)
{
return _recoverCalldata(hash, signature) == signer ? bytes4(0x1626ba7e) : bytes4(0);
}

function _recoverCalldata(bytes32 hash, bytes calldata signature)
internal
view
returns (address result)
{
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, hash)
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
and(
// If the signature is exactly 65 bytes in length.
eq(signature.length, 65),
// If `s` in lower half order, such that the signature is not malleable.
lt(mload(0x60), _MALLEABILITY_THRESHOLD_PLUS_ONE)
), // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x00, // Start of output.
0x20 // Size of output.
)
)
result := mload(0x00)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
}
return SignatureCheckerLib.isValidSignatureNowCalldata(signer, hash, signature)
? bytes4(0x1626ba7e)
: bytes4(0);
}
}

0 comments on commit 3ada077

Please sign in to comment.