Skip to content

Commit

Permalink
hardhat base utils i
Browse files Browse the repository at this point in the history
  • Loading branch information
livingrockrises committed Apr 7, 2024
1 parent 2d832cc commit e6d5769
Show file tree
Hide file tree
Showing 11 changed files with 1,001 additions and 4 deletions.
5 changes: 5 additions & 0 deletions contracts/mocks/Imports.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ pragma solidity ^0.8.24;
/* solhint-disable reason-string */

import "account-abstraction/contracts/core/EntryPoint.sol";
import "account-abstraction/contracts/core/EntryPointSimulations.sol";

import "@biconomy-devx/erc7579-msa/contracts/SmartAccount.sol";
import "@biconomy-devx/erc7579-msa/contracts/factory/AccountFactory.sol";

3 changes: 3 additions & 0 deletions contracts/mocks/MockValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pragma solidity ^0.8.24;

import "@biconomy-devx/erc7579-msa/test/foundry/mocks/MockValidator.sol";
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,40 @@
"url": "https://github.com/bcnmy"
},
"dependencies": {
"@biconomy-devx/erc7579-msa": "^0.0.4",
"@openzeppelin/contracts": "^5.0.1",
"hardhat": "^2.20.1"
},
"devDependencies": {
"@bonadocs/docgen": "^1.0.1-alpha.1",
"@ethersproject/abstract-provider": "^5.7.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.6",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
"@nomicfoundation/hardhat-network-helpers": "^1.0.10",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.4",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@prb/test": "^0.6.4",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.11",
"@types/mocha": ">=10.0.6",
"@types/node": ">=20.11.19",
"account-abstraction": "github:eth-infinitism/account-abstraction#develop",
"chai": "^4.3.7",
"codecov": "^3.8.3",
"ethers": "^6.11.1",
"forge-std": "github:foundry-rs/forge-std#v1.7.6",
"modulekit": "github:rhinestonewtf/modulekit",
"solady": "github:vectorized/solady",
"account-abstraction": "github:eth-infinitism/account-abstraction#develop",
"hardhat-deploy": "^0.11.45",
"hardhat-deploy-ethers": "^0.4.1",
"hardhat-gas-reporter": "^1.0.10",
"hardhat-storage-layout": "^0.1.7",
"modulekit": "github:rhinestonewtf/modulekit",
"prettier": "^3.2.5",
"prettier-plugin-solidity": "^1.3.1",
"sentinellist": "github:zeroknots/sentinellist",
"solady": "github:vectorized/solady",
"solhint": "^4.1.1",
"solhint-plugin-prettier": "^0.1.0",
"solidity-coverage": "^0.8.7",
Expand Down
5 changes: 4 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/
@prb/test/=node_modules/@prb/test/
forge-std/=node_modules/forge-std/
modulekit/=node_modules/modulekit/src/
account-abstraction=node_modules/account-abstraction/
modulekit/=node_modules/modulekit/src/
sentinellist/=node_modules/sentinellist/
solady/=node_modules/solady
3 changes: 3 additions & 0 deletions test/hardhat/Lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ describe("Lock", function () {
const Lock = await ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });

const SmartAccount = await ethers.getContractFactory("SmartAccount");
const smartAccount = await SmartAccount.deploy();

return { lock, unlockTime, lockedAmount, owner, otherAccount };
}

Expand Down
166 changes: 166 additions & 0 deletions test/hardhat/biconomy-sponsorship-paymaster-specs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { ethers } from "hardhat";
import { expect } from "chai";
import { AbiCoder, AddressLike, BytesLike, Signer, parseEther, toBeHex } from "ethers";
import {
EntryPoint,
EntryPoint__factory,
MockValidator,
MockValidator__factory,
SmartAccount,
SmartAccount__factory,
AccountFactory,
AccountFactory__factory,
BiconomySponsorshipPaymaster,
BiconomySponsorshipPaymaster__factory
} from "../../typechain-types";

import { DefaultsForUserOp, fillAndSign, fillSignAndPack, packUserOp, simulateValidation } from './utils/userOpHelpers'
import { parseValidationData } from "./utils/testUtils";


export const AddressZero = ethers.ZeroAddress;

const MOCK_VALID_UNTIL = "0x00000000deadbeef";
const MOCK_VALID_AFTER = "0x0000000000001234";
const MARKUP = 1100000;
export const ENTRY_POINT_V7 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";

const coder = AbiCoder.defaultAbiCoder()

export async function deployEntryPoint(
provider = ethers.provider
): Promise<EntryPoint> {
const epf = await (await ethers.getContractFactory("EntryPoint")).deploy();
// Retrieve the deployed contract bytecode
const deployedCode = await ethers.provider.getCode(
await epf.getAddress(),
);

// Use hardhat_setCode to set the contract code at the specified address
await ethers.provider.send("hardhat_setCode", [ENTRY_POINT_V7, deployedCode]);

return epf.attach(ENTRY_POINT_V7) as EntryPoint;
}

