Skip to content

Commit

Permalink
feat: add deployer service
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Aug 27, 2024
1 parent f0ff7c2 commit f46314e
Show file tree
Hide file tree
Showing 24 changed files with 2,473 additions and 211 deletions.
Empty file modified .github/scripts/deploy.sh
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions .github/workflows/coordinator-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ env:
COORDINATOR_PRIVATE_KEY_PATH: "./priv.key"
COORDINATOR_TALLY_ZKEY_NAME: "TallyVotes_10-1-2_test"
COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME: "ProcessMessages_10-2-1-2_test"
COORDINATOR_TALLY_NON_QV_ZKEY_NAME: "TallyVotesNonQv_10-1-2_test"
COORDINATOR_MESSAGE_PROCESS_NON_QV_ZKEY_NAME: "ProcessMessagesNonQv_10-2-1-2_test"
COORDINATOR_ZKEY_PATH: "./zkeys/"
COORDINATOR_RAPIDSNARK_EXE: "~/rapidsnark/build/prover"
PIMLICO_API_KEY: "pim_"
Expand Down
6 changes: 6 additions & 0 deletions packages/coordinator/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ COORDINATOR_PRIVATE_KEY_PATH="./priv.key"
# https://maci.pse.dev/docs/trusted-setup
COORDINATOR_TALLY_ZKEY_NAME=TallyVotes_10-1-2_test

# Non QV version
COORDINATOR_TALLY_NON_QV_ZKEY_NAME=TallyVotesNonQv_10-1-2_test

# Make sure you have zkeys folder
# https://maci.pse.dev/docs/trusted-setup
COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME=ProcessMessages_10-2-1-2_test

# Non QV version
COORDINATOR_MESSAGE_PROCESS_NON_QV_ZKEY_NAME=ProcessMessagesNonQv_10-2-1-2_test

# Rapidsnark executable path
COORDINATOR_RAPIDSNARK_EXE=

Expand Down
10 changes: 5 additions & 5 deletions packages/coordinator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@
"hardhat": "^2.22.6",
"helmet": "^7.1.0",
"lowdb": "^1.0.0",
"maci-circuits": "^2.1.0",
"maci-cli": "^2.1.0",
"maci-contracts": "^2.1.0",
"maci-domainobjs": "^2.0.0",
"maci-subgraph": "^2.1.0",
"maci-circuits": "^2.2.0",
"maci-cli": "^2.2.0",
"maci-contracts": "^2.2.1",
"maci-domainobjs": "^2.2.0",
"maci-subgraph": "^2.2.0",
"mustache": "^4.2.0",
"permissionless": "^0.1.44",
"reflect-metadata": "^0.2.0",
Expand Down
132 changes: 132 additions & 0 deletions packages/coordinator/tests/aa.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { createWalletClient, Hex, http, parseEther, zeroAddress } from "viem";

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

There should be at least one empty line between import groups

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'createWalletClient' is defined but never used

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'createWalletClient' is defined but never used

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'http' is defined but never used

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'http' is defined but never used

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'parseEther' is defined but never used

Check failure on line 1 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'parseEther' is defined but never used
import { DeployerService } from "../ts/deployer/deployer.service";
import { FileService } from "../ts/file/file.service";
import { SessionKeysService } from "../ts/sessionKeys/sessionKeys.service";
import { genPimlicoRPCUrl, getPublicClient } from "../ts/common/accountAbstraction";

Check failure on line 5 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

`../ts/common/accountAbstraction` import should occur before import of `../ts/deployer/deployer.service`

Check failure on line 5 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'genPimlicoRPCUrl' is defined but never used

Check failure on line 5 in packages/coordinator/tests/aa.test.ts

View workflow job for this annotation

GitHub Actions / check (lint:ts)

'genPimlicoRPCUrl' is defined but never used
import { ErrorCodes, ESupportedNetworks } from "../ts/common";
import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";
import { ENTRYPOINT_ADDRESS_V07 } from "permissionless";
import { KERNEL_V3_1 } from "@zerodev/sdk/constants";
import { addressToEmptyAccount, createKernelAccount, createKernelAccountClient } from "@zerodev/sdk";
import { toECDSASigner } from "@zerodev/permissions/signers";
import { serializePermissionAccount, toPermissionValidator } from "@zerodev/permissions";

import dotenv from "dotenv";
import { privateKeyToAccount } from "viem/accounts";
import { testMaciDeploymentConfig } from "../ts/deployer/__tests__/utils";
import { toSudoPolicy } from "@zerodev/permissions/policies";
import { optimismSepolia } from "viem/chains";

dotenv.config();

const entryPoint = ENTRYPOINT_ADDRESS_V07;
const kernelVersion = KERNEL_V3_1;

