Skip to content

Commit

Permalink
WIP: 4337
Browse files Browse the repository at this point in the history
  • Loading branch information
akshay-ap committed Oct 27, 2023
1 parent ac66d1c commit 074d5f5
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 19 deletions.
9 changes: 7 additions & 2 deletions contracts/contracts/ERC4337Plugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ struct UserOperation {
contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetadata {
address public immutable PLUGIN_ADDRESS;
ISafeProtocolManager public immutable SAFE_PROTOCOL_MANAGER;
address public immutable SAFE_PROTOCOL_FUNCTION_HANDLER;

address payable public immutable ENTRY_POINT;
uint256 internal constant SIGNATURE_VALID = 0;
// value in case of signature failure, with no time-range.
Expand All @@ -71,11 +73,13 @@ contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetad

constructor(
ISafeProtocolManager safeCoreProtocolManager,
address safeCoreProtocolFunctionHandler,
address payable entryPoint
) BasePluginWithEventMetadata(PluginMetadata({name: "ERC4337 Plugin", version: "1.0.0", permissions: 1, iconUrl: "", appUrl: ""})) {
PLUGIN_ADDRESS = address(this);
SAFE_PROTOCOL_MANAGER = safeCoreProtocolManager;
ENTRY_POINT = entryPoint;
SAFE_PROTOCOL_FUNCTION_HANDLER = safeCoreProtocolFunctionHandler;
}

function validateUserOp(
Expand Down Expand Up @@ -137,9 +141,10 @@ contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetad
require(address(this) != PLUGIN_ADDRESS, "Only delegatecall");

ISafe safe = ISafe(address(this));
safe.setFallbackHandler(address(SAFE_PROTOCOL_MANAGER));
safe.setFallbackHandler(SAFE_PROTOCOL_FUNCTION_HANDLER);
safe.enableModule(address(SAFE_PROTOCOL_MANAGER));
safe.enablePlugin(PLUGIN_ADDRESS, MODULE_TYPE_PLUGIN);

ISafe(address(SAFE_PROTOCOL_MANAGER)).enablePlugin(PLUGIN_ADDRESS, MODULE_TYPE_PLUGIN);
safe.setFunctionHandler(this.validateUserOp.selector, PLUGIN_ADDRESS);
safe.setFunctionHandler(this.execTransaction.selector, PLUGIN_ADDRESS);
}
Expand Down
8 changes: 7 additions & 1 deletion contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const argv : any = yargs
.help(false)
.version(false).argv;

const { NODE_URL, MNEMONIC, INFURA_KEY, ETHERSCAN_API_KEY} = process.env;
const { NODE_URL, MNEMONIC, INFURA_KEY, ETHERSCAN_API_KEY, ERC4337_TEST_SAFE_CORE_PROTOCOL_FUNCTION_HANDLER, ERC4337_TEST_SAFE_CORE_PROTOCOL_MANAGER_ADDRESS} = process.env;

import "./src/tasks/test_registry"

Expand Down Expand Up @@ -91,6 +91,12 @@ const config: HardhatUserConfig = {
},
recoverer: {
default: 1
},
safeCoreProtocolManager: {
default: ERC4337_TEST_SAFE_CORE_PROTOCOL_MANAGER_ADDRESS || 2
},
safeCoreProtocolFunctionHandler: {
default: ERC4337_TEST_SAFE_CORE_PROTOCOL_FUNCTION_HANDLER || ""
}
}
};
Expand Down
8 changes: 7 additions & 1 deletion contracts/src/deploy/deploy_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ZeroAddress } from "ethers";

const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deployer, recoverer } = await getNamedAccounts();
const { deployer, recoverer, safeCoreProtocolManager, safeCoreProtocolFunctionHandler } = await getNamedAccounts();
const { deploy } = deployments;

// execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)
Expand Down Expand Up @@ -35,6 +35,12 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
deterministicDeployment: true,
});

await deploy("ERC4337Plugin", {
from: deployer,
args: [safeCoreProtocolManager, safeCoreProtocolFunctionHandler, "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"],
log: true,
deterministicDeployment: true,
});
};