describe("EntryPoint with Biconomy Sponsorship Paymaster", function () {
let entryPoint: EntryPoint;
let depositorSigner: Signer;
let walletOwner: Signer;
let walletAddress: string, paymasterAddress: string;
let paymasterDepositorId: string;
let ethersSigner: Signer[];
let offchainSigner: Signer, deployer: Signer, feeCollector: Signer;
let paymaster: BiconomySponsorshipPaymaster;
let smartWalletImp: SmartAccount;
let ecdsaModule: MockValidator;
let walletFactory: AccountFactory;

beforeEach(async function () {
ethersSigner = await ethers.getSigners();
entryPoint = await deployEntryPoint();

deployer = ethersSigner[0];
offchainSigner = ethersSigner[1];
depositorSigner = ethersSigner[2];
feeCollector = ethersSigner[3];
walletOwner = deployer;

paymasterDepositorId = await depositorSigner.getAddress();

const offchainSignerAddress = await offchainSigner.getAddress();
const walletOwnerAddress = await walletOwner.getAddress();
const feeCollectorAddess = await feeCollector.getAddress();

ecdsaModule = await new MockValidator__factory(
deployer
).deploy();

paymaster =
await new BiconomySponsorshipPaymaster__factory(deployer).deploy(
await deployer.getAddress(),
await entryPoint.getAddress(),
offchainSignerAddress,
feeCollectorAddess
);

smartWalletImp = await new SmartAccount__factory(
deployer
).deploy();

walletFactory = await new AccountFactory__factory(deployer).deploy(
await smartWalletImp.getAddress(),
);

await walletFactory
.connect(deployer)
.addStake( 86400, { value: parseEther("2") });

const smartAccountDeploymentIndex = 0;

// Module initialization data, encoded
const moduleInstallData = ethers.solidityPacked(["address"], [walletOwnerAddress]);

await walletFactory.createAccount(
await ecdsaModule.getAddress(),
moduleInstallData,
smartAccountDeploymentIndex
);

const expected = await walletFactory.getCounterFactualAddress(
await ecdsaModule.getAddress(),
moduleInstallData,
smartAccountDeploymentIndex
);

walletAddress = expected;

paymasterAddress = await paymaster.getAddress();

await paymaster
.connect(deployer)
.addStake(86400, { value: parseEther("2") });

await paymaster.depositFor(paymasterDepositorId, { value: parseEther("1") });

await entryPoint.depositTo(paymasterAddress, { value: parseEther("1") });
});

describe("#validatePaymasterUserOp and #sendSponsoredTx", () => {
it("succeed with valid signature", async () => {
const nonceKey = ethers.zeroPadBytes(await ecdsaModule.getAddress(), 24);
const userOp1 = await fillAndSign({
sender: walletAddress,
paymaster: paymasterAddress,
paymasterData: ethers.concat([
ethers.zeroPadValue(paymasterDepositorId, 20),
ethers.zeroPadValue(toBeHex(MOCK_VALID_UNTIL), 6),
ethers.zeroPadValue(toBeHex(MOCK_VALID_AFTER), 6),
ethers.zeroPadValue(toBeHex(MARKUP), 4),
'0x' + '00'.repeat(65)
])
}, walletOwner, entryPoint, 'getNonce', nonceKey)
const hash = await paymaster.getHash(packUserOp(userOp1), paymasterDepositorId, MOCK_VALID_UNTIL, MOCK_VALID_AFTER, MARKUP)
const sig = await offchainSigner.signMessage(ethers.getBytes(hash))
const userOp = await fillSignAndPack({
...userOp1,
paymaster: paymasterAddress,
paymasterData: ethers.concat([
ethers.zeroPadValue(paymasterDepositorId, 20),
ethers.zeroPadValue(toBeHex(MOCK_VALID_UNTIL), 6),
ethers.zeroPadValue(toBeHex(MOCK_VALID_AFTER), 6),
ethers.zeroPadValue(toBeHex(MARKUP), 4),
sig
])
}, walletOwner, entryPoint, 'getNonce', nonceKey)
console.log("userOp: ", userOp);
const res = await simulateValidation(userOp, await entryPoint.getAddress())
const validationData = parseValidationData(res.returnInfo.paymasterValidationData)
expect(validationData).to.eql({
aggregator: AddressZero,
validAfter: parseInt(MOCK_VALID_AFTER),
validUntil: parseInt(MOCK_VALID_UNTIL)
})
});
});
})

