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

Add generation of contract clients and an AssembledTransaction abstraction #891

Merged
merged 2 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# paths = ["/path/to/override"] # path dependency overrides

[alias] # command aliases
install_soroban = "install --version 20.1.1 --root ./target soroban-cli --debug"
# b = "build --target wasm32-unknown-unknown --release"
# c = "check"
# t = "test"
# r = "run"
# rr = "run --release"
# recursive_example = "rr --example recursions"
# space_example = ["run", "--release", "--", "\"command list\""]

[build]
# jobs = 1 # number of parallel jobs, defaults to # of CPUs
# rustc = "rustc" # the rust compiler tool
# rustc-wrapper = "…" # run this wrapper instead of `rustc`
# rustc-workspace-wrapper = "…" # run this wrapper instead of `rustc` for workspace members
# rustdoc = "rustdoc" # the doc generator tool
# target = "wasm32-unknown-unknown" # build for the target triple (ignored by `cargo install`)
# target-dir = "target" # path of where to place all generated artifacts
# rustdocflags = ["…", "…"] # custom flags to pass to rustdoc
# incremental = true # whether or not to enable incremental compilation
# dep-info-basedir = "…" # path for the base directory for targets in depfiles

# [doc]
# browser = "chromium" # browser to use with `cargo doc --open`,
# # overrides the `BROWSER` environment variable

# [env]
# # Set ENV_VAR_NAME=value for any process run by Cargo
# ENV_VAR_NAME = "value"
# # Set even if already present in environment
# ENV_VAR_NAME_2 = { value = "value", force = true }
# # Value is relative to .cargo directory containing `config.toml`, make absolute
# ENV_VAR_NAME_3 = { value = "relative/path", relative = true }

# [future-incompat-report]
# frequency = 'always' # when to display a notification about a future incompat report

# [cargo-new]
# vcs = "none" # VCS to use ('git', 'hg', 'pijul', 'fossil', 'none')

# [http]
# debug = false # HTTP debugging
# proxy = "host:port" # HTTP proxy in libcurl format
# ssl-version = "tlsv1.3" # TLS version to use
# ssl-version.max = "tlsv1.3" # maximum TLS version
# ssl-version.min = "tlsv1.1" # minimum TLS version
# timeout = 30 # timeout for each HTTP request, in seconds
# low-speed-limit = 10 # network timeout threshold (bytes/sec)
# cainfo = "cert.pem" # path to Certificate Authority (CA) bundle
# check-revoke = true # check for SSL certificate revocation
# multiplexing = true # HTTP/2 multiplexing
# user-agent = "…" # the user-agent header

# [install]
# root = "/some/path" # `cargo install` destination directory

# [net]
# retry = 2 # network retries
# git-fetch-with-cli = true # use the `git` executable for git operations
# offline = true # do not access the network

# [net.ssh]
# known-hosts = ["..."] # known SSH host keys

# [patch.<registry>]
# # Same keys as for [patch] in Cargo.toml

# [profile.<name>] # Modify profile settings via config.
# inherits = "dev" # Inherits settings from [profile.dev].
# opt-level = 0 # Optimization level.
# debug = true # Include debug info.
# split-debuginfo = '...' # Debug info splitting behavior.
# debug-assertions = true # Enables debug assertions.
# overflow-checks = true # Enables runtime integer overflow checks.
# lto = false # Sets link-time optimization.
# panic = 'unwind' # The panic strategy.
# incremental = true # Incremental compilation.
# codegen-units = 16 # Number of code generation units.
# rpath = false # Sets the rpath linking option.
# [profile.<name>.build-override] # Overrides build-script settings.
# # Same keys for a normal profile.
# [profile.<name>.package.<name>] # Override profile for a package.
# # Same keys for a normal profile (minus `panic`, `lto`, and `rpath`).

# [registries.<name>] # registries other than crates.io
# index = "…" # URL of the registry index
# token = "…" # authentication token for the registry

# [registry]
# default = "…" # name of the default registry
# token = "…" # authentication token for crates.io

