Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Haypierre committed Dec 17, 2024
1 parent 1fee2d5 commit a96bef8
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 33 deletions.
6 changes: 6 additions & 0 deletions demo/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export const baseSepoliaBundlerClient = createBundlerClient({
transport: http(`http://localhost:8080/bundler/${baseSepolia.id}`),
});

// export const baseSepoliaBundlerClient = createBundlerClient({
// client: baseSepoliaPublicClient,
// paymaster: getPaymasterActions("base"),
// transport: http(`https://api.pimlico.io/v2/base-sepolia/rpc?apikey=pim_QZqpZqQWSfNVFFhLxMRqDz`),
// });

export const optimismBundlerClient = createBundlerClient({
client: optimismPublicClient,
paymaster: getPaymasterActions("optimism"),
Expand Down
2 changes: 1 addition & 1 deletion demo/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const openfortAccountFactory =
export const owner = "0x9590Ed0C18190a310f4e93CAccc4CC17270bED40";

export const paymasters = {
base: "0xA1D5C22d20C41998026e32dEE0eaF9cbC56f7d6E",
base: "0x097a1ACDa7452584dB79d369E4822a63373B5C32",
optimism: "0x7926E12044F7f29150F5250B1A335a145298308d",
};

Expand Down
3 changes: 2 additions & 1 deletion demo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ program
functionName: "mint",
args: [account],
}
]
],
verificationGasLimit: 10000000n,
});

