diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 741bae2..68e867e 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -35,11 +35,13 @@ "@types/fs-extra": "^5.0.4", "@types/mocha": "^5.2.6", "@types/node": "^18", + "@types/sinon": "^17", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "eslint": "^8", "hardhat": "^2.0.0", "mocha": "^10", + "sinon": "^17.0.1", "ts-node": "^10", "typescript": "^5" }, diff --git a/packages/plugin/src/utils.ts b/packages/plugin/src/utils.ts index f0716b8..e3ec55e 100644 --- a/packages/plugin/src/utils.ts +++ b/packages/plugin/src/utils.ts @@ -24,13 +24,14 @@ export function getConfigEnvironmentVariable( export async function getNetworkChainId( network: string, - hre: HardhatRuntimeEnvironment + hre: HardhatRuntimeEnvironment, + HttpProviderClass = HttpProvider ): Promise { const networkConfig = hre.config.networks[network]; let chainID = networkConfig.chainId; if (!chainID) { assert("httpHeaders" in networkConfig); - const httpProvider = new HttpProvider(networkConfig.url, { + const httpProvider = new HttpProviderClass(networkConfig.url, { providerOptions: { headers: networkConfig.httpHeaders }, }); const web3 = new Web3(httpProvider); diff --git a/packages/plugin/test/fixture-projects/hardhat-project/hardhat.config.ts b/packages/plugin/test/fixture-projects/hardhat-project/hardhat.config.ts index e12dc06..5fd15c2 100644 --- a/packages/plugin/test/fixture-projects/hardhat-project/hardhat.config.ts +++ b/packages/plugin/test/fixture-projects/hardhat-project/hardhat.config.ts @@ -1,6 +1,6 @@ // We load the plugin here. -import {HardhatUserConfig} from "hardhat/types"; -import {Environment} from "@buildwithsygma/sygma-sdk-core"; +import { HardhatUserConfig } from "hardhat/types"; +import { Environment } from "@buildwithsygma/sygma-sdk-core"; import "../../../src/index"; @@ -10,12 +10,15 @@ const config: HardhatUserConfig = { networks: { sepolia: { chainId: 11155111, - url: 'https://localhost:8080' + url: "https://localhost:8080", }, goerli: { chainId: 5, - url: 'https://localhost:8080' - } + url: "https://localhost:8080", + }, + goerliNoChainId: { + url: "https://localhost:8080", + }, }, multichain: { environment: Environment.TESTNET, diff --git a/packages/plugin/test/helpers.ts b/packages/plugin/test/helpers.ts index 6efee77..34d44c0 100644 --- a/packages/plugin/test/helpers.ts +++ b/packages/plugin/test/helpers.ts @@ -1,6 +1,6 @@ +import path from "path"; import { resetHardhatContext } from "hardhat/plugins-testing"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -import path from "path"; declare module "mocha" { interface Context { diff --git a/packages/plugin/test/mocks.ts b/packages/plugin/test/mocks.ts new file mode 100644 index 0000000..9dd17f2 --- /dev/null +++ b/packages/plugin/test/mocks.ts @@ -0,0 +1,42 @@ +import { HttpProvider } from "web3"; +import { Domain, Network } from "@buildwithsygma/sygma-sdk-core"; + +// TODO: Refactor to be reusable... and maybe add sinon +export function createMockHttpProvider(): typeof HttpProvider { + return MockHttpProvider as unknown as typeof HttpProvider; +} + +class MockHttpProvider { + async request(payload: { method: string }) { + if (payload.method === "eth_chainId") { + return { jsonrpc: "2.0", id: 1, result: "0x5" }; // Example chain ID in hex format + } + } +} + +export const mockDomains: Domain[] = [ + { + id: 1, + chainId: 5, + name: "goerli", + type: Network.EVM, + }, + { + id: 2, + chainId: 11155111, + name: "sepolia", + type: Network.EVM, + }, + { + id: 6, + chainId: 17000, + name: "holesky", + type: Network.EVM, + }, + { + id: 7, + chainId: 80001, + name: "mumbai", + type: Network.EVM, + }, +]; diff --git a/packages/plugin/test/project.test.ts b/packages/plugin/test/project.test.ts index 283ef1c..ff1cfa7 100644 --- a/packages/plugin/test/project.test.ts +++ b/packages/plugin/test/project.test.ts @@ -1,11 +1,11 @@ -import 'mocha' -import {assert, expect, use} from "chai"; +import "mocha"; +import { assert, use } from "chai"; import chaiAsPromised from "chai-as-promised"; +import { Environment } from "@buildwithsygma/sygma-sdk-core"; import { MultichainHardhatRuntimeEnvironmentField } from "../src/MultichainHardhatRuntimeEnvironmentField"; import { useEnvironment } from "./helpers"; -import {Environment} from "@buildwithsygma/sygma-sdk-core"; use(chaiAsPromised); @@ -34,6 +34,5 @@ describe("Integration tests examples", function () { describe("Hardhat Runtime Environment extension", function () { useEnvironment("hardhat-project"); - }); }); diff --git a/packages/plugin/test/utils.test.ts b/packages/plugin/test/utils.test.ts new file mode 100644 index 0000000..f1066b9 --- /dev/null +++ b/packages/plugin/test/utils.test.ts @@ -0,0 +1,201 @@ +import "mocha"; +import { assert, expect, use } from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { Environment } from "@buildwithsygma/sygma-sdk-core"; +import helloSygmaContract from "@chainsafe/hardhat-plugin-multichain-deploy-contracts/artifacts/contracts/mocks/HelloSygma.sol/HelloSygma"; +import { HardhatPluginError } from "hardhat/plugins"; +import { + encodeInitData, + getConfigEnvironmentVariable, + getNetworkChainId, + mapNetworkArgs, + sumedFees, +} from "../src/utils"; +import { NetworkArguments } from "../src/types"; +import { createMockHttpProvider, mockDomains } from "./mocks"; +import { useEnvironment } from "./helpers"; + +use(chaiAsPromised); + +describe("Unit tests for utils", function () { + describe("getConfigEnvironmentVariable", function () { + useEnvironment("hardhat-project"); + + it("Should return valid environment from config", function () { + assert.deepEqual( + getConfigEnvironmentVariable(this.hre), + Environment.TESTNET + ); + }); + }); + + describe("getNetworkChainId", function () { + useEnvironment("hardhat-project"); + + it("Retrieve chainID from config", async function () { + const chainId = await getNetworkChainId("goerli", this.hre); + expect(chainId).to.equal(5); + }); + + it("Retrieve chainID from node if is not available in config", async function () { + const chainId = await getNetworkChainId( + "goerliNoChainId", + this.hre, + createMockHttpProvider() + ); + expect(chainId).to.equal(5); + }); + }); + + describe("sumedFees", function () { + it("Sum's all available types and return current result", function () { + const sum = sumedFees([4, BigInt(3), "2", "0x1"]); + expect(sum).to.equal("10"); + }); + + [ + { type: "Number", values: [5, 5, 5] }, + { type: "Bigint", values: [BigInt(5), BigInt(5), BigInt(5)] }, + { type: "String", values: ["5", "5", "5"] }, + { type: "HexString", values: ["0x5", "0x5", "0x5"] }, + ].forEach(({ type, values }) => { + it(`Sum's values of type ${type} and return current value`, function () { + const sum = sumedFees(values); + expect(sum).to.equal("15"); + }); + }); + }); + + describe("encodeInitData", function () { + const { abi } = helloSygmaContract; + + it("Return current encoded data for specified method", function () { + expect(encodeInitData(abi, "setName", ["Pepe"])).to.be.equal( + "0xc47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045065706500000000000000000000000000000000000000000000000000000000" + ); + }); + + it("Should fail on unexciting method", function () { + assert.throw( + () => { + encodeInitData(abi, "nonExisting", ["GirlFriend"]); + }, + HardhatPluginError, + "InitMethod nonExisting not foud in ABI" + ); + }); + }); + + describe("mapNetworkArgs", function () { + const { abi } = helloSygmaContract; + + it("Return current encoded data, without init data", function () { + const networkArgs: NetworkArguments = { + goerli: { args: [5] }, + holesky: { args: [5] }, + }; + + assert.deepEqual(mapNetworkArgs(abi, networkArgs, mockDomains), { + deployDomainIDs: [BigInt(1), BigInt(6)], + constructorArgs: [ + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x0000000000000000000000000000000000000000000000000000000000000005", + ], + initDatas: [new Uint8Array(), new Uint8Array()], + }); + }); + + it("Return current encoded data, with init data", function () { + const networkArgs: NetworkArguments = { + goerli: { + args: [5], + initData: { initMethodName: "setName", initMethodArgs: ["chain"] }, + }, + holesky: { + args: [5], + initData: { initMethodName: "setName", initMethodArgs: ["safe"] }, + }, + }; + + assert.deepEqual(mapNetworkArgs(abi, networkArgs, mockDomains), { + deployDomainIDs: [BigInt(1), BigInt(6)], + constructorArgs: [ + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x0000000000000000000000000000000000000000000000000000000000000005", + ], + initDatas: [ + "0xc47f002700000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005636861696e000000000000000000000000000000000000000000000000000000", + "0xc47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047361666500000000000000000000000000000000000000000000000000000000", + ], + }); + }); + + it("Return current encoded data, with mixed init data", function () { + const networkArgs: NetworkArguments = { + goerli: { args: [5] }, + holesky: { + args: [5], + initData: { initMethodName: "setName", initMethodArgs: ["safe"] }, + }, + }; + + assert.deepEqual(mapNetworkArgs(abi, networkArgs, mockDomains), { + deployDomainIDs: [BigInt(1), BigInt(6)], + constructorArgs: [ + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x0000000000000000000000000000000000000000000000000000000000000005", + ], + initDatas: [ + new Uint8Array(), + "0xc47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047361666500000000000000000000000000000000000000000000000000000000", + ], + }); + }); + + it("Should fail on missing domain", function () { + const networkArgs: NetworkArguments = { + sepolia: { + args: [5], + initData: { initMethodName: "setName", initMethodArgs: ["chain"] }, + }, + goreli: { + args: [5], + initData: { initMethodName: "setName", initMethodArgs: ["safe"] }, + }, + }; + + assert.throw( + () => { + mapNetworkArgs(abi, networkArgs, mockDomains); + }, + HardhatPluginError, + "Unavailable Networks in networkArgs" + ); + }); + + it("Should fail on missing wrong initMethodName", function () { + const networkArgs: NetworkArguments = { + sepolia: { + args: [5], + initData: { + initMethodName: "setLevel" as any /* Hack to bypass type-check */, + initMethodArgs: ["22"], + }, + }, + goreli: { + args: [5], + initData: { initMethodName: "setName", initMethodArgs: ["safe"] }, + }, + }; + + assert.throw( + () => { + mapNetworkArgs(abi, networkArgs, mockDomains); + }, + HardhatPluginError, + "InitMethod setLevel not foud in ABI" + ); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index ffa355d..8d025c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -228,11 +228,13 @@ __metadata: "@types/fs-extra": ^5.0.4 "@types/mocha": ^5.2.6 "@types/node": ^18 + "@types/sinon": ^17 chai: ^4.2.0 chai-as-promised: ^7.1.1 eslint: ^8 hardhat: ^2.0.0 mocha: ^10 + sinon: ^17.0.1 ts-node: ^10 typescript: ^5 web3: ^4.3.0 @@ -2622,6 +2624,51 @@ __metadata: languageName: node linkType: hard +"@sinonjs/commons@npm:^2.0.0": + version: 2.0.0 + resolution: "@sinonjs/commons@npm:2.0.0" + dependencies: + type-detect: 4.0.8 + checksum: babe3fdfc7dfb810f6918f2ae055032a1c7c18910595f1c6bfda87bb1737c1a57268d4ca78c3d8ad2fa4aae99ff79796fad76be735a5a38ab763c0b3cfad1ae7 + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: 4.0.8 + checksum: 1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^11.2.2": + version: 11.2.2 + resolution: "@sinonjs/fake-timers@npm:11.2.2" + dependencies: + "@sinonjs/commons": ^3.0.0 + checksum: a4218efa6fdafda622d02d4c0a6ab7df3641cb038bb0b14f0a3ee56f50c95aab4f1ab2d7798ce928b40c6fc1839465a558c9393a77e4dca879e1b2f8d60d8136 + languageName: node + linkType: hard + +"@sinonjs/samsam@npm:^8.0.0": + version: 8.0.0 + resolution: "@sinonjs/samsam@npm:8.0.0" + dependencies: + "@sinonjs/commons": ^2.0.0 + lodash.get: ^4.4.2 + type-detect: ^4.0.8 + checksum: c1654ad72ecd9efd4a57d756c492c1c17a197c3138da57b75ba1729562001ed1b3b9c656cce1bd1d91640bc86eb4185a72eced528d176fff09a3a01de28cdcc6 + languageName: node + linkType: hard + +"@sinonjs/text-encoding@npm:^0.7.2": + version: 0.7.2 + resolution: "@sinonjs/text-encoding@npm:0.7.2" + checksum: 583a45bf3643169e313ff9d4395aff28b0c4f330d3697e252c3effc13d4303ee30f83df542732c1a68617720e4ea6fc08d48a3d9151c9b354a7fc356a8e9b162 + languageName: node + linkType: hard + "@solidity-parser/parser@npm:^0.14.0": version: 0.14.5 resolution: "@solidity-parser/parser@npm:0.14.5" @@ -3040,6 +3087,22 @@ __metadata: languageName: node linkType: hard +"@types/sinon@npm:^17": + version: 17.0.3 + resolution: "@types/sinon@npm:17.0.3" + dependencies: + "@types/sinonjs__fake-timers": "*" + checksum: 6fc3aa497fd87826375de3dbddc2bf01c281b517c32c05edf95b5ad906382dc221bca01ca9d44fc7d5cb4c768f996f268154e87633a45b3c0b5cddca7ef5e2be + languageName: node + linkType: hard + +"@types/sinonjs__fake-timers@npm:*": + version: 8.1.5 + resolution: "@types/sinonjs__fake-timers@npm:8.1.5" + checksum: 2b8bdc246365518fc1b08f5720445093cce586183acca19a560be6ef81f824bd9a96c090e462f622af4d206406dadf2033c5daf99a51c1096da6494e5c8dc32e + languageName: node + linkType: hard + "@types/underscore@npm:*": version: 1.11.15 resolution: "@types/underscore@npm:1.11.15" @@ -5202,6 +5265,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.1.0": + version: 5.1.0 + resolution: "diff@npm:5.1.0" + checksum: 77a0d9beb9ed54796154ac2511872288432124ac90a1cabb1878783c9b4d81f1847f3b746a0630b1e836181461d2c76e1e6b95559bef86ed16294d114862e364 + languageName: node + linkType: hard + "difflib@npm:^0.2.4": version: 0.2.4 resolution: "difflib@npm:0.2.4" @@ -8344,6 +8414,13 @@ __metadata: languageName: node linkType: hard +"just-extend@npm:^6.2.0": + version: 6.2.0 + resolution: "just-extend@npm:6.2.0" + checksum: d41cbdb6d85b986d4deaf2144d81d4f7266cd408fc95189d046d63f610c2dc486b141aeb6ef319c2d76fe904d45a6bb31f19b098ff0427c35688e0c383fc0511 + languageName: node + linkType: hard + "keccak@npm:3.0.2": version: 3.0.2 resolution: "keccak@npm:3.0.2" @@ -8556,6 +8633,13 @@ __metadata: languageName: node linkType: hard +"lodash.get@npm:^4.4.2": + version: 4.4.2 + resolution: "lodash.get@npm:4.4.2" + checksum: 48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -9265,6 +9349,19 @@ __metadata: languageName: node linkType: hard +"nise@npm:^5.1.5": + version: 5.1.7 + resolution: "nise@npm:5.1.7" + dependencies: + "@sinonjs/commons": ^3.0.0 + "@sinonjs/fake-timers": ^11.2.2 + "@sinonjs/text-encoding": ^0.7.2 + just-extend: ^6.2.0 + path-to-regexp: ^6.2.1 + checksum: 4b9c5af2949d5489c7f2911b525cd446071d5e588a434869d7c1cc26a18c34077fb813c9484ba01c66b722d710dbd7ee4a30337b16a1b000275bf5c9b8f42331 + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -9761,6 +9858,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^6.2.1": + version: 6.2.1 + resolution: "path-to-regexp@npm:6.2.1" + checksum: 7a73811ca703e5c199e5b50b9649ab8f6f7b458a37f7dff9ea338815203f5b1f95fe8cb24d4fdfe2eab5d67ce43562d92534330babca35cdf3231f966adb9360 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -10893,6 +10997,20 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^17.0.1": + version: 17.0.1 + resolution: "sinon@npm:17.0.1" + dependencies: + "@sinonjs/commons": ^3.0.0 + "@sinonjs/fake-timers": ^11.2.2 + "@sinonjs/samsam": ^8.0.0 + diff: ^5.1.0 + nise: ^5.1.5 + supports-color: ^7.2.0 + checksum: 63e678bd58a959ebf69ddadb5410e9c31bd161b848b9dbec5f34f7e1485a8681810cd27d37bde072b68915b41b498f30bb5fb890c73e7a51dc281d1b1d573cfb + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -11276,7 +11394,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": +"supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -11628,7 +11746,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd