From d546c8e86165ec0630ebdb46c214f4377919b353 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Wed, 31 Jul 2024 16:39:39 -0400 Subject: [PATCH] fix: support latest soroban-examples The system-test started failing in https://github.com/stellar/stellar-cli/pull/1500 because it finally updated the version of `soroban-examples` to use a version that includes https://github.com/stellar/soroban-examples/pull/314. The previous `invoke.ts` logic assumed that the variable would be a Symbol, but the variable has been changed to a String. I don't want to break every project that still uses `system-test` with a stale `soroban-examples` hash, so here's what I did: - dynamically define `contract` using `import()`, using a `@ts-ignore` directive because this can error if `stellar-sdk` doesn't include a `contract` export. - if `contract` is there, then we don't need to know the type of the argument. It could be a Symbol or a String or anything else. - non-`contract` logic path stays unchanged, assuming `Symbol` --- features/dapp_develop/dapp_develop.feature | 4 +- invoke.ts | 107 +++++++++++++-------- 2 files changed, 67 insertions(+), 44 deletions(-) diff --git a/features/dapp_develop/dapp_develop.feature b/features/dapp_develop/dapp_develop.feature index 81383ba..99f5394 100644 --- a/features/dapp_develop/dapp_develop.feature +++ b/features/dapp_develop/dapp_develop.feature @@ -12,7 +12,7 @@ Scenario Outline: DApp developer compiles, installs, deploys and invokes a contr Examples: | Tool | ContractExampleSubPath | ContractName | ContractCompiledFileName | FunctionName | FunctionParams | Result | - | NODEJS | hello_world | soroban-hello-world-contract | soroban_hello_world_contract.wasm | hello | Aloha | ["Hello","Aloha"] | + | NODEJS | hello_world | soroban-hello-world-contract | soroban_hello_world_contract.wasm | hello | to:'Aloha' | ["Hello","Aloha"] | | CLI | hello_world | soroban-hello-world-contract | soroban_hello_world_contract.wasm | hello | --to=Aloha | ["Hello","Aloha"] | | NODEJS | increment | soroban-increment-contract | soroban_increment_contract.wasm | increment | | 1 | | CLI | increment | soroban-increment-contract | soroban_increment_contract.wasm | increment | | 1 | @@ -30,7 +30,7 @@ Scenario Outline: DApp developer compiles, deploys and invokes a contract Examples: | Tool | ContractExampleSubPath | ContractName | ContractCompiledFileName | FunctionName | FunctionParams | Result | EventCount | DiagEventCount | - | NODEJS | hello_world | soroban-hello-world-contract | soroban_hello_world_contract.wasm | hello | Aloha | ["Hello","Aloha"] | 0 | 1 | + | NODEJS | hello_world | soroban-hello-world-contract | soroban_hello_world_contract.wasm | hello | to:'Aloha' | ["Hello","Aloha"] | 0 | 1 | | CLI | hello_world | soroban-hello-world-contract | soroban_hello_world_contract.wasm | hello | --to=Aloha | ["Hello","Aloha"] | 0 | 1 | | NODEJS | increment | soroban-increment-contract | soroban_increment_contract.wasm | increment | | 1 | 0 | 1 | | CLI | increment | soroban-increment-contract | soroban_increment_contract.wasm | increment | | 1 | 0 | 1 | diff --git a/invoke.ts b/invoke.ts index 5381efd..dbff962 100755 --- a/invoke.ts +++ b/invoke.ts @@ -1,13 +1,14 @@ #!/usr/bin/env ts-node-script import { ArgumentParser } from 'argparse'; + import { Contract, Keypair, TransactionBuilder, SorobanRpc, scValToNative, - xdr + xdr, } from '@stellar/stellar-sdk'; const { Server } = SorobanRpc; @@ -33,53 +34,75 @@ async function main() { functionName, } = parser.parse_args() as Record; - const contract = new Contract(contractId); - const server = new Server(rpcUrl, { allowHttp: true }); - const secretKey = Keypair.fromSecret(source); - const account = secretKey.publicKey(); - const sourceAccount = await server.getAccount(account); - - // Some hacky param-parsing as csv. Generated Typescript bindings would be better. - const params: xdr.ScVal[] = functionParams - ? functionParams.split(",").map((p) => xdr.ScVal.scvSymbol(p)) : []; + const keypair = Keypair.fromSecret(source); + const account = keypair.publicKey(); - const originalTxn = new TransactionBuilder(sourceAccount, { - fee: "100", + // @ts-ignore contract client only available in stellar-sdk ≥12 + const { contract } = await import('@stellar/stellar-sdk'); + if (contract) { + const client = await contract.Client.from({ + allowHttp: true, + rpcUrl, networkPassphrase, - }) - .addOperation(contract.call(functionName, ...params)) - .setTimeout(30) - .build(); + contractId, + publicKey: account, + ...contract.basicNodeSigner(keypair, networkPassphrase), + }); + const args: Record = {}; + functionParams.split(",").forEach((p) => { + const [name, value] = p.split(":"); + args[name] = value; + }); + // @ts-ignore client[functionName] is defined dynamically + const { result } = await client[functionName](args); + console.log(JSON.stringify(result)); + return; + } else { + const server = new Server(rpcUrl, { allowHttp: true }); + const sourceAccount = await server.getAccount(account); + const contract = new Contract(contractId); + // Some hacky param-parsing as csv. Generated Typescript bindings would be better. + const params: xdr.ScVal[] = functionParams + ? functionParams.split(",").map((p) => xdr.ScVal.scvSymbol(p.split(':')[1])) : []; - const txn = await server.prepareTransaction(originalTxn); - txn.sign(secretKey); - const send = await server.sendTransaction(txn); - if (send.errorResult) { - throw new Error(`Transaction failed: ${JSON.stringify(send)}`); - } - let response = await server.getTransaction(send.hash); - for (let i = 0; i < 50; i++) { - switch (response.status) { - case "NOT_FOUND": { - // retry - await new Promise(resolve => setTimeout(resolve, 100)); - response = await server.getTransaction(send.hash); - break; + const originalTxn = new TransactionBuilder(sourceAccount, { + fee: "100", + networkPassphrase, + }) + .addOperation(contract.call(functionName, ...params)) + .setTimeout(30) + .build(); + + const txn = await server.prepareTransaction(originalTxn); + txn.sign(keypair); + const send = await server.sendTransaction(txn); + if (send.errorResult) { + throw new Error(`Transaction failed: ${JSON.stringify(send)}`); } - case "SUCCESS": { - if (!response.returnValue) { - throw new Error(`No invoke host fn return value provided: ${JSON.stringify(response)}`); + let response = await server.getTransaction(send.hash); + for (let i = 0; i < 50; i++) { + switch (response.status) { + case "NOT_FOUND": { + // retry + await new Promise(resolve => setTimeout(resolve, 100)); + response = await server.getTransaction(send.hash); + break; } + case "SUCCESS": { + if (!response.returnValue) { + throw new Error(`No invoke host fn return value provided: ${JSON.stringify(response)}`); + } - const parsed = scValToNative(response.returnValue); - console.log(JSON.stringify(parsed)); - return; - } - case "FAILED": { - throw new Error(`Transaction failed: ${JSON.stringify(response)}`); - } - default: - throw new Error(`Unknown transaction status: ${response.status}`); + const parsed = scValToNative(response.returnValue); + console.log(JSON.stringify(parsed)); + return; + } + case "FAILED": { + throw new Error(`Transaction failed: ${JSON.stringify(response)}`); + } + default: + throw new Error(`Unknown transaction status: ${response.status}`); + } } } throw new Error("Transaction timed out");