/**
* Generate an approval for a session key
*
* @returns - the approval
*/
export const generateApproval = async (sessionKeyAddress: Hex): Promise<string> => {
const publicClient = getPublicClient(ESupportedNetworks.OPTIMISM_SEPOLIA);

const sessionKeySigner = privateKeyToAccount(process.env.TEST_PRIVATE_KEY! as `0x${string}`);
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer: sessionKeySigner,
entryPoint,
kernelVersion,
});

const emptyAccount = addressToEmptyAccount(sessionKeyAddress);
const emptySessionKeySigner = toECDSASigner({ signer: emptyAccount });

const permissionPlugin = await toPermissionValidator(publicClient, {
entryPoint,
kernelVersion,
signer: emptySessionKeySigner,
policies: [toSudoPolicy({})],
});

const sessionKeyAccount = await createKernelAccount(publicClient, {
entryPoint,
kernelVersion,
plugins: {
sudo: ecdsaValidator,
regular: permissionPlugin,
},
});

return serializePermissionAccount(sessionKeyAccount);
};

describe("Account Abstraction e2e", () => {
const fileService = new FileService();
const sessionKeyService = new SessionKeysService(fileService);
const deployerService = new DeployerService(sessionKeyService, fileService);

const sessionKeyAddress = sessionKeyService.generateSessionKey().sessionKeyAddress;

let approval: string;

beforeAll(async () => {
approval = await generateApproval(sessionKeyAddress);
});

describe("sessionKeys", () => {
it("should create a client from a session key and an approval", async () => {
const client = await sessionKeyService.generateClientFromSessionKey(
sessionKeyAddress,
approval,
ESupportedNetworks.OPTIMISM_SEPOLIA,
);

expect(client).toBeDefined();
expect(client.transport.key).toBe("http");
expect(client.key).toBe("Account");
expect(client.account.address).not.toBe(zeroAddress);
expect(client.account.kernelVersion).toBe(kernelVersion);
expect(client.account.entryPoint).toBe(entryPoint);
// this is an account with limited permissions so no sudo validator
expect(client.account.kernelPluginManager.address).toBe(zeroAddress);
expect(client.account.kernelPluginManager.sudoValidator).toBe(undefined);

// try a transaction
await client.sendTransaction({
to: zeroAddress,
value: 0n,
data: "0x",
authorizationList: [],
// @ts-expect-error
account: client.account,
});
});

it("should not allow to create a client after the session key has been deactivated", async () => {
sessionKeyService.deactivateSessionKey(sessionKeyAddress);

await expect(
sessionKeyService.generateClientFromSessionKey(
sessionKeyAddress,
approval,
ESupportedNetworks.OPTIMISM_SEPOLIA,
),
).rejects.toThrow(ErrorCodes.SESSION_KEY_NOT_FOUND);
});
});

describe("deployMaci", () => {
it("should deploy all maci related contracts", async () => {
const newSessionKeyAddress = sessionKeyService.generateSessionKey().sessionKeyAddress;
const approval = await generateApproval(newSessionKeyAddress);

const maciAddress = await deployerService.deployMaci({
approval,
sessionKeyAddress: newSessionKeyAddress,
chain: ESupportedNetworks.OPTIMISM_SEPOLIA,
config: testMaciDeploymentConfig,
});

expect(maciAddress).not.toBe(zeroAddress);
});
});
});
2 changes: 2 additions & 0 deletions packages/coordinator/ts/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from "@nestjs/common";
import { ThrottlerModule } from "@nestjs/throttler";

