Skip to content

Commit

Permalink
Format markdown files
Browse files Browse the repository at this point in the history
  • Loading branch information
webmaster128 committed Apr 4, 2020
1 parent 3e18790 commit f2c4a29
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 167 deletions.
103 changes: 54 additions & 49 deletions Building.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Building WebAssembly Smart Contracts

The subdirectories are various examples of compiling smart contracts.
Here are some tips useful for creating your own.
The subdirectories are various examples of compiling smart contracts. Here are
some tips useful for creating your own.

## Setup

Expand All @@ -13,27 +13,29 @@ via `cargo new --lib sample`. Then add the following to `Cargo.toml`:
crate-type = ["cdylib", "rlib"]
```

The `cdylib` is needed for the wasm target.
The `rlib` is needed to compile artifacts for integration tests (and benchmarking).
The `cdylib` is needed for the wasm target. The `rlib` is needed to compile
artifacts for integration tests (and benchmarking).

**Note** throughout this demo I will use the name `hackatom` for the project.
Please replace it with the real name of your crate. I intentionally didn't use `<name>`,
so you can cut and paste for a quick demo. Just update this when making real code.
Please replace it with the real name of your crate. I intentionally didn't use
`<name>`, so you can cut and paste for a quick demo. Just update this when
making real code.

## Requirements

You must support the rust target `wasm32-unknown-unknown`.

Check which ones you currently have installed via `rustup target list --installed`.
If wasm32 is not on that list, install using `rustup target add wasm32-unknown-unknown`

Check which ones you currently have installed via
`rustup target list --installed`. If wasm32 is not on that list, install using
`rustup target add wasm32-unknown-unknown`

## Building

Go into the subdirectory, called `sample` from now on:

To compile the code, run `cargo build --release --target wasm32-unknown-unknown`.
The output will be in `target/wasm32-unknown-unknown/release/hackatom.wasm`
To compile the code, run
`cargo build --release --target wasm32-unknown-unknown`. The output will be in
`target/wasm32-unknown-unknown/release/hackatom.wasm`

You probably don't want to explicitly set the target every time, so you can just
add the following to `.cargo/config`:
Expand All @@ -43,11 +45,14 @@ add the following to `.cargo/config`:
wasm = "build --release --target wasm32-unknown-unknown"
```

And you can now just call `cargo wasm` to build it, and `cargo test` to run tests.
And you can now just call `cargo wasm` to build it, and `cargo test` to run
tests.

**Note** Using `build.target` seems to force tests to use that target as well, remove this or find a work-around.
**Note** Using `build.target` seems to force tests to use that target as well,
remove this or find a work-around.
[This discussion](https://internals.rust-lang.org/t/set-default-target-for-cargo-build-but-not-for-cargo-test/9777)
and [closed PR](https://github.com/rust-lang/cargo/pull/6825) seem to suggest this will never be done.
and [closed PR](https://github.com/rust-lang/cargo/pull/6825) seem to suggest
this will never be done.

## Optimizations

Expand All @@ -56,9 +61,9 @@ Here are some things to make it smaller.

### Smaller builds

If you want to request the compiler to make smaller binaries,
you can hit a few flags (which raise compile time significantly).
Try adding this custom profile to Cargo.toml:
If you want to request the compiler to make smaller binaries, you can hit a few
flags (which raise compile time significantly). Try adding this custom profile
to Cargo.toml:

```yaml
[profile.release]
Expand All @@ -73,19 +78,20 @@ incremental = false
overflow-checks = true
```

**IMPORTANT** it is essential that codegen-units is set to 1 for deterministic builds.
Otherwise, you will have a different wasm output with each compile.
**IMPORTANT** it is essential that codegen-units is set to 1 for deterministic
builds. Otherwise, you will have a different wasm output with each compile.

## Shrinking the output

After compiling your contract, take a look at the size of the original output:
`du -sh target/wasm32-unknown-unknown/release/hackatom.wasm`, it is likely around 1.5 MB.
Most of that is unneeded and can easily be trimmed. The first approach is to use
[`wasm-pack`](https://github.com/rustwasm/wasm-pack) to build it.
This is designed for exporting small wasm builds and js bindings for the web, but is
also the most actively maintained stack for trimmed wasm builds with rust.
[`wasm-gc`](https://github.com/alexcrichton/wasm-gc), the older alternative, has
been deprecated for this approach. (Note you must [install wasm-pack first](https://rustwasm.github.io/wasm-pack/installer/)):
`du -sh target/wasm32-unknown-unknown/release/hackatom.wasm`, it is likely
around 1.5 MB. Most of that is unneeded and can easily be trimmed. The first
approach is to use [`wasm-pack`](https://github.com/rustwasm/wasm-pack) to build
it. This is designed for exporting small wasm builds and js bindings for the
web, but is also the most actively maintained stack for trimmed wasm builds with
rust. [`wasm-gc`](https://github.com/alexcrichton/wasm-gc), the older
alternative, has been deprecated for this approach. (Note you must
[install wasm-pack first](https://rustwasm.github.io/wasm-pack/installer/)):

```sh
cargo wasm
Expand All @@ -96,10 +102,9 @@ du -h pkg/hackatom_bg.wasm
```

A bit smaller, huh? For the sample contract, this is around 64kB, but this
varies a lot contract-by-contract.
If you have plenty of dependencies and this is still too big,
you can do a bit of investigation of where the size comes from, and maybe
change your dependencies:
varies a lot contract-by-contract. If you have plenty of dependencies and this
is still too big, you can do a bit of investigation of where the size comes
from, and maybe change your dependencies:

```sh
cargo install twiggy
Expand All @@ -118,14 +123,15 @@ wasm-nm -i contract.wasm

## Ultra-Compression

You can still get a bit smaller. Note the symbol names that were used in twiggy. Well,
those come from inside the wasm build. You can strip out these symbols and other debug
info, that won't be usable when running anyway. For this we use `wasm-opt` from the
[enscripten toolchain](). This is a bunch of C++ code that needs to be compiled, and to
simplify the whole process, as well as create reproduceable builds, we have created
[`cosmwasm-opt`](https://github.com/confio/cosmwasm-opt),
which contains a `Dockerfile` that you can use to run both `wasm-pack` and `wasm-opt`.
To make the build, just run the following in the project directory:
You can still get a bit smaller. Note the symbol names that were used in twiggy.
Well, those come from inside the wasm build. You can strip out these symbols and
other debug info, that won't be usable when running anyway. For this we use
`wasm-opt` from the [enscripten toolchain](). This is a bunch of C++ code that
needs to be compiled, and to simplify the whole process, as well as create
reproduceable builds, we have created
[`cosmwasm-opt`](https://github.com/confio/cosmwasm-opt), which contains a
`Dockerfile` that you can use to run both `wasm-pack` and `wasm-opt`. To make
the build, just run the following in the project directory:

```sh
docker run --rm -u $(id -u):$(id -g) -v $(pwd):/code confio/cosmwasm-opt:0.4.1
Expand All @@ -134,14 +140,15 @@ du -h contract.wasm
```

Note that this always outputs the file as `contract.wasm`, not with the name of
the project (yes, every tool chain has a different output location).
For the hackatom sample, I now get down to 52kB, an 18% improvement.
This is as far as you can minimize the input, without removing actual functionality.
the project (yes, every tool chain has a different output location). For the
hackatom sample, I now get down to 52kB, an 18% improvement. This is as far as
you can minimize the input, without removing actual functionality.

While we cannot trim down the wasm code anymore, we can still reduce the size a bit
for loading in blockchain transactions. We [soon plan](https://github.com/confio/go-cosmwasm/issues/20)
to allow gzip-ed wasm in the transactions posted to the chain, which will further
reduce gas cost of the code upload. Check out this final output:
While we cannot trim down the wasm code anymore, we can still reduce the size a
bit for loading in blockchain transactions. We
[soon plan](https://github.com/confio/go-cosmwasm/issues/20) to allow gzip-ed
wasm in the transactions posted to the chain, which will further reduce gas cost
of the code upload. Check out this final output:

```sh
$ gzip -k contract.wasm
Expand All @@ -152,7 +159,5 @@ $ du -h contract.wasm*

And there you have it. We have gone from 1.5MB for the naive build, to 72kB with
the standard minification tooling, all the way down to 20kB with very aggressive
trimming and compression. Less than 1.5% of the original size. This is indeed something
you can easily fit inside a transaction.


trimming and compression. Less than 1.5% of the original size. This is indeed
something you can easily fit inside a transaction.
49 changes: 26 additions & 23 deletions EntryPoints.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Defining Entry Points to Wasm

## Exports
## Exports

`exports` are the functions that we export to the outside world. After
compilation these will be the only entry points that the application can call
Expand All @@ -14,18 +14,19 @@ To make an export in rust code, you can add the following lines:
pub extern "C" fn double(n: i32) -> i32 {
n * 2
}
```
```

Note that you need the `#[no_mangle]` directive to keep the naming, and declare
it as `pub extern "C"` to create a proper C ABI, which is the standard interface
for Web Assembly, as well as FFI.

## Imports

If we want to interact with the outside world, the smart contract needs to define
a set of `imports`. These are function signatures we expect to be implemented by
the environment and provided when the VM is instantiated. If the proper imports
are not provided, you will receive an error upon instantiating the contract.
If we want to interact with the outside world, the smart contract needs to
define a set of `imports`. These are function signatures we expect to be
implemented by the environment and provided when the VM is instantiated. If the
proper imports are not provided, you will receive an error upon instantiating
the contract.

```rust
extern "C" {
Expand All @@ -34,20 +35,22 @@ extern "C" {
}
```

The above expects the runtime to provide read/write access to some
(persistent) singleton. Notably, the contract has no knowledge how it is stored,
and no way to "jailbreak" or access other element of the database.
The above expects the runtime to provide read/write access to some (persistent)
singleton. Notably, the contract has no knowledge how it is stored, and no way
to "jailbreak" or access other element of the database.

## Memory Management

If you look closely, you will see every function definition in `exports` accepts
a fixed number of arguments of type `i32` and returns one result of type `i32` (or `void`).
With such limitations, how can one pass in a `struct` to the contract, or even
a serialized byte array (eg. json blob). And how can we return a string back?
If you look closely, you will see every function definition in `exports` accepts
a fixed number of arguments of type `i32` and returns one result of type `i32`
(or `void`). With such limitations, how can one pass in a `struct` to the
contract, or even a serialized byte array (eg. json blob). And how can we return
a string back?

There is one more way in which the runtime can interact with a smart contract
instance. It can directly read and write to the linear memory of the smart
contract. In general, contracts are expected to export two well-defined functions:
contract. In general, contracts are expected to export two well-defined
functions:

```rust
#[no_mangle]
Expand All @@ -66,15 +69,15 @@ pub extern "C" fn deallocate(pointer: *mut c_void, capacity: usize) {
}
```

`allocate` heap allocates `size` bytes and tells the wasm alloc library not to clear it
(forget), after which it returns the pointer (integer offset) to the caller.
The caller can now safely write up to `size` bytes to the given offset, eg.
`copy(data, vm.Memory[offset:offset+size])`. Of course, this passes the responsibility
of freeing the memory from the wasm code to the caller, so make sure to
call `deallocate()` on the memory reference after the function call finishes.
`allocate` heap allocates `size` bytes and tells the wasm alloc library not to
clear it (forget), after which it returns the pointer (integer offset) to the
caller. The caller can now safely write up to `size` bytes to the given offset,
eg. `copy(data, vm.Memory[offset:offset+size])`. Of course, this passes the
responsibility of freeing the memory from the wasm code to the caller, so make
sure to call `deallocate()` on the memory reference after the function call
finishes.

We can explore more complex and idiomatic ways of passing data between the
environment and the wasm contract, but for now, just ensure you export these
two functions and the runtime will make use of them to get `string` and `[]byte`
environment and the wasm contract, but for now, just ensure you export these two
functions and the runtime will make use of them to get `string` and `[]byte`
into and out of the wasm contract.

Loading

0 comments on commit f2c4a29

Please sign in to comment.