Skip to content

Commit

Permalink
Merge pull request #2 from b2network/b2-dev
Browse files Browse the repository at this point in the history
release v1.0.1
  • Loading branch information
thecookfrankie authored Nov 21, 2023
2 parents 654abf1 + 91ad417 commit 9dfa668
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 42 deletions.
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)
})
})

0 comments on commit 9dfa668

Please sign in to comment.