Skip to content

Add an Advanced Terra example. #52

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ members = [
"pyth-sdk-solana/test-contract",
"pyth-sdk-terra",
"examples/terra-contract",
"examples/terra-contract-advanced",
]
43 changes: 43 additions & 0 deletions examples/terra-contract-advanced/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "example-terra-contract-advanced"
version = "0.1.0"
authors = ["Pyth Data Association"]
edition = "2018"

exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
"contract.wasm",
"hash.txt",
]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib", "rlib"]

[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []

[package.metadata.scripts]
optimize = """docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.12.5
"""

[dependencies]
cosmwasm-std = { version = "0.16.2" }
cosmwasm-storage = { version = "0.16.0" }
cw-storage-plus = "0.8.0"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
pyth-sdk-terra = { version = "0.4.0", path = "../../pyth-sdk-terra" } # Remove path and use version only when you use this example on your own.

[dev-dependencies]
cosmwasm-schema = { version = "0.16.0" }
stochastic = { version = "0.5.2" }
probability = { version = "0.15.12" }
fraction = { version = "0.10.0" }
94 changes: 94 additions & 0 deletions examples/terra-contract-advanced/Developing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Developing

This document contains guidance regarding building, testing and preparing your contracts for production.

## Prerequisites

Before starting, make sure you have [rustup](https://rustup.rs/) along with a
recent `rustc` and `cargo` version installed. Rust version 1.58.1 or above is required.

And you need to have the `wasm32-unknown-unknown` target installed as well.

You can check that via:

```sh
rustc --version
cargo --version
rustup target list --installed
# if wasm32 is not listed above, run this
rustup target add wasm32-unknown-unknown
```

This example uses relative paths in `Cargo.toml`; you must remove any `path` components within `Cargo.toml` dependencies if you intend to compile this code outside of the `pyth-sdk-terra` repository, otherwise this will fail to compile. For example:

```diff
- pyth-sdk-terra = { version = "0.3.0", path = "../../pyth-sdk-terra" }
+ pyth-sdk-terra = { version = "0.3.0" }
```

## Compiling

After changing the contract, make sure you can compile and run it before
making any changes. Go into the repository and do:

```sh
# this will produce a wasm build in ./target/wasm32-unknown-unknown/release/exxamle_terra_contract.wasm
cargo build --release --target wasm32-unknown-unknown
```
## Generating JSON Schema

While the Wasm calls (`instantiate`, `execute`, `query`) accept JSON, this is not enough
information to use it. You need to expose the schema for the expected messages to the
clients. You can generate this schema by calling `cargo run --example schema`, which will output
4 files in `./schema`, corresponding to the 3 message types the contract accepts,
as well as the internal `State`.

These files are in standard json-schema format, which should be usable by various
client side tools, either to auto-generate codecs, or just to validate incoming
json wrt. the defined schema.

## Preparing the Wasm bytecode for production

Before you upload it to a chain, you need to ensure the smallest output size possible,
as this will be included in the body of a transaction. You also want to have a
reproducible build process, so third parties can verify that the uploaded Wasm
code did indeed come from the claimed rust code.

To solve both these issues, CosmWasm have produced `rust-optimizer`, a docker image to
produce an extremely small build output in a consistent manner. The suggested way
to run it is this:

```sh
cd path/to/cargo/root
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.12.6
```

Or, If you're on an arm64 machine, you should use a docker image built with arm64.
```sh
cd path/to/cargo/root
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer-arm64:0.12.6
```

You must mount the contract code to `/code`. You can use a absolute path instead
of `$(pwd)` if you don't want to `cd` to the directory first. The other two
volumes are nice for speedup. Mounting `/code/target` in particular is useful
to avoid docker overwriting your local dev files with root permissions.
Note the `/code/target` cache is unique for each contract being compiled to limit
interference, while the registry cache is global.

This is rather slow compared to local compilations, especially the first compile
of a given contract. The use of the two volume caches is very useful to speed up
following compiles of the same contract.

This produces an `artifacts` directory with a `PROJECT_NAME.wasm`, as well as
`checksums.txt`, containing the Sha256 hash of the wasm file.
The wasm file is compiled deterministically (anyone else running the same
docker on the same git commit should get the identical file with the same Sha256 hash).
It is also stripped and minimized for upload to a blockchain (it is also compressed using
gzip in the uploading process to make it even smaller).
13 changes: 13 additions & 0 deletions examples/terra-contract-advanced/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Pyth SDK Example Contract with Example Mocking

This repository contains an example contract that demonstrates how to use and
mock the Pyth oracle. This also includes a test showing an example of how to
generate mocked Pyth Price data to test against.

The test itself can be found in `contract.rs`, which feeds the contract with
the following price action:

![](./prices.png)

For information on how to build and deploy this contract, refer to the README
of the simple example contract [here](../terra-contract/README.md).
29 changes: 29 additions & 0 deletions examples/terra-contract-advanced/examples/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::env::current_dir;
use std::fs::create_dir_all;

use cosmwasm_schema::{
export_schema,
remove_schemas,
schema_for,
};

use example_terra_contract_advanced::msg::{
ExecuteMsg,
FetchPriceResponse,
InstantiateMsg,
QueryMsg,
};
use example_terra_contract_advanced::state::State;

fn main() {
let mut out_dir = current_dir().unwrap();
out_dir.push("schema");
create_dir_all(&out_dir).unwrap();
remove_schemas(&out_dir).unwrap();

export_schema(&schema_for!(InstantiateMsg), &out_dir);
export_schema(&schema_for!(ExecuteMsg), &out_dir);
export_schema(&schema_for!(QueryMsg), &out_dir);
export_schema(&schema_for!(State), &out_dir);
export_schema(&schema_for!(FetchPriceResponse), &out_dir);
}
Binary file added examples/terra-contract-advanced/prices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions examples/terra-contract-advanced/schema/execute_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"type": "string",
"enum": []
}
43 changes: 43 additions & 0 deletions examples/terra-contract-advanced/schema/fetch_price_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "FetchPriceResponse",
"type": "object",
"required": [
"current_price",
"ema_price"
],
"properties": {
"current_price": {
"$ref": "#/definitions/Price"
},
"ema_price": {
"$ref": "#/definitions/Price"
}
},
"definitions": {
"Price": {
"description": "A price with a degree of uncertainty, represented as a price +- a confidence interval.\n\nThe confidence interval roughly corresponds to the standard error of a normal distribution. Both the price and confidence are stored in a fixed-point numeric representation, `x * 10^expo`, where `expo` is the exponent. For example:\n\n``` use pyth_sdk::Price; Price { price: 12345, conf: 267, expo: -2 }; // represents 123.45 +- 2.67 Price { price: 123, conf: 1, expo: 2 }; // represents 12300 +- 100 ```\n\n`Price` supports a limited set of mathematical operations. All of these operations will propagate any uncertainty in the arguments into the result. However, the uncertainty in the result may overestimate the true uncertainty (by at most a factor of `sqrt(2)`) due to computational limitations. Furthermore, all of these operations may return `None` if their result cannot be represented within the numeric representation (e.g., the exponent is so small that the price does not fit into an i64). Users of these methods should (1) select their exponents to avoid this problem, and (2) handle the `None` case gracefully.",
"type": "object",
"required": [
"conf",
"expo",
"price"
],
"properties": {
"conf": {
"description": "Confidence Interval.",
"type": "string"
},
"expo": {
"description": "Exponent.",
"type": "integer",
"format": "int32"
},
"price": {
"description": "Price.",
"type": "string"
}
}
}
}
}
22 changes: 22 additions & 0 deletions examples/terra-contract-advanced/schema/instantiate_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InstantiateMsg",
"type": "object",
"required": [
"price_feed_id",
"pyth_contract_addr"
],
"properties": {
"price_feed_id": {
"$ref": "#/definitions/Identifier"
},
"pyth_contract_addr": {
"type": "string"
}
},
"definitions": {
"Identifier": {
"type": "string"
}
}
}
18 changes: 18 additions & 0 deletions examples/terra-contract-advanced/schema/query_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"fetch_price"
],
"properties": {
"fetch_price": {
"type": "object"
}
},
"additionalProperties": false
}
]
}
26 changes: 26 additions & 0 deletions examples/terra-contract-advanced/schema/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "State",
"type": "object",
"required": [
"price_feed_id",
"pyth_contract_addr"
],
"properties": {
"price_feed_id": {
"$ref": "#/definitions/Identifier"
},
"pyth_contract_addr": {
"$ref": "#/definitions/Addr"
}
},
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Identifier": {
"type": "string"
}
}
}
Loading