From 1a26cf3b043487c87b3293745a11563d3c1282e8 Mon Sep 17 00:00:00 2001 From: maxufeng Date: Fri, 18 Apr 2025 12:34:37 +0800 Subject: [PATCH] feat: add astrontestnet network --- README.md | 1 + package-lock.json | 50 +++--- package.json | 8 +- .../get-astrontestnet.test.ts.snap | 20 +++ .../__test__/get-astrontestnet.test.ts | 22 +++ src/common/networks.ts | 9 ++ .../document-store-astrontestnet.test.ts | 120 +++++++++++++++ ...title-escrow-factory-astrontestnet.test.ts | 99 ++++++++++++ .../helpers-astrontestnet.test.ts | 17 +++ .../token-registry-astrontestnet.test.ts | 144 ++++++++++++++++++ .../grant-role-astrontestnet.test.ts | 126 +++++++++++++++ .../issue-astrontestnet.test.ts | 135 ++++++++++++++++ .../revoke-astrontestnet.test.ts | 135 ++++++++++++++++ .../revoke-role-astrontestnet.test.ts | 126 +++++++++++++++ .../transfer-ownership-astrontestnet.test.ts | 132 ++++++++++++++++ .../acceptReturned-astrontestnet.test.ts | 60 ++++++++ ...NominatedBeneficiary-astrontestnet.test.ts | 114 ++++++++++++++ .../nominateBeneficiary-astrontestnet.test.ts | 99 ++++++++++++ .../rejectReturned-astrontestnet.test.ts | 96 ++++++++++++ .../returnDocument-astrontestnet.test.ts | 75 +++++++++ .../transferHolder-astrontestnet.test.ts | 79 ++++++++++ .../transferOwners-astrontestnet.test.ts | 120 +++++++++++++++ .../issue-astrontestnet.test.ts | 115 ++++++++++++++ .../transaction-astrontestnet.test.ts | 39 +++++ .../__tests__/wallet-astrontestnet.test.ts | 82 ++++++++++ 25 files changed, 1994 insertions(+), 29 deletions(-) create mode 100644 src/commands/dns/txt-record/__test__/__snapshots__/get-astrontestnet.test.ts.snap create mode 100644 src/commands/dns/txt-record/__test__/get-astrontestnet.test.ts create mode 100644 src/implementations/deploy/document-store/document-store-astrontestnet.test.ts create mode 100644 src/implementations/deploy/title-escrow-factory/title-escrow-factory-astrontestnet.test.ts create mode 100644 src/implementations/deploy/token-registry/helpers-astrontestnet.test.ts create mode 100644 src/implementations/deploy/token-registry/token-registry-astrontestnet.test.ts create mode 100644 src/implementations/document-store/grant-role-astrontestnet.test.ts create mode 100644 src/implementations/document-store/issue-astrontestnet.test.ts create mode 100644 src/implementations/document-store/revoke-astrontestnet.test.ts create mode 100644 src/implementations/document-store/revoke-role-astrontestnet.test.ts create mode 100644 src/implementations/document-store/transfer-ownership-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/acceptReturned-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/endorseNominatedBeneficiary-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/nominateBeneficiary-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/rejectReturned-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/returnDocument-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/transferHolder-astrontestnet.test.ts create mode 100644 src/implementations/title-escrow/transferOwners-astrontestnet.test.ts create mode 100644 src/implementations/token-registry/issue-astrontestnet.test.ts create mode 100644 src/implementations/transaction/transaction-astrontestnet.test.ts create mode 100644 src/implementations/utils/__tests__/wallet-astrontestnet.test.ts diff --git a/README.md b/README.md index a1199062..d3b7d931 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ npx -p @tradetrust-tt/tradetrust-cli tradetrust | Stability | stability | 101010 | Stability | Mainnet | | Stability Testnet | stabilitytestnet | 20180427 | Stability | Testnet | | Astron | Astron | 1338 | Astron | Mainnet | +| Astron Testnet | Astrontestnet | 21002 | Astron | Testnet | _Note: Network Name is the name used in the CLI --network options_ diff --git a/package-lock.json b/package-lock.json index c230e3bb..0625d47c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,12 @@ "dependencies": { "@govtechsg/oa-encryption": "^1.3.5", "@snyk/protect": "^1.1196.0", - "@tradetrust-tt/dnsprove": "^2.16.0", + "@tradetrust-tt/dnsprove": "^2.17.0", "@tradetrust-tt/document-store": "^4.1.1", - "@tradetrust-tt/token-registry": "^5.1.3", + "@tradetrust-tt/token-registry": "^5.2.0", "@tradetrust-tt/tradetrust": "^6.10.0", - "@tradetrust-tt/tradetrust-config": "^1.17.0", - "@tradetrust-tt/tt-verify": "^9.3.0", + "@tradetrust-tt/tradetrust-config": "^1.18.0", + "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/trustvc": "^1.2.7", "ajv": "^8.4.0", "ajv-formats": "^2.1.0", @@ -5423,9 +5423,9 @@ "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==" }, "node_modules/@tradetrust-tt/dnsprove": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/dnsprove/-/dnsprove-2.16.0.tgz", - "integrity": "sha512-2/wcmWcqznAVsA0Dojk9exuW63VqdwXwL12RMU9M29q+d/RzGuDUbNJ4/fg8liNd0Psgd2iCKGtxo6Z4M3wa0g==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/dnsprove/-/dnsprove-2.17.0.tgz", + "integrity": "sha512-UvPLQMGRWt0AQjxqQScokWcOU4+q/nLoXNnqw5tPruE6mXU9Q3jw5FXKpH/03otxHN2HCpjiN1no3O6bmc1jEA==", "dependencies": { "axios": "1.7.2", "debug": "^4.3.1", @@ -5444,9 +5444,9 @@ } }, "node_modules/@tradetrust-tt/token-registry": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/token-registry/-/token-registry-5.1.3.tgz", - "integrity": "sha512-V8jWWy24MgHSfYynHNQkFzH39a9iSHj38Da8tFsRcM1lHee3wrRBMk6DR6FkpKThdTjD2+6NTOFp8A/F25K0oQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/token-registry/-/token-registry-5.2.0.tgz", + "integrity": "sha512-VM09UNdCR5hQT/49EySG/vnSXa33kjjreU1CHc+nHdhWk8Sz47Z1ScznZbj0JYKeIKakWTjh8usnQbPTPjnGEw==", "dependencies": { "ethers": "^6.13.4" } @@ -5473,9 +5473,9 @@ } }, "node_modules/@tradetrust-tt/tradetrust": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust/-/tradetrust-6.10.0.tgz", - "integrity": "sha512-PYGukWhj6q1adPvgADlU/H2d8x30vholEQ0ss6wtPwXKfN1bs+2a+9m2SN170pilOR1CYJRkml3hXLwo2pqSEw==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust/-/tradetrust-6.10.1.tgz", + "integrity": "sha512-Vk5TOlRKFbZ0qMitcYDcUGU5Nu5E5POHNlBnv61lrPoMq0gseIBaprgF/gIRC0Q4LVQLjCPOi1ufW11bDSpILQ==", "hasInstallScript": true, "dependencies": { "@govtechsg/jsonld": "^0.1.1", @@ -5483,7 +5483,7 @@ "ajv-formats": "^2.1.1", "cross-fetch": "^4.0.0", "debug": "^4.3.4", - "ethers": "^5.7.2", + "ethers": "^5.8.0", "flatley": "^5.2.0", "js-base64": "^3.7.7", "js-sha3": "^0.9.3", @@ -5497,9 +5497,9 @@ } }, "node_modules/@tradetrust-tt/tradetrust-config": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust-config/-/tradetrust-config-1.17.0.tgz", - "integrity": "sha512-FKK/FsIyXQ8eVqs6CSFYH7bLlN4pDQ88KpJTIZkrMvyXjacg+uV+qtuQ5cI22SaF7d/wRGfYY1tHFIej6rFHeA==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust-config/-/tradetrust-config-1.18.0.tgz", + "integrity": "sha512-xu+KBddy41C4GO+KJEeTVUKrd1HLMebu8Obis6Ua6DIVfC8Rh7Ya5ygTrVSxDYelznrhwZ+54Xdz3/AhDaPSuA==", "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -5530,18 +5530,18 @@ "license": "MIT" }, "node_modules/@tradetrust-tt/tt-verify": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/tt-verify/-/tt-verify-9.3.0.tgz", - "integrity": "sha512-34mvyg0x1EtbNqwzw4j+R/lCg6yU9zp/jPOn+SwsataCJQrzzoLy0F8++PWzaGtEp7yRmoHZHrEbIaObDh7aOQ==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/tt-verify/-/tt-verify-9.4.0.tgz", + "integrity": "sha512-ZcMlpBvGf4vr9iJRHtZY+xwo9/aHdvn77s6jXs2hisv0s+2E6MU5C1h9mDf3sUanSAP4PAnSmcthg63AjW/MXA==", "dependencies": { - "@tradetrust-tt/dnsprove": "^2.16.0", - "@tradetrust-tt/document-store": "^4.1.0", - "@tradetrust-tt/token-registry": "^5.1.0", - "@tradetrust-tt/tradetrust": "^6.10.0", + "@tradetrust-tt/dnsprove": "^2.17.0", + "@tradetrust-tt/document-store": "^4.1.1", + "@tradetrust-tt/token-registry": "^5.2.0", + "@tradetrust-tt/tradetrust": "^6.10.1", "axios": "^1.7.2", "debug": "^4.3.1", "did-resolver": "^3.1.0", - "ethers": "^5.7.2", + "ethers": "^5.8.0", "ethr-did-resolver": "^4.3.3", "node-cache": "^5.1.2", "runtypes": "^6.3.0", diff --git a/package.json b/package.json index 8b210dd9..bb1f4119 100644 --- a/package.json +++ b/package.json @@ -71,12 +71,12 @@ "dependencies": { "@govtechsg/oa-encryption": "^1.3.5", "@snyk/protect": "^1.1196.0", - "@tradetrust-tt/dnsprove": "^2.16.0", + "@tradetrust-tt/dnsprove": "^2.17.0", "@tradetrust-tt/document-store": "^4.1.1", - "@tradetrust-tt/token-registry": "^5.1.3", + "@tradetrust-tt/token-registry": "^5.2.0", "@tradetrust-tt/tradetrust": "^6.10.0", - "@tradetrust-tt/tradetrust-config": "^1.17.0", - "@tradetrust-tt/tt-verify": "^9.3.0", + "@tradetrust-tt/tradetrust-config": "^1.18.0", + "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/trustvc": "^1.2.7", "ajv": "^8.4.0", "ajv-formats": "^2.1.0", diff --git a/src/commands/dns/txt-record/__test__/__snapshots__/get-astrontestnet.test.ts.snap b/src/commands/dns/txt-record/__test__/__snapshots__/get-astrontestnet.test.ts.snap new file mode 100644 index 00000000..7a97faba --- /dev/null +++ b/src/commands/dns/txt-record/__test__/__snapshots__/get-astrontestnet.test.ts.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`get should return dns-txt 1`] = ` +[ + { + "addr": "0xb1Bf514b3893357813F366282E887eE221D5C2dA", + "dnssec": false, + "net": "ethereum", + "netId": "21002", + "type": "openatts", + }, + { + "addr": "0xdAEe89A37fEEBCBFAc94aBA63Fb163808dAc38Fb", + "dnssec": false, + "net": "ethereum", + "netId": "21002", + "type": "openatts", + }, +] +`; diff --git a/src/commands/dns/txt-record/__test__/get-astrontestnet.test.ts b/src/commands/dns/txt-record/__test__/get-astrontestnet.test.ts new file mode 100644 index 00000000..d928b02a --- /dev/null +++ b/src/commands/dns/txt-record/__test__/get-astrontestnet.test.ts @@ -0,0 +1,22 @@ +import { handler } from "../get"; + +describe("get", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it("should return dns-txt", async () => { + const consoleSpy = jest.spyOn(console, "table"); + await handler({ location: "dev-astronlayer2.bitfactory.cn" }); + + const mockCall1 = consoleSpy.mock.calls[0][0]; + mockCall1.sort((a: any, b: any) => { + if (a.netId < b.netId) return -1; + if (a.netId > b.netId) return 1; + if (a.addr < b.addr) return -1; + if (a.addr > b.addr) return 1; + return 0; + }); + // dns-txt + expect(mockCall1).toMatchSnapshot(); + }); +}); diff --git a/src/common/networks.ts b/src/common/networks.ts index 3b04f60b..7cb1b6f4 100644 --- a/src/common/networks.ts +++ b/src/common/networks.ts @@ -24,6 +24,7 @@ export enum NetworkCmdName { StabilityTestnet = "stabilitytestnet", Stability = "stability", Astron = "astron", + AstronTestnet = "astrontestnet", } const defaultInfuraProvider = @@ -114,6 +115,14 @@ export const supportedNetwork: { currency: "ASTRON", gasStation: gasStation("https://astronscanl2.bitfactory.cn/gas-station"), }, + [NetworkCmdName.AstronTestnet]: { + explorer: "https://dev-astronscanl2.bitfactory.cn/", + provider: jsonRpcProvider("https://dev-astronlayer2.bitfactory.cn/query/"), + networkId: 21002, + networkName: NetworkCmdName.AstronTestnet, + currency: "ASTRON", + gasStation: gasStation("https://dev-astronscanl2.bitfactory.cn/gas-station"), + }, }; export const getSupportedNetwork = (networkCmdName: string): SupportedNetwork => { diff --git a/src/implementations/deploy/document-store/document-store-astrontestnet.test.ts b/src/implementations/deploy/document-store/document-store-astrontestnet.test.ts new file mode 100644 index 00000000..046c109f --- /dev/null +++ b/src/implementations/deploy/document-store/document-store-astrontestnet.test.ts @@ -0,0 +1,120 @@ +import { deployDocumentStore } from "./document-store"; +import { join } from "path"; +import { Wallet } from "ethers"; +import { DocumentStoreFactory } from "@tradetrust-tt/document-store"; +import { DeployDocumentStoreCommand } from "../../../commands/deploy/deploy.types"; + +jest.mock("@tradetrust-tt/document-store"); + +const deployParams: DeployDocumentStoreCommand = { + storeName: "Test Document Store", + owner: "0x1234", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("document-store", () => { + describe("deployDocumentStore", () => { + const documentStoreFactory: any = DocumentStoreFactory; + const mockedDocumentStoreFactory: jest.Mock = documentStoreFactory; + const mockedDeploy: jest.Mock = mockedDocumentStoreFactory.prototype.deploy; + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedDocumentStoreFactory.mockReset(); + mockedDeploy.mockReset(); + mockedDeploy.mockResolvedValue({ + deployTransaction: { hash: "hash", wait: () => Promise.resolve({ contractAddress: "contractAddress" }) }, + }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + + await deployDocumentStore({ + storeName: "Test", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedDocumentStoreFactory.mock.calls[0][0]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + + it("should take in the key from key file", async () => { + await deployDocumentStore({ + storeName: "Test", + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "..", "examples", "sample-key"), + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedDocumentStoreFactory.mock.calls[0][0]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await deployDocumentStore(deployParams); + + const passedSigner: Wallet = mockedDocumentStoreFactory.mock.calls[0][0]; + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedDeploy.mock.calls[0][0]).toStrictEqual(deployParams.storeName); + expect(mockedDeploy.mock.calls[0][1]).toStrictEqual(deployParams.owner); + // price should be any length string of digits + expect(mockedDeploy.mock.calls[0][2].maxPriorityFeePerGas.toString()).toStrictEqual(expect.stringMatching(/\d+/)); + expect(instance.contractAddress).toBe("contractAddress"); + }); + + it("should allow errors to bubble up", async () => { + mockedDeploy.mockRejectedValue(new Error("An Error")); + await expect(deployDocumentStore(deployParams)).rejects.toThrow("An Error"); + }); + + it("should throw when keys are not found anywhere", async () => { + await expect( + deployDocumentStore({ + storeName: "Test", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1, + }) + ).rejects.toThrow( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ); + }); + + it("should default the owner as the deployer", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + + await deployDocumentStore({ + maxPriorityFeePerGasScale: 1, + storeName: "Test", + network: "astrontestnet", + dryRun: false, + }); + + const passedSigner: Wallet = mockedDocumentStoreFactory.mock.calls[0][0]; + const addr = await passedSigner.getAddress(); + expect(mockedDeploy.mock.calls[0][1]).toStrictEqual(addr); + }); + }); +}); diff --git a/src/implementations/deploy/title-escrow-factory/title-escrow-factory-astrontestnet.test.ts b/src/implementations/deploy/title-escrow-factory/title-escrow-factory-astrontestnet.test.ts new file mode 100644 index 00000000..71a615e3 --- /dev/null +++ b/src/implementations/deploy/title-escrow-factory/title-escrow-factory-astrontestnet.test.ts @@ -0,0 +1,99 @@ +import { join } from "path"; +import { Wallet } from "ethers"; +import { DeployTitleEscrowFactoryCommand } from "../../../commands/deploy/deploy.types"; +import { TitleEscrowFactory__factory } from "@tradetrust-tt/token-registry/contracts"; +import { deployTitleEscrowFactory } from "./title-escrow-factory"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const deployParams: DeployTitleEscrowFactoryCommand = { + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + dryRun: false, + maxPriorityFeePerGasScale: 1.0, +}; + +describe("title escrow factory", () => { + describe("deployTitleEscrowFactory", () => { + const mockedTitleEscrowFactory: jest.Mock = TitleEscrowFactory__factory as any; + const mockedDeploy: jest.Mock = mockedTitleEscrowFactory.prototype.deploy; + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTitleEscrowFactory.mockReset(); + mockedDeploy.mockReset(); + mockedDeploy.mockResolvedValue({ + deployTransaction: { hash: "hash", blockNumber: 1 }, + deployed: () => Promise.resolve(), + address: "contractAddress", + }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + + await deployTitleEscrowFactory({ + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1.0, + }); + + const passedSigner: Wallet = mockedTitleEscrowFactory.mock.calls[0][0]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + + it("should take in the key from key file", async () => { + await deployTitleEscrowFactory({ + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "..", "examples", "sample-key"), + dryRun: false, + maxPriorityFeePerGasScale: 1.0, + }); + + const passedSigner: Wallet = mockedTitleEscrowFactory.mock.calls[0][0]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await deployTitleEscrowFactory(deployParams); + + const passedSigner: Wallet = mockedTitleEscrowFactory.mock.calls[0][0]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + // price should be any length string of digits + expect(instance.contractAddress).toBe("contractAddress"); + }); + + it("should allow errors to bubble up", async () => { + mockedDeploy.mockRejectedValue(new Error("An Error")); + await expect(deployTitleEscrowFactory(deployParams)).rejects.toThrow("An Error"); + }); + + it("should throw when keys are not found anywhere", async () => { + await expect( + deployTitleEscrowFactory({ + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1.0, + }) + ).rejects.toThrow( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ); + }); + }); +}); diff --git a/src/implementations/deploy/token-registry/helpers-astrontestnet.test.ts b/src/implementations/deploy/token-registry/helpers-astrontestnet.test.ts new file mode 100644 index 00000000..0b5b4171 --- /dev/null +++ b/src/implementations/deploy/token-registry/helpers-astrontestnet.test.ts @@ -0,0 +1,17 @@ +/* eslint-disable jest/no-disabled-tests */ +import { isAddress } from "ethers/lib/utils"; +import { getDefaultContractAddress } from "./helpers"; + +describe.skip("valid Token Registry Factory Address", () => { + it("should return deployer address", () => { + const { TitleEscrowFactory, TokenImplementation, Deployer } = getDefaultContractAddress(21002); + + expect(TitleEscrowFactory).toBeDefined(); + expect(TokenImplementation).toBeDefined(); + expect(Deployer).toBeDefined(); + + expect(isAddress(TitleEscrowFactory || "")).toBe(true); + expect(isAddress(TokenImplementation || "")).toBe(true); + expect(isAddress(Deployer || "")).toBe(true); + }); +}); diff --git a/src/implementations/deploy/token-registry/token-registry-astrontestnet.test.ts b/src/implementations/deploy/token-registry/token-registry-astrontestnet.test.ts new file mode 100644 index 00000000..f3012340 --- /dev/null +++ b/src/implementations/deploy/token-registry/token-registry-astrontestnet.test.ts @@ -0,0 +1,144 @@ +/* eslint-disable jest/no-disabled-tests */ +import { Contract } from "ethers"; +import { DeployTokenRegistryCommand } from "../../../commands/deploy/deploy.types"; +import { encodeInitParams } from "./helpers"; +import { deployTokenRegistry } from "./token-registry"; + +const deployParams: DeployTokenRegistryCommand = { + registryName: "Test", + registrySymbol: "Tst", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + maxPriorityFeePerGasScale: 1, + dryRun: false, + standalone: false, +}; + +describe.skip("deploy Token Registry", () => { + const mockedEthersContract: jest.Mock = Contract as any; + // eslint-disable-next-line jest/prefer-spy-on + mockedEthersContract.prototype.deploy = jest.fn(); + const mockedDeploy: jest.Mock = mockedEthersContract.prototype.deploy; + + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + beforeEach(() => { + mockedDeploy.mockReset(); + mockedDeploy.mockResolvedValue({ + hash: "hash", + blockNumber: "blockNumber", + wait: () => + Promise.resolve({ + events: [ + { + topics: [ + "0x3588ebb5c75fdf91927f8472318f41513ee567c2612a5ce52ac840dcf6f162f5", // deployment + "0x000000000000000000000000e7163d4666e15ec691f0ba0ec300fe1dd71ae2de", + "0x000000000000000000000000b30ba3b8ba59ed74a9d55ded647b4516823ac750", + "0x00000000000000000000000088f17c6964c859cf0bb9ee53d91685b2529e0976", + ], + data: "0x0000000000000000000000000f1fc16ef456d0fdc7c53d1d94f4a2e97c7064e3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000088f17c6964c859cf0bb9ee53d91685b2529e09760000000000000000000000000000000000000000000000000000000000000001410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000", + args: [ + "0xe7163d4666e15ec691f0ba0ec300fe1dd71ae2de", + "0xA0Da221B3cd3e863425E1F1D34B9307D295a9d03", + "0x88f17c6964c859cf0bb9ee53d91685b2529e0976", + "0x0f1fc16ef456d0fdc7c53d1d94f4a2e97c7064e3", + "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000088f17c6964c859cf0bb9ee53d91685b2529e09760000000000000000000000000000000000000000000000000000000000000001410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000", + ] as unknown, + }, + ], + }), + }); + }); + + it("should pass in the correct params and return the deployed instance", async () => { + await deployTokenRegistry(deployParams); + + const expectedInitParams = encodeInitParams({ + name: deployParams.registryName, + symbol: deployParams.registrySymbol, + deployer: "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", + }); + + expect(mockedDeploy.mock.calls[0][0]).toBe("0xA0Da221B3cd3e863425E1F1D34B9307D295a9d03"); + expect(mockedDeploy.mock.calls[0][1]).toEqual(expectedInitParams); + + // price should be any length string of digits + // expect(mockedDeploy.mock.calls[0][2].gasPrice.toString()).toStrictEqual(expect.stringMatching(/\d+/)); + // expect(instance.contractAddress).toBe("contractAddress"); // TODO + }); + + it("should pass in the correct params with standalone and return the deployed instance", async () => { + const deployStandalone = { + standalone: true, + ...deployParams, + }; + await deployTokenRegistry(deployStandalone); + + const expectedInitParams = encodeInitParams({ + name: deployParams.registryName, + symbol: deployParams.registrySymbol, + deployer: "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", + }); + + expect(mockedDeploy.mock.calls[0][0]).toBe("0xA0Da221B3cd3e863425E1F1D34B9307D295a9d03"); + expect(mockedDeploy.mock.calls[0][1]).toEqual(expectedInitParams); + + // price should be any length string of digits + // expect(mockedDeploy.mock.calls[0][2].gasPrice.toString()).toStrictEqual(expect.stringMatching(/\d+/)); + // expect(instance.contractAddress).toBe("contractAddress"); // TODO + }); + + it("should pass in the correct params with unspecified standalone and return the deployed instance", async () => { + const deployParamsUnspecified = deployParams; + delete deployParamsUnspecified.standalone; + await deployTokenRegistry(deployParamsUnspecified); + + const expectedInitParams = encodeInitParams({ + name: deployParams.registryName, + symbol: deployParams.registrySymbol, + deployer: "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", + }); + + expect(mockedDeploy.mock.calls[0][0]).toBe("0xA0Da221B3cd3e863425E1F1D34B9307D295a9d03"); + expect(mockedDeploy.mock.calls[0][1]).toEqual(expectedInitParams); + + // price should be any length string of digits + // expect(mockedDeploy.mock.calls[0][2].gasPrice.toString()).toStrictEqual(expect.stringMatching(/\d+/)); + // expect(instance.contractAddress).toBe("contractAddress"); // TODO + }); + + it("should allow errors to bubble up", async () => { + mockedDeploy.mockRejectedValue(new Error("An Error")); + await expect(deployTokenRegistry(deployParams)).rejects.toThrow("An Error"); + }); + + it("should throw when keys are not found anywhere", async () => { + delete process.env.OA_PRIVATE_KEY; + await expect( + deployTokenRegistry({ + registryName: "Test", + registrySymbol: "Tst", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1.0, + }) + ).rejects.toThrow( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ); + }); +}); diff --git a/src/implementations/document-store/grant-role-astrontestnet.test.ts b/src/implementations/document-store/grant-role-astrontestnet.test.ts new file mode 100644 index 00000000..a465d1a7 --- /dev/null +++ b/src/implementations/document-store/grant-role-astrontestnet.test.ts @@ -0,0 +1,126 @@ +import { grantDocumentStoreRole } from "./grant-role"; + +import { Wallet } from "ethers"; +import { DocumentStoreFactory } from "@tradetrust-tt/document-store"; +import { DocumentStoreRoleCommand } from "../../commands/document-store/document-store-command.type"; +import { addAddressPrefix } from "../../utils"; +import { join } from "path"; + +jest.mock("@tradetrust-tt/document-store"); + +const deployParams: DocumentStoreRoleCommand = { + account: "0xabcd", + role: "issuer", + address: "0x1234", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + dryRun: false, + maxPriorityFeePerGasScale: 1, +}; + +// TODO the following test is very fragile and might break on every interface change of DocumentStoreFactory +// ideally must setup ganache, and run the function over it +describe("document-store", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + describe("grant document store issuer role to wallet", () => { + const mockedDocumentStoreFactory: jest.Mock = DocumentStoreFactory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnect: jest.Mock = mockedDocumentStoreFactory.connect; + const mockedGrantRole = jest.fn(); + const mockedCallStaticGrantRole = jest.fn().mockResolvedValue(undefined); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedDocumentStoreFactory.mockReset(); + mockedConnect.mockReset(); + mockedCallStaticGrantRole.mockClear(); + mockedConnect.mockReturnValue({ + grantRole: mockedGrantRole, + DEFAULT_ADMIN_ROLE: jest.fn().mockResolvedValue("ADMIN"), + ISSUER_ROLE: jest.fn().mockResolvedValue("ISSUER"), + REVOKER_ROLE: jest.fn().mockResolvedValue("REVOKER"), + callStatic: { + grantRole: mockedCallStaticGrantRole, + }, + }); + mockedGrantRole.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await grantDocumentStoreRole(deployParams); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedCallStaticGrantRole).toHaveBeenCalledTimes(1); + expect(mockedGrantRole.mock.calls[0][0]).toBe("ISSUER"); + expect(mockedGrantRole.mock.calls[0][1]).toEqual(deployParams.account); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should accept account without 0x prefix and return deployed instance", async () => { + const instance = await grantDocumentStoreRole({ + ...deployParams, + account: addAddressPrefix("abcd"), + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedCallStaticGrantRole).toHaveBeenCalledTimes(1); + expect(mockedGrantRole.mock.calls[0][0]).toBe("ISSUER"); + expect(mockedGrantRole.mock.calls[0][1]).toEqual(deployParams.account); + + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + await grantDocumentStoreRole({ + account: "0xabcd", + address: "0x1234", + network: "astrontestnet", + dryRun: false, + role: "admin", + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + it("should take in the key from key file", async () => { + await grantDocumentStoreRole({ + account: "0xabcd", + address: "0x1234", + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "examples", "sample-key"), + dryRun: false, + role: "admin", + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + }); +}); diff --git a/src/implementations/document-store/issue-astrontestnet.test.ts b/src/implementations/document-store/issue-astrontestnet.test.ts new file mode 100644 index 00000000..a217a715 --- /dev/null +++ b/src/implementations/document-store/issue-astrontestnet.test.ts @@ -0,0 +1,135 @@ +import { issueToDocumentStore } from "./issue"; +import { Wallet } from "ethers"; +import { DocumentStoreFactory } from "@tradetrust-tt/document-store"; +import { DocumentStoreIssueCommand } from "../../commands/document-store/document-store-command.type"; +import { addAddressPrefix } from "../../utils"; +import { join } from "path"; + +jest.mock("@tradetrust-tt/document-store"); + +const deployParams: DocumentStoreIssueCommand = { + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +// TODO the following test is very fragile and might break on every interface change of DocumentStoreFactory +// ideally must setup ganache, and run the function over it +describe("issue document-store", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + const mockedDocumentStoreFactory: jest.Mock = DocumentStoreFactory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnect: jest.Mock = mockedDocumentStoreFactory.connect; + const mockedIssue = jest.fn(); + const mockCallStaticIssue = jest.fn().mockResolvedValue(undefined); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedDocumentStoreFactory.mockReset(); + mockedConnect.mockReset(); + mockCallStaticIssue.mockClear(); + mockedConnect.mockReturnValue({ + issue: mockedIssue, + callStatic: { + issue: mockCallStaticIssue, + }, + }); + mockedIssue.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + + await issueToDocumentStore({ + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await issueToDocumentStore(deployParams); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockCallStaticIssue).toHaveBeenCalledTimes(1); + expect(mockedIssue.mock.calls[0][0]).toEqual(deployParams.hash); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should take in the key from key file", async () => { + await issueToDocumentStore({ + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "examples", "sample-key"), + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + + it("should accept hash without 0x prefix and return deployed instance", async () => { + const instance = await issueToDocumentStore({ ...deployParams, hash: addAddressPrefix("abcd") }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockCallStaticIssue).toHaveBeenCalledTimes(1); + expect(mockedIssue.mock.calls[0][0]).toEqual(deployParams.hash); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should allow errors to bubble up", async () => { + mockedConnect.mockImplementation(() => { + throw new Error("An Error"); + }); + await expect(issueToDocumentStore(deployParams)).rejects.toThrow("An Error"); + }); + + it("should throw when keys are not found anywhere", async () => { + await expect( + issueToDocumentStore({ + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1, + }) + ).rejects.toThrow( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ); + }); +}); diff --git a/src/implementations/document-store/revoke-astrontestnet.test.ts b/src/implementations/document-store/revoke-astrontestnet.test.ts new file mode 100644 index 00000000..86649a2d --- /dev/null +++ b/src/implementations/document-store/revoke-astrontestnet.test.ts @@ -0,0 +1,135 @@ +import { revokeToDocumentStore } from "./revoke"; + +import { Wallet } from "ethers"; +import { DocumentStoreFactory } from "@tradetrust-tt/document-store"; +import { DocumentStoreRevokeCommand } from "../../commands/document-store/document-store-command.type"; +import { addAddressPrefix } from "../../utils"; +import { join } from "path"; + +jest.mock("@tradetrust-tt/document-store"); + +const deployParams: DocumentStoreRevokeCommand = { + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +// TODO the following test is very fragile and might break on every interface change of DocumentStoreFactory +// ideally must setup ganache, and run the function over it +describe("document-store", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + describe("revokeDocumentStore", () => { + const mockedDocumentStoreFactory: jest.Mock = DocumentStoreFactory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnect: jest.Mock = mockedDocumentStoreFactory.connect; + const mockedRevoke = jest.fn(); + const mockCallStaticRevoke = jest.fn().mockResolvedValue(undefined); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedDocumentStoreFactory.mockReset(); + mockedConnect.mockReset(); + mockCallStaticRevoke.mockClear(); + mockedConnect.mockReturnValue({ + revoke: mockedRevoke, + callStatic: { + revoke: mockCallStaticRevoke, + }, + }); + mockedRevoke.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await revokeToDocumentStore(deployParams); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockCallStaticRevoke).toHaveBeenCalledTimes(1); + expect(mockedRevoke.mock.calls[0][0]).toEqual(deployParams.hash); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should accept hash without 0x prefix and return deployed instance", async () => { + const instance = await revokeToDocumentStore({ ...deployParams, hash: addAddressPrefix("abcd") }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockCallStaticRevoke).toHaveBeenCalledTimes(1); + expect(mockedRevoke.mock.calls[0][0]).toEqual(deployParams.hash); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + await revokeToDocumentStore({ + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + + it("should take in the key from key file", async () => { + await revokeToDocumentStore({ + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "examples", "sample-key"), + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + + it("should allow errors to bubble up", async () => { + mockedConnect.mockImplementation(() => { + throw new Error("An Error"); + }); + await expect(revokeToDocumentStore(deployParams)).rejects.toThrow("An Error"); + }); + + it("should throw when keys are not found anywhere", async () => { + await expect( + revokeToDocumentStore({ + hash: "0xabcd", + address: "0x1234", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1, + }) + ).rejects.toThrow( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ); + }); + }); +}); diff --git a/src/implementations/document-store/revoke-role-astrontestnet.test.ts b/src/implementations/document-store/revoke-role-astrontestnet.test.ts new file mode 100644 index 00000000..36d871d8 --- /dev/null +++ b/src/implementations/document-store/revoke-role-astrontestnet.test.ts @@ -0,0 +1,126 @@ +import { revokeDocumentStoreRole } from "./revoke-role"; + +import { Wallet } from "ethers"; +import { DocumentStoreFactory } from "@tradetrust-tt/document-store"; +import { DocumentStoreRoleCommand } from "../../commands/document-store/document-store-command.type"; +import { addAddressPrefix } from "../../utils"; +import { join } from "path"; + +jest.mock("@tradetrust-tt/document-store"); + +const deployParams: DocumentStoreRoleCommand = { + account: "0xabcd", + role: "issuer", + address: "0x1234", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + dryRun: false, + maxPriorityFeePerGasScale: 1, +}; + +// TODO the following test is very fragile and might break on every interface change of DocumentStoreFactory +// ideally must setup ganache, and run the function over it +describe("document-store", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + describe("revoke document store issuer role to wallet", () => { + const mockedDocumentStoreFactory: jest.Mock = DocumentStoreFactory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnect: jest.Mock = mockedDocumentStoreFactory.connect; + const mockedRevokeRole = jest.fn(); + const mockedCallStaticRevokeRole = jest.fn().mockResolvedValue(undefined); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedDocumentStoreFactory.mockReset(); + mockedConnect.mockReset(); + mockedCallStaticRevokeRole.mockClear(); + mockedConnect.mockReturnValue({ + revokeRole: mockedRevokeRole, + DEFAULT_ADMIN_ROLE: jest.fn().mockResolvedValue("ADMIN"), + ISSUER_ROLE: jest.fn().mockResolvedValue("ISSUER"), + REVOKER_ROLE: jest.fn().mockResolvedValue("REVOKER"), + callStatic: { + revokeRole: mockedCallStaticRevokeRole, + }, + }); + mockedRevokeRole.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await revokeDocumentStoreRole(deployParams); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedCallStaticRevokeRole).toHaveBeenCalledTimes(1); + expect(mockedRevokeRole.mock.calls[0][0]).toBe("ISSUER"); + expect(mockedRevokeRole.mock.calls[0][1]).toEqual(deployParams.account); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should accept account without 0x prefix and return deployed instance", async () => { + const instance = await revokeDocumentStoreRole({ + ...deployParams, + account: addAddressPrefix("abcd"), + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedCallStaticRevokeRole).toHaveBeenCalledTimes(1); + expect(mockedRevokeRole.mock.calls[0][0]).toBe("ISSUER"); + expect(mockedRevokeRole.mock.calls[0][1]).toEqual(deployParams.account); + + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + await revokeDocumentStoreRole({ + account: "0xabcd", + address: "0x1234", + network: "astrontestnet", + dryRun: false, + role: "admin", + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + it("should take in the key from key file", async () => { + await revokeDocumentStoreRole({ + account: "0xabcd", + address: "0x1234", + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "examples", "sample-key"), + dryRun: false, + role: "admin", + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + }); +}); diff --git a/src/implementations/document-store/transfer-ownership-astrontestnet.test.ts b/src/implementations/document-store/transfer-ownership-astrontestnet.test.ts new file mode 100644 index 00000000..73989c63 --- /dev/null +++ b/src/implementations/document-store/transfer-ownership-astrontestnet.test.ts @@ -0,0 +1,132 @@ +import { Wallet } from "ethers"; +import { DocumentStoreFactory } from "@tradetrust-tt/document-store"; +import { DocumentStoreTransferOwnershipCommand } from "../../commands/document-store/document-store-command.type"; +import { addAddressPrefix } from "../../utils"; +import { join } from "path"; +import { transferDocumentStoreOwnership } from "./transfer-ownership"; + +jest.mock("@tradetrust-tt/document-store"); + +const deployParams: DocumentStoreTransferOwnershipCommand = { + newOwner: "0xabcd", + address: "0x1234", + network: "astrontestnet", + key: "0000000000000000000000000000000000000000000000000000000000000001", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +// TODO the following test is very fragile and might break on every interface change of DocumentStoreFactory +// ideally must setup ganache, and run the function over it +describe("document-store", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + describe("transfer document store owner role to wallet", () => { + const mockedDocumentStoreFactory: jest.Mock = DocumentStoreFactory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnect: jest.Mock = mockedDocumentStoreFactory.connect; + const mockedGrantRole = jest.fn(); + const mockedRevokeRole = jest.fn(); + const mockedCallStaticGrantRole = jest.fn().mockResolvedValue(undefined); + const mockedCallStaticRevokeRole = jest.fn().mockResolvedValue(undefined); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedDocumentStoreFactory.mockReset(); + mockedConnect.mockReset(); + mockedCallStaticGrantRole.mockClear(); + mockedCallStaticRevokeRole.mockClear(); + mockedConnect.mockReturnValue({ + grantRole: mockedGrantRole, + revokeRole: mockedRevokeRole, + DEFAULT_ADMIN_ROLE: jest.fn().mockResolvedValue("ADMIN"), + callStatic: { + grantRole: mockedCallStaticGrantRole, + revokeRole: mockedCallStaticRevokeRole, + }, + }); + mockedGrantRole.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + mockedRevokeRole.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + it("should pass in the correct params and return the deployed instance", async () => { + const instance = await transferDocumentStoreOwnership(deployParams); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedCallStaticGrantRole).toHaveBeenCalledTimes(1); + expect(mockedGrantRole.mock.calls[0][0]).toBe("ADMIN"); + expect(mockedGrantRole.mock.calls[0][1]).toEqual(deployParams.newOwner); + + await expect(instance.grantTransaction).resolves.toStrictEqual({ transactionHash: "transactionHash" }); + await expect(instance.revokeTransaction).resolves.toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should accept account without 0x prefix and return deployed instance", async () => { + const instance = await transferDocumentStoreOwnership({ + ...deployParams, + newOwner: addAddressPrefix("abcd"), + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${deployParams.key}`); + expect(mockedConnect.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedCallStaticGrantRole).toHaveBeenCalledTimes(1); + expect(mockedGrantRole.mock.calls[0][0]).toBe("ADMIN"); + expect(mockedGrantRole.mock.calls[0][1]).toEqual(deployParams.newOwner); + + await expect(instance.grantTransaction).resolves.toStrictEqual({ transactionHash: "transactionHash" }); + await expect(instance.revokeTransaction).resolves.toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should take in the key from environment variable", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + await transferDocumentStoreOwnership({ + newOwner: "0xabcd", + address: "0x1234", + network: "astrontestnet", + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${process.env.OA_PRIVATE_KEY}`); + }); + it("should take in the key from key file", async () => { + await transferDocumentStoreOwnership({ + newOwner: "0xabcd", + address: "0x1234", + network: "astrontestnet", + keyFile: join(__dirname, "..", "..", "..", "examples", "sample-key"), + dryRun: false, + maxPriorityFeePerGasScale: 1, + }); + + const passedSigner: Wallet = mockedConnect.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x0000000000000000000000000000000000000000000000000000000000000003`); + }); + }); +}); diff --git a/src/implementations/title-escrow/acceptReturned-astrontestnet.test.ts b/src/implementations/title-escrow/acceptReturned-astrontestnet.test.ts new file mode 100644 index 00000000..0ac87245 --- /dev/null +++ b/src/implementations/title-escrow/acceptReturned-astrontestnet.test.ts @@ -0,0 +1,60 @@ +import { TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { BaseTitleEscrowCommand as TitleEscrowReturnDocumentCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { acceptReturned } from "./acceptReturned"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const acceptReturnedDocumentParams: TitleEscrowReturnDocumentCommand = { + tokenRegistry: "0x1122", + tokenId: "0x12345", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + describe("accepts returned transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockBurnToken = jest.fn(); + const mockCallStaticBurnToken = jest.fn().mockResolvedValue(undefined); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockReset(); + mockedConnectERC721.mockReset(); + + mockBurnToken.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + + mockedConnectERC721.mockReturnValue({ + burn: mockBurnToken, + callStatic: { + burn: mockCallStaticBurnToken, + }, + }); + mockBurnToken.mockClear(); + mockCallStaticBurnToken.mockClear(); + }); + it("should pass in the correct params and successfully accepts a returned transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await acceptReturned({ + ...acceptReturnedDocumentParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(acceptReturnedDocumentParams.tokenRegistry, passedSigner); + expect(mockCallStaticBurnToken).toHaveBeenCalledTimes(1); + expect(mockBurnToken).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/implementations/title-escrow/endorseNominatedBeneficiary-astrontestnet.test.ts b/src/implementations/title-escrow/endorseNominatedBeneficiary-astrontestnet.test.ts new file mode 100644 index 00000000..b41e3033 --- /dev/null +++ b/src/implementations/title-escrow/endorseNominatedBeneficiary-astrontestnet.test.ts @@ -0,0 +1,114 @@ +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { TitleEscrowNominateBeneficiaryCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { endorseNominatedBeneficiary } from "./endorseNominatedBeneficiary"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const endorseNominatedBeneficiaryParams: TitleEscrowNominateBeneficiaryCommand = { + tokenId: "0xzyxw", + tokenRegistry: "0x1234", + newBeneficiary: "0x1232", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + describe("endorse transfer of owner of transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockedTokenFactory: jest.Mock = TitleEscrow__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectTokenFactory: jest.Mock = mockedTokenFactory.connect; + + const mockedTitleEscrowAddress = "0x2133"; + const mockedOwnerOf = jest.fn(); + + const mockTransferOwners = jest.fn(); + const mockCallStaticTransferOwners = jest.fn().mockResolvedValue(undefined); + + const mockedBeneficiary = "0xdssfs"; + const mockGetBeneficiary = jest.fn(); + + beforeAll(() => { + mockedOwnerOf.mockReturnValue(mockedTitleEscrowAddress); + mockGetBeneficiary.mockReturnValue(mockedBeneficiary); + + mockedConnectERC721.mockReturnValue({ + ownerOf: mockedOwnerOf, + }); + + mockedConnectTokenFactory.mockReturnValue({ + transferBeneficiary: mockTransferOwners, + beneficiary: mockGetBeneficiary, + callStatic: { + transferBeneficiary: mockCallStaticTransferOwners, + }, + }); + mockTransferOwners.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockClear(); + mockedConnectERC721.mockClear(); + mockedTokenFactory.mockClear(); + mockedConnectTokenFactory.mockClear(); + mockedOwnerOf.mockClear(); + mockTransferOwners.mockClear(); + mockCallStaticTransferOwners.mockClear(); + }); + + it("should pass in the correct params and call the following procedures to invoke an endorsement of transfer of owner of a transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await endorseNominatedBeneficiary({ + ...endorseNominatedBeneficiaryParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(endorseNominatedBeneficiaryParams.tokenRegistry, passedSigner); + expect(mockedOwnerOf).toHaveBeenCalledWith(endorseNominatedBeneficiaryParams.tokenId); + expect(mockedConnectTokenFactory).toHaveBeenCalledWith(mockedTitleEscrowAddress, passedSigner); + expect(mockCallStaticTransferOwners).toHaveBeenCalledTimes(1); + expect(mockTransferOwners).toHaveBeenCalledTimes(1); + }); + + it("should throw an error if nominee is the owner address", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await expect( + endorseNominatedBeneficiary({ + ...endorseNominatedBeneficiaryParams, + newBeneficiary: "0xdssfs", + key: privateKey, + }) + ).rejects.toThrow(`new beneficiary address is the same as the current beneficiary address`); + }); + }); +}); diff --git a/src/implementations/title-escrow/nominateBeneficiary-astrontestnet.test.ts b/src/implementations/title-escrow/nominateBeneficiary-astrontestnet.test.ts new file mode 100644 index 00000000..6e82c320 --- /dev/null +++ b/src/implementations/title-escrow/nominateBeneficiary-astrontestnet.test.ts @@ -0,0 +1,99 @@ +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { TitleEscrowNominateBeneficiaryCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { nominateBeneficiary } from "./nominateBeneficiary"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const nominateBeneficiaryParams: TitleEscrowNominateBeneficiaryCommand = { + newBeneficiary: "0fosui", + tokenId: "0xzyxw", + tokenRegistry: "0x1234", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + describe("nominate change of owner of transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockedTokenFactory: jest.Mock = TitleEscrow__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectTokenFactory: jest.Mock = mockedTokenFactory.connect; + const mockedOwnerOf = jest.fn(); + const mockNominateBeneficiary = jest.fn(); + const mockedTitleEscrowAddress = "0x2133"; + const mockedBeneficiary = "0xdssfs"; + const mockedHolder = "0xdsfls"; + const mockGetBeneficiary = jest.fn(); + const mockGetHolder = jest.fn(); + const mockCallStaticNominateBeneficiary = jest.fn().mockResolvedValue(undefined); + + beforeAll(() => { + mockGetBeneficiary.mockResolvedValue(mockedBeneficiary); + mockGetHolder.mockResolvedValue(mockedHolder); + mockedConnectERC721.mockReturnValue({ + ownerOf: mockedOwnerOf, + }); + mockedConnectTokenFactory.mockReturnValue({ + nominate: mockNominateBeneficiary, + beneficiary: mockGetBeneficiary, + holder: mockGetHolder, + callStatic: { + nominate: mockCallStaticNominateBeneficiary, + }, + }); + mockedOwnerOf.mockReturnValue(mockedTitleEscrowAddress); + mockNominateBeneficiary.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockClear(); + mockedConnectERC721.mockClear(); + mockedTokenFactory.mockClear(); + mockedConnectTokenFactory.mockClear(); + mockedOwnerOf.mockClear(); + mockNominateBeneficiary.mockClear(); + mockGetBeneficiary.mockClear(); + mockGetHolder.mockClear(); + mockCallStaticNominateBeneficiary.mockClear(); + }); + + it("should pass in the correct params and call the following procedures to invoke an nomination of change of owner of a transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await nominateBeneficiary({ + ...nominateBeneficiaryParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(nominateBeneficiaryParams.tokenRegistry, passedSigner); + expect(mockedOwnerOf).toHaveBeenCalledWith(nominateBeneficiaryParams.tokenId); + expect(mockedConnectTokenFactory).toHaveBeenCalledWith(mockedTitleEscrowAddress, passedSigner); + expect(mockCallStaticNominateBeneficiary).toHaveBeenCalledTimes(1); + expect(mockNominateBeneficiary).toHaveBeenCalledTimes(1); + }); + + it("should throw an error if new owner addresses is the same as current owner", async () => { + mockGetBeneficiary.mockReturnValue(nominateBeneficiaryParams.newBeneficiary); + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await expect( + nominateBeneficiary({ + ...nominateBeneficiaryParams, + key: privateKey, + }) + ).rejects.toThrow("new beneficiary address is the same as the current beneficiary address"); + }); + }); +}); diff --git a/src/implementations/title-escrow/rejectReturned-astrontestnet.test.ts b/src/implementations/title-escrow/rejectReturned-astrontestnet.test.ts new file mode 100644 index 00000000..33b5b02d --- /dev/null +++ b/src/implementations/title-escrow/rejectReturned-astrontestnet.test.ts @@ -0,0 +1,96 @@ +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { BaseTitleEscrowCommand as TitleEscrowReturnDocumentCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { rejectReturned } from "./rejectReturned"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const rejectReturnedDocumentParams: TitleEscrowReturnDocumentCommand = { + tokenRegistry: "0x1122", + tokenId: "0x12345", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + describe("rejects returned transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockedTitleEscrowFactory: jest.Mock = TitleEscrow__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectTitleEscrowFactory: jest.Mock = mockedTitleEscrowFactory.connect; + + const mockedBeneficiary = jest.fn(); + const mockedHolder = jest.fn(); + const mockRestoreTitle = jest.fn(); + const mockTransferEvent = jest.fn(); + const mockQueryFilter = jest.fn(); + const mockCallStaticRestoreTitle = jest.fn().mockResolvedValue(undefined); + + const mockedLastTitleEscrowAddress = "0xMockedLastTitleEscrowAddress"; + const mockedLastBeneficiary = "0xMockedLastBeneficiaryAddress"; + const mockedLastHolder = "0xMockedLastHolderAddress"; + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockReset(); + mockedConnectERC721.mockReset(); + mockedTitleEscrowFactory.mockReset(); + mockedConnectTitleEscrowFactory.mockReset(); + + mockedBeneficiary.mockReturnValue(mockedLastBeneficiary); + mockedHolder.mockReturnValue(mockedLastHolder); + mockRestoreTitle.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + mockTransferEvent.mockReturnValue({ + address: "0x1122", + topics: ["0x00000", null, null, "0x12345"], + }); + mockQueryFilter.mockReturnValue([ + { + args: [mockedLastTitleEscrowAddress, "0x1122"], + }, + ]); + + mockedConnectTitleEscrowFactory.mockReturnValue({ + beneficiary: mockedBeneficiary, + holder: mockedHolder, + }); + mockedConnectERC721.mockReturnValue({ + restore: mockRestoreTitle, + filters: { Transfer: mockTransferEvent }, + queryFilter: mockQueryFilter, + callStatic: { + restore: mockCallStaticRestoreTitle, + }, + }); + mockedBeneficiary.mockClear(); + mockedHolder.mockClear(); + mockRestoreTitle.mockClear(); + mockTransferEvent.mockClear(); + mockQueryFilter.mockClear(); + mockCallStaticRestoreTitle.mockClear(); + }); + + it("should pass in the correct params and successfully rejects a returned transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await rejectReturned({ + ...rejectReturnedDocumentParams, + key: privateKey, + }); + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(rejectReturnedDocumentParams.tokenRegistry, passedSigner); + expect(mockCallStaticRestoreTitle).toHaveBeenCalledTimes(1); + expect(mockRestoreTitle.mock.calls[0][0]).toBe(rejectReturnedDocumentParams.tokenId); + }); + }); +}); diff --git a/src/implementations/title-escrow/returnDocument-astrontestnet.test.ts b/src/implementations/title-escrow/returnDocument-astrontestnet.test.ts new file mode 100644 index 00000000..a8cdc829 --- /dev/null +++ b/src/implementations/title-escrow/returnDocument-astrontestnet.test.ts @@ -0,0 +1,75 @@ +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { BaseTitleEscrowCommand as TitleEscrowReturnDocumentCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { returnDocument } from "./returnDocument"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const returnDocumentParams: TitleEscrowReturnDocumentCommand = { + tokenRegistry: "0x1122", + tokenId: "0x12345", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + describe("return transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockedTitleEscrowFactory: jest.Mock = TitleEscrow__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectTitleEscrowFactory: jest.Mock = mockedTitleEscrowFactory.connect; + const mockedOwnerOf = jest.fn(); + const mockReturn = jest.fn(); + const mockCallStaticReturn = jest.fn().mockResolvedValue(undefined); + const mockedTitleEscrowAddress = "0x2133"; + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockReset(); + mockedConnectERC721.mockReset(); + mockedTitleEscrowFactory.mockReset(); + mockedConnectTitleEscrowFactory.mockReset(); + + mockedOwnerOf.mockReturnValue(mockedTitleEscrowAddress); + mockReturn.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + mockedConnectERC721.mockReturnValue({ + ownerOf: mockedOwnerOf, + }); + mockedConnectTitleEscrowFactory.mockReturnValue({ + returnToIssuer: mockReturn, + callStatic: { + returnToIssuer: mockCallStaticReturn, + }, + }); + + mockedOwnerOf.mockClear(); + mockReturn.mockClear(); + mockCallStaticReturn.mockClear(); + }); + it("should pass in the correct params and successfully return a transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await returnDocument({ + ...returnDocumentParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(returnDocumentParams.tokenRegistry, passedSigner); + expect(mockedOwnerOf).toHaveBeenCalledWith(returnDocumentParams.tokenId); + expect(mockedConnectTitleEscrowFactory).toHaveBeenCalledWith(mockedTitleEscrowAddress, passedSigner); + expect(mockCallStaticReturn).toHaveBeenCalledTimes(1); + expect(mockReturn).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/implementations/title-escrow/transferHolder-astrontestnet.test.ts b/src/implementations/title-escrow/transferHolder-astrontestnet.test.ts new file mode 100644 index 00000000..a67574f8 --- /dev/null +++ b/src/implementations/title-escrow/transferHolder-astrontestnet.test.ts @@ -0,0 +1,79 @@ +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { TitleEscrowTransferHolderCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { transferHolder } from "./transferHolder"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const transferHolderParams: TitleEscrowTransferHolderCommand = { + newHolder: "0xabcd", + tokenId: "0xzyxw", + tokenRegistry: "0x1234", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + describe("change holder of transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + + const mockedTokenFactory: jest.Mock = TitleEscrow__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectTokenFactory: jest.Mock = mockedTokenFactory.connect; + const mockedOwnerOf = jest.fn(); + const mockTransferHolder = jest.fn(); + const mockCallStaticTransferHolder = jest.fn().mockResolvedValue(undefined); + const mockedTitleEscrowAddress = "0x2133"; + + beforeAll(() => { + mockedOwnerOf.mockReturnValue(mockedTitleEscrowAddress); + mockTransferHolder.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + mockedConnectERC721.mockReturnValue({ + ownerOf: mockedOwnerOf, + }); + mockedConnectTokenFactory.mockReturnValue({ + transferHolder: mockTransferHolder, + callStatic: { + transferHolder: mockCallStaticTransferHolder, + }, + }); + }); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockClear(); + mockedConnectERC721.mockClear(); + mockedTokenFactory.mockClear(); + mockedConnectTokenFactory.mockClear(); + mockedOwnerOf.mockClear(); + mockTransferHolder.mockClear(); + mockCallStaticTransferHolder.mockClear(); + }); + + it("should pass in the correct params and call the following procedures to invoke a change in holder of a transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await transferHolder({ + ...transferHolderParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(transferHolderParams.tokenRegistry, passedSigner); + expect(mockedOwnerOf).toHaveBeenCalledWith(transferHolderParams.tokenId); + expect(mockedConnectTokenFactory).toHaveBeenCalledWith(mockedTitleEscrowAddress, passedSigner); + expect(mockCallStaticTransferHolder).toHaveBeenCalledTimes(1); + expect(mockTransferHolder).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/implementations/title-escrow/transferOwners-astrontestnet.test.ts b/src/implementations/title-escrow/transferOwners-astrontestnet.test.ts new file mode 100644 index 00000000..a5480e23 --- /dev/null +++ b/src/implementations/title-escrow/transferOwners-astrontestnet.test.ts @@ -0,0 +1,120 @@ +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { TitleEscrowEndorseTransferOfOwnersCommand } from "../../commands/title-escrow/title-escrow-command.type"; +import { transferOwners } from "./transferOwners"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const endorseChangeOwnersParams: TitleEscrowEndorseTransferOfOwnersCommand = { + newHolder: "0xabcd", + newOwner: "0fosui", + tokenId: "0xzyxw", + tokenRegistry: "0x1234", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("title-escrow", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + + describe("endorse change of owners of transferable record", () => { + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockedTokenFactory: jest.Mock = TitleEscrow__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + + const mockedConnectTokenFactory: jest.Mock = mockedTokenFactory.connect; + const mockedOwnerOf = jest.fn(); + const mockTransferOwners = jest.fn(); + const mockCallStaticTransferOwners = jest.fn().mockResolvedValue(undefined); + const mockedTitleEscrowAddress = "0x2133"; + const mockedBeneficiary = "0xdssfs"; + const mockedHolder = "0xdsfls"; + const mockGetBeneficiary = jest.fn(); + const mockGetHolder = jest.fn(); + + beforeAll(() => { + mockGetBeneficiary.mockReturnValue(mockedBeneficiary); + mockGetHolder.mockReturnValue(mockedHolder); + mockedConnectERC721.mockReturnValue({ + ownerOf: mockedOwnerOf, + }); + mockedConnectTokenFactory.mockReturnValue({ + transferOwners: mockTransferOwners, + beneficiary: mockGetBeneficiary, + holder: mockGetHolder, + callStatic: { + transferOwners: mockCallStaticTransferOwners, + }, + }); + mockedOwnerOf.mockReturnValue(mockedTitleEscrowAddress); + mockTransferOwners.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockClear(); + mockedConnectERC721.mockClear(); + mockedTokenFactory.mockClear(); + mockedConnectTokenFactory.mockClear(); + mockedOwnerOf.mockClear(); + mockTransferOwners.mockClear(); + mockGetBeneficiary.mockClear(); + mockGetHolder.mockClear(); + mockCallStaticTransferOwners.mockClear(); + }); + + it("should pass in the correct params and call the following procedures to invoke an endorsement of change of owner of a transferable record", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await transferOwners({ + ...endorseChangeOwnersParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721).toHaveBeenCalledWith(endorseChangeOwnersParams.tokenRegistry, passedSigner); + expect(mockedOwnerOf).toHaveBeenCalledWith(endorseChangeOwnersParams.tokenId); + expect(mockedConnectTokenFactory).toHaveBeenCalledWith(mockedTitleEscrowAddress, passedSigner); + expect(mockGetBeneficiary).toHaveBeenCalledTimes(1); + expect(mockGetHolder).toHaveBeenCalledTimes(1); + expect(mockCallStaticTransferOwners).toHaveBeenCalledTimes(1); + expect(mockTransferOwners).toHaveBeenCalledTimes(1); + }); + + it("should throw an error if new owner and new holder addresses are the same as current owner and holder addressses", async () => { + mockGetBeneficiary.mockReturnValue(endorseChangeOwnersParams.newOwner); + mockGetHolder.mockReturnValue(endorseChangeOwnersParams.newHolder); + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + await expect( + transferOwners({ + ...endorseChangeOwnersParams, + key: privateKey, + }) + ).rejects.toThrow("new owner and new holder addresses are the same as the current owner and holder addresses"); + }); + }); +}); diff --git a/src/implementations/token-registry/issue-astrontestnet.test.ts b/src/implementations/token-registry/issue-astrontestnet.test.ts new file mode 100644 index 00000000..7e7bdad8 --- /dev/null +++ b/src/implementations/token-registry/issue-astrontestnet.test.ts @@ -0,0 +1,115 @@ +import { TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { Wallet } from "ethers"; + +import { TokenRegistryIssueCommand } from "../../commands/token-registry/token-registry-command.type"; +import { addAddressPrefix } from "../../utils"; +import { issueToTokenRegistry } from "./issue"; + +jest.mock("@tradetrust-tt/token-registry/contracts"); + +const deployParams: TokenRegistryIssueCommand = { + beneficiary: "0xabcd", + holder: "0xabce", + tokenId: "0xzyxw", + address: "0x1234", + network: "astrontestnet", + maxPriorityFeePerGasScale: 1, + dryRun: false, +}; + +describe("token-registry", () => { + describe("issue", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + const mockedTradeTrustTokenFactory: jest.Mock = TradeTrustToken__factory as any; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock static method + const mockedConnectERC721: jest.Mock = mockedTradeTrustTokenFactory.connect; + const mockedIssue = jest.fn(); + const mockCallStaticSafeMint = jest.fn().mockResolvedValue(undefined); + + const mockTtErc721Contract = { + mint: mockedIssue, + callStatic: { + mint: mockCallStaticSafeMint, + }, + }; + + beforeAll(() => { + mockedIssue.mockReturnValue({ + hash: "hash", + wait: () => Promise.resolve({ transactionHash: "transactionHash" }), + }); + }); + + beforeEach(() => { + delete process.env.OA_PRIVATE_KEY; + mockedTradeTrustTokenFactory.mockClear(); + mockCallStaticSafeMint.mockClear(); + mockedConnectERC721.mockReset(); + mockedConnectERC721.mockResolvedValue(mockTtErc721Contract); + }); + + it("should pass in the correct params and return the deployed instance", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + const instance = await issueToTokenRegistry({ + ...deployParams, + key: privateKey, + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedIssue.mock.calls[0][0]).toEqual(deployParams.beneficiary); + expect(mockedIssue.mock.calls[0][1]).toEqual(deployParams.holder); + expect(mockedIssue.mock.calls[0][2]).toEqual(deployParams.tokenId); + expect(mockCallStaticSafeMint).toHaveBeenCalledTimes(1); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should accept tokenId without 0x prefix and return deployed instance", async () => { + const privateKey = "0000000000000000000000000000000000000000000000000000000000000001"; + const instance = await issueToTokenRegistry({ + ...deployParams, + key: privateKey, + tokenId: addAddressPrefix("zyxw"), + }); + + const passedSigner: Wallet = mockedConnectERC721.mock.calls[0][1]; + expect(passedSigner.privateKey).toBe(`0x${privateKey}`); + expect(mockedConnectERC721.mock.calls[0][0]).toEqual(deployParams.address); + expect(mockedIssue.mock.calls[0][0]).toEqual(deployParams.beneficiary); + expect(mockedIssue.mock.calls[0][1]).toEqual(deployParams.holder); + expect(mockedIssue.mock.calls[0][2]).toEqual(deployParams.tokenId); + expect(mockCallStaticSafeMint).toHaveBeenCalledTimes(1); + expect(instance).toStrictEqual({ transactionHash: "transactionHash" }); + }); + + it("should throw when keys are not found anywhere", async () => { + await expect(issueToTokenRegistry(deployParams)).rejects.toThrow( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ); + }); + + it("should allow errors to bubble up", async () => { + process.env.OA_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000002"; + mockedConnectERC721.mockImplementation(() => { + throw new Error("An Error"); + }); + await expect(issueToTokenRegistry(deployParams)).rejects.toThrow("An Error"); + }); + }); +}); diff --git a/src/implementations/transaction/transaction-astrontestnet.test.ts b/src/implementations/transaction/transaction-astrontestnet.test.ts new file mode 100644 index 00000000..cf7c5ad2 --- /dev/null +++ b/src/implementations/transaction/transaction-astrontestnet.test.ts @@ -0,0 +1,39 @@ +import { cancelTransaction } from "./transaction"; +import { getWalletOrSigner } from "../utils/wallet"; +import path from "path"; +import signale from "signale"; +import { BigNumber } from "ethers/lib/ethers"; +jest.mock("../utils/wallet"); + +const mockGetWallet = getWalletOrSigner as jest.Mock; + +describe("document-store", () => { + describe("cancelTransaction", () => { + const signaleInfoSpy = jest.spyOn(signale, "info"); + + it("success in retrieving transaction nonce and gas price using --transaction-hash", async () => { + const mockGetTransaction = jest.fn(); + mockGetTransaction.mockResolvedValue({ + nonce: 10, + gasPrice: BigNumber.from(3), + }); + + const mockSendTransaction = jest.fn(); + + mockGetWallet.mockResolvedValue({ + provider: { getTransaction: mockGetTransaction }, + sendTransaction: mockSendTransaction, + getAddress: () => "0xC84b0719A82626417c40f3168513dFABDB6A9079", + }); + + await cancelTransaction({ + transactionHash: "0x456bba58226f03e3fb7d72b5143ceecfb6bfb66b00586929f6d60890ec264c2c", + network: "astrontestnet", + keyFile: path.resolve(__dirname, "./key.file"), + }); + expect(signaleInfoSpy).toHaveBeenNthCalledWith(1, "Transaction detail retrieved. Nonce: 10, Gas-price: 3"); + expect(mockSendTransaction.mock.calls[0][0].nonce.toNumber()).toBe(10); + expect(mockSendTransaction.mock.calls[0][0].gasPrice.toNumber()).toBe(6); + }); + }); +}); diff --git a/src/implementations/utils/__tests__/wallet-astrontestnet.test.ts b/src/implementations/utils/__tests__/wallet-astrontestnet.test.ts new file mode 100644 index 00000000..c34dee34 --- /dev/null +++ b/src/implementations/utils/__tests__/wallet-astrontestnet.test.ts @@ -0,0 +1,82 @@ +import { prompt } from "inquirer"; +import path from "path"; +import { getWalletOrSigner } from "../wallet"; +jest.mock("inquirer"); + +// assigning the mock so that we get correct typing +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +const promptMock: jest.Mock = prompt; + +const privateKey = "0xcd27dc84c82c5814e7edac518edd5f263e7db7f25adb7a1afe13996a95583cf2"; +const walletAddress = "0xB26B4941941C51a4885E5B7D3A1B861E54405f90"; + +describe("wallet", () => { + // increase timeout because ethers is throttling + jest.setTimeout(30_000); + jest.spyOn(global, "fetch").mockImplementation( + jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + standard: { + maxPriorityFee: 0, + maxFee: 0, + }, + }), + }) + ) as jest.Mock + ); + afterEach(() => { + delete process.env.OA_PRIVATE_KEY; + promptMock.mockRestore(); + }); + it("should return the wallet when providing the key using environment variable", async () => { + process.env.OA_PRIVATE_KEY = privateKey; + const wallet = await getWalletOrSigner({ network: "astrontestnet" }); + await expect(wallet.getAddress()).resolves.toStrictEqual(walletAddress); + expect(wallet.privateKey).toStrictEqual(privateKey); + }); + it("should return the wallet when providing the key using key option", async () => { + const wallet = await getWalletOrSigner({ network: "astrontestnet", key: privateKey }); + await expect(wallet.getAddress()).resolves.toStrictEqual(walletAddress); + expect(wallet.privateKey).toStrictEqual(privateKey); + }); + it("should return the wallet when providing the key using key-file option", async () => { + const wallet = await getWalletOrSigner({ + network: "astrontestnet", + keyFile: path.resolve(__dirname, "./key.file"), + }); + await expect(wallet.getAddress()).resolves.toStrictEqual(walletAddress); + expect(wallet.privateKey).toStrictEqual(privateKey); + }); + it("should return the wallet when providing an encrypted wallet", async () => { + promptMock.mockReturnValue({ password: "password123" }); + + const wallet = await getWalletOrSigner({ + network: "astrontestnet", + encryptedWalletPath: path.resolve(__dirname, "./wallet.json"), + progress: () => void 0, // shut up progress bar + }); + await expect(wallet.getAddress()).resolves.toStrictEqual(walletAddress); + expect(wallet.privateKey).toStrictEqual(privateKey); + }); + it("should throw an error when the wallet password is invalid", async () => { + promptMock.mockReturnValue({ password: "invalid" }); + + await expect( + getWalletOrSigner({ + network: "astrontestnet", + encryptedWalletPath: path.resolve(__dirname, "./wallet.json"), + progress: () => void 0, // shut up progress bar + }) + ).rejects.toStrictEqual(new Error("invalid password")); + }); + it("should throw an error when no option is provided", async () => { + await expect(getWalletOrSigner({ network: "astrontestnet" })).rejects.toStrictEqual( + new Error( + "No private key found in OA_PRIVATE_KEY, key, key-file, please supply at least one or supply an encrypted wallet path, or provide aws kms signer information" + ) + ); + }); +});