Skip to content

Commit

Permalink
Merge pull request #350 from coinbase/contract_test
Browse files Browse the repository at this point in the history
chore: Optimize Smart Contract API input and add more tests
  • Loading branch information
jianlunz-cb authored Dec 19, 2024
2 parents 148edb4 + 56f7a6d commit a50d751
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 34 deletions.
30 changes: 17 additions & 13 deletions src/coinbase/smart_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import {
NFTContractOptions,
TokenContractOptions,
MultiTokenContractOptions,
RegisterContractOptions,
TransactionStatus,
PaginationOptions,
PaginationResponse,
UpdateContractOptions,
} from "./types";
import { Coinbase } from "./coinbase";
import { delay } from "./utils";
Expand Down Expand Up @@ -105,18 +107,19 @@ export class SmartContract {
/**
* Register a smart contract.
*
* @param networkId - The network ID.
* @param contractAddress - The contract address.
* @param abi - The ABI of the contract.
* @param contractName - The contract name.
* @param options - The options to register a smart contract.
* @param options.networkId - The network ID.
* @param options.contractAddress - The contract address.
* @param options.abi - The ABI of the contract.
* @param options.contractName - The contract name.
* @returns The smart contract.
*/
public static async register(
networkId: string,
contractAddress: string,
abi: object,
contractName?: string,
): Promise<SmartContract> {
public static async register({
networkId,
contractAddress,
abi,
contractName,
}: RegisterContractOptions): Promise<SmartContract> {
const response = await Coinbase.apiClients.smartContract!.registerSmartContract(
networkId,
contractAddress,
Expand Down Expand Up @@ -317,11 +320,12 @@ export class SmartContract {
/**
* Update a smart contract.
*
* @param abi - The new ABI of the contract.
* @param contractName - The new contract name.
* @param options - The options to update a smart contract.
* @param options.abi - The new ABI of the contract.
* @param options.contractName - The new contract name.
* @returns The smart contract.
*/
public async update(abi?: object, contractName?: string): Promise<SmartContract> {
public async update({ abi, contractName }: UpdateContractOptions): Promise<SmartContract> {
const response = await Coinbase.apiClients.smartContract!.updateSmartContract(
this.getNetworkId(),
this.getContractAddress(),
Expand Down
18 changes: 18 additions & 0 deletions src/coinbase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,24 @@ export type UpdateWebhookOptions = {
eventTypeFilter?: WebhookEventTypeFilter;
};

/**
* Options for registering a smart contract.
*/
export type RegisterContractOptions = {
networkId: string;
contractAddress: string;
abi: object;
contractName?: string;
};

/**
* Options for updating a smart contract.
*/
export type UpdateContractOptions = {
abi?: object;
contractName?: string;
};

/**
* ContractInvocationAPI client type definition.
*/
Expand Down
124 changes: 103 additions & 21 deletions src/tests/smart_contract_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ describe("SmartContract", () => {
.fn()
.mockResolvedValue({ data: erc20ExternalModel });

const smartContract = await SmartContract.register(
networkId,
contractAddress,
testAllReadTypesABI,
contractName,
);
const smartContract = await SmartContract.register({
networkId: networkId,
contractAddress: contractAddress,
abi: testAllReadTypesABI,
contractName: contractName,
});

expect(Coinbase.apiClients.smartContract!.registerSmartContract).toHaveBeenCalledWith(
networkId,
Expand All @@ -94,7 +94,12 @@ describe("SmartContract", () => {
.fn()
.mockRejectedValue(new Error("Failed to register the smart contract"));
await expect(
SmartContract.register(networkId, contractAddress, testAllReadTypesABI, contractName),
SmartContract.register({
networkId: networkId,
contractAddress: contractAddress,
abi: testAllReadTypesABI,
contractName: contractName,
}),
).rejects.toThrow("Failed to register the smart contract");
});
});
Expand All @@ -103,21 +108,21 @@ describe("SmartContract", () => {
const networkId = erc20ExternalModel.network_id;
const contractAddress = erc20ExternalModel.contract_address;

const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel));
const updatedAbiJson = { abi: "data2" };
updatedContract.contract_name = "UpdatedContractName";
updatedContract.abi = JSON.stringify(updatedAbiJson);

it("should update an existing smart contract", async () => {
const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel));
const updatedAbiJson = { abi: "data2" };
updatedContract.contract_name = "UpdatedContractName";
updatedContract.abi = JSON.stringify(updatedAbiJson);

Coinbase.apiClients.smartContract = smartContractApiMock;
Coinbase.apiClients.smartContract.updateSmartContract = jest
.fn()
.mockResolvedValue({ data: updatedContract });

const smartContract = await erc20ExternalSmartContract.update(
updatedAbiJson,
updatedContract.contract_name,
);
const smartContract = await erc20ExternalSmartContract.update({
abi: updatedAbiJson,
contractName: updatedContract.contract_name,
});

expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith(
networkId,
Expand All @@ -133,12 +138,87 @@ describe("SmartContract", () => {
expect(smartContract.getContractName()).toEqual(updatedContract.contract_name);
});

it("should update an existing smart contract - update contract name only", async () => {
const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel));
updatedContract.contract_name = "UpdatedContractName";

Coinbase.apiClients.smartContract = smartContractApiMock;
Coinbase.apiClients.smartContract.updateSmartContract = jest
.fn()
.mockResolvedValue({ data: updatedContract });

const smartContract = await erc20ExternalSmartContract.update({
contractName: updatedContract.contract_name,
});

expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith(
networkId,
contractAddress,
{
contract_name: updatedContract.contract_name,
abi: undefined,
},
);
expect(smartContract).toBeInstanceOf(SmartContract);
expect(smartContract.getContractAddress()).toBe(contractAddress);
expect(smartContract.getAbi()).toEqual(erc20ExternalSmartContract.getAbi());
expect(smartContract.getContractName()).toEqual(updatedContract.contract_name);
});

it("should update an existing smart contract - update abi only", async () => {
const updatedContract = JSON.parse(JSON.stringify(erc20ExternalModel));
const updatedAbiJson = { abi: "data2" };
updatedContract.abi = JSON.stringify(updatedAbiJson);

Coinbase.apiClients.smartContract = smartContractApiMock;
Coinbase.apiClients.smartContract.updateSmartContract = jest
.fn()
.mockResolvedValue({ data: updatedContract });

const smartContract = await erc20ExternalSmartContract.update({ abi: updatedAbiJson });

expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith(
networkId,
contractAddress,
{
contract_name: undefined,
abi: updatedContract.abi,
},
);
expect(smartContract).toBeInstanceOf(SmartContract);
expect(smartContract.getContractAddress()).toBe(contractAddress);
expect(smartContract.getAbi()).toEqual(updatedAbiJson);
expect(smartContract.getContractName()).toEqual(erc20ExternalSmartContract.getContractName());
});

it("should update an existing smart contract - no update", async () => {
Coinbase.apiClients.smartContract = smartContractApiMock;
Coinbase.apiClients.smartContract.updateSmartContract = jest
.fn()
.mockResolvedValue({ data: erc20ExternalModel });

const smartContract = await erc20ExternalSmartContract.update({});

expect(Coinbase.apiClients.smartContract!.updateSmartContract).toHaveBeenCalledWith(
networkId,
contractAddress,
{},
);
expect(smartContract).toBeInstanceOf(SmartContract);
expect(smartContract.getContractAddress()).toBe(contractAddress);
expect(smartContract.getAbi()).toEqual(erc20ExternalSmartContract.getAbi());
expect(smartContract.getContractName()).toEqual(erc20ExternalSmartContract.getContractName());
});

it("should throw an error if update fails", async () => {
Coinbase.apiClients.smartContract!.updateSmartContract = jest
.fn()
.mockRejectedValue(new Error("Failed to update the smart contract"));
await expect(
erc20ExternalSmartContract.update(testAllReadTypesABI, updatedContract.contract_name),
erc20ExternalSmartContract.update({
abi: testAllReadTypesABI,
contractName: erc20ExternalSmartContract.getContractName(),
}),
).rejects.toThrow("Failed to update the smart contract");
});
});
Expand Down Expand Up @@ -195,7 +275,7 @@ describe("SmartContract", () => {
});
});

describe('#getWalletId', () => {
describe("#getWalletId", () => {
it("returns the smart contract wallet ID", () => {
expect(erc20SmartContract.getWalletId()).toEqual(VALID_SMART_CONTRACT_ERC20_MODEL.wallet_id);
});
Expand Down Expand Up @@ -474,16 +554,18 @@ describe("SmartContract", () => {
});

it("throws an error when the smart contract is external", async () => {
expect(externalSmartContract.reload()).rejects.toThrow("Cannot reload an external SmartContract");
expect(externalSmartContract.reload()).rejects.toThrow(
"Cannot reload an external SmartContract",
);
});
});

describe("#toString", () => {
it("returns the same value as toString", () => {
expect(erc20SmartContract.toString()).toEqual(
`SmartContract{id: '${erc20SmartContract.getId()}', networkId: '${erc20SmartContract.getNetworkId()}', ` +
`contractAddress: '${erc20SmartContract.getContractAddress()}', deployerAddress: '${erc20SmartContract.getDeployerAddress()}', ` +
`type: '${erc20SmartContract.getType()}'}`,
`contractAddress: '${erc20SmartContract.getContractAddress()}', deployerAddress: '${erc20SmartContract.getDeployerAddress()}', ` +
`type: '${erc20SmartContract.getType()}'}`,
);
});
});
Expand Down

0 comments on commit a50d751

Please sign in to comment.