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 df5c839
Show file tree
Hide file tree
Showing 20 changed files with 2,272 additions and 198 deletions.
Empty file modified .github/scripts/deploy.sh
100644 → 100755
Empty file.
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
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 {}
90 changes: 90 additions & 0 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 @@ -18,3 +36,75 @@ export const genPimlicoRPCUrl = (network: string): string => {

return `https://api.pimlico.io/v2/${network}/rpc?apikey=${pimlicoAPIKey}`;
};

/**
* Get a public client
* @param rpcUrl - the RPC URL
* @returns the public client
*/
export const getPublicClient = (rpcUrl: string, chainName: ESupportedNetworks): PublicClient<HttpTransport, Chain> =>
createPublicClient({
transport: http(rpcUrl),
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(bundlerUrl, 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;
};
11 changes: 10 additions & 1 deletion packages/coordinator/ts/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,14 @@ 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",
}
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;
110 changes: 65 additions & 45 deletions packages/coordinator/ts/common/networks.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,84 @@
import {
arbitrum,
arbitrumSepolia,
base,
baseSepolia,
bsc,
type Chain,
gnosis,
holesky,
linea,
lineaSepolia,
mainnet,
optimism,
optimismSepolia,
polygon,
scroll,
scrollSepolia,
sepolia,
} from "viem/chains";

export enum ESupportedNetworks {
ETHEREUM = "mainnet",
OPTIMISM = "optimism",
OPTIMISM_SEPOLIA = "optimism-sepolia",
BSC = "bsc",
BSC_CHAPEL = "chapel",
GNOSIS_CHAIN = "gnosis",
FUSE = "fuse",
POLYGON = "matic",
FANTOM_OPERA = "fantom",
ZKSYNC_ERA_TESTNET = "zksync-era-testnet",
BOBA = "boba",
MOONBEAM = "moonbeam",
MOONRIVER = "moonriver",
MOONBASE_ALPHA = "mbase",
FANTOM_TESTNET = "fantom-testnet",
ARBITRUM_ONE = "arbitrum-one",
CELO = "celo",
AVALANCHE_FUJI = "fuji",
AVALANCHE = "avalanche",
CELO_ALFAJORES = "celo-alfajores",
HOLESKY = "holesky",
AURORA = "aurora",
AURORA_TESTNET = "aurora-testnet",
HARMONY = "harmony",
LINEA_SEPOLIA = "linea-sepolia",
GNOSIS_CHIADO = "gnosis-chiado",
MODE_SEPOLIA = "mode-sepolia",
MODE = "mode-mainnet",
BASE_SEPOLIA = "base-sepolia",
ZKSYNC_ERA_SEPOLIA = "zksync-era-sepolia",
POLYGON_ZKEVM = "polygon-zkevm",
ZKSYNC_ERA = "zksync-era",
ETHEREUM_SEPOLIA = "sepolia",
ARBITRUM_SEPOLIA = "arbitrum-sepolia",
LINEA = "linea",
BASE = "base",
SCROLL_SEPOLIA = "scroll-sepolia",
SCROLL = "scroll",
BLAST_MAINNET = "blast-mainnet",
ASTAR_ZKEVM_MAINNET = "astar-zkevm-mainnet",
SEI_TESTNET = "sei-testnet",
BLAST_TESTNET = "blast-testnet",
ETHERLINK_TESTNET = "etherlink-testnet",
XLAYER_SEPOLIA = "xlayer-sepolia",
XLAYER_MAINNET = "xlayer-mainnet",
POLYGON_AMOY = "polygon-amoy",
ZKYOTO_TESTNET = "zkyoto-testnet",
POLYGON_ZKEVM_CARDONA = "polygon-zkevm-cardona",
SEI_MAINNET = "sei-mainnet",
ROOTSTOCK_MAINNET = "rootstock",
IOTEX_MAINNET = "iotex",
NEAR_MAINNET = "near-mainnet",
NEAR_TESTNET = "near-testnet",
COSMOS = "cosmoshub-4",
COSMOS_HUB = "theta-testnet-001",
OSMOSIS = "osmosis-1",
OSMO_TESTNET = "osmo-test-4",
ARWEAVE = "arweave-mainnet",
BITCOIN = "btc",
SOLANA = "solana-mainnet-beta",
INJECTIVE_MAINNET = "injective-mainnet",
INJECTIVE_TESTNET = "injective-testnet",
}

/**
* Get the Viem chain for a given network
*
* @param network - the network to get the chain for
* @returns the Viem chain
*/
export const viemChain = (network: ESupportedNetworks): Chain => {
switch (network) {
case ESupportedNetworks.ETHEREUM:
return mainnet;
case ESupportedNetworks.ETHEREUM_SEPOLIA:
return sepolia;
case ESupportedNetworks.ARBITRUM_ONE:
return arbitrum;
case ESupportedNetworks.ARBITRUM_SEPOLIA:
return arbitrumSepolia;
case ESupportedNetworks.BASE_SEPOLIA:
return baseSepolia;
case ESupportedNetworks.LINEA_SEPOLIA:
return lineaSepolia;
case ESupportedNetworks.SCROLL_SEPOLIA:
return scrollSepolia;
case ESupportedNetworks.SCROLL:
return scroll;
case ESupportedNetworks.BASE:
return base;
case ESupportedNetworks.HOLESKY:
return holesky;
case ESupportedNetworks.LINEA:
return linea;
case ESupportedNetworks.BSC:
return bsc;
case ESupportedNetworks.GNOSIS_CHAIN:
return gnosis;
case ESupportedNetworks.POLYGON:
return polygon;
case ESupportedNetworks.OPTIMISM:
return optimism;
case ESupportedNetworks.OPTIMISM_SEPOLIA:
return optimismSepolia;
default:
throw new Error(`Unsupported network: ${network}`);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Test } from "@nestjs/testing";
import { zeroAddress } from "viem";

import { ErrorCodes, ESupportedNetworks } from "../../common";
import { CryptoService } from "../../crypto/crypto.service";
import { FileService } from "../../file/file.service";
import { SessionKeysService } from "../../sessionKeys/sessionKeys.service";
import { DeployerController } from "../deployer.controller";
import { DeployerService } from "../deployer.service";

import { testMaciDeploymentConfig, testPollDeploymentConfig } from "./utils";

describe("DeployerController", () => {
afterEach(() => {
jest.clearAllMocks();
});

let deployerController: DeployerController;

const mockDeployerService = {
deployMaci: jest.fn(),
deployPoll: jest.fn(),
};

const defaultDeployMaciReturn: string = zeroAddress;
const defaultDeployPollReturn = "0";

const deployerControllerFail = new DeployerController(
new DeployerService(new SessionKeysService(new CryptoService(), new FileService()), new FileService()),
);

beforeEach(async () => {
const app = await Test.createTestingModule({
controllers: [DeployerController],
})
.useMocker((token) => {
if (token === DeployerService) {
mockDeployerService.deployMaci.mockResolvedValue(defaultDeployMaciReturn);
mockDeployerService.deployPoll.mockResolvedValue(defaultDeployPollReturn);
return mockDeployerService;
}

return jest.fn();
})
.compile();

deployerController = app.get<DeployerController>(DeployerController);
});

describe("v1/deploy/maci", () => {
test("should deploy all contract", async () => {
const response = await deployerController.deployMACIContracts({
chain: ESupportedNetworks.OPTIMISM_SEPOLIA,
approval: "",
sessionKeyAddress: "0x",
config: testMaciDeploymentConfig,
});

expect(response).toEqual(defaultDeployMaciReturn);
});

test("should return 400 bad request when the service throws", async () => {
await expect(
deployerControllerFail.deployMACIContracts({
chain: ESupportedNetworks.OPTIMISM_SEPOLIA,
approval: "",
sessionKeyAddress: "0x",
config: testMaciDeploymentConfig,
}),
).rejects.toThrow(ErrorCodes.DECRYPTION);
});
});

describe("v1/deploy/poll", () => {
test("should deploy a new poll", async () => {
const response = await deployerController.deployPoll({
chain: ESupportedNetworks.OPTIMISM_SEPOLIA,
approval: "",
sessionKeyAddress: "0x",
config: testPollDeploymentConfig,
});

expect(response).toEqual(defaultDeployPollReturn);
});

test("should return 400 bad request when the service throws", async () => {
await expect(
deployerControllerFail.deployPoll({
chain: ESupportedNetworks.OPTIMISM_SEPOLIA,
approval: "",
sessionKeyAddress: "0x",
config: testPollDeploymentConfig,
}),
).rejects.toThrow(ErrorCodes.MACI_NOT_DEPLOYED);
});
});
});
Loading

0 comments on commit df5c839

Please sign in to comment.