deploy.tags = ["plugins"];
Expand Down
49 changes: 34 additions & 15 deletions contracts/test/ERC4337Plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const ERC4337_TEST_ENV_VARIABLES_DEFINED =
typeof process.env.ERC4337_TEST_SINGLETON_ADDRESS !== "undefined" &&
typeof process.env.ERC4337_TEST_MNEMONIC !== "undefined" &&
typeof process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_MANAGER_ADDRESS !== "undefined" &&
typeof process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_REGISTRY !== "undefined";
typeof process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_REGISTRY !== "undefined" &&
typeof process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_FUNCTION_HANDLER !== "undefined";

const itif = ERC4337_TEST_ENV_VARIABLES_DEFINED ? it : it.skip;
const SAFE_FACTORY_ADDRESS = process.env.ERC4337_TEST_SAFE_FACTORY_ADDRESS;
Expand All @@ -25,6 +26,7 @@ const BUNDLER_URL = process.env.ERC4337_TEST_BUNDLER_URL;
const NODE_URL = process.env.ERC4337_TEST_NODE_URL;
const MNEMONIC = process.env.ERC4337_TEST_MNEMONIC;
const SAFE_CORE_PROTOCOL_MANAGER_ADDRESS = process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_MANAGER_ADDRESS;
const SAFE_CORE_PROTOCOL_FUNCTION_HANDLER = process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_FUNCTION_HANDLER || ZeroAddress;
const SAFE_CORE_PROTOCOL_REGISTRY = process.env.ERC4337_TEST_SAFE_CORE_PROTOCOL_REGISTRY;
const FOUR337_PLUGIN_ADDRESS = process.env.ERC4337_TEST_4337_PLUGIN_ADDRESS;

