Skip to content

Commit

Permalink
0.1.0(ts): deploy contract (#201)
Browse files Browse the repository at this point in the history
* first pass adding deploy contract cdp action

* linting

* the lost file

* tests
  • Loading branch information
stat authored and 0xRAG committed Jan 31, 2025
1 parent 8dd68d9 commit ac98d81
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CdpWalletProvider } from "../../wallet_providers";
import { CdpActionProvider } from "./cdpActionProvider";
import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas";
import { SmartContract } from "@coinbase/coinbase-sdk";

// Mock the entire module
jest.mock("@coinbase/coinbase-sdk");
Expand Down Expand Up @@ -59,6 +60,7 @@ describe("CDP Action Provider", () => {
let actionProvider: CdpActionProvider;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockExternalAddressInstance: jest.Mocked<any>;
let mockWallet: jest.Mocked<CdpWalletProvider>;

beforeEach(() => {
// Reset all mocks before each test
Expand All @@ -72,6 +74,13 @@ describe("CDP Action Provider", () => {

// Mock the constructor to return our mock instance
(ExternalAddress as jest.Mock).mockImplementation(() => mockExternalAddressInstance);

mockWallet = {
deployToken: jest.fn(),
deployContract: jest.fn(),
getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"),
getNetwork: jest.fn().mockReturnValue({ networkId: "base-sepolia" }),
} as unknown as jest.Mocked<CdpWalletProvider>;
});

describe("addressReputation", () => {
Expand Down Expand Up @@ -112,8 +121,6 @@ describe("CDP Action Provider", () => {
});

describe("deployToken", () => {
let mockWallet: jest.Mocked<CdpWalletProvider>;

beforeEach(() => {
mockWallet = {
deployToken: jest.fn().mockResolvedValue({
Expand Down Expand Up @@ -160,14 +167,7 @@ describe("CDP Action Provider", () => {
});

describe("faucet", () => {
let mockWallet: jest.Mocked<CdpWalletProvider>;

beforeEach(() => {
mockWallet = {
getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"),
getNetwork: jest.fn().mockReturnValue({ networkId: "base-sepolia" }),
} as unknown as jest.Mocked<CdpWalletProvider>;

mockExternalAddressInstance.faucet.mockResolvedValue({
wait: jest.fn().mockResolvedValue({
getTransactionLink: jest.fn().mockReturnValue("tx-link"),
Expand Down Expand Up @@ -212,4 +212,68 @@ describe("CDP Action Provider", () => {
expect(result).toBe(`Error requesting faucet funds: ${error}`);
});
});

describe("deployContract", () => {
const CONTRACT_ADDRESS = "0x123456789abcdef";
const TRANSACTION_LINK = "https://etherscan.io/tx/0xghijkl987654321";
const MOCK_CONTRACT_NAME = "Test Contract";
const MOCK_SOLIDITY_VERSION = "0.8.0";
const MOCK_SOLIDITY_INPUT_JSON = "{}";
const MOCK_CONSTRUCTOR_ARGS = { arg1: "value1", arg2: "value2" };

beforeEach(() => {
mockWallet.deployContract.mockResolvedValue({
wait: jest.fn().mockResolvedValue({
getContractAddress: jest.fn().mockReturnValue(CONTRACT_ADDRESS),
getTransaction: jest.fn().mockReturnValue({
getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK),
}),
}),
} as unknown as SmartContract);
});

it("should successfully deploy a contract", async () => {
const args = {
solidityVersion: MOCK_SOLIDITY_VERSION,
solidityInputJson: MOCK_SOLIDITY_INPUT_JSON,
contractName: MOCK_CONTRACT_NAME,
constructorArgs: MOCK_CONSTRUCTOR_ARGS,
};

const response = await actionProvider.deployContract(mockWallet, args);

expect(mockWallet.deployContract).toHaveBeenCalledWith({
solidityVersion: "0.8.0+commit.c7dfd78e",
solidityInputJson: MOCK_SOLIDITY_INPUT_JSON,
contractName: MOCK_CONTRACT_NAME,
constructorArgs: MOCK_CONSTRUCTOR_ARGS,
});
expect(response).toContain(
`Deployed contract ${MOCK_CONTRACT_NAME} at address ${CONTRACT_ADDRESS}`,
);
expect(response).toContain(`Transaction link: ${TRANSACTION_LINK}`);
});

it("should handle deployment errors", async () => {
const args = {
solidityVersion: MOCK_SOLIDITY_VERSION,
solidityInputJson: MOCK_SOLIDITY_INPUT_JSON,
contractName: MOCK_CONTRACT_NAME,
constructorArgs: MOCK_CONSTRUCTOR_ARGS,
};

const error = new Error("An error has occurred");
mockWallet.deployContract.mockRejectedValue(error);

const response = await actionProvider.deployContract(mockWallet, args);

expect(mockWallet.deployContract).toHaveBeenCalledWith({
solidityVersion: "0.8.0+commit.c7dfd78e",
solidityInputJson: MOCK_SOLIDITY_INPUT_JSON,
contractName: MOCK_CONTRACT_NAME,
constructorArgs: MOCK_CONSTRUCTOR_ARGS,
});
expect(response).toBe(`Error deploying contract: ${error}`);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { ExternalAddress } from "@coinbase/coinbase-sdk";
import { z } from "zod";
import { ActionProvider } from "../action_provider";
import { CdpWalletProvider } from "../../wallet_providers";

import { CreateAction } from "../action_decorator";
import { ExternalAddress } from "@coinbase/coinbase-sdk";
import { AddressReputationSchema, DeployTokenSchema, RequestFaucetFundsSchema } from "./schemas";
import { ActionProvider } from "../action_provider";
import { Network } from "../../network";
import { CdpWalletProvider } from "../../wallet_providers";

import { SolidityVersions } from "./constants";
import {
AddressReputationSchema,
DeployContractSchema,
DeployTokenSchema,
RequestFaucetFundsSchema,
} from "./schemas";

/**
* CdpActionProvider is an action provider for Cdp.
Expand Down Expand Up @@ -43,6 +51,56 @@ This tool checks the reputation of an address on a given network. It takes:
}
}

/**
* Deploys a contract.
*
* @param walletProvider - The wallet provider to deploy the contract from
* @param args - The input arguments for the action
* @returns A message containing the deployed contract address and details
*/
@CreateAction({
name: "deploy_contract",
description: `
Deploys smart contract with required args: solidity version (string), solidity input json (string), contract name (string), and optional constructor args (Dict[str, Any])
Input json structure:
{"language":"Solidity","settings":{"remappings":[],"outputSelection":{"*":{"*":["abi","evm.bytecode"]}}},"sources":{}}
You must set the outputSelection to {"*":{"*":["abi","evm.bytecode"]}} in the settings. The solidity version must be >= 0.8.0 and <= 0.8.28.
Sources should contain one or more contracts with the following structure:
{"contract_name.sol":{"content":"contract code"}}
The contract code should be escaped. Contracts cannot import from external contracts but can import from one another.
Constructor args are required if the contract has a constructor. They are a key-value
map where the key is the arg name and the value is the arg value. Encode uint/int/bytes/string/address values as strings, boolean values as true/false. For arrays/tuples, encode based on contained type.`,
schema: DeployContractSchema,
})
async deployContract(
walletProvider: CdpWalletProvider,
args: z.infer<typeof DeployContractSchema>,
): Promise<string> {
try {
const solidityVersion = SolidityVersions[args.solidityVersion];

const contract = await walletProvider.deployContract({
solidityVersion: solidityVersion,
solidityInputJson: args.solidityInputJson,
contractName: args.contractName,
constructorArgs: args.constructorArgs ?? {},
});

const result = await contract.wait();

return `Deployed contract ${args.contractName} at address ${result.getContractAddress()}. Transaction link: ${result
.getTransaction()!
.getTransactionLink()}`;
} catch (error) {
return `Error deploying contract: ${error}`;
}
}

/**
* Deploys a token.
*
Expand Down
31 changes: 31 additions & 0 deletions cdp-agentkit-core/typescript/src/action_providers/cdp/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const SolidityVersions = {
"0.8.28": "0.8.28+commit.7893614a",
"0.8.27": "0.8.27+commit.40a35a09",
"0.8.26": "0.8.26+commit.8a97fa7a",
"0.8.25": "0.8.25+commit.b61c2a91",
"0.8.24": "0.8.24+commit.e11b9ed9",
"0.8.23": "0.8.23+commit.f704f362",
"0.8.22": "0.8.22+commit.4fc1097e",
"0.8.21": "0.8.21+commit.d9974bed",
"0.8.20": "0.8.20+commit.a1b79de6",
"0.8.19": "0.8.19+commit.7dd6d404",
"0.8.18": "0.8.18+commit.87f61d96",
"0.8.17": "0.8.17+commit.8df45f5f",
"0.8.16": "0.8.16+commit.07a7930e",
"0.8.15": "0.8.15+commit.e14f2714",
"0.8.14": "0.8.14+commit.80d49f37",
"0.8.13": "0.8.13+commit.abaa5c0e",
"0.8.12": "0.8.12+commit.f00d7308",
"0.8.11": "0.8.11+commit.d7f03943",
"0.8.10": "0.8.10+commit.fc410830",
"0.8.9": "0.8.9+commit.e5eed63a",
"0.8.8": "0.8.8+commit.dddeac2f",
"0.8.7": "0.8.7+commit.e28d00a7",
"0.8.6": "0.8.6+commit.11564f7e",
"0.8.5": "0.8.5+commit.a4f2e591",
"0.8.4": "0.8.4+commit.c7e474f2",
"0.8.3": "0.8.3+commit.8d00100c",
"0.8.2": "0.8.2+commit.661d1103",
"0.8.1": "0.8.1+commit.df193b15",
"0.8.0": "0.8.0+commit.c7dfd78e",
} as const;
27 changes: 23 additions & 4 deletions cdp-agentkit-core/typescript/src/action_providers/cdp/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from "zod";
import { SolidityVersions } from "./constants";

/**
* Input schema for address reputation check.
Expand All @@ -15,14 +16,22 @@ export const AddressReputationSchema = z
.describe("Input schema for address reputation check");

/**
* Input schema for request faucet funds action.
* Input schema for deploy contract action.
*/
export const RequestFaucetFundsSchema = z
export const DeployContractSchema = z
.object({
assetId: z.string().optional().describe("The optional asset ID to request from faucet"),
solidityVersion: z
.enum(Object.keys(SolidityVersions) as [string, ...string[]])
.describe("The solidity compiler version"),
solidityInputJson: z.string().describe("The input json for the solidity compiler"),
contractName: z.string().describe("The name of the contract class to be deployed"),
constructorArgs: z
.record(z.string(), z.any())
.describe("The constructor arguments for the contract")
.optional(),
})
.strip()
.describe("Instructions for requesting faucet funds");
.describe("Instructions for deploying an arbitrary contract");

/**
* Input schema for deploy token action.
Expand All @@ -35,3 +44,13 @@ export const DeployTokenSchema = z
})
.strip()
.describe("Instructions for deploying a token");

/**
* Input schema for request faucet funds action.
*/
export const RequestFaucetFundsSchema = z
.object({
assetId: z.string().optional().describe("The optional asset ID to request from faucet"),
})
.strip()
.describe("Instructions for requesting faucet funds");
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,15 @@ export class CdpWalletProvider extends EvmWalletProvider {
/**
* Gets the balance of the wallet.
*
* @returns The balance of the wallet.
* @returns The balance of the wallet in wei
*/
async getBalance(): Promise<bigint> {
// TODO: Implement
throw Error("Unimplemented");
if (!this.#cdpWallet) {
throw new Error("Wallet not initialized");
}

const balance = await this.#cdpWallet.getBalance("eth");
return BigInt(balance.mul(10 ** 18).toString());
}

/**
Expand Down Expand Up @@ -227,4 +231,29 @@ export class CdpWalletProvider extends EvmWalletProvider {

return this.#cdpWallet.deployToken(options);
}

/**
* Deploys a contract.
*
* @param options - The options for contract deployment
* @param options.solidityVersion - The version of the Solidity compiler to use (e.g. "0.8.0+commit.c7dfd78e")
* @param options.solidityInputJson - The JSON input for the Solidity compiler containing contract source and settings
* @param options.contractName - The name of the contract to deploy
* @param options.constructorArgs - Key-value map of constructor args
*
* @returns A Promise that resolves to the deployed contract instance
* @throws Error if wallet is not initialized
*/
async deployContract(options: {
solidityVersion: string;
solidityInputJson: string;
contractName: string;
constructorArgs: Record<string, unknown>;
}): Promise<SmartContract> {
if (!this.#cdpWallet) {
throw new Error("Wallet not initialized");
}

return this.#cdpWallet.deployContract(options);
}
}

0 comments on commit ac98d81

Please sign in to comment.