# [source.<name>] # source definition and replacement
# replace-with = "…" # replace this source with the given named source
# directory = "…" # path to a directory source
# registry = "…" # URL to a registry source
# local-registry = "…" # path to a local registry source
# git = "…" # URL of a git repository source
# branch = "…" # branch name for the git repository
# tag = "…" # tag name for the git repository
# rev = "…" # revision for the git repository

# [target.<triple>]
# linker = "…" # linker to use
# runner = "…" # wrapper to run executables
# rustflags = ["…", "…"] # custom flags for `rustc`

# [target.<cfg>]
# runner = "…" # wrapper to run executables
# rustflags = ["…", "…"] # custom flags for `rustc`

# [target.<triple>.<links>] # `links` build script override
# rustc-link-lib = ["foo"]
# rustc-link-search = ["/path/to/foo"]
# rustc-flags = ["-L", "/some/path"]
# rustc-cfg = ['key="value"']
# rustc-env = {key = "value"}
# rustc-cdylib-link-arg = ["…"]
# metadata_key1 = "value"
# metadata_key2 = "value"

# [term]
# quiet = false # whether cargo output is quiet
# verbose = false # whether cargo provides verbose output
# color = 'auto' # whether cargo colorizes output
# progress.when = 'auto' # whether cargo shows progress bar
# progress.width = 80 # width of progress bar
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SOROBAN_NETWORK_PASSPHRASE="Standalone Network ; February 2017"
SOROBAN_RPC_URL="http://localhost:8000/soroban/rpc"
39 changes: 39 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: ContractClient

on:
push:
branches: [master, release/**]
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
pull_request:

jobs:
test:
name: test generated ContractClient
runs-on: ubuntu-22.04
services:
rpc:
image: stellar/quickstart:soroban-dev@sha256:0ad51035cf7caba2fd99c7c1fad0945df6932be7d5c893e1520ccdef7d6a6ffe
ports:
- 8000:8000
env:
ENABLE_LOGS: true
NETWORK: local
ENABLE_SOROBAN_RPC: true
options: >-
--health-cmd "curl --no-progress-meter --fail-with-body -X POST \"http://localhost:8000/soroban/rpc\" -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":8675309,\"method\":\"getNetwork\"}' && curl --no-progress-meter \"http://localhost:8000/friendbot\" | grep '\"invalid_field\": \"addr\"'"
--health-interval 10s
--health-timeout 5s
--health-retries 50
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

# Workaround for some `yarn` nonsense, see:
# https://github.com/yarnpkg/yarn/issues/6312#issuecomment-429685210
- run: yarn install --network-concurrency 1
- run: yarn build:prod
- run: yarn test:e2e

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ js-stellar-base/

test/unit/out/
.vscode/launch.json

target
.soroban
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
"build:prod": "cross-env NODE_ENV=production yarn _build",
"build:node": "yarn _babel && yarn build:ts",
"build:ts": "tsc -p ./config/tsconfig.json",
"build:test": "tsc -p ./test/tsconfig.json",
"build:test": "tsc -p ./test/unit/tsconfig.json",
"build:browser": "webpack -c config/webpack.config.browser.js",
"build:docs": "cross-env NODE_ENV=docs yarn _babel",
"clean": "rm -rf lib/ dist/ coverage/ .nyc_output/ jsdoc/",
"clean": "rm -rf lib/ dist/ coverage/ .nyc_output/ jsdoc/ test/e2e/.soroban test/e2e/contract-*.txt test/e2e/wasms/specs/*.json",
"docs": "yarn build:docs && jsdoc -c ./config/.jsdoc.json --verbose",
"test": "yarn build:test && yarn test:node && yarn test:integration && yarn test:browser",
"test:e2e": "./test/e2e/initialize.sh && ava",
"test:node": "yarn _nyc mocha --recursive 'test/unit/**/*.js'",
"test:integration": "yarn _nyc mocha --recursive 'test/integration/**/*.js'",
"test:browser": "karma start config/karma.conf.js",
Expand Down Expand Up @@ -76,6 +77,8 @@
]
},
"devDependencies": {
"ava": "^5.3.1",
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
"dotenv": "^16.3.1",
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.6",
"@babel/eslint-parser": "^7.22.15",
Expand All @@ -96,7 +99,6 @@
"@types/randombytes": "^2.0.1",
"@types/sinon": "^17.0.2",
"@types/urijs": "^1.19.20",
"@typescript-eslint/parser": "^6.14.0",
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
"axios-mock-adapter": "^1.22.0",
"babel-loader": "^9.1.3",
"babel-plugin-istanbul": "^6.1.1",
Expand Down Expand Up @@ -151,5 +153,13 @@
"randombytes": "^2.1.0",
"toml": "^3.0.0",
"urijs": "^1.19.1"
},
"ava": {
"files": [
"./test/e2e/src/test-*"
],
"require": [
"dotenv/config"
]
}
}
84 changes: 83 additions & 1 deletion src/contract_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import {
Contract,
scValToBigInt,
} from ".";
import {
AssembledTransaction,
ContractClient,
ContractClientOptions,
MethodOptions,
} from './soroban';