Expand Down Expand Up @@ -77,9 +79,21 @@ describe.only("ERC4337 Plugin", () => {
const safeProtocolManager = await ethers
.getContractFactory("SafeProtocolManager")
.then((factory) => factory.deploy(signers[0].address, testRegistryDeployment.getAddress()));

// This code should be uncommented after release a npm package that has separate FunctionHandlerManager
// const safeProtocolFunctionHandler = await ethers
// .getContractFactory("FunctionHandlerManager")
// .then((factory) => factory.deploy(signers[0].address, testRegistryDeployment.getAddress()));

const safeProtocolFunctionHandler = await ethers.deployContract("MockContract");

const erc4337Plugin = await ethers
.getContractFactory("ERC4337Plugin")
.then((factory) => factory.deploy(safeProtocolManager.getAddress(), entryPoint.getAddress()));
.then((factory) =>
factory.deploy(safeProtocolManager.getAddress(), safeProtocolFunctionHandler.target, entryPoint.getAddress()),
);

console.log("erc4337 plugin", erc4337Plugin.target )

await testRegistryDeployment
.addModule(erc4337Plugin.getAddress(), ModuleType.Plugin + ModuleType.FunctionHandler)
Expand Down Expand Up @@ -140,10 +154,11 @@ describe.only("ERC4337 Plugin", () => {
console.log(`Using 4337 Plugin address from the environment variable`);
erc4337Plugin = await hre.ethers.getContractAt("ERC4337Plugin", FOUR337_PLUGIN_ADDRESS, wallet);
} else {

console.log(`Deploying ERC4337Plugin...`);
erc4337Plugin = await ethers
.getContractFactory("ERC4337Plugin", wallet)
.then((factory) => factory.deploy(SAFE_CORE_PROTOCOL_MANAGER_ADDRESS, entryPoints[0]))
.then((factory) => factory.deploy(SAFE_CORE_PROTOCOL_MANAGER_ADDRESS, SAFE_CORE_PROTOCOL_FUNCTION_HANDLER, entryPoints[0]))
.then((tx) => tx.waitForDeployment());
await sleep(10000);
console.log(`Deployed ERC4337Plugin at ${await erc4337Plugin.getAddress()}`);
Expand Down Expand Up @@ -171,14 +186,14 @@ describe.only("ERC4337 Plugin", () => {
};
};

it("should be initialized correctly", async () => {
it.skip("should be initialized correctly", async () => {
const { erc4337Plugin } = await hardhatNetworkSetup();
expect(await erc4337Plugin.name()).to.be.eq("ERC4337 Plugin");
expect(await erc4337Plugin.version()).to.be.eq("1.0.0");
expect(await erc4337Plugin.permissions()).to.be.eq(1);
});

it("can retrieve metadata for the module", async () => {
it.skip("can retrieve metadata for the module", async () => {
const { erc4337Plugin } = await hardhatNetworkSetup();
expect(await loadPluginMetadata(hre, erc4337Plugin)).to.be.deep.eq({
name: "ERC4337 Plugin",
Expand All @@ -189,7 +204,7 @@ describe.only("ERC4337 Plugin", () => {
});
});

it("can validate a signed user operation and send the prefund", async () => {
it.skip("can validate a signed user operation and send the prefund", async () => {
const { erc4337Plugin, signers, safe, safeWithPluginInterface, entryPoint } = await hardhatNetworkSetup();

const userOperation: UserOperation = {
Expand Down Expand Up @@ -229,7 +244,7 @@ describe.only("ERC4337 Plugin", () => {
expect(await hre.ethers.provider.getBalance(safe)).to.be.eq(0);
});

it("rejects a signed user operation if the signature is invalid", async () => {
it.skip("rejects a signed user operation if the signature is invalid", async () => {
const { erc4337Plugin, signers, safe, safeWithPluginInterface, entryPoint } = await hardhatNetworkSetup();

const userOperation: UserOperation = {
Expand Down Expand Up @@ -262,7 +277,7 @@ describe.only("ERC4337 Plugin", () => {
).to.be.equal(1);
});

it("can execute a transaction coming from the entrypoint", async () => {
it.skip("can execute a transaction coming from the entrypoint", async () => {
const { signers, safe, safeWithPluginInterface, entryPoint } = await hardhatNetworkSetup();
const randomAddress = hre.ethers.hexlify(hre.ethers.randomBytes(20));
const oneEther = 10n ** 18n;
Expand All @@ -273,7 +288,7 @@ describe.only("ERC4337 Plugin", () => {
expect(await hre.ethers.provider.getBalance(randomAddress)).to.be.eq(oneEther);
});

it("rejects validation requests coming from an address that is not the entrypoint", async () => {
it.skip("rejects validation requests coming from an address that is not the entrypoint", async () => {
const { erc4337Plugin, signers, safe, safeWithPluginInterface } = await hardhatNetworkSetup();

const userOperation: UserOperation = {
Expand Down Expand Up @@ -306,7 +321,7 @@ describe.only("ERC4337 Plugin", () => {
).to.be.revertedWith("Only entrypoint");
});

it("rejects execution requests coming from an address that is not the entrypoint", async () => {
it.skip("rejects execution requests coming from an address that is not the entrypoint", async () => {
const { signers, safe, safeWithPluginInterface } = await hardhatNetworkSetup();
const randomAddress = hre.ethers.hexlify(hre.ethers.randomBytes(20));
const oneEther = 10n ** 18n;
Expand Down Expand Up @@ -387,7 +402,11 @@ describe.only("ERC4337 Plugin", () => {
ZeroAddress,
]);
console.log(`Obtaining safe address...`);
const safeAddress = await safeProxyFactory.createProxyWithNonce.staticCall(await safeSingleton.getAddress(), initializer, 73);
const safeAddress = await safeProxyFactory.createProxyWithNonce.staticCall(
await safeSingleton.getAddress(),
initializer,
73,
);
console.log({ safeAddress });

const initCode =
Expand All @@ -401,9 +420,9 @@ describe.only("ERC4337 Plugin", () => {
sender: safeAddress,
nonce: 0,
callData: "0x",
callGasLimit: 12100,
verificationGasLimit: 512694,
preVerificationGas: 51938,
callGasLimit: 500000,
verificationGasLimit: 600000,
preVerificationGas: 200000,
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndData: "0x",
Expand All @@ -423,7 +442,7 @@ describe.only("ERC4337 Plugin", () => {
// Native tokens for the pre-fund 💸
await wallet.sendTransaction({ to: safeAddress, value: hre.ethers.parseEther("0.005") }).then((tx) => tx.wait(1));
// The bundler uses a different node, so we need to allow it sometime to sync
await sleep(10000);
await sleep(20000);

const operation = await bundlerProvider.send("eth_sendUserOperation", [userOperation, await entryPoint.getAddress()]);

Expand Down

0 comments on commit 074d5f5

Please sign in to comment.