import { CryptoModule } from "./crypto/crypto.module";
import { DeployerModule } from "./deployer/deployer.module";
import { FileModule } from "./file/file.module";
import { ProofModule } from "./proof/proof.module";
import { SessionKeysModule } from "./sessionKeys/sessionKeys.module";
Expand All @@ -20,6 +21,7 @@ import { SubgraphModule } from "./subgraph/subgraph.module";
SubgraphModule,
ProofModule,
SessionKeysModule,
DeployerModule,
],
})
export class AppModule {}
119 changes: 115 additions & 4 deletions packages/coordinator/ts/common/accountAbstraction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import { deserializePermissionAccount } from "@zerodev/permissions";
import { toECDSASigner } from "@zerodev/permissions/signers";
import { createKernelAccountClient, KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk";
import { KERNEL_V3_1 } from "@zerodev/sdk/constants";
import dotenv from "dotenv";
import { ENTRYPOINT_ADDRESS_V07 } from "permissionless";
import { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types";
import {
createPublicClient,
http,
type HttpTransport,
type TransactionReceipt,
type Transport,
type Hex,
type PublicClient,
Chain,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";

import { ErrorCodes } from "./errors";
import { ESupportedNetworks, viemChain } from "./networks";

dotenv.config();

Expand All @@ -10,11 +28,104 @@ dotenv.config();
* @returns the RPCUrl for the network
*/
export const genPimlicoRPCUrl = (network: string): string => {
const pimlicoAPIKey = process.env.PIMLICO_API_KEY;
// const pimlicoAPIKey = process.env.PIMLICO_API_KEY;

if (!pimlicoAPIKey) {
throw new Error(ErrorCodes.PIMLICO_API_KEY_NOT_SET);
// if (!pimlicoAPIKey) {
// throw new Error(ErrorCodes.PIMLICO_API_KEY_NOT_SET);
// }

// return `https://api.pimlico.io/v2/${network}/rpc?apikey=${pimlicoAPIKey}`;
return "https://rpc.zerodev.app/api/v2/bundler/f1cb192e-7962-4d12-8b70-bb2f7781d5a5";
};

/**
* Generate the RPCUrl for Alchemy based on the chain we need to interact with
* @param network - the network we want to interact with
* @returns the RPCUrl for the network
*/
export const genAlchemyRPCUrl = (network: string): string => {
const rpcAPIKey = process.env.RPC_API_KEY;

if (!rpcAPIKey) {
throw new Error(ErrorCodes.RPC_API_KEY_NOT_SET);
}

return `https://api.pimlico.io/v2/${network}/rpc?apikey=${pimlicoAPIKey}`;
switch (network) {
case ESupportedNetworks.OPTIMISM_SEPOLIA:
return `https://opt-sepolia.g.alchemy.com/v2/${rpcAPIKey}`;
default:
throw new Error(ErrorCodes.UNSUPPORTED_NETWORK);
}
};

/**
* Get a public client
* @param rpcUrl - the RPC URL
* @returns the public client
*/
export const getPublicClient = (chainName: ESupportedNetworks): PublicClient<HttpTransport, Chain> =>
createPublicClient({
transport: http(genAlchemyRPCUrl(chainName)),
chain: viemChain(chainName),
});

/**
* Get a Kernel account handle given a session key
* @param sessionKey
* @param chainId
*/
export const getKernelClient = async (
sessionKey: Hex,
approval: string,
chain: ESupportedNetworks,
): Promise<
KernelAccountClient<
ENTRYPOINT_ADDRESS_V07_TYPE,
Transport,
Chain,
KernelSmartAccount<ENTRYPOINT_ADDRESS_V07_TYPE, HttpTransport, Chain>
>
> => {
const bundlerUrl = genPimlicoRPCUrl(chain);
const publicClient = getPublicClient(chain);

// Using a stored private key
const sessionKeySigner = toECDSASigner({
signer: privateKeyToAccount(sessionKey),
});

const sessionKeyAccount = await deserializePermissionAccount(
publicClient,
ENTRYPOINT_ADDRESS_V07,
KERNEL_V3_1,
approval,
sessionKeySigner,
);

const kernelClient = createKernelAccountClient({
bundlerTransport: http(bundlerUrl),
entryPoint: ENTRYPOINT_ADDRESS_V07,
account: sessionKeyAccount,
chain: viemChain(chain),
});

return kernelClient;
};

/**
* The topic for the contract creation event
*/
export const contractCreationEventTopic = "0x4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b511";

/**
* Get the address of the newly deployed contract from a transaction receipt
* @param receipt - The transaction receipt
* @returns The address of the newly deployed contract
*/
export const getDeployedContractAddress = (receipt: TransactionReceipt): string | undefined => {
const addr = receipt.logs.find((log) => log.topics[0] === contractCreationEventTopic);

const deployedAddress = addr ? `0x${addr.topics[1]?.slice(26)}` : undefined;

return deployedAddress;
};
13 changes: 12 additions & 1 deletion packages/coordinator/ts/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,16 @@ export enum ErrorCodes {
SUBGRAPH_DEPLOY = "7",
SESSION_KEY_NOT_FOUND = "8",
PIMLICO_API_KEY_NOT_SET = "9",
INVALID_APPROVAL = "10",
UNSUPPORTED_VOICE_CREDIT_PROXY = "10",
UNSUPPORTED_GATEKEEPER = "11",
FAILED_TO_DEPLOY_CONTRACT = "12",
FAILED_TO_SET_MACI_INSTANCE_ON_GATEKEEPER = "13",
FAILED_TO_GET_NEXT_POLL_ID = "14",
INVALID_APPROVAL = "15",
MACI_NOT_DEPLOYED = "16",
VERIFIER_NOT_DEPLOYED = "17",
VK_REGISTRY_NOT_DEPLOYED = "18",
FAILED_TO_SET_VERIFYING_KEYS_ON_VK_REGISTRY = "19",
UNSUPPORTED_NETWORK = "20",
RPC_API_KEY_NOT_SET = "21",
}
1 change: 1 addition & 0 deletions packages/coordinator/ts/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { ErrorCodes } from "./errors";
export { ESupportedNetworks } from "./networks";
export const MESSAGE_TREE_ARITY = 5n;
Loading

0 comments on commit f46314e

Please sign in to comment.