mirror of
https://github.com/luxfi/eoa-contracts.git
synced 2026-10-30 16:58:32 +00:00
300 lines
8.7 KiB
TypeScript
300 lines
8.7 KiB
TypeScript
import { ethers, deployments } from "hardhat";
|
|
import {
|
|
BundlerTestEnvironment,
|
|
UserOperationSubmissionError,
|
|
} from "./bundlerEnvironment";
|
|
import { expect } from "chai";
|
|
import {
|
|
getEcdsaOwnershipRegistryModule,
|
|
getEntryPoint,
|
|
getMockToken,
|
|
getSmartAccountFactory,
|
|
getSmartAccountImplementation,
|
|
getSmartAccountWithModule,
|
|
} from "../../utils/setupHelper";
|
|
import {
|
|
EcdsaOwnershipRegistryModule__factory,
|
|
ForbiddenOpcodeInvokingAuthModule__factory,
|
|
} from "../../../typechain";
|
|
import { makeEcdsaModuleUserOp } from "../../utils/userOp";
|
|
import { encodeTransfer } from "../../utils/testUtils";
|
|
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
|
|
|
describe("Bundler Environment", async () => {
|
|
let signers: SignerWithAddress[];
|
|
let deployer: SignerWithAddress,
|
|
alice: SignerWithAddress,
|
|
bob: SignerWithAddress,
|
|
charlie: SignerWithAddress,
|
|
smartAccountOwner: SignerWithAddress;
|
|
let environment: BundlerTestEnvironment;
|
|
|
|
const setupTests = deployments.createFixture(async ({ deployments }) => {
|
|
await deployments.fixture();
|
|
|
|
const mockToken = await getMockToken();
|
|
|
|
const ecdsaModule = await getEcdsaOwnershipRegistryModule();
|
|
const ecdsaOwnershipSetupData =
|
|
EcdsaOwnershipRegistryModule__factory.createInterface().encodeFunctionData(
|
|
"initForSmartAccount",
|
|
[await smartAccountOwner.getAddress()]
|
|
);
|
|
|
|
const smartAccountDeploymentIndex = 0;
|
|
|
|
const userSA = await getSmartAccountWithModule(
|
|
ecdsaModule.address,
|
|
ecdsaOwnershipSetupData,
|
|
smartAccountDeploymentIndex
|
|
);
|
|
|
|
await deployer.sendTransaction({
|
|
to: userSA.address,
|
|
value: ethers.utils.parseEther("10"),
|
|
});
|
|
|
|
await mockToken.mint(userSA.address, ethers.utils.parseEther("1000000"));
|
|
|
|
const entryPoint = await getEntryPoint();
|
|
|
|
return {
|
|
entryPoint,
|
|
smartAccountImplementation: await getSmartAccountImplementation(),
|
|
smartAccountFactory: await getSmartAccountFactory(),
|
|
mockToken: mockToken,
|
|
ecdsaModule: ecdsaModule,
|
|
userSA: userSA,
|
|
};
|
|
});
|
|
|
|
before(async function () {
|
|
const chainId = (await ethers.provider.getNetwork()).chainId;
|
|
if (chainId !== BundlerTestEnvironment.BUNDLER_ENVIRONMENT_CHAIN_ID) {
|
|
this.skip();
|
|
}
|
|
|
|
environment = await BundlerTestEnvironment.getDefaultInstance();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
signers = await ethers.getSigners();
|
|
[deployer, alice, bob, charlie, smartAccountOwner] = signers;
|
|
});
|
|
|
|
afterEach(async function () {
|
|
const chainId = (await ethers.provider.getNetwork()).chainId;
|
|
if (chainId !== BundlerTestEnvironment.BUNDLER_ENVIRONMENT_CHAIN_ID) {
|
|
this.skip();
|
|
}
|
|
|
|
await Promise.all([
|
|
environment.revert(environment.defaultSnapshot!),
|
|
environment.resetBundler(),
|
|
]);
|
|
});
|
|
|
|
it("Default Signers should have funds after environment setup", async () => {
|
|
for (const signer of signers) {
|
|
expect(await ethers.provider.getBalance(signer.address)).to.be.gte(
|
|
BundlerTestEnvironment.DEFAULT_FUNDING_AMOUNT
|
|
);
|
|
}
|
|
});
|
|
|
|
it("Should be able to revert to snapshot", async () => {
|
|
await setupTests();
|
|
|
|
const aliceBalance = await ethers.provider.getBalance(alice.address);
|
|
const bobBalance = await ethers.provider.getBalance(bob.address);
|
|
|
|
const snapshot = await environment.snapshot();
|
|
|
|
await expect(
|
|
alice.sendTransaction({
|
|
to: bob.address,
|
|
value: ethers.utils.parseEther("1"),
|
|
})
|
|
).to.not.be.reverted;
|
|
|
|
await environment.revert(snapshot);
|
|
|
|
expect(await ethers.provider.getBalance(alice.address)).to.eq(aliceBalance);
|
|
expect(await ethers.provider.getBalance(bob.address)).to.eq(bobBalance);
|
|
});
|
|
|
|
it("Should be able to submit user operation using bundler", async () => {
|
|
const { entryPoint, mockToken, userSA, ecdsaModule } = await setupTests();
|
|
|
|
const charlieTokenBalanceBefore = await mockToken.balanceOf(
|
|
charlie.address
|
|
);
|
|
const tokenAmountToTransfer = ethers.utils.parseEther("0.5345");
|
|
|
|
const userOp = await makeEcdsaModuleUserOp(
|
|
"execute_ncC",
|
|
[
|
|
mockToken.address,
|
|
ethers.utils.parseEther("0"),
|
|
encodeTransfer(charlie.address, tokenAmountToTransfer.toString()),
|
|
],
|
|
userSA.address,
|
|
smartAccountOwner,
|
|
entryPoint,
|
|
ecdsaModule.address,
|
|
{
|
|
preVerificationGas: 50000,
|
|
}
|
|
);
|
|
|
|
await environment.sendUserOperation(userOp, entryPoint.address);
|
|
|
|
expect(await mockToken.balanceOf(charlie.address)).to.equal(
|
|
charlieTokenBalanceBefore.add(tokenAmountToTransfer)
|
|
);
|
|
});
|
|
|
|
const setupTestsWithRecusantValidationModule = deployments.createFixture(
|
|
async ({ deployments }) => {
|
|
await deployments.fixture();
|
|
|
|
const mockToken = await getMockToken();
|
|
|
|
const ecdsaModule = await new ForbiddenOpcodeInvokingAuthModule__factory(
|
|
deployer
|
|
).deploy();
|
|
const ecdsaOwnershipSetupData =
|
|
ForbiddenOpcodeInvokingAuthModule__factory.createInterface().encodeFunctionData(
|
|
"initForSmartAccount",
|
|
[await smartAccountOwner.getAddress()]
|
|
);
|
|
|
|
const smartAccountDeploymentIndex = 0;
|
|
|
|
const userSA = await getSmartAccountWithModule(
|
|
ecdsaModule.address,
|
|
ecdsaOwnershipSetupData,
|
|
smartAccountDeploymentIndex
|
|
);
|
|
|
|
await deployer.sendTransaction({
|
|
to: userSA.address,
|
|
value: ethers.utils.parseEther("10"),
|
|
});
|
|
|
|
await mockToken.mint(userSA.address, ethers.utils.parseEther("1000000"));
|
|
|
|
const entryPoint = await getEntryPoint();
|
|
|
|
return {
|
|
entryPoint,
|
|
smartAccountImplementation: await getSmartAccountImplementation(),
|
|
smartAccountFactory: await getSmartAccountFactory(),
|
|
mockToken: mockToken,
|
|
ecdsaModule: ecdsaModule,
|
|
userSA: userSA,
|
|
};
|
|
}
|
|
);
|
|
|
|
it("Should not submit user operation that calls TIMESTAMP in the validation phase", async () => {
|
|
const { entryPoint, mockToken, userSA, ecdsaModule } =
|
|
await setupTestsWithRecusantValidationModule();
|
|
const tokenAmountToTransfer = ethers.utils.parseEther("0.5345");
|
|
|
|
const userOp = await makeEcdsaModuleUserOp(
|
|
"execute_ncC",
|
|
[
|
|
mockToken.address,
|
|
ethers.utils.parseEther("0"),
|
|
encodeTransfer(charlie.address, tokenAmountToTransfer.toString()),
|
|
],
|
|
userSA.address,
|
|
smartAccountOwner,
|
|
entryPoint,
|
|
ecdsaModule.address,
|
|
{
|
|
preVerificationGas: 50000,
|
|
}
|
|
);
|
|
|
|
const expectedError = new UserOperationSubmissionError(
|
|
'{"message":"account uses banned opcode: TIMESTAMP","code":-32502}'
|
|
);
|
|
let thrownError: Error | null = null;
|
|
|
|
try {
|
|
await environment.sendUserOperation(userOp, entryPoint.address);
|
|
} catch (e) {
|
|
thrownError = e as Error;
|
|
}
|
|
|
|
expect(thrownError).to.deep.equal(expectedError);
|
|
});
|
|
|
|
it("Should not submit userOp which accesses not associated storage in the validation phase", async () => {
|
|
const { entryPoint, mockToken, userSA, ecdsaModule } = await setupTests();
|
|
|
|
const charlieTokenBalanceBefore = await mockToken.balanceOf(
|
|
charlie.address
|
|
);
|
|
const tokenAmountToTransfer = ethers.utils.parseEther("0.54245");
|
|
|
|
const storageAccessViolatingEcdsaModule = await (
|
|
await ethers.getContractFactory("WrongStorageAccessValidationModule")
|
|
).deploy();
|
|
|
|
// enable and initiate the violating module
|
|
const setupCalldata =
|
|
storageAccessViolatingEcdsaModule.interface.encodeFunctionData(
|
|
"initForSmartAccount",
|
|
[await smartAccountOwner.getAddress()]
|
|
);
|
|
|
|
const userOp = await makeEcdsaModuleUserOp(
|
|
"setupAndEnableModule",
|
|
[storageAccessViolatingEcdsaModule.address, setupCalldata],
|
|
userSA.address,
|
|
smartAccountOwner,
|
|
entryPoint,
|
|
ecdsaModule.address,
|
|
{
|
|
preVerificationGas: 50000,
|
|
}
|
|
);
|
|
await environment.sendUserOperation(userOp, entryPoint.address);
|
|
|
|
const violatingUserOp = await makeEcdsaModuleUserOp(
|
|
"execute_ncC",
|
|
[
|
|
mockToken.address,
|
|
ethers.utils.parseEther("0"),
|
|
encodeTransfer(charlie.address, tokenAmountToTransfer.toString()),
|
|
],
|
|
userSA.address,
|
|
smartAccountOwner,
|
|
entryPoint,
|
|
storageAccessViolatingEcdsaModule.address,
|
|
{
|
|
preVerificationGas: 50000,
|
|
}
|
|
);
|
|
|
|
const expectedError = new UserOperationSubmissionError(
|
|
'{"message":"account has forbidden read from'
|
|
);
|
|
let thrownError: Error | null = null;
|
|
|
|
try {
|
|
await environment.sendUserOperation(violatingUserOp, entryPoint.address);
|
|
} catch (e) {
|
|
thrownError = e as Error;
|
|
}
|
|
|
|
expect(thrownError).to.contain(expectedError);
|
|
expect(await mockToken.balanceOf(charlie.address)).to.equal(
|
|
charlieTokenBalanceBefore
|
|
);
|
|
});
|
|
});
|