-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: alchemy gas manager fix for dummy paymaster middleware for entr…
…ypoint v7
- Loading branch information
Showing
18 changed files
with
602 additions
and
108 deletions.
There are no files selected for viewing
362 changes: 362 additions & 0 deletions
362
packages/alchemy/e2e-tests/simple-account-v7.e2e.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,362 @@ | ||
import { | ||
LocalAccountSigner, | ||
createSimpleSmartAccount, | ||
getEntryPoint, | ||
sepolia, | ||
type UserOperationStruct, | ||
} from "@alchemy/aa-core"; | ||
import type { CreateSimpleAccountParams } from "@alchemy/aa-core/dist/types/account/simple.js"; | ||
import { Alchemy, Network } from "alchemy-sdk"; | ||
import { custom } from "viem"; | ||
import * as simulateUoActions from "../src/actions/simulateUserOperationChanges.js"; | ||
import { alchemyEnhancedApiActions } from "../src/client/decorators/alchemyEnhancedApis.js"; | ||
import { createAlchemySmartAccountClientFromRpcClient } from "../src/client/internal/smartAccountClientFromRpc.js"; | ||
import { | ||
createAlchemyPublicRpcClient, | ||
type AlchemySmartAccountClientConfig, | ||
} from "../src/index.js"; | ||
import { | ||
API_KEY, | ||
LIGHT_ACCOUNT_OWNER_MNEMONIC, | ||
PAYMASTER_POLICY_ID, | ||
} from "./constants.js"; | ||
|
||
const simulateUoChangesSpy = vi.spyOn( | ||
simulateUoActions, | ||
"simulateUserOperationChanges" | ||
); | ||
|
||
const chain = sepolia; | ||
const network = Network.ETH_SEPOLIA; | ||
|
||
describe("Simple Account Entrypoint v0.7 with Client Tests", () => { | ||
const signer = LocalAccountSigner.mnemonicToAccountSigner( | ||
LIGHT_ACCOUNT_OWNER_MNEMONIC | ||
); | ||
|
||
it("should successfully get counterfactual address", async () => { | ||
const provider = await givenConnectedProvider({ signer, chain }); | ||
expect(provider.getAddress()).toMatchInlineSnapshot( | ||
'"0xB9db1056b648FD36338dDFb6aF40E891b0266c0d"' | ||
); | ||
}); | ||
|
||
it("should execute successfully", async () => { | ||
const provider = await givenConnectedProvider({ signer, chain }); | ||
|
||
const result = await provider.sendUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
}); | ||
|
||
const txnHash = provider.waitForUserOperationTransaction(result); | ||
|
||
await expect(txnHash).resolves.not.toThrowError(); | ||
}, 100000); | ||
|
||
it("should fail to execute if account address is not deployed and not correct", async () => { | ||
const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F"; | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
accountAddress, | ||
}); | ||
|
||
const result = provider.sendUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
}); | ||
|
||
await expect(result).rejects.toThrowError(); | ||
}); | ||
|
||
it("simple account v7 should successfully execute with alchemy paymaster info", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
}); | ||
|
||
const result = await provider.sendUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
}); | ||
const txnHash = provider.waitForUserOperationTransaction(result); | ||
|
||
await expect(txnHash).resolves.not.toThrowError(); | ||
}, 100000); | ||
|
||
it("should bypass paymaster when paymasterAndData of user operation overrides is set to 0x", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
}); | ||
|
||
const uoStruct = (await provider.buildUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
overrides: { | ||
paymasterAndData: "0x", // bypass paymaster | ||
}, | ||
})) as UserOperationStruct<"0.6.0">; | ||
|
||
expect(uoStruct.paymasterAndData).toBe("0x"); | ||
}, 100000); | ||
|
||
it("should successfully override fees and gas when using paymaster", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
}); | ||
|
||
await expect( | ||
provider | ||
.buildUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
overrides: { | ||
maxFeePerGas: 1n, | ||
maxPriorityFeePerGas: 1n, | ||
callGasLimit: 1n, | ||
verificationGasLimit: 1n, | ||
preVerificationGas: 1n, | ||
}, | ||
}) | ||
.then( | ||
({ | ||
maxFeePerGas, | ||
maxPriorityFeePerGas, | ||
callGasLimit, | ||
verificationGasLimit, | ||
preVerificationGas, | ||
}) => ({ | ||
maxFeePerGas, | ||
maxPriorityFeePerGas, | ||
callGasLimit, | ||
verificationGasLimit, | ||
preVerificationGas, | ||
}) | ||
) | ||
).resolves.toMatchInlineSnapshot(` | ||
{ | ||
"callGasLimit": "0x1", | ||
"maxFeePerGas": "0x1", | ||
"maxPriorityFeePerGas": "0x1", | ||
"preVerificationGas": "0x1", | ||
"verificationGasLimit": "0x1", | ||
} | ||
`); | ||
}, 100000); | ||
|
||
it("should successfully use paymaster with fee opts", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
opts: { | ||
feeOptions: { | ||
maxFeePerGas: { multiplier: 1.5 }, | ||
maxPriorityFeePerGas: { multiplier: 1.5 }, | ||
preVerificationGas: { multiplier: 1.5 }, | ||
}, | ||
}, | ||
}); | ||
|
||
const result = await provider.sendUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
}); | ||
const txnHash = provider.waitForUserOperationTransaction(result); | ||
|
||
await expect(txnHash).resolves.not.toThrowError(); | ||
}, 100000); | ||
|
||
it("should execute successfully via drop and replace", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
}); | ||
|
||
const result = await provider.sendUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
}); | ||
const replacedResult = await provider.dropAndReplaceUserOperation({ | ||
uoToDrop: result.request, | ||
}); | ||
|
||
const txnHash = provider.waitForUserOperationTransaction(replacedResult); | ||
await expect(txnHash).resolves.not.toThrowError(); | ||
}, 100000); | ||
|
||
it("should execute successfully via drop and replace when using paymaster", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
}); | ||
|
||
const result = await provider.sendUserOperation({ | ||
uo: { | ||
target: provider.getAddress(), | ||
data: "0x", | ||
}, | ||
}); | ||
|
||
const replacedResult = await provider.dropAndReplaceUserOperation({ | ||
uoToDrop: result.request, | ||
}); | ||
|
||
const txnHash = provider.waitForUserOperationTransaction(replacedResult); | ||
await expect(txnHash).resolves.not.toThrowError(); | ||
}, 100000); | ||
|
||
it("should get token balances for the smart account", async () => { | ||
const alchemy = new Alchemy({ | ||
apiKey: API_KEY!, | ||
network, | ||
}); | ||
|
||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
}).then((x) => x.extend(alchemyEnhancedApiActions(alchemy))); | ||
|
||
const address = provider.getAddress(); | ||
const balances = await provider.core.getTokenBalances(address); | ||
expect(balances.tokenBalances.length).toMatchInlineSnapshot("1"); | ||
}, 50000); | ||
|
||
it("should get owned nfts for the smart account", async () => { | ||
const alchemy = new Alchemy({ | ||
apiKey: API_KEY!, | ||
network, | ||
}); | ||
|
||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
gasManagerConfig: { | ||
policyId: PAYMASTER_POLICY_ID, | ||
}, | ||
}).then((x) => x.extend(alchemyEnhancedApiActions(alchemy))); | ||
|
||
const address = provider.getAddress(); | ||
const nfts = await provider.nft.getNftsForOwner(address); | ||
expect(nfts.ownedNfts).toMatchInlineSnapshot("[]"); | ||
}, 100000); | ||
|
||
it("should correctly simulate asset changes for the user operation", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
}); | ||
|
||
const simulatedAssetChanges = await provider.simulateUserOperation({ | ||
uo: { | ||
target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", | ||
data: "0x", | ||
value: 1n, | ||
}, | ||
}); | ||
|
||
expect( | ||
simulatedAssetChanges.changes.filter( | ||
(x) => x.to === "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" | ||
) | ||
).toMatchInlineSnapshot(` | ||
[ | ||
{ | ||
"amount": "0.000000000000000001", | ||
"assetType": "NATIVE", | ||
"changeType": "TRANSFER", | ||
"contractAddress": null, | ||
"decimals": 18, | ||
"from": "0x86f3b0211764971ad0fc8c8898d31f5d792fad84", | ||
"logo": "https://static.alchemyapi.io/images/network-assets/eth.png", | ||
"name": "Ethereum", | ||
"rawAmount": "1", | ||
"symbol": "ETH", | ||
"to": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", | ||
"tokenId": null, | ||
}, | ||
] | ||
`); | ||
}, 50000); | ||
|
||
it("should simulate as part of middleware stack when added to provider", async () => { | ||
const provider = await givenConnectedProvider({ | ||
signer, | ||
chain, | ||
useSimulation: true, | ||
}); | ||
|
||
await provider.buildUserOperation({ | ||
uo: { | ||
target: provider.account.getEntryPoint().address, | ||
data: "0x", | ||
value: 1n, | ||
}, | ||
}); | ||
|
||
expect(simulateUoChangesSpy).toHaveBeenCalledOnce(); | ||
}, 100000); | ||
}); | ||
|
||
const givenConnectedProvider = async ({ | ||
chain, | ||
signer, | ||
accountAddress, | ||
opts, | ||
gasManagerConfig, | ||
useSimulation = false, | ||
}: Omit<CreateSimpleAccountParams, "transport"> & | ||
Omit<AlchemySmartAccountClientConfig, "transport">) => { | ||
const client = createAlchemyPublicRpcClient({ | ||
chain, | ||
connectionConfig: { apiKey: API_KEY! }, | ||
}); | ||
const account = await createSimpleSmartAccount({ | ||
chain, | ||
signer, | ||
entryPoint: getEntryPoint(chain, { version: "0.7.0" }), | ||
transport: custom(client), | ||
accountAddress, | ||
}); | ||
return createAlchemySmartAccountClientFromRpcClient({ | ||
client, | ||
account, | ||
opts, | ||
gasManagerConfig, | ||
useSimulation, | ||
}); | ||
}; |
Oops, something went wrong.