Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release v1.0.1 #2

Merged
merged 4 commits into from
Nov 21, 2023
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
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none"
}
114 changes: 73 additions & 41 deletions contracts/samples/VerifyingPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;

Expand All @@ -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
);
}

/**
Expand All @@ -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
)
);
}

/**
Expand All @@ -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:];
}
}
18 changes: 18 additions & 0 deletions contracts/test/TestVerifyingPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "../samples/VerifyingPaymaster.sol";

contract TestVerifyingPaymaster is VerifyingPaymaster {
constructor(
IEntryPoint entryPoint,
address verifyingSigner
) VerifyingPaymaster(entryPoint, verifyingSigner) {
return;
}


function _requireFromEntryPoint() internal pure override {
return;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@b2network/aa-core",
"version": "1.0.0",
"version": "1.0.1",
"description": "ERC-4337 Account Abstraction Implementation",
"files": [
"LICENSE",
Expand Down
61 changes: 61 additions & 0 deletions test/uo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
EntryPoint,
TestVerifyingPaymaster,
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',
callData:
'0x519454470000000000000000000000005a407533259b41721622b4875c56aa64adb6302a000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
signature:
'0x00000000df1edcd8bd57df38c1b8ed82bbd56c3db495f99468013bb9ec40ea9a2a11a42e181bf561b4bdeec3ba595f16e9b727fadaec88a231b80e7697aa693ed1f7e5b61b',
maxFeePerGas: '0x8e5aad3c',
maxPriorityFeePerGas: '0x59682f00',
paymasterAndData:
'0x6dfCB5E63EAb3d4F7CEE4A3d3Aa0152BA755c6C500000000000000000000000000000000000000000000000000000000655c37d400000000000000000000000000000000000000000000000000000000655c29c415690faee849c1c5775e1f0765d91ee8f8a56ef011173159113b6eb8785c1bd164df0b6f1f81c6cd31af7b910b3241bdb7bd0b0b8b852a7c3ce9bf8b1d217ee01b',
callGasLimit: '0x14de5',
verificationGasLimit: '0x633da',
preVerificationGas: '0xe606'
}

const offchainSignerAddress = '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'

describe.skip('UO', () => {
const ethersSigner = ethers.provider.getSigner()
let entryPoint: EntryPoint
let testPaymaster: TestVerifyingPaymaster

before(async function () {
this.timeout(20000)
entryPoint = await deployEntryPoint()
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)
})
})
Loading