const userOpHash = await getUserOperationHash({
Expand Down
95 changes: 75 additions & 20 deletions demo/paymaster.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PaymasterActions, GetPaymasterDataParameters, GetPaymasterDataReturnType, GetPaymasterStubDataParameters, GetPaymasterStubDataReturnType, UserOperation, PackedUserOperation } from "viem/account-abstraction";
import { chainIDs, paymasters, paymasterVerifier, supportedChain, tokenA, vaultA } from "./constants";
import { Hex, keccak256, encodeAbiParameters, Address, concat, encodePacked, numberToHex, pad, toHex } from "viem";
import { Hex, keccak256, encodeAbiParameters, Address, concat, encodePacked, numberToHex, pad, toHex, toBytes, } from "viem";
import { publicClients } from "./clients";


Expand All @@ -16,6 +16,9 @@ export function getPaymasterActions(chain: supportedChain): PaymasterActions {
const verificationGasLimit = parameters.verificationGasLimit || BigInt(1e5);
const callGasLimit = parameters.callGasLimit || BigInt(1e5);

console.log("verificationGasLimit", verificationGasLimit);
console.log("callGasLimit", callGasLimit);

const userOp: PackedUserOperation = {
accountGasLimits: `0x${verificationGasLimit.toString(16)}${callGasLimit.toString(16)}` as Hex,
gasFees: `0x${parameters.maxFeePerGas!.toString(16)}${parameters.maxPriorityFeePerGas!.toString(16)}` as Hex,
Expand All @@ -28,8 +31,13 @@ export function getPaymasterActions(chain: supportedChain): PaymasterActions {
paymasterAndData: "0x",
};

console.log("pmAddress", pmAddress);
console.log("userOp.preVerificationGas", numberToHex(userOp.preVerificationGas, { size: 16 }));
console.log("postVerificationGas", numberToHex(postVerificationGas, { size: 16 }));
console.log("validUntil", numberToHex(validUntil, { size: 6 }));
console.log("validAfter", numberToHex(validAfter, { size: 6 }));
const hash = await computeHash(userOp, chain, validUntil, validAfter);
const signature = await paymasterVerifier.sign({ hash });
const signature = await paymasterVerifier.signMessage({ message: hash });
return {
paymasterAndData: concat([
pmAddress,
Expand All @@ -46,33 +54,80 @@ export function getPaymasterActions(chain: supportedChain): PaymasterActions {
getPaymasterStubData: async (
parameters: GetPaymasterStubDataParameters,
): Promise<GetPaymasterStubDataReturnType> => {

return {
paymasterAndData: (await getPaymasterActions(chain).getPaymasterData(parameters)).paymasterAndData as Hex,
};

// Simulation reverts: "transferFrom | approve | mint" reverted with the following signature": Insufficient allowance
// return {
// paymasterAndData: "0x" as Hex,
// };


// return {
// paymasterAndData:
// `${pmAddress}00000000000000000000000000000000000000000000000000000000deadbeef000000000000000000000000000000000000000000000000000000000000123400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000000000000000000000009896803c3e4f3f2488eab12a788db10240c757d360350a2a3938f37237ddfe070d132a28796766b9f0ef1f04cd3678ac598d76c9bec7e70f11bfae500873879b2fead31b` as Hex,
// };
},
};
}


async function computeHash(userOp: PackedUserOperation, chain: supportedChain, validUntil: bigint, validAfter: bigint) {
const encodedData = concat([
userOp.sender,
pad(numberToHex(userOp.nonce), { size: 32 }),
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.accountGasLimits,
keccak256(concat([getRepayToken(userOp.sender), getSponsorTokens(userOp.sender, chain)])),
toHex(BigInt(encodePacked(["uint128", "uint128"], [userOp.preVerificationGas, 100000n])), { size: 32 }),
pad(numberToHex(userOp.preVerificationGas), { size: 16 }),
userOp.gasFees,
pad(numberToHex(chainIDs[chain]), { size: 32 }),
paymasters[chain] as Address,
pad(numberToHex(validUntil), { size: 6 }),
pad(numberToHex(validAfter), { size: 6 })
]);

const hash = keccak256(encodedData);
return hash;

console.log("userOp.sender", userOp.sender);
console.log("userOp.nonce", userOp.nonce);
console.log("userOp.initCode", userOp.initCode);
console.log("userOp.callData", userOp.callData);
console.log("userOp.accountGasLimits", userOp.accountGasLimits);
console.log("getRepayToken(userOp.sender)", getRepayToken(userOp.sender));
console.log("getSponsorTokens(userOp.sender, chain)", getSponsorTokens(userOp.sender, chain));

console.log("pre/post verification gas");
console.log(encodePacked(["uint128", "uint128"], [100000n, 100000n]));

const encodedData = encodeAbiParameters(
[
{ type: "address", name: "sender" },
{ type: "uint256", name: "nonce" },
{ type: "bytes32", name: "initCodeHash" },
{ type: "bytes32", name: "callDataHash" },
{ type: "bytes32", name: "accountGasLimits" },
{ type: "bytes32", name: "tokensHash" },
{ type: "bytes32", name: "gasInfo" },
{ type: "uint256", name: "preVerificationGas" },
{ type: "bytes32", name: "gasFees" },
{ type: "uint256", name: "chainId" },
{ type: "address", name: "paymaster" },
{ type: "uint48", name: "validUntil" },
{ type: "uint48", name: "validAfter" }
],
[
userOp.sender,
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
pad(userOp.accountGasLimits, { size: 32 }),
keccak256(encodeAbiParameters(
[{ type: "bytes" }, { type: "bytes" }],
[getRepayToken(userOp.sender), getSponsorTokens(userOp.sender, chain)]
)),
encodePacked(["uint128", "uint128"], [userOp.preVerificationGas, 100000n]),
userOp.preVerificationGas,
pad(userOp.gasFees, { size: 32 }),
BigInt(chainIDs[chain]),
paymasters[chain] as Address,
Number(validUntil),
Number(validAfter)
]
)


console.log("encodedData", encodedData);
const userOpHash = keccak256(encodedData);
console.log("userOpHash", userOpHash);
return userOpHash;
}

function getRepayToken(sender: Address) {
Expand Down
7 changes: 6 additions & 1 deletion src/mocks/DemoNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ contract DemoNFT is Ownable, AccessControl, ERC721URIStorage {
/// Interface functions
////////////////////////////////////////////////////////////////////////

function supportsInterface(bytes4 interfaceId) public view override(ERC721URIStorage, AccessControl) returns (bool) {
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721URIStorage, AccessControl)
returns (bool)
{
return ERC721URIStorage.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
}
}
5 changes: 3 additions & 2 deletions src/paymasters/CABPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
// can't use userOp.hash(), since it contains also the paymasterAndData itself.
address sender = userOp.getSender();
(,, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData);

(bytes calldata repayTokenData, bytes calldata sponsorTokenData,) = parsePaymasterSignature(signature);

return keccak256(
Expand All @@ -106,7 +107,7 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
keccak256(userOp.callData),
userOp.accountGasLimits,
keccak256(abi.encode(repayTokenData, sponsorTokenData)),
uint256(bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_DATA_OFFSET])),
bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_DATA_OFFSET]),
userOp.preVerificationGas,
userOp.gasFees,
block.chainid,
Expand Down Expand Up @@ -138,9 +139,9 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
IERC20(sponsorToken.token).approve(sponsorToken.spender, sponsorToken.amount);
}

// check the invoice
bytes32 invoiceId =
invoiceManager.getInvoiceId(userOp.getSender(), address(this), userOp.nonce, block.chainid, repayTokens);

bytes32 hash = MessageHashUtils.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter));

// don't revert on signature failure: return SIG_VALIDATION_FAILED
Expand Down
46 changes: 38 additions & 8 deletions test/CABPaymater.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {ICrossL2Prover} from "@vibc-core-smart-contracts/contracts/interfaces/IC

