From a6055c2f41d38356d091a44385e5890048262178 Mon Sep 17 00:00:00 2001 From: thecookfrankie <95396011+thecookfrankie@users.noreply.github.com> Date: Tue, 21 Nov 2023 14:12:26 +0800 Subject: [PATCH 1/3] fix(VerifyingPaymaster): adjust UO packing --- contracts/samples/VerifyingPaymaster.sol | 114 ++++++++++++++-------- contracts/test/TestVerifyingPaymaster.sol | 18 ++++ test/uo.test.ts | 74 ++++++++++++++ 3 files changed, 165 insertions(+), 41 deletions(-) create mode 100644 contracts/test/TestVerifyingPaymaster.sol create mode 100644 test/uo.test.ts diff --git a/contracts/samples/VerifyingPaymaster.sol b/contracts/samples/VerifyingPaymaster.sol index 556ba5c2..882f80df 100644 --- a/contracts/samples/VerifyingPaymaster.sol +++ b/contracts/samples/VerifyingPaymaster.sol @@ -4,8 +4,10 @@ pragma solidity ^0.8.12; /* solhint-disable reason-string */ /* solhint-disable no-inline-assembly */ -import "../core/BasePaymaster.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../core/BasePaymaster.sol"; +import { calldataKeccak } from "../core/Helpers.sol"; + /** * A sample paymaster that uses external service to decide whether to pay for the UserOp. * The paymaster trusts an external signer to sign the transaction. @@ -16,7 +18,6 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; * - the account checks a signature to prove identity and account ownership. */ contract VerifyingPaymaster is BasePaymaster { - using ECDSA for bytes32; using UserOperationLib for UserOperation; @@ -26,26 +27,33 @@ contract VerifyingPaymaster is BasePaymaster { uint256 private constant SIGNATURE_OFFSET = 84; - constructor(IEntryPoint _entryPoint, address _verifyingSigner) BasePaymaster(_entryPoint) { + constructor( + IEntryPoint _entryPoint, + address _verifyingSigner + ) BasePaymaster(_entryPoint) { verifyingSigner = _verifyingSigner; } mapping(address => uint256) public senderNonce; - function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) { - // lighter signature scheme. must match UserOp.ts#packUserOp - bytes calldata pnd = userOp.paymasterAndData; - // copy directly the userOp from calldata up to (but not including) the paymasterAndData. - // this encoding depends on the ABI encoding of calldata, but is much lighter to copy - // than referencing each field separately. - assembly { - let ofs := userOp - let len := sub(sub(pnd.offset, ofs), 32) - ret := mload(0x40) - mstore(0x40, add(ret, add(len, 32))) - mstore(ret, len) - calldatacopy(add(ret, 32), ofs, len) - } + function pack( + UserOperation calldata userOp + ) internal pure returns (bytes memory ret) { + bytes32 hashInitCode = calldataKeccak(userOp.initCode); + bytes32 hashCallData = calldataKeccak(userOp.callData); + + return + abi.encode( + userOp.sender, + userOp.nonce, + hashInitCode, + hashCallData, + // userOp.callGasLimit, + // userOp.verificationGasLimit + // userOp.preVerificationGas, + userOp.maxFeePerGas, + userOp.maxPriorityFeePerGas + ); } /** @@ -55,18 +63,23 @@ contract VerifyingPaymaster is BasePaymaster { * note that this signature covers all fields of the UserOperation, except the "paymasterAndData", * which will carry the signature itself. */ - function getHash(UserOperation calldata userOp, uint48 validUntil, uint48 validAfter) - public view returns (bytes32) { + function getHash( + UserOperation calldata userOp, + uint48 validUntil, + uint48 validAfter + ) public view returns (bytes32) { //can't use userOp.hash(), since it contains also the paymasterAndData itself. - - return keccak256(abi.encode( - pack(userOp), - block.chainid, - address(this), - senderNonce[userOp.getSender()], - validUntil, - validAfter - )); + return + keccak256( + abi.encode( + pack(userOp), + block.chainid, + address(this), + senderNonce[userOp.getSender()], + validUntil, + validAfter + ) + ); } /** @@ -76,29 +89,48 @@ contract VerifyingPaymaster is BasePaymaster { * paymasterAndData[20:84] : abi.encode(validUntil, validAfter) * paymasterAndData[84:] : signature */ - function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 /*userOpHash*/, uint256 requiredPreFund) - internal override returns (bytes memory context, uint256 validationData) { + function _validatePaymasterUserOp( + UserOperation calldata userOp, + bytes32 /*userOpHash*/, + uint256 requiredPreFund + ) internal override returns (bytes memory context, uint256 validationData) { (requiredPreFund); - (uint48 validUntil, uint48 validAfter, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData); - //ECDSA library supports both 64 and 65-byte long signatures. + ( + uint48 validUntil, + uint48 validAfter, + bytes calldata signature + ) = parsePaymasterAndData(userOp.paymasterAndData); + // ECDSA library supports both 64 and 65-byte long signatures. // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and not "ECDSA" - require(signature.length == 64 || signature.length == 65, "VerifyingPaymaster: invalid signature length in paymasterAndData"); - bytes32 hash = ECDSA.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter)); + require( + signature.length == 64 || signature.length == 65, + "VerifyingPaymaster: invalid signature length in paymasterAndData" + ); + bytes32 hash = getHash(userOp, validUntil, validAfter); senderNonce[userOp.getSender()]++; - //don't revert on signature failure: return SIG_VALIDATION_FAILED - if (verifyingSigner != ECDSA.recover(hash, signature)) { - return ("",_packValidationData(true,validUntil,validAfter)); + // don't revert on signature failure: return SIG_VALIDATION_FAILED + if (verifyingSigner != ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature)) { + return ("", _packValidationData(true, validUntil, validAfter)); } - //no need for other on-chain validation: entire UserOp should have been checked + // no need for other on-chain validation: entire UserOp should have been checked // by the external service prior to signing it. - return ("",_packValidationData(false,validUntil,validAfter)); + return ("", _packValidationData(false, validUntil, validAfter)); } - function parsePaymasterAndData(bytes calldata paymasterAndData) public pure returns(uint48 validUntil, uint48 validAfter, bytes calldata signature) { - (validUntil, validAfter) = abi.decode(paymasterAndData[VALID_TIMESTAMP_OFFSET:SIGNATURE_OFFSET],(uint48, uint48)); + function parsePaymasterAndData( + bytes calldata paymasterAndData + ) + public + pure + returns (uint48 validUntil, uint48 validAfter, bytes calldata signature) + { + (validUntil, validAfter) = abi.decode( + paymasterAndData[VALID_TIMESTAMP_OFFSET:SIGNATURE_OFFSET], + (uint48, uint48) + ); signature = paymasterAndData[SIGNATURE_OFFSET:]; } } diff --git a/contracts/test/TestVerifyingPaymaster.sol b/contracts/test/TestVerifyingPaymaster.sol new file mode 100644 index 00000000..3766c3e1 --- /dev/null +++ b/contracts/test/TestVerifyingPaymaster.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "../samples/VerifyingPaymaster.sol"; + +contract TestVerifyingPaymaster is VerifyingPaymaster { + VerifyingPaymaster pm; + + constructor( + IEntryPoint entryPoint, + address verifyingSigner + ) VerifyingPaymaster(entryPoint, verifyingSigner) {} + + + function _requireFromEntryPoint() internal pure override { + return; + } +} diff --git a/test/uo.test.ts b/test/uo.test.ts new file mode 100644 index 00000000..605d75b1 --- /dev/null +++ b/test/uo.test.ts @@ -0,0 +1,74 @@ +import { Wallet } from "ethers"; +import { + EntryPoint, + VerifyingPaymaster, + VerifyingPaymaster__factory, + TestVerifyingPaymaster, + TestVerifyingPaymaster__factory, +} from "../typechain"; +import { createAccountOwner, deployEntryPoint } from "./testutils"; +import { ethers } from "hardhat"; +import { hexZeroPad } from "ethers/lib/utils"; + +const uo = { + initCode: + "0xCCAC4418779B02f7f2bfBAa122534206de2e3358296601cd000000000000000000000000277a60fe8b476df00295ed8d89afca39f7f73187000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000144d1f578940000000000000000000000004afa6aeb5bd397d8e7f8889af8595227805c059c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a407533259b41721622b4875c56aa64adb6302a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", + sender: "0x0A500fC0F9fBC773A1AbdcBd8159285A103c3633", + nonce: "0x0", + callData: + "0x519454470000000000000000000000005a407533259b41721622b4875c56aa64adb6302a000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + signature: + "0x00000000df1edcd8bd57df38c1b8ed82bbd56c3db495f99468013bb9ec40ea9a2a11a42e181bf561b4bdeec3ba595f16e9b727fadaec88a231b80e7697aa693ed1f7e5b61b", + maxFeePerGas: "0x8e5aad3c", + maxPriorityFeePerGas: "0x59682f00", + paymasterAndData: + "0x6dfCB5E63EAb3d4F7CEE4A3d3Aa0152BA755c6C500000000000000000000000000000000000000000000000000000000655c37d400000000000000000000000000000000000000000000000000000000655c29c415690faee849c1c5775e1f0765d91ee8f8a56ef011173159113b6eb8785c1bd164df0b6f1f81c6cd31af7b910b3241bdb7bd0b0b8b852a7c3ce9bf8b1d217ee01b", + callGasLimit: "0x14de5", + verificationGasLimit: "0x633da", + preVerificationGas: "0xe606", +}; + +const offchainSignerAddress = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"; + +describe.skip("UO", () => { + let entryPoint: EntryPoint; + let accountOwner: Wallet; + const ethersSigner = ethers.provider.getSigner(); + let offchainSigner: Wallet; + + let paymaster: VerifyingPaymaster; + let testPaymaster: TestVerifyingPaymaster; + + before(async function () { + this.timeout(20000); + entryPoint = await deployEntryPoint(); + offchainSigner = createAccountOwner(); + accountOwner = createAccountOwner(); + + paymaster = await new VerifyingPaymaster__factory(ethersSigner).deploy( + entryPoint.address, + offchainSignerAddress + ); + testPaymaster = await new TestVerifyingPaymaster__factory( + ethersSigner + ).deploy(entryPoint.address, offchainSignerAddress); + }); + + it("should pack uo", async () => { + const { validUntil, validAfter } = + await testPaymaster.parsePaymasterAndData(uo.paymasterAndData); + console.log("validUntil", validUntil); + console.log("validAfter", validAfter); + const hash = await testPaymaster.getHash(uo, validUntil, validAfter); + console.log(hash); + }); + + it("should validatePaymasterUserOp", async () => { + const { validationData } = await testPaymaster.callStatic.validatePaymasterUserOp( + uo, + hexZeroPad("0x", 32), + 0 + ); + console.log(validationData); + }); +}); From b7df971272812cf6f6461afb517e68580f737a44 Mon Sep 17 00:00:00 2001 From: thecookfrankie <95396011+thecookfrankie@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:01:47 +0800 Subject: [PATCH 2/3] release: release v1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be5eba80..ff664b57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@b2network/aa-core", - "version": "1.0.0", + "version": "1.0.1", "description": "ERC-4337 Account Abstraction Implementation", "files": [ "LICENSE", From 91ad417c642c885990c11d8f5facaf65a7aa35d4 Mon Sep 17 00:00:00 2001 From: thecookfrankie <95396011+thecookfrankie@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:16:02 +0800 Subject: [PATCH 3/3] style: fix linting issues --- .prettierrc | 5 ++ contracts/test/TestVerifyingPaymaster.sol | 6 +- test/uo.test.ts | 101 ++++++++++------------ 3 files changed, 52 insertions(+), 60 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8c158f2d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "none" +} \ No newline at end of file diff --git a/contracts/test/TestVerifyingPaymaster.sol b/contracts/test/TestVerifyingPaymaster.sol index 3766c3e1..4e2de937 100644 --- a/contracts/test/TestVerifyingPaymaster.sol +++ b/contracts/test/TestVerifyingPaymaster.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.13; import "../samples/VerifyingPaymaster.sol"; contract TestVerifyingPaymaster is VerifyingPaymaster { - VerifyingPaymaster pm; - constructor( IEntryPoint entryPoint, address verifyingSigner - ) VerifyingPaymaster(entryPoint, verifyingSigner) {} + ) VerifyingPaymaster(entryPoint, verifyingSigner) { + return; + } function _requireFromEntryPoint() internal pure override { diff --git a/test/uo.test.ts b/test/uo.test.ts index 605d75b1..7572d9e0 100644 --- a/test/uo.test.ts +++ b/test/uo.test.ts @@ -1,74 +1,61 @@ -import { Wallet } from "ethers"; import { EntryPoint, - VerifyingPaymaster, - VerifyingPaymaster__factory, TestVerifyingPaymaster, - TestVerifyingPaymaster__factory, -} from "../typechain"; -import { createAccountOwner, deployEntryPoint } from "./testutils"; -import { ethers } from "hardhat"; -import { hexZeroPad } from "ethers/lib/utils"; + TestVerifyingPaymaster__factory +} from '../typechain' +import { deployEntryPoint } from './testutils' +import { ethers } from 'hardhat' +import { hexZeroPad } from 'ethers/lib/utils' const uo = { initCode: - "0xCCAC4418779B02f7f2bfBAa122534206de2e3358296601cd000000000000000000000000277a60fe8b476df00295ed8d89afca39f7f73187000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000144d1f578940000000000000000000000004afa6aeb5bd397d8e7f8889af8595227805c059c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a407533259b41721622b4875c56aa64adb6302a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", - sender: "0x0A500fC0F9fBC773A1AbdcBd8159285A103c3633", - nonce: "0x0", + '0xCCAC4418779B02f7f2bfBAa122534206de2e3358296601cd000000000000000000000000277a60fe8b476df00295ed8d89afca39f7f73187000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000144d1f578940000000000000000000000004afa6aeb5bd397d8e7f8889af8595227805c059c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a407533259b41721622b4875c56aa64adb6302a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000', + sender: '0x0A500fC0F9fBC773A1AbdcBd8159285A103c3633', + nonce: '0x0', callData: - "0x519454470000000000000000000000005a407533259b41721622b4875c56aa64adb6302a000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + '0x519454470000000000000000000000005a407533259b41721622b4875c56aa64adb6302a000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', signature: - "0x00000000df1edcd8bd57df38c1b8ed82bbd56c3db495f99468013bb9ec40ea9a2a11a42e181bf561b4bdeec3ba595f16e9b727fadaec88a231b80e7697aa693ed1f7e5b61b", - maxFeePerGas: "0x8e5aad3c", - maxPriorityFeePerGas: "0x59682f00", + '0x00000000df1edcd8bd57df38c1b8ed82bbd56c3db495f99468013bb9ec40ea9a2a11a42e181bf561b4bdeec3ba595f16e9b727fadaec88a231b80e7697aa693ed1f7e5b61b', + maxFeePerGas: '0x8e5aad3c', + maxPriorityFeePerGas: '0x59682f00', paymasterAndData: - "0x6dfCB5E63EAb3d4F7CEE4A3d3Aa0152BA755c6C500000000000000000000000000000000000000000000000000000000655c37d400000000000000000000000000000000000000000000000000000000655c29c415690faee849c1c5775e1f0765d91ee8f8a56ef011173159113b6eb8785c1bd164df0b6f1f81c6cd31af7b910b3241bdb7bd0b0b8b852a7c3ce9bf8b1d217ee01b", - callGasLimit: "0x14de5", - verificationGasLimit: "0x633da", - preVerificationGas: "0xe606", -}; + '0x6dfCB5E63EAb3d4F7CEE4A3d3Aa0152BA755c6C500000000000000000000000000000000000000000000000000000000655c37d400000000000000000000000000000000000000000000000000000000655c29c415690faee849c1c5775e1f0765d91ee8f8a56ef011173159113b6eb8785c1bd164df0b6f1f81c6cd31af7b910b3241bdb7bd0b0b8b852a7c3ce9bf8b1d217ee01b', + callGasLimit: '0x14de5', + verificationGasLimit: '0x633da', + preVerificationGas: '0xe606' +} -const offchainSignerAddress = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"; +const offchainSignerAddress = '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720' -describe.skip("UO", () => { - let entryPoint: EntryPoint; - let accountOwner: Wallet; - const ethersSigner = ethers.provider.getSigner(); - let offchainSigner: Wallet; - - let paymaster: VerifyingPaymaster; - let testPaymaster: TestVerifyingPaymaster; +describe.skip('UO', () => { + const ethersSigner = ethers.provider.getSigner() + let entryPoint: EntryPoint + let testPaymaster: TestVerifyingPaymaster before(async function () { - this.timeout(20000); - entryPoint = await deployEntryPoint(); - offchainSigner = createAccountOwner(); - accountOwner = createAccountOwner(); - - paymaster = await new VerifyingPaymaster__factory(ethersSigner).deploy( - entryPoint.address, - offchainSignerAddress - ); + this.timeout(20000) + entryPoint = await deployEntryPoint() testPaymaster = await new TestVerifyingPaymaster__factory( ethersSigner - ).deploy(entryPoint.address, offchainSignerAddress); - }); + ).deploy(entryPoint.address, offchainSignerAddress) + }) - it("should pack uo", async () => { + it('should pack uo', async () => { const { validUntil, validAfter } = - await testPaymaster.parsePaymasterAndData(uo.paymasterAndData); - console.log("validUntil", validUntil); - console.log("validAfter", validAfter); - const hash = await testPaymaster.getHash(uo, validUntil, validAfter); - console.log(hash); - }); - - it("should validatePaymasterUserOp", async () => { - const { validationData } = await testPaymaster.callStatic.validatePaymasterUserOp( - uo, - hexZeroPad("0x", 32), - 0 - ); - console.log(validationData); - }); -}); + await testPaymaster.parsePaymasterAndData(uo.paymasterAndData) + console.log('validUntil', validUntil) + console.log('validAfter', validAfter) + const hash = await testPaymaster.getHash(uo, validUntil, validAfter) + console.log(hash) + }) + + it('should validatePaymasterUserOp', async () => { + const { validationData } = + await testPaymaster.callStatic.validatePaymasterUserOp( + uo, + hexZeroPad('0x', 32), + 0 + ) + console.log(validationData) + }) +})