141 changes: 141 additions & 0 deletions test/hardhat/utils/deployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { BytesLike, HDNodeWallet, Signer } from "ethers";
import { deployments, ethers } from "hardhat";
import { AccountFactory, BiconomySponsorshipPaymaster, EntryPoint, MockValidator, SmartAccount } from "../../../typechain-types";
import { TASK_DEPLOY } from "hardhat-deploy";
import { DeployResult } from "hardhat-deploy/dist/types";

export const ENTRY_POINT_V7 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";

/**
* Generic function to deploy a contract using ethers.js.
*
* @param contractName The name of the contract to deploy.
* @param deployer The Signer object representing the deployer account.
* @returns A promise that resolves to the deployed contract instance.
*/
export async function deployContract<T>(
contractName: string,
deployer: Signer,
): Promise<T> {
const ContractFactory = await ethers.getContractFactory(
contractName,
deployer,
);
const contract = await ContractFactory.deploy();
await contract.waitForDeployment();
return contract as T;
}

/**
* Deploys the EntryPoint contract with a deterministic deployment.
* @returns A promise that resolves to the deployed EntryPoint contract instance.
*/
export async function getDeployedEntrypoint() : Promise<EntryPoint> {
const [deployer] = await ethers.getSigners();

// Deploy the contract normally to get its bytecode
const EntryPoint = await ethers.getContractFactory("EntryPoint");
const entryPoint = await EntryPoint.deploy();
await entryPoint.waitForDeployment();

// Retrieve the deployed contract bytecode
const deployedCode = await ethers.provider.getCode(
await entryPoint.getAddress(),
);

// Use hardhat_setCode to set the contract code at the specified address
await ethers.provider.send("hardhat_setCode", [ENTRY_POINT_V7, deployedCode]);

return EntryPoint.attach(ENTRY_POINT_V7) as EntryPoint;
}

/**
* Deploys the (MSA) Smart Account implementation contract with a deterministic deployment.
* @returns A promise that resolves to the deployed SA implementation contract instance.
*/
export async function getDeployedMSAImplementation(): Promise<SmartAccount> {
const accounts: Signer[] = await ethers.getSigners();
const addresses = await Promise.all(
accounts.map((account) => account.getAddress()),
);

const SmartAccount = await ethers.getContractFactory("SmartAccount");
const deterministicMSAImpl = await deployments.deploy("SmartAccount", {
from: addresses[0],
deterministicDeployment: true,
});

return SmartAccount.attach(deterministicMSAImpl.address) as SmartAccount;
}

/**
* Deploys the AccountFactory contract with a deterministic deployment.
* @returns A promise that resolves to the deployed EntryPoint contract instance.
*/
export async function getDeployedAccountFactory(
implementationAddress: string,
// Note: this could be converted to dto so that additional args can easily be passed
): Promise<AccountFactory> {
const accounts: Signer[] = await ethers.getSigners();
const addresses = await Promise.all(
accounts.map((account) => account.getAddress()),
);

const AccountFactory = await ethers.getContractFactory("AccountFactory");
const deterministicAccountFactory = await deployments.deploy(
"AccountFactory",
{
from: addresses[0],
deterministicDeployment: true,
args: [implementationAddress],
},
);

return AccountFactory.attach(
deterministicAccountFactory.address,
) as AccountFactory;
}

/**
* Deploys the MockValidator contract with a deterministic deployment.
* @returns A promise that resolves to the deployed MockValidator contract instance.
*/
export async function getDeployedMockValidator(): Promise<MockValidator> {
const accounts: Signer[] = await ethers.getSigners();
const addresses = await Promise.all(
accounts.map((account) => account.getAddress()),
);

const MockValidator = await ethers.getContractFactory("MockValidator");
const deterministicMockValidator = await deployments.deploy("MockValidator", {
from: addresses[0],
deterministicDeployment: true,
});

return MockValidator.attach(
deterministicMockValidator.address,
) as MockValidator;
}

/**
* Deploys the MockValidator contract with a deterministic deployment.
* @returns A promise that resolves to the deployed MockValidator contract instance.
*/
export async function getDeployedSponsorshipPaymaster(owner: string, entryPoint: string, verifyingSigner: string, feeCollector: string): Promise<BiconomySponsorshipPaymaster> {
const accounts: Signer[] = await ethers.getSigners();
const addresses = await Promise.all(
accounts.map((account) => account.getAddress()),
);

const BiconomySponsorshipPaymaster = await ethers.getContractFactory("BiconomySponsorshipPaymaster");
const deterministicSponsorshipPaymaster = await deployments.deploy("BiconomySponsorshipPaymaster", {
from: addresses[0],
deterministicDeployment: true,
args: [owner, entryPoint, verifyingSigner, feeCollector],
});

return BiconomySponsorshipPaymaster.attach(
deterministicSponsorshipPaymaster.address,
) as BiconomySponsorshipPaymaster;
}

Loading

0 comments on commit e6d5769

Please sign in to comment.