Skip to content

Commit

Permalink
feat: aa-core smart account provider fee options to handle more syste…
Browse files Browse the repository at this point in the history
…matic fee options for userops (#276)

* feat: aa-core smart account provider fee options to handle more systematic fee options for userops

* feat: remove fee options middleware and expose fee options to middleware functions

* feat: aa-alchemy ergonomic support for handling gas estimation (#243)

* feat: support updated overrides for alchemy request gas and paymaster and data

* feat: rebase parent where account middle func def is updated

* Update site/packages/aa-alchemy/middleware/withAlchemyGasManager.md

Co-authored-by: Ajay Vasisht <[email protected]>

* Update packages/core/src/utils/index.ts

Co-authored-by: Ajay Vasisht <[email protected]>

* Update packages/core/src/utils/index.ts

Co-authored-by: Ajay Vasisht <[email protected]>

* Update packages/core/src/provider/base.ts

Co-authored-by: Michael Moldoveanu <[email protected]>

* fix: alchemy gas manager 0x check for user op overrides

---------

Co-authored-by: Ajay Vasisht <[email protected]>
Co-authored-by: Michael Moldoveanu <[email protected]>
  • Loading branch information
3 people committed Dec 1, 2023
1 parent c2c1637 commit 33b8312
Show file tree
Hide file tree
Showing 23 changed files with 775 additions and 348 deletions.
54 changes: 23 additions & 31 deletions packages/accounts/src/light-account/e2e-tests/light-account.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { AlchemyProvider } from "@alchemy/aa-alchemy";
import { LocalAccountSigner, type SmartAccountSigner } from "@alchemy/aa-core";
import {
LocalAccountSigner,
Logger,
LogLevel,
type SmartAccountSigner,
type UserOperationFeeOptions,
} from "@alchemy/aa-core";
import {
isAddress,
type Address,
type Chain,
type HDAccount,
type Hash,
type HDAccount,
} from "viem";
import { generatePrivateKey } from "viem/accounts";
import { sepolia } from "viem/chains";
import {
LightSmartContractAccount,
getDefaultLightAccountFactoryAddress,
LightSmartContractAccount,
} from "../../index.js";
import {
API_KEY,
Expand All @@ -22,6 +28,8 @@ import {

const chain = sepolia;

Logger.setLogLevel(LogLevel.DEBUG);

describe("Light Account Tests", () => {
const owner: SmartAccountSigner<HDAccount> =
LocalAccountSigner.mnemonicToAccountSigner(LIGHT_ACCOUNT_OWNER_MNEMONIC);
Expand Down Expand Up @@ -107,7 +115,7 @@ describe("Light Account Tests", () => {
);

await expect(txnHash).resolves.not.toThrowError();
}, 50000);
}, 100000);

it("should fail to execute if account address is not deployed and not correct", async () => {
const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F";
Expand Down Expand Up @@ -147,15 +155,6 @@ describe("Light Account Tests", () => {
});

it("should transfer ownership successfully", async () => {
const provider = givenConnectedProvider({
owner,
chain,
feeOpts: {
baseFeeBufferPercent: 50n,
maxPriorityFeeBufferPercent: 50n,
preVerificationGasBufferPercent: 50n,
},
});
// create a throwaway address
const throwawayOwner = LocalAccountSigner.privateKeyToAccountSigner(
generatePrivateKey()
Expand All @@ -165,17 +164,6 @@ describe("Light Account Tests", () => {
chain,
});

// fund the throwaway address
const fundThrowawayResult = await provider.sendUserOperation({
target: await throwawayProvider.getAddress(),
data: "0x",
value: 10000000000000n,
});
const fundThrowawayTxnHash = provider.waitForUserOperationTransaction(
fundThrowawayResult.hash
);
await expect(fundThrowawayTxnHash).resolves.not.toThrowError();

// create new owner and transfer ownership
const newThrowawayOwner = LocalAccountSigner.privateKeyToAccountSigner(
generatePrivateKey()
Expand All @@ -184,6 +172,7 @@ describe("Light Account Tests", () => {
throwawayProvider,
newThrowawayOwner
);

const txnHash = throwawayProvider.waitForUserOperationTransaction(result);
await expect(txnHash).resolves.not.toThrowError();

Expand All @@ -200,21 +189,24 @@ const givenConnectedProvider = ({
owner,
chain,
accountAddress,
feeOpts,
feeOptions,
}: {
owner: SmartAccountSigner;
chain: Chain;
accountAddress?: Address;
feeOpts?: {
baseFeeBufferPercent?: bigint;
maxPriorityFeeBufferPercent?: bigint;
preVerificationGasBufferPercent?: bigint;
};
feeOptions?: UserOperationFeeOptions;
}) => {
const provider = new AlchemyProvider({
apiKey: API_KEY!,
chain,
feeOpts,
opts: {
txMaxRetries: 10,
feeOptions: {
maxFeePerGas: { percentage: 100 },
maxPriorityFeePerGas: { percentage: 100 },
...feeOptions,
},
},
}).connect(
(rpcClient) =>
new LightSmartContractAccount({
Expand Down
100 changes: 56 additions & 44 deletions packages/alchemy/e2e-tests/simple-account.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { UserOperationOverrides } from "@alchemy/aa-core";
import {
LogLevel,
Logger,
SimpleSmartContractAccount,
getDefaultSimpleAccountFactoryAddress,
type SmartAccountSigner,
type UserOperationFeeOptions,
} from "@alchemy/aa-core";
import { Alchemy, Network } from "alchemy-sdk";
import {
Expand All @@ -19,6 +23,8 @@ import { API_KEY, OWNER_MNEMONIC, PAYMASTER_POLICY_ID } from "./constants.js";
const chain = sepolia;
const network = Network.ETH_SEPOLIA;

Logger.setLogLevel(LogLevel.DEBUG);

describe("Simple Account Tests", () => {
const ownerAccount = mnemonicToAccount(OWNER_MNEMONIC);
const owner: SmartAccountSigner<HDAccount> = {
Expand Down Expand Up @@ -50,7 +56,7 @@ describe("Simple Account Tests", () => {
);

await expect(txnHash).resolves.not.toThrowError();
}, 50000);
}, 100000);

it("should fail to execute if account address is not deployed and not correct", async () => {
const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F";
Expand Down Expand Up @@ -81,10 +87,14 @@ describe("Simple Account Tests", () => {
);

await expect(txnHash).resolves.not.toThrowError();
}, 50000);
}, 100000);

it("should successfully override fees in alchemy paymaster", async () => {
const provider = givenConnectedProvider({ owner, chain })
const provider = givenConnectedProvider({
owner,
chain,
feeOptions: { maxFeePerGas: undefined, maxPriorityFeePerGas: undefined },
})
.withAlchemyGasManager({
policyId: PAYMASTER_POLICY_ID,
})
Expand All @@ -101,7 +111,7 @@ describe("Simple Account Tests", () => {
data: "0x",
})
).rejects.toThrow();
}, 50000);
}, 100000);

it("should support overrides for buildUserOperation", async () => {
const signer = givenConnectedProvider({
Expand All @@ -111,11 +121,12 @@ describe("Simple Account Tests", () => {
policyId: PAYMASTER_POLICY_ID,
});

const overrides = {
const overrides: UserOperationOverrides = {
maxFeePerGas: 100000000n,
maxPriorityFeePerGas: 100000000n,
paymasterAndData: "0x",
};

const uoStruct = await signer.buildUserOperation(
{
target: await signer.getAddress(),
Expand All @@ -129,16 +140,16 @@ describe("Simple Account Tests", () => {
overrides.maxPriorityFeePerGas
);
expect(uoStruct.paymasterAndData).toEqual(overrides.paymasterAndData);
}, 50000);
}, 100000);

it("should successfully use paymaster with fee opts", async () => {
const provider = givenConnectedProvider({
owner,
chain,
feeOpts: {
baseFeeBufferPercent: 50n,
maxPriorityFeeBufferPercent: 50n,
preVerificationGasBufferPercent: 50n,
feeOptions: {
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 50 },
preVerificationGas: { percentage: 50 },
},
});

Expand All @@ -151,7 +162,7 @@ describe("Simple Account Tests", () => {
);

await expect(txnHash).resolves.not.toThrowError();
}, 50000);
}, 100000);

it("should execute successfully via drop and replace", async () => {
const provider = givenConnectedProvider({
Expand All @@ -171,7 +182,7 @@ describe("Simple Account Tests", () => {
replacedResult.hash
);
await expect(txnHash).resolves.not.toThrowError();
}, 50000);
}, 100000);

it("should execute successfully via drop and replace when using paymaster", async () => {
const provider = givenConnectedProvider({
Expand All @@ -193,7 +204,7 @@ describe("Simple Account Tests", () => {
replacedResult.hash
);
await expect(txnHash).resolves.not.toThrowError();
}, 50000);
}, 100000);

it("should get token balances for the smart account", async () => {
const alchemy = new Alchemy({
Expand All @@ -212,7 +223,7 @@ describe("Simple Account Tests", () => {
const address = await provider.getAddress();
const balances = await provider.core.getTokenBalances(address);
expect(balances.tokenBalances.length).toMatchInlineSnapshot(`4`);
}, 50000);
}, 100000);

it("should get owned nfts for the smart account", async () => {
const alchemy = new Alchemy({
Expand All @@ -231,7 +242,7 @@ describe("Simple Account Tests", () => {
const address = await provider.getAddress();
const nfts = await provider.nft.getNftsForOwner(address);
expect(nfts.ownedNfts).toMatchInlineSnapshot("[]");
}, 50000);
}, 100000);

it("should correctly simulate asset changes for the user operation", async () => {
const provider = givenConnectedProvider({
Expand All @@ -246,28 +257,29 @@ describe("Simple Account Tests", () => {
value: 1n,
});

expect(simulatedAssetChanges).toMatchInlineSnapshot(`
expect(simulatedAssetChanges.changes.length).toEqual(2);
expect(
simulatedAssetChanges.changes.map((change) => ({
assertType: change.assetType,
changeType: change.changeType,
from: change.from.toLowerCase(),
to: change.to.toLowerCase(),
}))
).toEqual([
{
"changes": [
{
"amount": "0.000000000000000001",
"assetType": "NATIVE",
"changeType": "TRANSFER",
"contractAddress": null,
"decimals": 18,
"from": "0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4",
"logo": "https://static.alchemyapi.io/images/network-assets/eth.png",
"name": "Ethereum",
"rawAmount": "1",
"symbol": "ETH",
"to": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
"tokenId": null,
},
],
"error": null,
}
`);
}, 50000);
assertType: "NATIVE",
changeType: "TRANSFER",
from: (await provider.getAddress()).toLowerCase(),
to: provider.getEntryPointAddress().toLowerCase(),
},
{
assertType: "NATIVE",
changeType: "TRANSFER",
from: (await provider.getAddress()).toLowerCase(),
to: provider.getEntryPointAddress().toLowerCase(),
},
]);
}, 100000);

it("should simulate as part of middleware stack when added to provider", async () => {
const provider = givenConnectedProvider({
Expand All @@ -284,30 +296,30 @@ describe("Simple Account Tests", () => {
});

expect(spy).toHaveBeenCalledOnce();
}, 50000);
}, 100000);
});

const givenConnectedProvider = ({
owner,
chain,
accountAddress,
feeOpts,
feeOptions,
}: {
owner: SmartAccountSigner;
chain: Chain;
accountAddress?: Address;
feeOpts?: {
baseFeeBufferPercent?: bigint;
maxPriorityFeeBufferPercent?: bigint;
preVerificationGasBufferPercent?: bigint;
};
feeOptions?: UserOperationFeeOptions;
}) =>
new AlchemyProvider({
apiKey: API_KEY!,
chain,
feeOpts,
opts: {
txMaxRetries: 10,
feeOptions: {
...feeOptions,
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 50 },
},
},
}).connect(
(provider) =>
Expand Down
34 changes: 34 additions & 0 deletions packages/alchemy/src/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { UserOperationFeeOptions } from "@alchemy/aa-core";
import type { Chain } from "viem";
import {
arbitrum,
arbitrumGoerli,
arbitrumSepolia,
optimism,
optimismGoerli,
optimismSepolia,
} from "viem/chains";

export const getDefaultUserOperationFeeOptions = (
chain: Chain
): UserOperationFeeOptions => {
const feeOptions: UserOperationFeeOptions = {
maxFeePerGas: { percentage: 50 },
maxPriorityFeePerGas: { percentage: 5 },
};

if (
new Set<number>([
arbitrum.id,
arbitrumGoerli.id,
arbitrumSepolia.id,
optimism.id,
optimismGoerli.id,
optimismSepolia.id,
]).has(chain.id)
) {
feeOptions.preVerificationGas = { percentage: 5 };
}

return feeOptions;
};
Loading

0 comments on commit 33b8312

Please sign in to comment.