From d0178a0a4074d2a4ae79e2ae9ae3091edd0739b8 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Fri, 10 Nov 2023 20:12:56 +0100 Subject: [PATCH 01/16] Add test to ensure no regression on the stack trace issue --- xsuite/src/world/sworld.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index 4d2c2678..3c3b5996 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -301,9 +301,11 @@ test("SWallet.callContract.assertFail - Wrong code", async () => { gasLimit: 10_000_000, }) .assertFail({ code: 5 }), - ).rejects.toThrow( - "Failed with unexpected error code.\nExpected code: 5\nReceived code: 4", - ); + ).rejects.toMatchObject({ + message: + "Failed with unexpected error code.\nExpected code: 5\nReceived code: 4", + stack: expect.stringContaining("src/world/sworld.test.ts:295:3)"), + }); }); test("SWallet.callContract.assertFail - Wrong message", async () => { From e9bb17d19c9dc5c7c5ff1e8a92658e08c9c68eba Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sat, 11 Nov 2023 12:00:45 +0100 Subject: [PATCH 02/16] Fix request xEGLD --- xsuite/src/cli/cmd.test.ts | 33 +++++++++++++------------------ xsuite/src/cli/requestXegldCmd.ts | 10 ++++++---- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/xsuite/src/cli/cmd.test.ts b/xsuite/src/cli/cmd.test.ts index 66d81b8a..6e4ca624 100644 --- a/xsuite/src/cli/cmd.test.ts +++ b/xsuite/src/cli/cmd.test.ts @@ -102,25 +102,18 @@ test("request-xegld --wallet wallet.json", async () => { const walletPath = path.resolve("wallet.json"); const signer = Keystore.createFile_unsafe(walletPath, "1234").newSigner(); const address = signer.toString(); - let numBalanceReqs = 0; + let balances: number[] = []; const server = setupServer( - http.get("https://devnet-api.multiversx.com/blocks/latest", () => { - return Response.json({ - hash: "103b656af4fa9625962c5978e8cf69aca6918eb146a495bcf474f1c6a922be93", - }); - }), - http.get("https://devnet-api.multiversx.com/blocks", () => { - return Response.json([ - { - hash: "103b656af4fa9625962c5978e8cf69aca6918eb146a495bcf474f1c6a922be93", - }, - ]); - }), + http.get("https://devnet-api.multiversx.com/blocks/latest", () => + Response.json({ hash: "" }), + ), + http.get("https://devnet-api.multiversx.com/blocks", () => + Response.json([{ hash: "" }]), + ), http.get( `https://devnet-gateway.multiversx.com/address/${address}/balance`, () => { - numBalanceReqs += 1; - const balance = `${30n * 10n ** 18n * BigInt(numBalanceReqs)}`; + const balance = `${BigInt(balances.shift() ?? 0) * 10n ** 18n}`; return Response.json({ code: "successful", data: { balance } }); }, ), @@ -128,7 +121,9 @@ test("request-xegld --wallet wallet.json", async () => { server.listen(); stdoutInt.start(); input.injected.push("1234", "1234"); + balances = [0, 1]; await run(`request-xegld --wallet ${walletPath}`); + balances = [0, 10]; await run(`request-xegld --wallet ${walletPath} --password 1234`); stdoutInt.stop(); server.close(); @@ -137,18 +132,18 @@ test("request-xegld --wallet wallet.json", async () => { `Loading keystore wallet at "${walletPath}"...`, "Enter password: ", "", - `Claiming 30 xEGLD for address "${address}"...`, + `Claiming xEGLD for address "${address}"...`, "", "Open the URL and request tokens:", splittedStdoutData.at(6), "", - chalk.green("Wallet well received 30 xEGLD."), - `Claiming 30 xEGLD for address "${address}"...`, + chalk.green("Wallet well received 1 xEGLD."), + `Claiming xEGLD for address "${address}"...`, "", "Open the URL and request tokens:", splittedStdoutData.at(12), "", - chalk.green("Wallet well received 30 xEGLD."), + chalk.green("Wallet well received 10 xEGLD."), "", ]); }); diff --git a/xsuite/src/cli/requestXegldCmd.ts b/xsuite/src/cli/requestXegldCmd.ts index 1b13c2da..469e3887 100644 --- a/xsuite/src/cli/requestXegldCmd.ts +++ b/xsuite/src/cli/requestXegldCmd.ts @@ -11,7 +11,7 @@ import { logSuccess } from "./helpers"; export const registerRequestXegldCmd = (cmd: Command) => { cmd .command("request-xegld") - .description("Request 30 xEGLD (once per day).") + .description("Request xEGLD (once per day).") .requiredOption("--wallet ", "Wallet path") .option("--password ", "Wallet password") .action(action); @@ -32,7 +32,7 @@ const action = async ({ signer = KeystoreSigner.fromFile_unsafe(walletPath, password); } const address = signer.toString(); - log(`Claiming 30 xEGLD for address "${address}"...`); + log(`Claiming xEGLD for address "${address}"...`); const client = new NativeAuthClient({ origin: "https://devnet-wallet.multiversx.com", @@ -57,13 +57,15 @@ const action = async ({ const initialBalance = await devnetProxy.getAccountBalance(address); let balance = initialBalance; - while (balance - initialBalance < 30n * 10n ** 18n) { + while (balance <= initialBalance) { balance = await devnetProxy.getAccountBalance(address); await new Promise((r) => setTimeout(r, 1000)); } log(); - logSuccess("Wallet well received 30 xEGLD."); + logSuccess( + `Wallet well received ${(balance - initialBalance) / 10n ** 18n} xEGLD.`, + ); }; const devnetProxy = new Proxy("https://devnet-gateway.multiversx.com"); From 6c12875b92edc6df0f4c4785de260582e3bd7750 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sat, 11 Nov 2023 12:14:46 +0100 Subject: [PATCH 03/16] Improve backup sentence --- xsuite/src/cli/cmd.test.ts | 8 ++------ xsuite/src/cli/newWalletCmd.ts | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/xsuite/src/cli/cmd.test.ts b/xsuite/src/cli/cmd.test.ts index 6e4ca624..5753c1ec 100644 --- a/xsuite/src/cli/cmd.test.ts +++ b/xsuite/src/cli/cmd.test.ts @@ -42,9 +42,7 @@ test("new-wallet --wallet wallet.json", async () => { chalk.bold.blue("Private key:"), ...keystore.mnemonicWords.map((w, i) => ` ${i + 1}. ${w}`), "", - chalk.bold.yellow( - "Don't forget to backup the private key in a secure place.", - ), + chalk.bold.yellow("Please backup the private key in a secure place."), "", ]); }); @@ -79,9 +77,7 @@ test("new-wallet --wallet wallet.json --password 1234", async () => { chalk.bold.blue("Private key:"), ...keystore.mnemonicWords.map((w, i) => ` ${i + 1}. ${w}`), "", - chalk.bold.yellow( - "Don't forget to backup the private key in a secure place.", - ), + chalk.bold.yellow("Please backup the private key in a secure place."), "", ]); }); diff --git a/xsuite/src/cli/newWalletCmd.ts b/xsuite/src/cli/newWalletCmd.ts index c6fb047a..88963a8c 100644 --- a/xsuite/src/cli/newWalletCmd.ts +++ b/xsuite/src/cli/newWalletCmd.ts @@ -46,9 +46,5 @@ const action = async ({ log(chalk.bold.blue("Private key:")); log(keystore.mnemonicWords.map((w, i) => ` ${i + 1}. ${w}`).join("\n")); log(); - log( - chalk.bold.yellow( - `Don't forget to backup the private key in a secure place.`, - ), - ); + log(chalk.bold.yellow(`Please backup the private key in a secure place.`)); }; From 3669a25b4fb7e478de1cc00bf2801d7695b8030d Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sat, 11 Nov 2023 13:06:54 +0100 Subject: [PATCH 04/16] Use relative dir for cd --- xsuite/src/cli/cmd.test.ts | 24 +++++++++++++----------- xsuite/src/cli/newCmd.ts | 10 ++++++---- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/xsuite/src/cli/cmd.test.ts b/xsuite/src/cli/cmd.test.ts index 5753c1ec..1bfe7d00 100644 --- a/xsuite/src/cli/cmd.test.ts +++ b/xsuite/src/cli/cmd.test.ts @@ -164,10 +164,11 @@ test("new --dir contract && build --locked && build -r && test-rust && test-scen await run("new --dir contract"); stdoutInt.stop(); expect(fs.readdirSync(process.cwd()).length).toEqual(1); - const dirPath = path.resolve("contract"); + const dir = "contract"; + const absDir = path.resolve(dir); expect(stdoutInt.data.split("\n")).toEqual([ chalk.blue( - `Downloading contract ${chalk.magenta("blank")} in "${dirPath}"...`, + `Downloading contract ${chalk.magenta("blank")} in "${absDir}"...`, ), "", chalk.blue("Installing packages..."), @@ -176,7 +177,7 @@ test("new --dir contract && build --locked && build -r && test-rust && test-scen chalk.blue("Initialized a git repository."), "", chalk.green( - `Successfully created ${chalk.magenta("blank")} in "${dirPath}".`, + `Successfully created ${chalk.magenta("blank")} in "${absDir}".`, ), "", "Inside that directory, you can run several commands:", @@ -192,20 +193,20 @@ test("new --dir contract && build --locked && build -r && test-rust && test-scen "", "We suggest that you begin by typing:", "", - chalk.cyan(` cd ${dirPath}`), + chalk.cyan(` cd ${dir}`), chalk.cyan(" npm run build"), "", ]); const targetDir = path.join(__dirname, "..", "..", "..", "target"); - process.chdir(dirPath); + process.chdir(absDir); stdoutInt.start(); await run(`build --locked --target-dir ${targetDir}`); stdoutInt.stop(); expect(stdoutInt.data.split("\n")).toEqual([ chalk.blue("Building contract..."), - `(1/1) Building "${dirPath}"...`, + `(1/1) Building "${absDir}"...`, chalk.cyan( `$ cargo run --target-dir ${targetDir} build --locked --target-dir ${targetDir}`, ), @@ -217,7 +218,7 @@ test("new --dir contract && build --locked && build -r && test-rust && test-scen stdoutInt.stop(); expect(stdoutInt.data.split("\n")).toEqual([ chalk.blue("Building contract..."), - `(1/1) Building "${dirPath}"...`, + `(1/1) Building "${absDir}"...`, chalk.cyan( `$ cargo run --target-dir ${targetDir} build --target-dir ${targetDir}`, ), @@ -254,11 +255,12 @@ test(`new --starter vested-transfers --dir contract --no-git --no-install`, asyn stdoutInt.stop(); expect(fs.readdirSync(process.cwd()).length).toEqual(1); const contractChalk = chalk.magenta(contract); - const dirPath = path.resolve("contract"); + const dir = "contract"; + const absDir = path.resolve(dir); expect(stdoutInt.data.split("\n")).toEqual([ - chalk.blue(`Downloading contract ${contractChalk} in "${dirPath}"...`), + chalk.blue(`Downloading contract ${contractChalk} in "${absDir}"...`), "", - chalk.green(`Successfully created ${contractChalk} in "${dirPath}".`), + chalk.green(`Successfully created ${contractChalk} in "${absDir}".`), "", "Inside that directory, you can run several commands:", "", @@ -273,7 +275,7 @@ test(`new --starter vested-transfers --dir contract --no-git --no-install`, asyn "", "We suggest that you begin by typing:", "", - chalk.cyan(` cd ${dirPath}`), + chalk.cyan(` cd ${dir}`), chalk.cyan(" npm run build"), "", ]); diff --git a/xsuite/src/cli/newCmd.ts b/xsuite/src/cli/newCmd.ts index 96017b0b..4e01a676 100644 --- a/xsuite/src/cli/newCmd.ts +++ b/xsuite/src/cli/newCmd.ts @@ -33,14 +33,14 @@ const action = async ({ install?: boolean; git?: boolean; }) => { - dir = path.resolve(dir); + const absDir = path.resolve(dir); if (fs.existsSync(dir)) { - logError(`Directory already exists at "${dir}".`); + logError(`Directory already exists at "${absDir}".`); return; } else { fs.mkdirSync(dir, { recursive: true }); } - logTitle(`Downloading contract ${chalk.magenta(starter)} in "${dir}"...`); + logTitle(`Downloading contract ${chalk.magenta(starter)} in "${absDir}"...`); await downloadAndExtractContract(starter, dir); if (install) { log(); @@ -53,7 +53,9 @@ const action = async ({ } log(); log( - chalk.green(`Successfully created ${chalk.magenta(starter)} in "${dir}".`), + chalk.green( + `Successfully created ${chalk.magenta(starter)} in "${absDir}".`, + ), ); log(); log("Inside that directory, you can run several commands:"); From f68857dcd35a9cdb96f028ce01116569e5d5e755 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sat, 11 Nov 2023 18:28:24 +0100 Subject: [PATCH 05/16] Fix and uniformize interaction errors --- xsuite/src/proxy/proxy.ts | 14 +---- xsuite/src/world/sworld.test.ts | 32 ++++++++-- xsuite/src/world/world.ts | 108 +++++++++++++++++++++----------- 3 files changed, 100 insertions(+), 54 deletions(-) diff --git a/xsuite/src/proxy/proxy.ts b/xsuite/src/proxy/proxy.ts index 79f59c09..75a15e6e 100644 --- a/xsuite/src/proxy/proxy.ts +++ b/xsuite/src/proxy/proxy.ts @@ -121,18 +121,8 @@ export class Proxy { } static async query(baseUrl: string, query: BroadQuery) { - const { - returnData, - ...data - }: { - returnData: string[]; - returnCode: string; - returnMessage: string; - } = unrawRes(await Proxy.queryRaw(baseUrl, query)).data; - return { - returnData: returnData.map(b64ToHexString), - ...data, - }; + const res = unrawRes(await Proxy.queryRaw(baseUrl, query)); + return res.data as Record; } query(query: BroadQuery) { diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index 3c3b5996..f64f1e9f 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -280,6 +280,28 @@ test("SWallet.callContract with return", async () => { assertHexList(txResult.returnData, ["01"]); }); +test("SWallet.callContract - Stack trace", async () => { + await expect( + wallet.callContract({ + callee: contract, + funcName: "non_existent_function", + gasLimit: 10_000_000, + }), + ).rejects.toMatchObject({ + stack: expect.stringContaining("src/world/sworld.test.ts:284:3)"), + }); +}); + +test("SWallet.query.assertFail - Correct parameters", async () => { + await world + .query({ + callee: contract, + funcName: "require_positive", + funcArgs: [e.U64(0)], + }) + .assertFail({ code: 4, message: "Amount is not positive." }); +}); + test("SWallet.callContract.assertFail - Correct parameters", async () => { await wallet .callContract({ @@ -301,11 +323,9 @@ test("SWallet.callContract.assertFail - Wrong code", async () => { gasLimit: 10_000_000, }) .assertFail({ code: 5 }), - ).rejects.toMatchObject({ - message: - "Failed with unexpected error code.\nExpected code: 5\nReceived code: 4", - stack: expect.stringContaining("src/world/sworld.test.ts:295:3)"), - }); + ).rejects.toThrow( + "Failed with unexpected error code.\nExpected code: 5\nReceived code: 4", + ); }); test("SWallet.callContract.assertFail - Wrong message", async () => { @@ -333,5 +353,5 @@ test("SWallet.callContract.assertFail - Transaction not failing", async () => { gasLimit: 10_000_000, }) .assertFail(), - ).rejects.toThrow("Transaction has not failed."); + ).rejects.toThrow("No failure."); }); diff --git a/xsuite/src/world/world.ts b/xsuite/src/world/world.ts index 69f7a31e..e86f1032 100644 --- a/xsuite/src/world/world.ts +++ b/xsuite/src/world/world.ts @@ -1,4 +1,5 @@ import { AddressEncodable } from "../data/AddressEncodable"; +import { b64ToHexString } from "../data/utils"; import { CallContractTxParams, DeployContractTxParams, @@ -75,7 +76,18 @@ export class World { } query(query: Query) { - return this.proxy.query(query); + return InteractionPromise.from(this.#query(query)); + } + + async #query(query: Query): Promise { + const resQuery = await this.proxy.query(query); + if (![0, "ok"].includes(resQuery.returnCode)) { + throw new QueryError(resQuery.returnCode, resQuery.returnMessage); + } + return { + query: resQuery, + returnData: resQuery.returnData.map(b64ToHexString), + }; } } @@ -103,7 +115,7 @@ export class Wallet extends Signer { this.gasPrice = gasPrice; } - sign(data: Buffer): Promise { + sign(data: Buffer) { return this.signer.sign(data); } @@ -127,10 +139,8 @@ export class Wallet extends Signer { return this.proxy.getAccountWithKvs(this); } - executeTx( - txParams: Omit, - ): TxPromise { - return TxPromise.from(this.#executeTx(txParams)); + executeTx(txParams: Omit) { + return InteractionPromise.from(this.#executeTx(txParams)); } async #executeTx({ @@ -149,25 +159,26 @@ export class Wallet extends Signer { const txHash = await this.proxy.sendTx(tx); const resTx = await this.proxy.getCompletedTx(txHash); if (resTx.status !== "success") { - throw new Error(`Tx failed: 100 - Failure with status ${resTx.status}.`); + throw new TxError("errorStatus", resTx.status); } if (resTx.executionReceipt?.returnCode) { const { returnCode, returnMessage } = resTx.executionReceipt; - throw new Error(`Tx failed: ${returnCode} - ${returnMessage}`); + throw new TxError(returnCode, returnMessage); } const signalErrorEvent = resTx?.logs?.events.find( (e: any) => e.identifier === "signalError", ); if (signalErrorEvent) { - throw new Error("Tx failed: 100 - signalError event."); + const error = atob(signalErrorEvent.topics[1]); + throw new TxError("signalError", error); } return { tx: resTx }; } deployContract( txParams: Omit, - ): TxPromise { - return TxPromise.from(this.#deployContract(txParams)); + ) { + return InteractionPromise.from(this.#deployContract(txParams)); } async #deployContract( @@ -191,8 +202,8 @@ export class Wallet extends Signer { upgradeContract( txParams: Omit, - ): TxPromise { - return TxPromise.from(this.#upgradeContract(txParams)); + ) { + return InteractionPromise.from(this.#upgradeContract(txParams)); } async #upgradeContract( @@ -206,10 +217,8 @@ export class Wallet extends Signer { return { ...txResult, returnData }; } - transfer( - txParams: Omit, - ): TxPromise { - return TxPromise.from(this.#transfer(txParams)); + transfer(txParams: Omit) { + return InteractionPromise.from(this.#transfer(txParams)); } async #transfer( @@ -222,8 +231,8 @@ export class Wallet extends Signer { callContract( txParams: Omit, - ): TxPromise { - return TxPromise.from(this.#callContract(txParams)); + ) { + return InteractionPromise.from(this.#callContract(txParams)); } async #callContract( @@ -272,7 +281,32 @@ export class Contract extends AddressEncodable { } } -export class TxPromise implements PromiseLike { +class InteractionError extends Error { + interaction: string; + code: number | string; + msg: string; + + constructor(interaction: string, code: number | string, msg: string) { + super(`${interaction} failed: ${code} - ${msg}`); + this.interaction = interaction; + this.code = code; + this.msg = msg; + } +} + +class TxError extends InteractionError { + constructor(code: number | string, message: string) { + super("Transaction", code, message); + } +} + +class QueryError extends InteractionError { + constructor(code: number | string, message: string) { + super("Query", code, message); + } +} + +export class InteractionPromise implements PromiseLike { #promise: Promise; constructor( @@ -284,8 +318,8 @@ export class TxPromise implements PromiseLike { this.#promise = new Promise(executor); } - static from(promise: Promise): TxPromise { - return new TxPromise(promise.then.bind(promise)); + static from(promise: Promise): InteractionPromise { + return new InteractionPromise(promise.then.bind(promise)); } then( @@ -298,7 +332,7 @@ export class TxPromise implements PromiseLike { | undefined | null, ) { - return TxPromise.from(this.#promise.then(onfulfilled, onrejected)); + return InteractionPromise.from(this.#promise.then(onfulfilled, onrejected)); } catch( @@ -310,27 +344,24 @@ export class TxPromise implements PromiseLike { return this.then(null, onrejected); } - assertFail({ code, message }: { code?: number; message?: string } = {}) { + assertFail({ + code, + message, + }: { code?: number | string; message?: string } = {}) { return this.then(() => { - throw new Error("Transaction has not failed."); + throw new Error("No failure."); }).catch((error) => { - if (!(error instanceof Error)) { + if (!(error instanceof InteractionError)) { throw error; } - const matches = error.message.match(/Tx failed: (\d+) - (.+)/); - if (!matches) { - throw error; - } - const errorCode = parseInt(matches[1]); - const errorMessage = matches[2]; - if (code !== undefined && code !== errorCode) { + if (code !== undefined && code !== error.code) { throw new Error( - `Failed with unexpected error code.\nExpected code: ${code}\nReceived code: ${errorCode}`, + `Failed with unexpected error code.\nExpected code: ${code}\nReceived code: ${error.code}`, ); } - if (message !== undefined && message !== errorMessage) { + if (message !== undefined && message !== error.msg) { throw new Error( - `Failed with unexpected error message.\nExpected message: ${message}\nReceived message: ${errorMessage}`, + `Failed with unexpected error message.\nExpected message: ${message}\nReceived message: ${error.msg}`, ); } }); @@ -360,6 +391,11 @@ export const expandCode = (code: string) => { return code; }; +type QueryResult = { + query: any; + returnData: string[]; +}; + export type TxResult = { tx: any; }; From e551347e582773f9d69da0ce842bd7470f29944d Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sat, 11 Nov 2023 19:41:55 +0100 Subject: [PATCH 06/16] Add hex address --- xsuite/src/data/AddressDecoder.test.ts | 6 +++--- xsuite/src/data/AddressDecoder.ts | 4 ++-- xsuite/src/data/AddressEncodable.test.ts | 17 +++++++++++------ xsuite/src/data/AddressEncodable.ts | 18 +++++++++++------- xsuite/src/data/address.test.ts | 13 +++---------- xsuite/src/data/address.ts | 11 ++--------- xsuite/src/data/kvsEncoding.ts | 7 +++++-- xsuite/src/proxy/proxy.ts | 6 +++--- xsuite/src/world/utils.ts | 6 ++++-- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/xsuite/src/data/AddressDecoder.test.ts b/xsuite/src/data/AddressDecoder.test.ts index 9a7c8799..d37d38db 100644 --- a/xsuite/src/data/AddressDecoder.test.ts +++ b/xsuite/src/data/AddressDecoder.test.ts @@ -1,13 +1,13 @@ import { describe, it, expect } from "@jest/globals"; import { AddressDecoder } from "./AddressDecoder"; -import { addressByteLength, bytesToBech32 } from "./AddressEncodable"; +import { addressByteLength, bytesToBechAddress } from "./AddressEncodable"; describe("AddressDecoder", () => { describe("topDecode", () => { it("should decode bytes into the correct bech32 address", () => { const bytes = new Uint8Array(addressByteLength); const result = new AddressDecoder().topDecode(bytes); - expect(result).toBe(bytesToBech32(bytes)); + expect(result).toBe(bytesToBechAddress(bytes)); }); }); @@ -15,7 +15,7 @@ describe("AddressDecoder", () => { it("should decode bytes into the correct bech32 address", () => { const bytes = new Uint8Array(addressByteLength); const result = new AddressDecoder().nestDecode(bytes); - expect(result).toBe(bytesToBech32(bytes)); + expect(result).toBe(bytesToBechAddress(bytes)); }); }); }); diff --git a/xsuite/src/data/AddressDecoder.ts b/xsuite/src/data/AddressDecoder.ts index 6a96cece..c2d59050 100644 --- a/xsuite/src/data/AddressDecoder.ts +++ b/xsuite/src/data/AddressDecoder.ts @@ -1,11 +1,11 @@ -import { addressByteLength, bytesToBech32 } from "./AddressEncodable"; +import { addressByteLength, bytesToBechAddress } from "./AddressEncodable"; import { ByteReader } from "./ByteReader"; import { AbstractDecoder } from "./Decoder"; export class AddressDecoder extends AbstractDecoder { _topDecode(r: ByteReader) { const bytes = r.readExact(addressByteLength); - return bytesToBech32(bytes); + return bytesToBechAddress(bytes); } _nestDecode(r: ByteReader) { diff --git a/xsuite/src/data/AddressEncodable.test.ts b/xsuite/src/data/AddressEncodable.test.ts index b138c09d..b247f99b 100644 --- a/xsuite/src/data/AddressEncodable.test.ts +++ b/xsuite/src/data/AddressEncodable.test.ts @@ -2,11 +2,11 @@ import { describe, it, test, expect } from "@jest/globals"; import { AddressEncodable, addressByteLength, - bytesToBech32, + bytesToBechAddress, } from "./AddressEncodable"; describe("AddressEncodable", () => { - test("top encoding from address", () => { + test("top encoding from bech address", () => { const bechAddress = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; const bytesAddress = new Uint8Array(32); @@ -15,6 +15,13 @@ describe("AddressEncodable", () => { ); }); + test("top encoding from hex address", () => { + const hexAddress = + "0000000000000000000000000000000000000000000000000000000000000000"; + const bytesAddress = new Uint8Array(32); + expect(new AddressEncodable(hexAddress).toTopBytes()).toEqual(bytesAddress); + }); + test("top encoding from bytes", () => { const address = new Uint8Array(32); expect(new AddressEncodable(address).toTopBytes()).toEqual(address); @@ -28,7 +35,7 @@ describe("AddressEncodable", () => { test("bytesToBech32", () => { const bechAddress = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; - expect(bytesToBech32(new Uint8Array(32))).toEqual(bechAddress); + expect(bytesToBechAddress(new Uint8Array(32))).toEqual(bechAddress); }); test("correct address length", () => { @@ -38,9 +45,7 @@ describe("AddressEncodable", () => { it("should throw error if address HRP invalid", () => { const address = "btc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5mhdvz"; - expect(() => new AddressEncodable(address)).toThrow( - 'Address HRP is not "erd".', - ); + expect(() => new AddressEncodable(address)).toThrow("Invalid address HRP."); }); it("should throw error if address length too short", () => { diff --git a/xsuite/src/data/AddressEncodable.ts b/xsuite/src/data/AddressEncodable.ts index 059081ec..ed3ab094 100644 --- a/xsuite/src/data/AddressEncodable.ts +++ b/xsuite/src/data/AddressEncodable.ts @@ -1,5 +1,6 @@ import { bech32 } from "bech32"; import { Encodable } from "./Encodable"; +import { hexStringToBytes } from "./utils"; export class AddressEncodable extends Encodable { #bytes: Uint8Array; @@ -7,7 +8,7 @@ export class AddressEncodable extends Encodable { constructor(address: string | Uint8Array) { super(); if (typeof address === "string") { - address = bech32ToBytes(address); + address = strAddressToBytes(address); } if (address.byteLength !== addressByteLength) { throw new Error("Invalid address length."); @@ -24,19 +25,22 @@ export class AddressEncodable extends Encodable { } toString(): string { - return bytesToBech32(this.#bytes); + return bytesToBechAddress(this.#bytes); } } -export const bytesToBech32 = (bytes: Uint8Array): string => { +export const bytesToBechAddress = (bytes: Uint8Array): string => { const words = bech32.toWords(bytes); return bech32.encode(HRP, words); }; -const bech32ToBytes = (bechAddress: string): Uint8Array => { - const { prefix, words } = bech32.decode(bechAddress); - if (prefix != HRP) { - throw new Error(`Address HRP is not "${HRP}".`); +const strAddressToBytes = (strAddress: string): Uint8Array => { + if (strAddress.length === 2 * addressByteLength) { + return hexStringToBytes(strAddress); + } + const { prefix, words } = bech32.decode(strAddress); + if (prefix !== HRP) { + throw new Error(`Invalid address HRP.`); } return Uint8Array.from(bech32.fromWords(words)); }; diff --git a/xsuite/src/data/address.test.ts b/xsuite/src/data/address.test.ts index 2e50de19..778358fa 100644 --- a/xsuite/src/data/address.test.ts +++ b/xsuite/src/data/address.test.ts @@ -1,17 +1,10 @@ import { test, expect } from "@jest/globals"; -import { addressToBytes, addressToHexString } from "./address"; +import { addressToAddressEncodable } from "./address"; -test("addressToBytes", () => { - const bechAddress = - "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; - const bytesAddress = new Uint8Array(32); - expect(addressToBytes(bechAddress)).toEqual(bytesAddress); -}); - -test("addressToHexString", () => { +test("addressToAddressEncodable", () => { const bechAddress = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; const hexAddress = "0000000000000000000000000000000000000000000000000000000000000000"; - expect(addressToHexString(bechAddress)).toEqual(hexAddress); + expect(addressToAddressEncodable(bechAddress).toTopHex()).toEqual(hexAddress); }); diff --git a/xsuite/src/data/address.ts b/xsuite/src/data/address.ts index 9af1f552..1de06871 100644 --- a/xsuite/src/data/address.ts +++ b/xsuite/src/data/address.ts @@ -3,16 +3,9 @@ import { enc } from "./encoding"; export type Address = string | AddressEncodable; -export const addressToBytes = (address: Address) => { +export const addressToAddressEncodable = (address: Address) => { if (typeof address === "string") { address = enc.Addr(address); } - return address.toTopBytes(); -}; - -export const addressToHexString = (address: Address) => { - if (typeof address === "string") { - address = enc.Addr(address); - } - return address.toTopHex(); + return address; }; diff --git a/xsuite/src/data/kvsEncoding.ts b/xsuite/src/data/kvsEncoding.ts index cdd2ac04..3878fd85 100644 --- a/xsuite/src/data/kvsEncoding.ts +++ b/xsuite/src/data/kvsEncoding.ts @@ -1,6 +1,6 @@ import { Field, Type } from "protobufjs"; import { Encodable } from "./Encodable"; -import { Address, addressToBytes } from "./address"; +import { Address, addressToAddressEncodable } from "./address"; import { enc } from "./encoding"; import { hexToEncodable, Hex, hexToBytes } from "./hex"; import { Kv } from "./kvs"; @@ -161,7 +161,10 @@ const getEsdtKvs = ({ metadata.push(["Name", enc.Str(name).toTopBytes()]); } if (creator !== undefined) { - metadata.push(["Creator", addressToBytes(creator)]); + metadata.push([ + "Creator", + addressToAddressEncodable(creator).toTopBytes(), + ]); } if (royalties !== undefined) { metadata.push(["Royalties", royalties.toString()]); diff --git a/xsuite/src/proxy/proxy.ts b/xsuite/src/proxy/proxy.ts index 79f59c09..2312bc8e 100644 --- a/xsuite/src/proxy/proxy.ts +++ b/xsuite/src/proxy/proxy.ts @@ -1,6 +1,6 @@ import { e } from "../data"; import { Encodable } from "../data/Encodable"; -import { Address, addressToHexString } from "../data/address"; +import { Address, addressToAddressEncodable } from "../data/address"; import { Hex, hexToHexString } from "../data/hex"; import { RawKvs } from "../data/kvs"; import { b64ToHexString } from "../data/utils"; @@ -304,7 +304,7 @@ export class Tx { receiver = sender; const dataParts: string[] = []; dataParts.push("MultiESDTNFTTransfer"); - dataParts.push(addressToHexString(_receiver)); + dataParts.push(addressToAddressEncodable(_receiver).toTopHex()); dataParts.push(e.U(esdts.length).toTopHex()); for (const esdt of esdts) { dataParts.push(e.Str(esdt.id).toTopHex()); @@ -340,7 +340,7 @@ export class Tx { if (esdts?.length) { receiver = sender; dataParts.push("MultiESDTNFTTransfer"); - dataParts.push(addressToHexString(callee)); + dataParts.push(addressToAddressEncodable(callee).toTopHex()); dataParts.push(e.U(esdts.length).toTopHex()); for (const esdt of esdts) { dataParts.push(e.Str(esdt.id).toTopHex()); diff --git a/xsuite/src/world/utils.ts b/xsuite/src/world/utils.ts index 006cb126..f317df0b 100644 --- a/xsuite/src/world/utils.ts +++ b/xsuite/src/world/utils.ts @@ -1,12 +1,14 @@ import fs from "node:fs"; -import { Address, addressToHexString } from "../data/address"; +import { Address, addressToAddressEncodable } from "../data/address"; export const readFileHex = (path: string) => { return fs.readFileSync(path, "hex"); }; export const isContractAddress = (address: Address) => { - return addressToHexString(address).startsWith("0000000000000000"); + return addressToAddressEncodable(address) + .toTopHex() + .startsWith("0000000000000000"); }; export const numberToBytesAddress = ( From 08a78923dda5fb8acd006dae3214994e4d04dcb3 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 12 Nov 2023 13:29:08 +0100 Subject: [PATCH 07/16] Make simulnet working with never set address --- xsuite-simulnet/src/handleAddress.go | 17 ++++++-- xsuite/src/data/address.ts | 8 ++-- xsuite/src/proxy/proxy.ts | 16 ++++++-- xsuite/src/world/sworld.test.ts | 60 +++++++++++++++++++++++++++- xsuite/src/world/sworld.ts | 2 +- 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/xsuite-simulnet/src/handleAddress.go b/xsuite-simulnet/src/handleAddress.go index 093cfdab..16ffa087 100644 --- a/xsuite-simulnet/src/handleAddress.go +++ b/xsuite-simulnet/src/handleAddress.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/go-chi/chi" + worldmock "github.com/multiversx/mx-chain-vm-v1_4-go/mock/world" ) func (ae *Executor) HandleAddress(r *http.Request) (interface{}, error) { @@ -14,7 +15,7 @@ func (ae *Executor) HandleAddress(r *http.Request) (interface{}, error) { if err != nil { return nil, err } - account := ae.vmTestExecutor.World.AcctMap.GetAccount(address) + account := ae.getAccount(address) jData := map[string]interface{}{ "data": map[string]interface{}{ "account": map[string]interface{}{ @@ -37,7 +38,7 @@ func (ae *Executor) HandleAddressNonce(r *http.Request) (interface{}, error) { if err != nil { return nil, err } - account := ae.vmTestExecutor.World.AcctMap.GetAccount(address) + account := ae.getAccount(address) jData := map[string]interface{}{ "data": map[string]interface{}{ "nonce": account.Nonce, @@ -53,7 +54,7 @@ func (ae *Executor) HandleAddressBalance(r *http.Request) (interface{}, error) { if err != nil { return nil, err } - account := ae.vmTestExecutor.World.AcctMap.GetAccount(address) + account := ae.getAccount(address) jData := map[string]interface{}{ "data": map[string]interface{}{ "balance": account.Balance.String(), @@ -69,7 +70,7 @@ func (ae *Executor) HandleAddressKeys(r *http.Request) (interface{}, error) { if err != nil { return nil, err } - account := ae.vmTestExecutor.World.AcctMap.GetAccount(address) + account := ae.getAccount(address) jPairs := map[string]string{} for k, v := range account.Storage { if len(v) > 0 { @@ -84,3 +85,11 @@ func (ae *Executor) HandleAddressKeys(r *http.Request) (interface{}, error) { } return jData, nil } + +func (ae *Executor) getAccount(address []byte) *worldmock.Account { + account, ok := ae.vmTestExecutor.World.AcctMap[string(address)] + if ok { + return account + } + return ae.vmTestExecutor.World.AcctMap.CreateAccount(address, ae.vmTestExecutor.World) +} diff --git a/xsuite/src/data/address.ts b/xsuite/src/data/address.ts index 1de06871..26cbd221 100644 --- a/xsuite/src/data/address.ts +++ b/xsuite/src/data/address.ts @@ -1,11 +1,11 @@ import { AddressEncodable } from "./AddressEncodable"; import { enc } from "./encoding"; -export type Address = string | AddressEncodable; +export type Address = string | Uint8Array | AddressEncodable; export const addressToAddressEncodable = (address: Address) => { - if (typeof address === "string") { - address = enc.Addr(address); + if (address instanceof AddressEncodable) { + return address; } - return address; + return enc.Addr(address); }; diff --git a/xsuite/src/proxy/proxy.ts b/xsuite/src/proxy/proxy.ts index 47964b9c..5ac0c03a 100644 --- a/xsuite/src/proxy/proxy.ts +++ b/xsuite/src/proxy/proxy.ts @@ -130,7 +130,9 @@ export class Proxy { } static getAccountRaw(baseUrl: string, address: Address) { - return Proxy.fetchRaw(`${baseUrl}/address/${address}`); + return Proxy.fetchRaw( + `${baseUrl}/address/${addressToAddressEncodable(address)}`, + ); } getAccountRaw(address: Address) { @@ -161,7 +163,9 @@ export class Proxy { } static getAccountNonceRaw(baseUrl: string, address: Address) { - return Proxy.fetchRaw(`${baseUrl}/address/${address}/nonce`); + return Proxy.fetchRaw( + `${baseUrl}/address/${addressToAddressEncodable(address)}/nonce`, + ); } getAccountNonceRaw(address: Address) { @@ -178,7 +182,9 @@ export class Proxy { } static getAccountBalanceRaw(baseUrl: string, address: Address) { - return Proxy.fetchRaw(`${baseUrl}/address/${address}/balance`); + return Proxy.fetchRaw( + `${baseUrl}/address/${addressToAddressEncodable(address)}/balance`, + ); } getAccountBalanceRaw(address: Address) { @@ -195,7 +201,9 @@ export class Proxy { } static getAccountKvsRaw(baseUrl: string, address: Address) { - return Proxy.fetchRaw(`${baseUrl}/address/${address}/keys`); + return Proxy.fetchRaw( + `${baseUrl}/address/${addressToAddressEncodable(address)}/keys`, + ); } getAccountKvsRaw(address: Address) { diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index f64f1e9f..7050ab3a 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -11,6 +11,19 @@ let otherWallet: SWallet; let contract: SContract; const fftId = "FFT-abcdef"; const worldCode = "file:contracts/world/output/world.wasm"; +const zeroBechAddress = + "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; +const zeroHexAddress = + "0000000000000000000000000000000000000000000000000000000000000000"; +const zeroArrAddress = new Uint8Array(32); +const emptyAccount = { + nonce: 0, + balance: 0, + code: null, + codeMetadata: null, + owner: null, + kvs: {}, +}; beforeEach(async () => { world = await SWorld.start(); @@ -34,6 +47,51 @@ afterEach(async () => { await world.terminate(); }); +test("SWorld.proxy.getAccountNonce on empty bech address", async () => { + expect(await world.proxy.getAccountNonce(zeroBechAddress)).toEqual(0); +}); + +test("SWorld.proxy.getAccountNonce on empty hex address", async () => { + expect(await world.proxy.getAccountNonce(zeroHexAddress)).toEqual(0); +}); + +test("SWorld.proxy.getAccountNonce on empty array address", async () => { + expect(await world.proxy.getAccountNonce(zeroArrAddress)).toEqual(0); +}); + +test("SWorld.proxy.getAccountBalance on empty bech address", async () => { + expect(await world.proxy.getAccountBalance(zeroBechAddress)).toEqual(0n); +}); + +test("SWorld.proxy.getAccountBalance on empty hex address", async () => { + expect(await world.proxy.getAccountBalance(zeroHexAddress)).toEqual(0n); +}); + +test("SWorld.proxy.getAccountBalance on empty array address", async () => { + expect(await world.proxy.getAccountBalance(zeroArrAddress)).toEqual(0n); +}); + +test("SWorld.proxy.getAccountWithKvs on empty bech address", async () => { + assertAccount( + await world.proxy.getAccountWithKvs(zeroBechAddress), + emptyAccount, + ); +}); + +test("SWorld.proxy.getAccountWithKvs on empty hex address", async () => { + assertAccount( + await world.proxy.getAccountWithKvs(zeroHexAddress), + emptyAccount, + ); +}); + +test("SWorld.proxy.getAccountWithKvs on empty array address", async () => { + assertAccount( + await world.proxy.getAccountWithKvs(zeroArrAddress), + emptyAccount, + ); +}); + test("SWorld.createWallet", async () => { const wallet = await world.createWallet(); assertAccount(await wallet.getAccountWithKvs(), {}); @@ -288,7 +346,7 @@ test("SWallet.callContract - Stack trace", async () => { gasLimit: 10_000_000, }), ).rejects.toMatchObject({ - stack: expect.stringContaining("src/world/sworld.test.ts:284:3)"), + stack: expect.stringContaining("src/world/sworld.test.ts:"), }); }); diff --git a/xsuite/src/world/sworld.ts b/xsuite/src/world/sworld.ts index 89a3369c..829459d7 100644 --- a/xsuite/src/world/sworld.ts +++ b/xsuite/src/world/sworld.ts @@ -17,7 +17,7 @@ export class SWorld extends World { super({ proxy, chainId: "S", gasPrice }); this.proxy = proxy; this.sysAcc = new SContract({ - address: "erd1lllllllllllllllllllllllllllllllllllllllllllllllllllsckry7t", + address: new Uint8Array(32).fill(255), proxy, }); } From 7afd554c3e868841c657a8b89a94708e9197201f Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 12 Nov 2023 14:21:28 +0100 Subject: [PATCH 08/16] Uniformize address names --- xsuite/src/data/AddressDecoder.test.ts | 4 +-- xsuite/src/data/AddressEncodable.test.ts | 33 ++++++++++++------------ xsuite/src/world/sworld.test.ts | 8 +++--- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/xsuite/src/data/AddressDecoder.test.ts b/xsuite/src/data/AddressDecoder.test.ts index d37d38db..bde3328d 100644 --- a/xsuite/src/data/AddressDecoder.test.ts +++ b/xsuite/src/data/AddressDecoder.test.ts @@ -4,7 +4,7 @@ import { addressByteLength, bytesToBechAddress } from "./AddressEncodable"; describe("AddressDecoder", () => { describe("topDecode", () => { - it("should decode bytes into the correct bech32 address", () => { + it("should decode bytes into the correct bech address", () => { const bytes = new Uint8Array(addressByteLength); const result = new AddressDecoder().topDecode(bytes); expect(result).toBe(bytesToBechAddress(bytes)); @@ -12,7 +12,7 @@ describe("AddressDecoder", () => { }); describe("nestDecode", () => { - it("should decode bytes into the correct bech32 address", () => { + it("should decode bytes into the correct bech address", () => { const bytes = new Uint8Array(addressByteLength); const result = new AddressDecoder().nestDecode(bytes); expect(result).toBe(bytesToBechAddress(bytes)); diff --git a/xsuite/src/data/AddressEncodable.test.ts b/xsuite/src/data/AddressEncodable.test.ts index b247f99b..0a76c4bb 100644 --- a/xsuite/src/data/AddressEncodable.test.ts +++ b/xsuite/src/data/AddressEncodable.test.ts @@ -5,26 +5,29 @@ import { bytesToBechAddress, } from "./AddressEncodable"; +const zeroBechAddress = + "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; +const zeroHexAddress = + "0000000000000000000000000000000000000000000000000000000000000000"; +const zeroBytesAddress = new Uint8Array(32); + describe("AddressEncodable", () => { test("top encoding from bech address", () => { - const bechAddress = - "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; - const bytesAddress = new Uint8Array(32); - expect(new AddressEncodable(bechAddress).toTopBytes()).toEqual( - bytesAddress, + expect(new AddressEncodable(zeroBechAddress).toTopBytes()).toEqual( + zeroBytesAddress, ); }); test("top encoding from hex address", () => { - const hexAddress = - "0000000000000000000000000000000000000000000000000000000000000000"; - const bytesAddress = new Uint8Array(32); - expect(new AddressEncodable(hexAddress).toTopBytes()).toEqual(bytesAddress); + expect(new AddressEncodable(zeroHexAddress).toTopBytes()).toEqual( + zeroBytesAddress, + ); }); - test("top encoding from bytes", () => { - const address = new Uint8Array(32); - expect(new AddressEncodable(address).toTopBytes()).toEqual(address); + test("top encoding from bytes address", () => { + expect(new AddressEncodable(zeroBytesAddress).toTopBytes()).toEqual( + zeroBytesAddress, + ); }); test("nest encoding from bytes", () => { @@ -32,10 +35,8 @@ describe("AddressEncodable", () => { expect(new AddressEncodable(address).toNestBytes()).toEqual(address); }); - test("bytesToBech32", () => { - const bechAddress = - "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; - expect(bytesToBechAddress(new Uint8Array(32))).toEqual(bechAddress); + test("bytesToBechAddress", () => { + expect(bytesToBechAddress(new Uint8Array(32))).toEqual(zeroBechAddress); }); test("correct address length", () => { diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index 7050ab3a..d2611af5 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -15,7 +15,7 @@ const zeroBechAddress = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; const zeroHexAddress = "0000000000000000000000000000000000000000000000000000000000000000"; -const zeroArrAddress = new Uint8Array(32); +const zeroBytesAddress = new Uint8Array(32); const emptyAccount = { nonce: 0, balance: 0, @@ -56,7 +56,7 @@ test("SWorld.proxy.getAccountNonce on empty hex address", async () => { }); test("SWorld.proxy.getAccountNonce on empty array address", async () => { - expect(await world.proxy.getAccountNonce(zeroArrAddress)).toEqual(0); + expect(await world.proxy.getAccountNonce(zeroBytesAddress)).toEqual(0); }); test("SWorld.proxy.getAccountBalance on empty bech address", async () => { @@ -68,7 +68,7 @@ test("SWorld.proxy.getAccountBalance on empty hex address", async () => { }); test("SWorld.proxy.getAccountBalance on empty array address", async () => { - expect(await world.proxy.getAccountBalance(zeroArrAddress)).toEqual(0n); + expect(await world.proxy.getAccountBalance(zeroBytesAddress)).toEqual(0n); }); test("SWorld.proxy.getAccountWithKvs on empty bech address", async () => { @@ -87,7 +87,7 @@ test("SWorld.proxy.getAccountWithKvs on empty hex address", async () => { test("SWorld.proxy.getAccountWithKvs on empty array address", async () => { assertAccount( - await world.proxy.getAccountWithKvs(zeroArrAddress), + await world.proxy.getAccountWithKvs(zeroBytesAddress), emptyAccount, ); }); From bb7724e80ab2a8882255c2116e8e6b2da780a09e Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 12 Nov 2023 14:23:51 +0100 Subject: [PATCH 09/16] Fixes --- xsuite/src/world/sworld.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index d2611af5..012077a1 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -55,7 +55,7 @@ test("SWorld.proxy.getAccountNonce on empty hex address", async () => { expect(await world.proxy.getAccountNonce(zeroHexAddress)).toEqual(0); }); -test("SWorld.proxy.getAccountNonce on empty array address", async () => { +test("SWorld.proxy.getAccountNonce on empty bytes address", async () => { expect(await world.proxy.getAccountNonce(zeroBytesAddress)).toEqual(0); }); @@ -67,7 +67,7 @@ test("SWorld.proxy.getAccountBalance on empty hex address", async () => { expect(await world.proxy.getAccountBalance(zeroHexAddress)).toEqual(0n); }); -test("SWorld.proxy.getAccountBalance on empty array address", async () => { +test("SWorld.proxy.getAccountBalance on empty bytes address", async () => { expect(await world.proxy.getAccountBalance(zeroBytesAddress)).toEqual(0n); }); @@ -85,7 +85,7 @@ test("SWorld.proxy.getAccountWithKvs on empty hex address", async () => { ); }); -test("SWorld.proxy.getAccountWithKvs on empty array address", async () => { +test("SWorld.proxy.getAccountWithKvs on empty bytes address", async () => { assertAccount( await world.proxy.getAccountWithKvs(zeroBytesAddress), emptyAccount, From 3ca1d374c6776323f174762a689595f563e16b72 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 12 Nov 2023 16:13:41 +0100 Subject: [PATCH 10/16] Add response in errors --- xsuite/src/world/sworld.test.ts | 7 ++++-- xsuite/src/world/world.ts | 44 ++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index 012077a1..fa55e8a5 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -338,7 +338,7 @@ test("SWallet.callContract with return", async () => { assertHexList(txResult.returnData, ["01"]); }); -test("SWallet.callContract - Stack trace", async () => { +test("SWallet.callContract failure", async () => { await expect( wallet.callContract({ callee: contract, @@ -346,7 +346,10 @@ test("SWallet.callContract - Stack trace", async () => { gasLimit: 10_000_000, }), ).rejects.toMatchObject({ - stack: expect.stringContaining("src/world/sworld.test.ts:"), + message: expect.stringMatching( + /^Transaction failed: 1 - invalid function \(not found\) - Response:\n{/, + ), + stack: expect.stringMatching(/src\/world\/sworld\.test\.ts:[0-9]+:3\)$/), }); }); diff --git a/xsuite/src/world/world.ts b/xsuite/src/world/world.ts index e86f1032..0501e441 100644 --- a/xsuite/src/world/world.ts +++ b/xsuite/src/world/world.ts @@ -82,7 +82,11 @@ export class World { async #query(query: Query): Promise { const resQuery = await this.proxy.query(query); if (![0, "ok"].includes(resQuery.returnCode)) { - throw new QueryError(resQuery.returnCode, resQuery.returnMessage); + throw new QueryError( + resQuery.returnCode, + resQuery.returnMessage, + resQuery, + ); } return { query: resQuery, @@ -159,18 +163,18 @@ export class Wallet extends Signer { const txHash = await this.proxy.sendTx(tx); const resTx = await this.proxy.getCompletedTx(txHash); if (resTx.status !== "success") { - throw new TxError("errorStatus", resTx.status); + throw new TxError("errorStatus", resTx.status, resTx); } if (resTx.executionReceipt?.returnCode) { const { returnCode, returnMessage } = resTx.executionReceipt; - throw new TxError(returnCode, returnMessage); + throw new TxError(returnCode, returnMessage, resTx); } const signalErrorEvent = resTx?.logs?.events.find( (e: any) => e.identifier === "signalError", ); if (signalErrorEvent) { const error = atob(signalErrorEvent.topics[1]); - throw new TxError("signalError", error); + throw new TxError("signalError", error, resTx); } return { tx: resTx }; } @@ -188,13 +192,9 @@ export class Wallet extends Signer { const txResult = await this.#executeTx( Tx.getParamsToDeployContract(txParams), ); - const scDeployEvent = txResult.tx?.logs?.events.find( + const address = txResult.tx.logs.events.find( (e: any) => e.identifier === "SCDeploy", - ); - if (!scDeployEvent) { - throw new Error("No SCDeploy event."); - } - const address = scDeployEvent.address; + )!.address; const contract = new Contract({ address, proxy: this.proxy }); const returnData = getTxReturnData(txResult.tx); return { ...txResult, address, contract, returnData }; @@ -285,24 +285,34 @@ class InteractionError extends Error { interaction: string; code: number | string; msg: string; + response: any; - constructor(interaction: string, code: number | string, msg: string) { - super(`${interaction} failed: ${code} - ${msg}`); + constructor( + interaction: string, + code: number | string, + message: string, + response: any, + ) { + super( + `${interaction} failed: ${code} - ${message} - Response:\n` + + JSON.stringify(response, null, 2), + ); this.interaction = interaction; this.code = code; - this.msg = msg; + this.msg = message; + this.response = response; } } class TxError extends InteractionError { - constructor(code: number | string, message: string) { - super("Transaction", code, message); + constructor(code: number | string, message: string, response: any) { + super("Transaction", code, message, response); } } class QueryError extends InteractionError { - constructor(code: number | string, message: string) { - super("Query", code, message); + constructor(code: number | string, message: string, response: any) { + super("Query", code, message, response); } } From 9dc1166b975b9ecdd67f211f4df12ef45d95f169 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 12 Nov 2023 16:22:19 +0100 Subject: [PATCH 11/16] Release @xsuite/simulnet@0.0.8 xsuite@0.0.37 --- xsuite-simulnet/package.json | 2 +- xsuite/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xsuite-simulnet/package.json b/xsuite-simulnet/package.json index fef6f5c8..cd75a16e 100644 --- a/xsuite-simulnet/package.json +++ b/xsuite-simulnet/package.json @@ -1,6 +1,6 @@ { "name": "@xsuite/simulnet", - "version": "0.0.7", + "version": "0.0.8", "license": "MIT", "scripts": { "build": "run-script-os", diff --git a/xsuite/package.json b/xsuite/package.json index 266885d5..82652f1a 100644 --- a/xsuite/package.json +++ b/xsuite/package.json @@ -1,6 +1,6 @@ { "name": "xsuite", - "version": "0.0.36", + "version": "0.0.37", "license": "MIT", "bin": { "xsuite": "cli.js" From 72e4f14772372052e8809010fe1650c2d1f677aa Mon Sep 17 00:00:00 2001 From: lcswillems Date: Mon, 13 Nov 2023 17:53:22 +0100 Subject: [PATCH 12/16] Fix typo --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 61cb721d..bf05299c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -35,7 +35,7 @@ jobs: node-version: 18 pnpm-version: 8 - - name: Buid xsuite + - name: Build xsuite run: pnpm build-xsuite - name: Build docs From b42c711d4b3577f8374a60e63ab90b712ba9cdff Mon Sep 17 00:00:00 2001 From: lcswillems Date: Mon, 13 Nov 2023 18:16:21 +0100 Subject: [PATCH 13/16] Make gasLimit mandatory when creating a world --- xsuite/src/data/TupleDecoder.ts | 7 +-- xsuite/src/helpers.ts | 5 ++ xsuite/src/proxy/proxy.ts | 12 ++-- xsuite/src/world/sworld.ts | 44 +++++++++----- xsuite/src/world/world.test.ts | 2 +- xsuite/src/world/world.ts | 104 +++++++++++++++++++------------- 6 files changed, 107 insertions(+), 67 deletions(-) create mode 100644 xsuite/src/helpers.ts diff --git a/xsuite/src/data/TupleDecoder.ts b/xsuite/src/data/TupleDecoder.ts index 4d601b78..5efb6eeb 100644 --- a/xsuite/src/data/TupleDecoder.ts +++ b/xsuite/src/data/TupleDecoder.ts @@ -1,3 +1,4 @@ +import { Prettify } from "../helpers"; import { ByteReader } from "./ByteReader"; import { AbstractDecoder, Decoder } from "./Decoder"; @@ -25,8 +26,6 @@ export class TupleDecoder> extends AbstractDecoder< export type DecoderMap = Record>; -type DecoderMapToValueMap = { +type DecoderMapToValueMap = Prettify<{ [K in keyof T]: T[K] extends Decoder ? U : never; -} & { - // Pretiffy type: https://twitter.com/mattpocockuk/status/1622730173446557697 -}; +}>; diff --git a/xsuite/src/helpers.ts b/xsuite/src/helpers.ts new file mode 100644 index 00000000..57b30c83 --- /dev/null +++ b/xsuite/src/helpers.ts @@ -0,0 +1,5 @@ +// Pretiffy type: https://twitter.com/mattpocockuk/status/1622730173446557697 +// eslint-disable-next-line @typescript-eslint/ban-types +export type Prettify = { [K in keyof T]: T[K] } & {}; + +export type Optional = Omit & Partial>; diff --git a/xsuite/src/proxy/proxy.ts b/xsuite/src/proxy/proxy.ts index 5ac0c03a..0760a0cf 100644 --- a/xsuite/src/proxy/proxy.ts +++ b/xsuite/src/proxy/proxy.ts @@ -241,7 +241,7 @@ export class Tx { value: (params.value ?? 0n).toString(), receiver: params.receiver.toString(), sender: params.sender.toString(), - gasPrice: params.gasPrice ?? 0, + gasPrice: params.gasPrice, gasLimit: params.gasLimit, data: params.data === undefined ? undefined : btoa(params.data), chainID: params.chainId, @@ -469,7 +469,7 @@ export type TxParams = { value?: number | bigint; receiver: Address; sender: Address; - gasPrice?: number; + gasPrice: number; gasLimit: number; data?: string; chainId: string; @@ -480,7 +480,7 @@ export type DeployContractTxParams = { nonce: number; value?: number | bigint; sender: Address; - gasPrice?: number; + gasPrice: number; gasLimit: number; code: string; codeMetadata: CodeMetadata; @@ -498,7 +498,7 @@ export type UpgradeContractTxParams = { value?: number | bigint; callee: Address; sender: Address; - gasPrice?: number; + gasPrice: number; gasLimit: number; code: string; codeMetadata: CodeMetadata; @@ -512,7 +512,7 @@ export type TransferTxParams = { value?: number | bigint; receiver: Address; sender: Address; - gasPrice?: number; + gasPrice: number; gasLimit: number; esdts?: { id: string; nonce?: number; amount: number | bigint }[]; chainId: string; @@ -524,7 +524,7 @@ export type CallContractTxParams = { value?: number | bigint; callee: Address; sender: Address; - gasPrice?: number; + gasPrice: number; gasLimit: number; funcName: string; funcArgs?: Hex[]; diff --git a/xsuite/src/world/sworld.ts b/xsuite/src/world/sworld.ts index 829459d7..3157791b 100644 --- a/xsuite/src/world/sworld.ts +++ b/xsuite/src/world/sworld.ts @@ -1,10 +1,16 @@ +import { Prettify } from "../helpers"; import { SProxy } from "../proxy"; -import { DeployContractTxParams } from "../proxy/proxy"; import { Account, Block } from "../proxy/sproxy"; import { DummySigner, Signer } from "./signer"; import { startSimulnet } from "./simulnet"; import { isContractAddress, numberToBytesAddress } from "./utils"; -import { World, Contract, Wallet, expandCode } from "./world"; +import { + World, + Contract, + Wallet, + expandCode, + WorldDeployContractParams, +} from "./world"; let walletCounter = 0; let contractCounter = 0; @@ -13,7 +19,7 @@ export class SWorld extends World { proxy: SProxy; sysAcc: SContract; - constructor({ proxy, gasPrice }: { proxy: SProxy; gasPrice?: number }) { + constructor({ proxy, gasPrice }: { proxy: SProxy; gasPrice: number }) { super({ proxy, chainId: "S", gasPrice }); this.proxy = proxy; this.sysAcc = new SContract({ @@ -23,7 +29,7 @@ export class SWorld extends World { } static new({ proxyUrl, gasPrice }: { proxyUrl: string; gasPrice?: number }) { - return new SWorld({ proxy: new SProxy(proxyUrl), gasPrice }); + return new SWorld({ proxy: new SProxy(proxyUrl), gasPrice: gasPrice ?? 0 }); } static async start({ @@ -46,7 +52,7 @@ export class SWorld extends World { return new SContract({ address, proxy: this.proxy }); } - async createWallet(account: Omit = {}) { + async createWallet(account: SWorldCreateWalletAccount = {}) { walletCounter += 1; const address = numberToBytesAddress(walletCounter, false); const wallet = new SWallet({ @@ -59,7 +65,7 @@ export class SWorld extends World { return wallet; } - createContract(account: Omit = {}) { + createContract(account: SWorldCreateContractAccount = {}) { return createContract(this.proxy, account); } @@ -84,24 +90,22 @@ export class SWallet extends Wallet { signer: Signer; proxy: SProxy; chainId: string; - gasPrice?: number; + gasPrice: number; }) { super({ signer, proxy, chainId, gasPrice }); this.proxy = proxy; } - setAccount(account: Omit) { + setAccount(account: SWalletSetAccountAccount) { return setAccount(this.proxy, { address: this, ...account }); } - createContract(account: Omit) { + createContract(account: SWalletCreateContractAccount = {}) { return createContract(this.proxy, { ...account, owner: this }); } - deployContract( - txParams: Omit, - ) { - return super.deployContract(txParams).then((data) => ({ + deployContract(params: WorldDeployContractParams) { + return super.deployContract(params).then((data) => ({ ...data, contract: new SContract({ address: data.address, @@ -125,7 +129,7 @@ export class SContract extends Contract { this.proxy = proxy; } - setAccount(account: Omit) { + setAccount(account: SContractSetAccountAccount) { return setAccount(this.proxy, { address: this, ...account }); } } @@ -151,3 +155,15 @@ const createContract = async ( await contract.setAccount(account); return contract; }; + +type SWorldCreateWalletAccount = Prettify>; + +type SWorldCreateContractAccount = Prettify>; + +type SWalletSetAccountAccount = Prettify>; + +type SWalletCreateContractAccount = Prettify< + Omit +>; + +type SContractSetAccountAccount = Omit; diff --git a/xsuite/src/world/world.test.ts b/xsuite/src/world/world.test.ts index 1690c4a8..37b26280 100644 --- a/xsuite/src/world/world.test.ts +++ b/xsuite/src/world/world.test.ts @@ -6,7 +6,7 @@ import { World } from "./world"; test("World.new, World.newWallet, World.newContract", async () => { const proxyUrl = await startSimulnet(); - const world = World.new({ proxyUrl, chainId: "S" }); + const world = World.new({ proxyUrl, chainId: "S", gasPrice: 0 }); const wallet = world.newWallet(new DummySigner(new Uint8Array(32))); const contract = world.newContract(new Uint8Array(32)); expect(wallet.toTopBytes()).toEqual(new Uint8Array(32)); diff --git a/xsuite/src/world/world.ts b/xsuite/src/world/world.ts index 0501e441..9101269f 100644 --- a/xsuite/src/world/world.ts +++ b/xsuite/src/world/world.ts @@ -1,5 +1,6 @@ import { AddressEncodable } from "../data/AddressEncodable"; import { b64ToHexString } from "../data/utils"; +import { Optional, Prettify } from "../helpers"; import { CallContractTxParams, DeployContractTxParams, @@ -16,7 +17,7 @@ import { readFileHex } from "./utils"; export class World { proxy: Proxy; chainId: string; - gasPrice?: number; + gasPrice: number; constructor({ proxy, @@ -25,7 +26,7 @@ export class World { }: { proxy: Proxy; chainId: string; - gasPrice?: number; + gasPrice: number; }) { this.proxy = proxy; this.chainId = chainId; @@ -39,15 +40,15 @@ export class World { }: { proxyUrl: string; chainId: string; - gasPrice?: number; + gasPrice: number; }) { return new World({ proxy: new Proxy(proxyUrl), chainId, gasPrice }); } newWallet(signer: Signer) { return new Wallet({ - proxy: this.proxy, signer, + proxy: this.proxy, chainId: this.chainId, gasPrice: this.gasPrice, }); @@ -55,8 +56,8 @@ export class World { async newWalletFromFile(filePath: string) { return new Wallet({ - proxy: this.proxy, signer: await KeystoreSigner.fromFile(filePath), + proxy: this.proxy, chainId: this.chainId, gasPrice: this.gasPrice, }); @@ -64,8 +65,8 @@ export class World { newWalletFromFile_unsafe(filePath: string, password: string) { return new Wallet({ - proxy: this.proxy, signer: KeystoreSigner.fromFile_unsafe(filePath, password), + proxy: this.proxy, chainId: this.chainId, gasPrice: this.gasPrice, }); @@ -99,7 +100,7 @@ export class Wallet extends Signer { signer: Signer; proxy: Proxy; chainId: string; - gasPrice?: number; + gasPrice: number; constructor({ signer, @@ -110,11 +111,11 @@ export class Wallet extends Signer { signer: Signer; proxy: Proxy; chainId: string; - gasPrice?: number; + gasPrice: number; }) { super(signer.toTopBytes()); - this.proxy = proxy; this.signer = signer; + this.proxy = proxy; this.chainId = chainId; this.gasPrice = gasPrice; } @@ -143,18 +144,18 @@ export class Wallet extends Signer { return this.proxy.getAccountWithKvs(this); } - executeTx(txParams: Omit) { - return InteractionPromise.from(this.#executeTx(txParams)); + executeTx(params: WorldExecuteTxParams) { + return InteractionPromise.from(this.#executeTx(params)); } async #executeTx({ gasPrice, - ...txParams - }: Omit): Promise { + ...params + }: WorldExecuteTxParams): Promise { const nonce = await this.proxy.getAccountNonce(this); const tx = new Tx({ gasPrice: gasPrice ?? this.gasPrice, - ...txParams, + ...params, sender: this, nonce, chainId: this.chainId, @@ -179,18 +180,16 @@ export class Wallet extends Signer { return { tx: resTx }; } - deployContract( - txParams: Omit, - ) { - return InteractionPromise.from(this.#deployContract(txParams)); + deployContract(params: WorldDeployContractParams) { + return InteractionPromise.from(this.#deployContract(params)); } async #deployContract( - txParams: Omit, + params: WorldDeployContractParams, ): Promise { - txParams.code = expandCode(txParams.code); + params.code = expandCode(params.code); const txResult = await this.#executeTx( - Tx.getParamsToDeployContract(txParams), + Tx.getParamsToDeployContract(params), ); const address = txResult.tx.logs.events.find( (e: any) => e.identifier === "SCDeploy", @@ -200,46 +199,38 @@ export class Wallet extends Signer { return { ...txResult, address, contract, returnData }; } - upgradeContract( - txParams: Omit, - ) { - return InteractionPromise.from(this.#upgradeContract(txParams)); + upgradeContract(params: WorldUpgradeContractParams) { + return InteractionPromise.from(this.#upgradeContract(params)); } async #upgradeContract( - txParams: Omit, + params: WorldUpgradeContractParams, ): Promise { - txParams.code = expandCode(txParams.code); + params.code = expandCode(params.code); const txResult = await this.#executeTx( - Tx.getParamsToUpgradeContract({ sender: this, ...txParams }), + Tx.getParamsToUpgradeContract({ sender: this, ...params }), ); const returnData = getTxReturnData(txResult.tx); return { ...txResult, returnData }; } - transfer(txParams: Omit) { - return InteractionPromise.from(this.#transfer(txParams)); + transfer(params: WorldTransferParams) { + return InteractionPromise.from(this.#transfer(params)); } - async #transfer( - txParams: Omit, - ): Promise { - return this.#executeTx( - Tx.getParamsToTransfer({ sender: this, ...txParams }), - ); + #transfer(params: WorldTransferParams): Promise { + return this.#executeTx(Tx.getParamsToTransfer({ sender: this, ...params })); } - callContract( - txParams: Omit, - ) { - return InteractionPromise.from(this.#callContract(txParams)); + callContract(params: WorldCallContractParams) { + return InteractionPromise.from(this.#callContract(params)); } async #callContract( - txParams: Omit, + params: WorldCallContractParams, ): Promise { const txResult = await this.#executeTx( - Tx.getParamsToCallContract({ sender: this, ...txParams }), + Tx.getParamsToCallContract({ sender: this, ...params }), ); const returnData = getTxReturnData(txResult.tx); return { ...txResult, returnData }; @@ -401,6 +392,35 @@ export const expandCode = (code: string) => { return code; }; +type WorldExecuteTxParams = Prettify< + Optional, "gasPrice"> +>; + +export type WorldDeployContractParams = Prettify< + Optional< + Omit, + "gasPrice" + > +>; + +type WorldUpgradeContractParams = Prettify< + Optional< + Omit, + "gasPrice" + > +>; + +type WorldTransferParams = Prettify< + Optional, "gasPrice"> +>; + +type WorldCallContractParams = Prettify< + Optional< + Omit, + "gasPrice" + > +>; + type QueryResult = { query: any; returnData: string[]; From 855274a69210210382d5f02dfc110778c11ce716 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sat, 18 Nov 2023 20:32:15 +0100 Subject: [PATCH 14/16] Add explorer URL --- contracts/blank/interact/index.ts | 12 +- contracts/vested-transfers/interact/index.ts | 4 +- xsuite-simulnet/src/handleTransaction.go | 1 + xsuite/src/interact/envChain.ts | 31 ++++- xsuite/src/world/sworld.test.ts | 11 +- xsuite/src/world/sworld.ts | 74 ++++++++---- xsuite/src/world/world.test.ts | 2 +- xsuite/src/world/world.ts | 115 ++++++++++++------- 8 files changed, 173 insertions(+), 77 deletions(-) diff --git a/contracts/blank/interact/index.ts b/contracts/blank/interact/index.ts index 6c6a4b72..b8d6c9b7 100644 --- a/contracts/blank/interact/index.ts +++ b/contracts/blank/interact/index.ts @@ -3,9 +3,10 @@ import { envChain, World } from "xsuite"; import data from "./data.json"; const world = World.new({ - proxyUrl: envChain.publicProxyUrl(), chainId: envChain.id(), - gasPrice: 1000000000, + proxyUrl: envChain.publicProxyUrl(), + gasPrice: 1_000_000_000, + explorerUrl: envChain.explorerUrl(), }); const loadWallet = () => world.newWalletFromFile("wallet.json"); @@ -19,7 +20,8 @@ program.command("deploy").action(async () => { codeMetadata: ["upgradeable"], gasLimit: 20_000_000, }); - console.log("Result:", result); + console.log("Transaction:", result.tx.explorerUrl); + console.log("Contract:", result.contract.explorerUrl); }); program.command("upgrade").action(async () => { @@ -30,7 +32,7 @@ program.command("upgrade").action(async () => { codeMetadata: ["upgradeable"], gasLimit: 20_000_000, }); - console.log("Result:", result); + console.log("Transaction:", result.tx.explorerUrl); }); program.command("ClaimDeveloperRewards").action(async () => { @@ -40,7 +42,7 @@ program.command("ClaimDeveloperRewards").action(async () => { funcName: "ClaimDeveloperRewards", gasLimit: 10_000_000, }); - console.log("Result:", result); + console.log("Transaction:", result.tx.explorerUrl); }); program.parse(process.argv); diff --git a/contracts/vested-transfers/interact/index.ts b/contracts/vested-transfers/interact/index.ts index 8d0fb733..1559ab89 100644 --- a/contracts/vested-transfers/interact/index.ts +++ b/contracts/vested-transfers/interact/index.ts @@ -3,9 +3,9 @@ import { envChain, World } from "xsuite"; import data from "./data.json"; const world = World.new({ - proxyUrl: envChain.publicProxyUrl(), chainId: envChain.id(), - gasPrice: 1000000000, + proxyUrl: envChain.publicProxyUrl(), + gasPrice: 1_000_000_000, }); const loadWallet = () => world.newWalletFromFile("wallet.json"); diff --git a/xsuite-simulnet/src/handleTransaction.go b/xsuite-simulnet/src/handleTransaction.go index e2f78de8..235bd628 100644 --- a/xsuite-simulnet/src/handleTransaction.go +++ b/xsuite-simulnet/src/handleTransaction.go @@ -233,6 +233,7 @@ func (ae *Executor) HandleTransactionSend(r *http.Request) (interface{}, error) ae.txResps[txHash] = map[string]interface{}{ "data": map[string]interface{}{ "transaction": map[string]interface{}{ + "hash": txHash, "status": "success", "logs": logs, "smartContractResults": smartContractResults, diff --git a/xsuite/src/interact/envChain.ts b/xsuite/src/interact/envChain.ts index af1b8876..b9bcc6da 100644 --- a/xsuite/src/interact/envChain.ts +++ b/xsuite/src/interact/envChain.ts @@ -1,3 +1,15 @@ +export const devnetId = "D"; +export const testnetId = "T"; +export const mainnetId = "1"; + +export const devnetPublicProxyUrl = "https://devnet-gateway.multiversx.com"; +export const testnetPublicProxyUrl = "https://testnet-gateway.multiversx.com"; +export const mainnetPublicProxyUrl = "https://gateway.multiversx.com"; + +export const devnetExplorerUrl = "https://devnet-explorer.multiversx.com"; +export const testnetExplorerUrl = "https://testnet-explorer.multiversx.com"; +export const mainnetExplorerUrl = "https://mainnet-explorer.multiversx.com"; + export const envChain = { name: (): ChainName => { const chain = process.env.CHAIN; @@ -16,12 +28,23 @@ export const envChain = { } return value; }, - id: () => envChain.select({ devnet: "D", testnet: "T", mainnet: "1" }), + id: () => + envChain.select({ + devnet: devnetId, + testnet: testnetId, + mainnet: mainnetId, + }), publicProxyUrl: () => envChain.select({ - devnet: "https://devnet-gateway.multiversx.com", - testnet: "https://testnet-gateway.multiversx.com", - mainnet: "https://gateway.multiversx.com", + devnet: devnetPublicProxyUrl, + testnet: testnetPublicProxyUrl, + mainnet: mainnetPublicProxyUrl, + }), + explorerUrl: () => + envChain.select({ + devnet: devnetExplorerUrl, + testnet: testnetExplorerUrl, + mainnet: mainnetExplorerUrl, }), }; diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index fa55e8a5..a1e73f43 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -24,9 +24,10 @@ const emptyAccount = { owner: null, kvs: {}, }; +const explorerUrl = "http://explorer.local"; beforeEach(async () => { - world = await SWorld.start(); + world = await SWorld.start({ explorerUrl }); wallet = await world.createWallet({ balance: 10n ** 18n, kvs: [e.kvs.Esdts([{ id: fftId, amount: 10n ** 18n }])], @@ -94,6 +95,7 @@ test("SWorld.proxy.getAccountWithKvs on empty bytes address", async () => { test("SWorld.createWallet", async () => { const wallet = await world.createWallet(); + expect(wallet.explorerUrl).toEqual(`${explorerUrl}/accounts/${wallet}`); assertAccount(await wallet.getAccountWithKvs(), {}); }); @@ -104,6 +106,7 @@ test("SWorld.createWallet - is wallet address", async () => { test("SWorld.createContract", async () => { const contract = await world.createContract(); + expect(contract.explorerUrl).toEqual(`${explorerUrl}/accounts/${contract}`); assertAccount(await contract.getAccountWithKvs(), { code: "00" }); }); @@ -207,11 +210,13 @@ test("SContract.getAccountWithKvs + SContract.createContract", async () => { }); test("SWallet.executeTx", async () => { - await wallet.executeTx({ + const { tx } = await wallet.executeTx({ receiver: otherWallet, value: 10n ** 17n, gasLimit: 10_000_000, }); + expect(tx.hash).toBeTruthy(); + expect(tx.explorerUrl).toEqual(`${explorerUrl}/transactions/${tx.hash}`); assertAccount(await wallet.getAccountWithKvs(), { balance: 9n * 10n ** 17n, }); @@ -347,7 +352,7 @@ test("SWallet.callContract failure", async () => { }), ).rejects.toMatchObject({ message: expect.stringMatching( - /^Transaction failed: 1 - invalid function \(not found\) - Response:\n{/, + /^Transaction failed: 1 - invalid function \(not found\) - Result:\n{/, ), stack: expect.stringMatching(/src\/world\/sworld\.test\.ts:[0-9]+:3\)$/), }); diff --git a/xsuite/src/world/sworld.ts b/xsuite/src/world/sworld.ts index 3157791b..0de9a0db 100644 --- a/xsuite/src/world/sworld.ts +++ b/xsuite/src/world/sworld.ts @@ -19,24 +19,42 @@ export class SWorld extends World { proxy: SProxy; sysAcc: SContract; - constructor({ proxy, gasPrice }: { proxy: SProxy; gasPrice: number }) { - super({ proxy, chainId: "S", gasPrice }); + constructor({ + proxy, + gasPrice, + explorerUrl, + }: { + proxy: SProxy; + gasPrice: number; + explorerUrl?: string; + }) { + super({ proxy, chainId: "S", gasPrice, explorerUrl }); this.proxy = proxy; - this.sysAcc = new SContract({ - address: new Uint8Array(32).fill(255), - proxy, - }); + this.sysAcc = this.newContract(new Uint8Array(32).fill(255)); } - static new({ proxyUrl, gasPrice }: { proxyUrl: string; gasPrice?: number }) { - return new SWorld({ proxy: new SProxy(proxyUrl), gasPrice: gasPrice ?? 0 }); + static new({ + proxyUrl, + gasPrice, + explorerUrl, + }: { + proxyUrl: string; + gasPrice?: number; + explorerUrl?: string; + }) { + return new SWorld({ + proxy: new SProxy(proxyUrl), + gasPrice: gasPrice ?? 0, + explorerUrl, + }); } static async start({ gasPrice, - }: { gasPrice?: number } = {}): Promise { - const url = await startSimulnet(); - return SWorld.new({ proxyUrl: url, gasPrice }); + explorerUrl, + }: { gasPrice?: number; explorerUrl?: string } = {}): Promise { + const proxyUrl = await startSimulnet(); + return SWorld.new({ proxyUrl, gasPrice, explorerUrl }); } newWallet(signer: Signer): SWallet { @@ -45,28 +63,28 @@ export class SWorld extends World { proxy: this.proxy, chainId: this.chainId, gasPrice: this.gasPrice, + baseExplorerUrl: this.explorerUrl, }); } newContract(address: string | Uint8Array): SContract { - return new SContract({ address, proxy: this.proxy }); + return new SContract({ + address, + proxy: this.proxy, + baseExplorerUrl: this.explorerUrl, + }); } async createWallet(account: SWorldCreateWalletAccount = {}) { walletCounter += 1; const address = numberToBytesAddress(walletCounter, false); - const wallet = new SWallet({ - signer: new DummySigner(address), - proxy: this.proxy, - chainId: this.chainId, - gasPrice: this.gasPrice, - }); + const wallet = this.newWallet(new DummySigner(address)); await wallet.setAccount(account); return wallet; } createContract(account: SWorldCreateContractAccount = {}) { - return createContract(this.proxy, account); + return createContract(this.proxy, account, this.explorerUrl); } setCurrentBlockInfo(block: Block) { @@ -86,13 +104,15 @@ export class SWallet extends Wallet { proxy, chainId, gasPrice, + baseExplorerUrl, }: { signer: Signer; proxy: SProxy; chainId: string; gasPrice: number; + baseExplorerUrl?: string; }) { - super({ signer, proxy, chainId, gasPrice }); + super({ signer, proxy, chainId, gasPrice, baseExplorerUrl }); this.proxy = proxy; } @@ -101,7 +121,11 @@ export class SWallet extends Wallet { } createContract(account: SWalletCreateContractAccount = {}) { - return createContract(this.proxy, { ...account, owner: this }); + return createContract( + this.proxy, + { ...account, owner: this }, + this.baseExplorerUrl, + ); } deployContract(params: WorldDeployContractParams) { @@ -110,6 +134,7 @@ export class SWallet extends Wallet { contract: new SContract({ address: data.address, proxy: this.proxy, + baseExplorerUrl: this.baseExplorerUrl, }), })); } @@ -121,11 +146,13 @@ export class SContract extends Contract { constructor({ address, proxy, + baseExplorerUrl, }: { address: string | Uint8Array; proxy: SProxy; + baseExplorerUrl?: string; }) { - super({ address, proxy }); + super({ address, proxy, baseExplorerUrl }); this.proxy = proxy; } @@ -148,10 +175,11 @@ const setAccount = (proxy: SProxy, account: Account) => { const createContract = async ( proxy: SProxy, account: Omit = {}, + baseExplorerUrl?: string, ) => { contractCounter += 1; const address = numberToBytesAddress(contractCounter, true); - const contract = new SContract({ address, proxy }); + const contract = new SContract({ address, proxy, baseExplorerUrl }); await contract.setAccount(account); return contract; }; diff --git a/xsuite/src/world/world.test.ts b/xsuite/src/world/world.test.ts index 37b26280..5f3672ea 100644 --- a/xsuite/src/world/world.test.ts +++ b/xsuite/src/world/world.test.ts @@ -6,7 +6,7 @@ import { World } from "./world"; test("World.new, World.newWallet, World.newContract", async () => { const proxyUrl = await startSimulnet(); - const world = World.new({ proxyUrl, chainId: "S", gasPrice: 0 }); + const world = World.new({ chainId: "S", proxyUrl, gasPrice: 0 }); const wallet = world.newWallet(new DummySigner(new Uint8Array(32))); const contract = world.newContract(new Uint8Array(32)); expect(wallet.toTopBytes()).toEqual(new Uint8Array(32)); diff --git a/xsuite/src/world/world.ts b/xsuite/src/world/world.ts index 9101269f..de83ba2d 100644 --- a/xsuite/src/world/world.ts +++ b/xsuite/src/world/world.ts @@ -15,34 +15,45 @@ import { KeystoreSigner, Signer } from "./signer"; import { readFileHex } from "./utils"; export class World { - proxy: Proxy; chainId: string; + proxy: Proxy; gasPrice: number; + explorerUrl: string; constructor({ - proxy, chainId, + proxy, gasPrice, + explorerUrl = "", }: { - proxy: Proxy; chainId: string; + proxy: Proxy; gasPrice: number; + explorerUrl?: string; }) { this.proxy = proxy; this.chainId = chainId; this.gasPrice = gasPrice; + this.explorerUrl = explorerUrl; } static new({ - proxyUrl, chainId, + proxyUrl, gasPrice, + explorerUrl, }: { - proxyUrl: string; chainId: string; + proxyUrl: string; gasPrice: number; + explorerUrl?: string; }) { - return new World({ proxy: new Proxy(proxyUrl), chainId, gasPrice }); + return new World({ + chainId, + proxy: new Proxy(proxyUrl), + gasPrice, + explorerUrl, + }); } newWallet(signer: Signer) { @@ -51,29 +62,24 @@ export class World { proxy: this.proxy, chainId: this.chainId, gasPrice: this.gasPrice, + baseExplorerUrl: this.explorerUrl, }); } async newWalletFromFile(filePath: string) { - return new Wallet({ - signer: await KeystoreSigner.fromFile(filePath), - proxy: this.proxy, - chainId: this.chainId, - gasPrice: this.gasPrice, - }); + return this.newWallet(await KeystoreSigner.fromFile(filePath)); } newWalletFromFile_unsafe(filePath: string, password: string) { - return new Wallet({ - signer: KeystoreSigner.fromFile_unsafe(filePath, password), - proxy: this.proxy, - chainId: this.chainId, - gasPrice: this.gasPrice, - }); + return this.newWallet(KeystoreSigner.fromFile_unsafe(filePath, password)); } newContract(address: string | Uint8Array) { - return new Contract({ address, proxy: this.proxy }); + return new Contract({ + address, + proxy: this.proxy, + baseExplorerUrl: this.explorerUrl, + }); } query(query: Query) { @@ -101,23 +107,32 @@ export class Wallet extends Signer { proxy: Proxy; chainId: string; gasPrice: number; + explorerUrl: string; + baseExplorerUrl: string; constructor({ signer, proxy, chainId, gasPrice, + baseExplorerUrl = "", }: { signer: Signer; proxy: Proxy; chainId: string; gasPrice: number; + baseExplorerUrl?: string; }) { super(signer.toTopBytes()); this.signer = signer; this.proxy = proxy; this.chainId = chainId; this.gasPrice = gasPrice; + this.baseExplorerUrl = baseExplorerUrl; + this.explorerUrl = getAccountExplorerUrl( + this.baseExplorerUrl, + this.toString(), + ); } sign(data: Buffer) { @@ -162,7 +177,12 @@ export class Wallet extends Signer { }); await tx.sign(this); const txHash = await this.proxy.sendTx(tx); - const resTx = await this.proxy.getCompletedTx(txHash); + const { hash, ..._resTx } = await this.proxy.getCompletedTx(txHash); + // Destructuring gives an invalid type: https://github.com/microsoft/TypeScript/issues/56456 + const resTx = Object.assign(Object.assign({}, _resTx), { + explorerUrl: getTxExplorerUrl(this.baseExplorerUrl, hash), + hash, + }); if (resTx.status !== "success") { throw new TxError("errorStatus", resTx.status, resTx); } @@ -194,7 +214,11 @@ export class Wallet extends Signer { const address = txResult.tx.logs.events.find( (e: any) => e.identifier === "SCDeploy", )!.address; - const contract = new Contract({ address, proxy: this.proxy }); + const contract = new Contract({ + address, + proxy: this.proxy, + baseExplorerUrl: this.explorerUrl, + }); const returnData = getTxReturnData(txResult.tx); return { ...txResult, address, contract, returnData }; } @@ -239,16 +263,25 @@ export class Wallet extends Signer { export class Contract extends AddressEncodable { proxy: Proxy; + explorerUrl: string; + baseExplorerUrl: string; constructor({ address, proxy, + baseExplorerUrl = "", }: { address: string | Uint8Array; proxy: Proxy; + baseExplorerUrl?: string; }) { super(address); this.proxy = proxy; + this.baseExplorerUrl = baseExplorerUrl; + this.explorerUrl = getAccountExplorerUrl( + this.baseExplorerUrl, + this.toString(), + ); } getAccountNonce() { @@ -276,34 +309,34 @@ class InteractionError extends Error { interaction: string; code: number | string; msg: string; - response: any; + result: any; constructor( interaction: string, code: number | string, message: string, - response: any, + result: any, ) { super( - `${interaction} failed: ${code} - ${message} - Response:\n` + - JSON.stringify(response, null, 2), + `${interaction} failed: ${code} - ${message} - Result:\n` + + JSON.stringify(result, null, 2), ); this.interaction = interaction; this.code = code; this.msg = message; - this.response = response; + this.result = result; } } class TxError extends InteractionError { - constructor(code: number | string, message: string, response: any) { - super("Transaction", code, message, response); + constructor(code: number | string, message: string, result: any) { + super("Transaction", code, message, result); } } class QueryError extends InteractionError { - constructor(code: number | string, message: string, response: any) { - super("Query", code, message, response); + constructor(code: number | string, message: string, result: any) { + super("Query", code, message, result); } } @@ -369,6 +402,12 @@ export class InteractionPromise implements PromiseLike { } } +const getAccountExplorerUrl = (baseExplorerUrl: string, address: string) => + `${baseExplorerUrl}/accounts/${address}`; + +const getTxExplorerUrl = (baseExplorerUrl: string, txHash: string) => + `${baseExplorerUrl}/transactions/${txHash}`; + const getTxReturnData = (tx: any): string[] => { const writeLogEvent = tx?.logs?.events.find( (e: any) => e.identifier === "writeLog", @@ -427,15 +466,13 @@ type QueryResult = { }; export type TxResult = { - tx: any; + tx: Prettify<{ hash: string; explorerUrl: string } & Record>; }; -export type DeployContractTxResult = TxResult & { - address: string; - contract: Contract; - returnData: string[]; -}; +export type DeployContractTxResult = Prettify< + TxResult & { address: string; contract: Contract; returnData: string[] } +>; -export type CallContractTxResult = TxResult & { - returnData: string[]; -}; +export type CallContractTxResult = Prettify< + TxResult & { returnData: string[] } +>; From cf8ff52dfb2866f06053f384e3a99254549f6145 Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 19 Nov 2023 13:50:02 +0100 Subject: [PATCH 15/16] Default options for new methods --- contracts/blank/interact/index.ts | 3 - contracts/vested-transfers/interact/index.ts | 9 ++- xsuite/src/interact/envChain.ts | 28 +++++--- xsuite/src/world/sworld.test.ts | 18 +++++ xsuite/src/world/sworld.ts | 43 +++++++---- xsuite/src/world/world.ts | 75 +++++++++++++++++--- 6 files changed, 135 insertions(+), 41 deletions(-) diff --git a/contracts/blank/interact/index.ts b/contracts/blank/interact/index.ts index b8d6c9b7..4de24a6c 100644 --- a/contracts/blank/interact/index.ts +++ b/contracts/blank/interact/index.ts @@ -4,9 +4,6 @@ import data from "./data.json"; const world = World.new({ chainId: envChain.id(), - proxyUrl: envChain.publicProxyUrl(), - gasPrice: 1_000_000_000, - explorerUrl: envChain.explorerUrl(), }); const loadWallet = () => world.newWalletFromFile("wallet.json"); diff --git a/contracts/vested-transfers/interact/index.ts b/contracts/vested-transfers/interact/index.ts index 1559ab89..b8c3333a 100644 --- a/contracts/vested-transfers/interact/index.ts +++ b/contracts/vested-transfers/interact/index.ts @@ -4,8 +4,6 @@ import data from "./data.json"; const world = World.new({ chainId: envChain.id(), - proxyUrl: envChain.publicProxyUrl(), - gasPrice: 1_000_000_000, }); const loadWallet = () => world.newWalletFromFile("wallet.json"); @@ -19,7 +17,8 @@ program.command("deploy").action(async () => { codeMetadata: ["upgradeable"], gasLimit: 100_000_000, }); - console.log("Result:", result); + console.log("Transaction:", result.tx.explorerUrl); + console.log("Contract:", result.contract.explorerUrl); }); program.command("upgrade").action(async () => { @@ -30,7 +29,7 @@ program.command("upgrade").action(async () => { codeMetadata: ["upgradeable"], gasLimit: 100_000_000, }); - console.log("Result:", result); + console.log("Transaction:", result.tx.explorerUrl); }); program.command("ClaimDeveloperRewards").action(async () => { @@ -40,7 +39,7 @@ program.command("ClaimDeveloperRewards").action(async () => { funcName: "ClaimDeveloperRewards", gasLimit: 10_000_000, }); - console.log("Result:", result); + console.log("Transaction:", result.tx.explorerUrl); }); program.parse(process.argv); diff --git a/xsuite/src/interact/envChain.ts b/xsuite/src/interact/envChain.ts index b9bcc6da..d1d1d291 100644 --- a/xsuite/src/interact/envChain.ts +++ b/xsuite/src/interact/envChain.ts @@ -1,11 +1,15 @@ -export const devnetId = "D"; -export const testnetId = "T"; -export const mainnetId = "1"; +export const devnetChainId = "D"; +export const testnetChainId = "T"; +export const mainnetChainId = "1"; export const devnetPublicProxyUrl = "https://devnet-gateway.multiversx.com"; export const testnetPublicProxyUrl = "https://testnet-gateway.multiversx.com"; export const mainnetPublicProxyUrl = "https://gateway.multiversx.com"; +export const devnetMinGasPrice = 1_000_000_000; +export const testnetMinGasPrice = 1_000_000_000; +export const mainnetMinGasPrice = 1_000_000_000; + export const devnetExplorerUrl = "https://devnet-explorer.multiversx.com"; export const testnetExplorerUrl = "https://testnet-explorer.multiversx.com"; export const mainnetExplorerUrl = "https://mainnet-explorer.multiversx.com"; @@ -30,22 +34,28 @@ export const envChain = { }, id: () => envChain.select({ - devnet: devnetId, - testnet: testnetId, - mainnet: mainnetId, - }), + devnet: devnetChainId, + testnet: testnetChainId, + mainnet: mainnetChainId, + } as const), publicProxyUrl: () => envChain.select({ devnet: devnetPublicProxyUrl, testnet: testnetPublicProxyUrl, mainnet: mainnetPublicProxyUrl, - }), + } as const), + minGasPrice: () => + envChain.select({ + devnet: devnetMinGasPrice, + testnet: testnetMinGasPrice, + mainnet: mainnetMinGasPrice, + } as const), explorerUrl: () => envChain.select({ devnet: devnetExplorerUrl, testnet: testnetExplorerUrl, mainnet: mainnetExplorerUrl, - }), + } as const), }; const isChainName = (chain: any): chain is ChainName => { diff --git a/xsuite/src/world/sworld.test.ts b/xsuite/src/world/sworld.test.ts index a1e73f43..4441f526 100644 --- a/xsuite/src/world/sworld.test.ts +++ b/xsuite/src/world/sworld.test.ts @@ -93,6 +93,24 @@ test("SWorld.proxy.getAccountWithKvs on empty bytes address", async () => { ); }); +test("SWorld.new with defined chainId", () => { + expect(() => SWorld.new({ chainId: "D" })).toThrow( + "chainId is not undefined.", + ); +}); + +test("SWorld.newDevnet", () => { + expect(() => SWorld.newDevnet()).toThrow("newDevnet is not implemented."); +}); + +test("SWorld.newTestnet", () => { + expect(() => SWorld.newTestnet()).toThrow("newTestnet is not implemented."); +}); + +test("SWorld.newMainnet", () => { + expect(() => SWorld.newMainnet()).toThrow("newMainnet is not implemented."); +}); + test("SWorld.createWallet", async () => { const wallet = await world.createWallet(); expect(wallet.explorerUrl).toEqual(`${explorerUrl}/accounts/${wallet}`); diff --git a/xsuite/src/world/sworld.ts b/xsuite/src/world/sworld.ts index 0de9a0db..f3cebe49 100644 --- a/xsuite/src/world/sworld.ts +++ b/xsuite/src/world/sworld.ts @@ -10,6 +10,7 @@ import { Wallet, expandCode, WorldDeployContractParams, + WorldNewOptions, } from "./world"; let walletCounter = 0; @@ -28,27 +29,34 @@ export class SWorld extends World { gasPrice: number; explorerUrl?: string; }) { - super({ proxy, chainId: "S", gasPrice, explorerUrl }); + super({ chainId: "S", proxy, gasPrice, explorerUrl }); this.proxy = proxy; this.sysAcc = this.newContract(new Uint8Array(32).fill(255)); } - static new({ - proxyUrl, - gasPrice, - explorerUrl, - }: { - proxyUrl: string; - gasPrice?: number; - explorerUrl?: string; - }) { + static new(options: SWorldNewOptions) { + if (options.chainId !== undefined) { + throw new Error("chainId is not undefined."); + } return new SWorld({ - proxy: new SProxy(proxyUrl), - gasPrice: gasPrice ?? 0, - explorerUrl, + proxy: new SProxy(options.proxyUrl), + gasPrice: options.gasPrice ?? 0, + explorerUrl: options.explorerUrl, }); } + static newDevnet(): World { + throw new Error("newDevnet is not implemented."); + } + + static newTestnet(): World { + throw new Error("newTestnet is not implemented."); + } + + static newMainnet(): World { + throw new Error("newMainnet is not implemented."); + } + static async start({ gasPrice, explorerUrl, @@ -184,6 +192,15 @@ const createContract = async ( return contract; }; +type SWorldNewOptions = + | { + chainId?: undefined; + proxyUrl: string; + gasPrice?: number; + explorerUrl?: string; + } + | WorldNewOptions; + type SWorldCreateWalletAccount = Prettify>; type SWorldCreateContractAccount = Prettify>; diff --git a/xsuite/src/world/world.ts b/xsuite/src/world/world.ts index de83ba2d..0975233b 100644 --- a/xsuite/src/world/world.ts +++ b/xsuite/src/world/world.ts @@ -1,6 +1,20 @@ import { AddressEncodable } from "../data/AddressEncodable"; import { b64ToHexString } from "../data/utils"; import { Optional, Prettify } from "../helpers"; +import { + devnetMinGasPrice, + devnetExplorerUrl, + devnetPublicProxyUrl, + mainnetMinGasPrice, + mainnetChainId, + mainnetExplorerUrl, + mainnetPublicProxyUrl, + testnetMinGasPrice, + testnetExplorerUrl, + testnetPublicProxyUrl, + devnetChainId, + testnetChainId, +} from "../interact/envChain"; import { CallContractTxParams, DeployContractTxParams, @@ -37,17 +51,26 @@ export class World { this.explorerUrl = explorerUrl; } - static new({ - chainId, - proxyUrl, - gasPrice, - explorerUrl, - }: { - chainId: string; - proxyUrl: string; - gasPrice: number; - explorerUrl?: string; - }) { + static new({ chainId, proxyUrl, gasPrice, explorerUrl }: WorldNewOptions) { + if (chainId === "D") { + proxyUrl ??= devnetPublicProxyUrl; + gasPrice ??= devnetMinGasPrice; + explorerUrl ??= devnetExplorerUrl; + } else if (chainId === "T") { + proxyUrl ??= testnetPublicProxyUrl; + gasPrice ??= testnetMinGasPrice; + explorerUrl ??= testnetExplorerUrl; + } else if (chainId === "1") { + proxyUrl ??= mainnetPublicProxyUrl; + gasPrice ??= mainnetMinGasPrice; + explorerUrl ??= mainnetExplorerUrl; + } + if (proxyUrl === undefined) { + throw new Error("proxyUrl is not defined."); + } + if (gasPrice === undefined) { + throw new Error("gasPrice is not defined."); + } return new World({ chainId, proxy: new Proxy(proxyUrl), @@ -56,6 +79,18 @@ export class World { }); } + static newDevnet(options: WorldNewRealnetOptions = {}) { + return World.new({ chainId: devnetChainId, ...options }); + } + + static newTestnet(options: WorldNewRealnetOptions = {}) { + return World.new({ chainId: testnetChainId, ...options }); + } + + static newMainnet(options: WorldNewRealnetOptions = {}) { + return World.new({ chainId: mainnetChainId, ...options }); + } + newWallet(signer: Signer) { return new Wallet({ signer, @@ -431,6 +466,24 @@ export const expandCode = (code: string) => { return code; }; +export type WorldNewOptions = Prettify< + | ({ + chainId: "D" | "T" | "1"; + } & WorldNewRealnetOptions) + | { + chainId: string; + proxyUrl: string; + gasPrice: number; + explorerUrl?: string; + } +>; + +type WorldNewRealnetOptions = { + proxyUrl?: string; + gasPrice?: number; + explorerUrl?: string; +}; + type WorldExecuteTxParams = Prettify< Optional, "gasPrice"> >; From 69fd7bbfd31cf5572b17fecf81b2abc19ca5460c Mon Sep 17 00:00:00 2001 From: lcswillems Date: Sun, 19 Nov 2023 14:20:31 +0100 Subject: [PATCH 16/16] Release @xsuite/simulnet@0.0.9 xsuite@0.0.38 --- xsuite-simulnet/package.json | 2 +- xsuite/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xsuite-simulnet/package.json b/xsuite-simulnet/package.json index cd75a16e..3013ed94 100644 --- a/xsuite-simulnet/package.json +++ b/xsuite-simulnet/package.json @@ -1,6 +1,6 @@ { "name": "@xsuite/simulnet", - "version": "0.0.8", + "version": "0.0.9", "license": "MIT", "scripts": { "build": "run-script-os", diff --git a/xsuite/package.json b/xsuite/package.json index 82652f1a..b3ef6e00 100644 --- a/xsuite/package.json +++ b/xsuite/package.json @@ -1,6 +1,6 @@ { "name": "xsuite", - "version": "0.0.37", + "version": "0.0.38", "license": "MIT", "bin": { "xsuite": "cli.js"