Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rough first pass at generating typescript contract bindings #881

Closed
wants to merge 4 commits into from

Conversation

paulbellamy
Copy link
Contributor

@paulbellamy paulbellamy commented Mar 3, 2023

What

A rough first pass at generating typescript bindings for the contract spec.

Why

This approach is higher maintenance (as the js sdk etc changes), and we can't reuse the rust strval.rs code. But, it also avoids all the rust->wasm->js machinery.

With this, you can do:

import SorobanClient from 'soroban-client';
const xdr = SorobanClient.xdr;

// Import our generated binding for the hello world contract
import { Contract } from './contract';

// And we get an easy helper which can parse native js types to build our xdr.Operation
const contract = new Contract(contractId);
const operation = contract.hello("world");

// The rest of the txn sending flow is the same as before.
const server = new SorobanClient.Server('http://localhost:8000/soroban/rpc', { allowHttp: true });
const source = await server.getAccount(accountId);
const txn = new SorobanClient.TransactionBuilder(source, {
  fee: 100,
  networkPassphrase: SorobanClient.Networks.STANDALONE,
  v1: true,
})
  .addOperation(operation)
  .setTimeout(SorobanClient.TimeoutInfinite)
  .build();
console.debug("Transaction:", txn.toEnvelope().toXDR('base64').toString());
const result = await server.simulateTransaction(txn);
console.debug(result);

Known limitations

  • Only a few primitive types implemented so far, not UDTs, etc...
  • We should push the parser code into js-stellar-base, then emit something like const val_xdr = xdr.ScVal.fromJSON(js_obj, spec) and xdr.ScVal.toJSON(...) for each.
    • Pro: Would simplify the rust parser emission
    • Con: Would spread the parser logic across two repos.
    • Pro: Would allow js apps to use the parsers as well.
  • Future: Should also figure out how to generate parsers for the operation result values.

Copy link
Member

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a good direction to me.

We may have similar testing issues that we have with xdrgen, where in the repo the code is written we can't reliably test the outputs because the outputs are a different langauge to what we're building in this repo. There might be an argument for separating the soroban-spec crate into its own repo where we actually somehow do more elaborate testing of the generated outputs. I don't think we need to do this now though, just thinking out loud. Also, if the tests end up being simple enough, as they appear right now in this pull request, just doing string comparisons may be fine. @paulbellamy Do you have thoughts on this?

let header = r#"import * as SorobanClient from "soroban-client";
let xdr = SorobanClient.xdr;

// TODO: Move all the non-trivial conversions to js-stellar-base and use them them from there.
Copy link
Member

@leighmcculloch leighmcculloch Apr 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 As much as possible we can move utilities into a lib sgtm.

When we do that we need to think about how to version those things. A low effort approach to start with might be to include a comment at the top that says it is dependent on a specific version of the lib. If we build the version of the lib into a constant in the lib we can do a runtime version check at the beginning of the generated code to ensure that when people use generated code they use it with the correct version of the sdk.

Comment on lines +61 to +73
if (value < BigInt(0)) {
// Clear the top bit
buf[0] &= 0x7f;
}

// left-pad with zeros up to 16 bytes
let padded = Buffer.alloc(16);
buf.copy(padded, padded.length-buf.length);

if (value < BigInt(0)) {
// Set the top bit
padded[0] |= 0x80;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why does the top bit get cleared and then set again during the padding process?


function bigintFromBytes(signed: boolean, ...bytes: (string | number | bigint)[]): bigint {
let sign = 1;
if (signed && bytes[0] === 0x80) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ If I'm understanding this correctly this is checking the top byte, not just the top bit. I think you need to & with 0x80.

@leighmcculloch
Copy link
Member

What's the plan with this PR?

@paulbellamy
Copy link
Contributor Author

@willemneal from aha labs is working on a better version. This will be closed in favor of that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants