Soroban contract with TinyGo
Tags: soroban, sorobanathon
Published: 2023-02-07 16:16:00

## Invoking a soroban contract built with TinyGo

This post describes my experience with building a simple smart contract for Soroban with TinyGo, deploying and invoking its function with the `soroban-cli` and on `futurenet`.


I started on 5 February 2023 and at that time, the available TinyGo version was 0.26.0. The Soroban interface version of the host functions was 27.

First, I installed [TinyGo](https://tinygo.org/) and developed a very simple smart contract. It adds two `uint32` values and return the result.

##  Contract code

```go
package main

//export add
func add(x uint64, y uint64) uint64 {
	res := toU32(x) + toU32(y)
	return fromU32(res)
}

// main is required for the `wasm` target, even if it isn't used.
func  main() {}

// Extracts the 32-bit unsigned integer from a host value that represents a 32-bit unsigned integer.
func toU32(val uint64) uint32 {
	return  uint32(val >> 4)
}

// Creates a host value that represents a 32-bit unsigned integer.
func fromU32(val uint32) uint64 {
	return  addTagToBody(0, uint64(val))
}

// Adds a given tag to a host value body.
func  addTagToBody(tag uint8, body uint64) uint64 {
	return (body << 4) | uint64((tag << 1)) | 1
}
```

As you can see, our function is called `add`. It should add two `uint32` values. As input parameters, it receives 2 host values (`uint64`) and returns the result as host value too.

First, we convert the received host values to `uint32`, add them, and then return the result as host value.

## Building with tinyGo

Building with TinyGo first generates a huge `wasm` file:

```tinygo build -o add.wasm -target=wasm -wasm-abi=generic add.go```

```-rwxr-xr-x   1 chris  staff  56506  7 Feb 14:34 add.wasm```

However, TinyGo offers a few [build flags](https://tinygo.org/docs/reference/usage/important-options/) and after some experimentation, I ended up with this command:

```tinygo build -o add.wasm -target=wasm -wasm-abi=generic -scheduler=none --no-debug -panic=trap -opt z -gc=leaking add.go```

First issue here: `-gc`! It lets one specify the memory manager (garbage collector) to be used. There are 3 options available:   `none`, `leaking` and `conservative`. I wanted to use `none` of course, but unfortunately that did not work out. It gave me the following error:

```
tinygo:wasm-ld: error: /var/folders/w4/lcgfp2855g3f2k2_zs16tbnr0000gn/T/tinygo1603362656/main.o: undefined symbol: runtime.alloc
failed to run tool: wasm-ld
error: failed to link /var/folders/w4/lcgfp2855g3f2k2_zs16tbnr0000gn/T/tinygo1603362656/main: exit status 1
``` 
More about this later ...

So I ended up with the command above, generating a smaller (but still huge) `wasm` file:

```
-rwxr-xr-x   1 chris  staff  3355  7 Feb 15:03 add.wasm
```
If one converts the `wasm` file to `wat` (textual representation), then one can read its content. It contains 1200 lines of code. See: [gist: add.wat](https://gist.github.com/christian-rogobete/4638ff024c23ce5cd9d142e3f3450be7).

 A lot of strange logic and 4 not so welcome exports:

```
...
(func $malloc.command_export (export "malloc") (type $t1) (param $p0 i32) (result i32)
    (call $malloc
      (local.get $p0))
    (call $__wasm_call_dtors))
  (func $free.command_export (export "free") (type $t5) (param $p0 i32)
    (call $free
      (local.get $p0))
    (call $__wasm_call_dtors))
  (func $calloc.command_export (export "calloc") (type $t6) (param $p0 i32) (param $p1 i32) (result i32)
    (call $calloc
      (local.get $p0)
      (local.get $p1))
    (call $__wasm_call_dtors))
  (func $realloc.command_export (export "realloc") (type $t6) (param $p0 i32) (param $p1 i32) (result i32)
...
```
However, I decided to go forward with it and see if I can deploy the contract and invoke its `add` function using the `soroban-cli`.

## Adding meta and contract spec 

As described in the Soroban docs, the `wasm` module needs to contain 2 custom sections, [meta](https://soroban.stellar.org/docs/sdks/byo#meta-generation), describing the host interface version and [contract spec](https://soroban.stellar.org/docs/sdks/byo#contract-spec-generation), describing the exported functions.

In AssemblyScript, the compiler frontend (asc) provides a mechanism to hook into the compilation process before, while and after the module is being compiled. This can be used with so called [Transforms](https://www.assemblyscript.org/compiler.html#transforms) and [Hooks](https://www.assemblyscript.org/compiler.html#hooks), which allowed me to add the 2 custom sections to the module in the [AssemblyScript SDK](https://github.com/Soneso/as-soroban-sdk).

However, in TinyGo there is no such mechanism. After a question in the TinyGo Gophers slack channel one of the maintainers suggested to compile the sections separately and link them using a CGo flag: `#cgo LDFLAGS: somefile.o`. But after searching more for a solution, I found [Wabin](https://github.com/tetratelabs/wabin), which allowed me to programmatically add the 2 custom sections to the compiled module.  

Here is a gist containing my code doing so:
[addsec.go](https://gist.github.com/christian-rogobete/1b8d2506f270938de1d4b3b0aefc7dce)

Finally! I had the sections added and I was ready to invoke the `add` function using the `soroban-cli`.

## Inspecting the contract

The `soroban-cli` offers an inspect functionality that inspects a WASM file listing contract functions, meta, etc.  

I tried that first and here is the result:
`soroban inspect --wasm add.wasm`

```
File: add.wasm
Env Meta: AAAAAAAAAAAAAAAb
• Interface Version: 27
Contract Spec: AAAAAAAAAANhZGQAAAAAAgAAAAF4AAAAAAAAAQAAAAF5AAAAAAAAAQAAAAEAAAAB
• Function: add ([ScSpecFunctionInputV0 { name: StringM(x), type_: U32 }, ScSpecFunctionInputV0 { name: StringM(y), type_: U32 }]) -> ([U32])
``` 
Looks good! :) 

## Invoking add

Command:

```soroban invoke --wasm add.wasm --id 1 --fn add --arg 2 --arg 3```

Result:

```
error: HostError
Value: Status(VmError(Validation))

Debug events (newest first):
0: "Validation"

Backtrace (newest first):
0: backtrace::capture::Backtrace::new_unresolved
1: soroban_env_host::host::err_helper::<impl soroban_env_host::host::Host>::err
2: soroban_env_host::vm::Vm::new
3: soroban_env_host::host::Host::call_n
4: soroban_env_host::host::Host::invoke_function
5: soroban::invoke::Cmd::run_in_sandbox
6: soroban::run::{{closure}}
7: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
8: tokio::runtime::park::CachedParkThread::block_on
9: tokio::runtime::scheduler::multi_thread::MultiThread::block_on
10: tokio::runtime::runtime::Runtime::block_on
11: soroban::main
```
Oh no, no, no! Now go and find the reason! :)

```0: backtrace::capture::Backtrace::new_unresolved```

is this line of code in the `soroban-env-host` rust code (in [soroban-env-host/src/host/errors.rs](https://github.com/stellar/rs-soroban-env/blob/main/soroban-env-host/src/host/error.rs#L114)):

```rust 
let  backtrace  =  backtrace::Backtrace::new_unresolved();
```

When I had managed to build `soroban-env`, write a test and try to debug it, I was tortured by the thought that I have to get rid of the strange logic and the malloc like exports from the module.  

But `-gc=none` didn't work, so I decided to ask in the TinyGo Gophers slack channel again.  After some discussion, a maintainer of TinyGo pointed out a current [pull request](https://github.com/tinygo-org/tinygo/pull/3142) that aims to get rid of that exports and their logic (which were exported by accident).

Ok, sounds good, but when will this be available? Probably in the next version. But I wanted it now, so I decided to build TinyGo from that branch and try again.

## Building TinyGo from the wasm-no-malloc branch

On the TinyGo website there is a [description](https://tinygo.org/docs/guides/build/) of how to build TinyGo for development.

With the new built version I tried it again:

## Building the new wasm file

Command:
```.../build/tinygo build -o add.wasm -target=wasm -wasm-abi=generic -scheduler=none --no-debug -panic=trap -opt z -gc=none add.go```

Result:
```-rwxr-xr-x   1 chris  staff  456  7 Feb 16:08 add.wasm```

cool! 

and now optimize with `wasm-opt`:

```wasm-opt -Oz add.wasm -o add.wasm```

Result:
```-rwxr-xr-x   1 chris  staff  313  7 Feb 16:10 add.wasm```

even better, **313** bytes and we started with **56506**! See the new [wat file](https://gist.github.com/christian-rogobete/d9c4c5a5e285d58dbb5eb56e614d26d8).

Next, add the custom sections to the module and try again with the `soroban-cli`

```soroban invoke --wasm add.wasm --id 1 --fn add --arg 2 --arg 3```

Result:
`5`

Tadaaa! 

And same on `futurenet`:
![futurenet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uuekdk4ybkzevt5efczy.png)

## Conclusion

We can now build contracts with TinyGo and start experimenting. In the upcoming version of TinyGo - 0.27.0 - the pull request will hopefully be merged, so we don't need to build TinyGo from the `wasm-no-malloc` branch.

There is still a long way to go, but a first proof of concept is here. Maybe this will end up in a new Soroban SDK for TinyGo ;)