contract CABPaymasterTest is Test {
uint256 immutable BASE_CHAIN_ID = 8453;
uint256 immutable OPTIMISM_CHAIN_ID = 11155420;
uint256 immutable PAYMSTER_BASE_MOCK_ERC20_BALANCE = 100000;

CABPaymaster public paymaster;
Expand Down Expand Up @@ -85,8 +86,7 @@ contract CABPaymasterTest is Test {
function getEncodedSponsorTokens(uint8 len) internal returns (bytes memory encodedSponsorToken) {
IPaymasterVerifier.SponsorToken[] memory sponsorTokens = new IPaymasterVerifier.SponsorToken[](len);
for (uint8 i = 0; i < len; i++) {
sponsorTokens[i] =
IPaymasterVerifier.SponsorToken({token: address(mockERC20), spender: rekt, amount: 10000});
sponsorTokens[i] = IPaymasterVerifier.SponsorToken({token: address(mockERC20), spender: rekt, amount: 500});
encodedSponsorToken = bytes.concat(
encodedSponsorToken,
bytes20(sponsorTokens[i].token),
Expand All @@ -101,7 +101,7 @@ contract CABPaymasterTest is Test {
IInvoiceManager.RepayTokenInfo[] memory repayTokens = new IInvoiceManager.RepayTokenInfo[](len);
for (uint8 i = 0; i < len; i++) {
repayTokens[i] =
IInvoiceManager.RepayTokenInfo({vault: openfortVault, amount: 10000, chainId: BASE_CHAIN_ID});
IInvoiceManager.RepayTokenInfo({vault: openfortVault, amount: 500, chainId: OPTIMISM_CHAIN_ID});
encodedRepayToken = bytes.concat(
encodedRepayToken,
bytes20(address(repayTokens[i].vault)),
Expand All @@ -114,8 +114,9 @@ contract CABPaymasterTest is Test {

function testValidateUserOp() public {
vm.chainId(BASE_CHAIN_ID);
bytes memory sponsorTokensBytes = getEncodedSponsorTokens(2);
bytes memory repayTokensBytes = getEncodedRepayTokens(2);
bytes memory sponsorTokensBytes = getEncodedSponsorTokens(1);
bytes memory repayTokensBytes = getEncodedRepayTokens(1);

uint48 validUntil = 1732810044 + 1000;
uint48 validAfter = 1732810044;
uint128 preVerificationGas = 1e5;
Expand All @@ -133,7 +134,7 @@ contract CABPaymasterTest is Test {

PackedUserOperation memory userOp = PackedUserOperation({
sender: rekt,
nonce: 0,
nonce: 31994562304018791559173496635392,
initCode: "",
callData: "",
accountGasLimits: bytes32(uint256(1e18)),
Expand All @@ -143,15 +144,34 @@ contract CABPaymasterTest is Test {
signature: ""
});

console.log("encodedData");
console.logBytes(
abi.encode(
userOp.sender,
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.accountGasLimits,
keccak256(abi.encode(repayTokensBytes, sponsorTokensBytes)),
bytes32(abi.encodePacked(preVerificationGas, postVerificationGas)),
userOp.preVerificationGas,
userOp.gasFees,
block.chainid,
address(paymaster),
validUntil,
validAfter
)
);

bytes32 userOpHash = keccak256(
abi.encode(
userOp.sender,
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.accountGasLimits,
keccak256(abi.encode(sponsorTokensBytes, repayTokensBytes)),
uint256(bytes32(abi.encodePacked(preVerificationGas, postVerificationGas))),
keccak256(abi.encode(repayTokensBytes, sponsorTokensBytes)),
bytes32(abi.encodePacked(preVerificationGas, postVerificationGas)),
userOp.preVerificationGas,
userOp.gasFees,
block.chainid,
Expand All @@ -165,7 +185,17 @@ contract CABPaymasterTest is Test {
vm.sign(verifyingSignerPrivateKey, MessageHashUtils.toEthSignedMessageHash(userOpHash));
// Append signature to paymasterAndData
bytes memory signature = abi.encodePacked(r, s, v);

userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, signature);

console.log("userOp.paymasterAndData");
console.logBytes(userOp.paymasterAndData);

console.log("address paymaster", address(paymaster));

// DEMO paymasterAndData with sponsorToken address replaced by MockToken address to pass the approve in the paymaster
// userOp.paymasterAndData = hex"A1D5C22d20C41998026e32dEE0eaF9cbC56f7d6E00000000000000000000000000000000000000000000000000000000000186a0000001363a9600000126f856018e2048c85Eae2a4443408C284221B33e6190646300000000000000000000000000000000000000000000000000000000000001f40000000000000000000000000000000000000000000000000000000000aa37dc01a0Cb889707d426A7A386870A03bc70d1b069759812c3f9EA0D2b4bb098216C8456BEc7748982081900000000000000000000000000000000000000000000000000000000000001f459644ed81cb971f32335b3b06c6114ddebda7b11fa5b721121392a7b9cfcc9ed05f0cbf5bf814ca8bd7e72ac661aea62323119b190c5493ee3cd5d48d1f262e61c";

vm.startPrank(address(entryPoint));
(bytes memory context, uint256 validationData) =
paymaster.validatePaymasterUserOp(userOp, userOpHash, type(uint256).max);
Expand Down

0 comments on commit a96bef8

Please sign in to comment.