-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2d832cc
commit e6d5769
Showing
11 changed files
with
1,001 additions
and
4 deletions.
There are no files selected for viewing
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
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,3 @@ | ||
pragma solidity ^0.8.24; | ||
|
||
import "@biconomy-devx/erc7579-msa/test/foundry/mocks/MockValidator.sol"; |
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
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 |
---|---|---|
@@ -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 |
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
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,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) | ||
}) | ||
}); | ||
}); | ||
}) | ||
|
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,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; | ||
} | ||
|
Oops, something went wrong.