export interface Union<T> {
tag: string;
Expand Down Expand Up @@ -163,7 +169,9 @@ export class ContractSpec {
}
let output = outputs[0];
if (output.switch().value === xdr.ScSpecType.scSpecTypeResult().value) {
return this.scValToNative(val, output.result().okType());
return new AssembledTransaction.Result.Ok(
this.scValToNative(val, output.result().okType())
);
}
return this.scValToNative(val, output);
}
Expand Down Expand Up @@ -678,6 +686,60 @@ export class ContractSpec {
return num;
}

/**
* Gets the XDR error cases from the spec.
*
* @returns {xdr.ScSpecFunctionV0[]} all contract functions
*
*/
errorCases(): xdr.ScSpecUdtErrorEnumCaseV0[] {
return this.entries
.filter(
(entry) =>
entry.switch().value ===
xdr.ScSpecEntryKind.scSpecEntryUdtErrorEnumV0().value
)
.flatMap((entry) => (entry.value() as xdr.ScSpecUdtErrorEnumV0).cases());
}

/**
* Generate a class from the contract spec that where each contract method gets included with a possibly-JSified name.
*
* Each method returns an AssembledTransaction object that can be used to sign and submit the transaction.
*/
generateContractClient(options: ContractClientOptions): ContractClient {
const spec = this;
let methods = this.funcs();
const contractClient = new ContractClient(spec, options);
for (let method of methods) {
let name = method.name().toString();
let jsName = toLowerCamelCase(name);
// @ts-ignore
contractClient[jsName] = async (
args: Record<string, any>,
options: MethodOptions
) => {
return await AssembledTransaction.fromSimulation({
method: name,
args: spec.funcArgsToScVals(name, args),
...options,
...contractClient.options,
errorTypes: spec
.errorCases()
.reduce(
(acc, curr) => ({
...acc,
[curr.value()]: { message: curr.doc().toString() },
}),
{} as Pick<ContractClientOptions, "errorTypes">
),
parseResultXdr: (result: xdr.ScVal) => spec.funcResToNative(name, result),
});
};
}
return contractClient;
}

/**
* Converts the contract spec to a JSON schema.
*
Expand Down Expand Up @@ -1138,3 +1200,23 @@ function enumToJsonSchema(udt: xdr.ScSpecUdtEnumV0): any {
}
return res;
}

/**
* converts a snake_case string to camelCase
*/
export function toLowerCamelCase(str: string): string {
return str.replace(/_\w/g, (m) => m[1].toUpperCase());
}

export type u32 = number;
export type i32 = number;
export type u64 = bigint;
export type i64 = bigint;
export type u128 = bigint;
export type i128 = bigint;
export type u256 = bigint;
export type i256 = bigint;
export type Option<T> = T | undefined;
export type Typepoint = bigint;
export type Duration = bigint;

2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export * as Horizon from './horizon';

// Soroban RPC-related classes to expose
export * as SorobanRpc from './soroban';
export { ContractSpec } from './contract_spec';
export * from './contract_spec';

// expose classes and functions from stellar-base
export * from '@stellar/stellar-base';
Expand Down
Loading
Loading