Skip to content

Commit

Permalink
fix: support latest soroban-examples (#97)
Browse files Browse the repository at this point in the history
The system-test started failing in
stellar/stellar-cli#1500 because it finally
updated the version of `soroban-examples` to use a version that includes
stellar/soroban-examples#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`
  • Loading branch information
chadoh committed Jul 31, 2024
1 parent 278bdfa commit a0b9d3e
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 44 deletions.
4 changes: 2 additions & 2 deletions features/dapp_develop/dapp_develop.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |
Expand Down
107 changes: 65 additions & 42 deletions invoke.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -33,53 +34,75 @@ async function main() {
functionName,
} = parser.parse_args() as Record<string, string>;

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<string, any> = {};
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");
Expand Down

0 comments on commit a0b9d3e

Please sign in to comment.