Skip to content

Instantly share code, notes, and snippets.

@dakom
Last active November 19, 2023 08:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dakom/d75b3d94f17166a3be4d1ff2c734141c to your computer and use it in GitHub Desktop.
Save dakom/d75b3d94f17166a3be4d1ff2c734141c to your computer and use it in GitHub Desktop.
Cosmwasm notes

Wasmd setup

Basic WASMD Setup

after make install (or on apple silicon: LEDGER_ENABLED=false make install)

our chain-id is going to be localwasmd our gas denomination is going to be configured to uwasm staking denomination is the default ustake

the following sortof mimics the explicit steps in https://github.com/CosmWasm/wasmd/blob/main/contrib/local/setup_wasmd.sh

adding two users: tester1 and validator1

  • wipe ~/.wasmd if it exists
  • wasmd init localwasmd --chain-id localwasmd --overwrite
  • edit ~/.wasmd/config/app.toml
    • set minimum-gas-prices to 0.025uwasm
  • edit ~/.wasmd/config/client.toml
    • set chain-id to localwasmd
  • edit ~/.wasmd/config/genesis.json
    • change stake to ustake
  • edit ~/.wasmd/config/config.toml
    • under Consensus Configuration: change all the timeout_ stuff to 200ms for faster local dev block time
  • wasmd keys add tester1
  • wasmd add-genesis-account $(wasmd keys show -a tester1) 10000000000uwasm,10000000000ustake
  • wasmd keys add validator1
  • wasmd add-genesis-account $(wasmd keys show -a validator1) 10000000000uwasm,10000000000ustake
  • wasmd gentx validator1 "250000000ustake" --chain-id="localwasmd" --amount="250000000ustake"
  • wasmd collect-gentxs

now wasmd start should just work

Running WASMD to see logs

  1. wasmd start --log_level panic --trace (panic may be a bit extreme - point is some level lower than info, which is just way too noisy to see anything)
  2. deps.api.debug() contract messages will now appear in the wasmd console

Hacking WASMD

Some tips for how it all fits together: CosmWasm/cosmwasm#746 (comment)

SETUP

  1. in wasmd go.mod replace( github.com/CosmWasm/wasmvm => path/to/my/wasmvm)
  2. in local wasmvm edit libwasmvm's Cargo.toml to point at a local cosmwasm-vm via path = ...
  3. generally point at local crates as-needed (e.g. a local cosmwasm-std, local cw-storage-plus, etc.)

ITERATIVE DEV

  1. Make changes in local cosmwasm-vm
  2. recompile the libwasmvm (make build-rust in wasmvm)
  3. rebuild wasmd so it will use this new wasmvm build (LEDGER_ENABLED=false make install on osx)

e.g. editing do_debug() like this will show some interesting info:

println!("GAS LEFT: {}", env.get_gas_left());
env.with_gas_state(|gas_state| {
    println!("GAS STATE: {:?}", gas_state);
});

Gas and storage

Keys can be "complex" and are concatenated with an internal namespacing scheme, which both contribute to their hard limits.

Max gas for executions is per-block and configured in the genesis file, e.g.

  "consensus_params": {
    "block": {
      "max_gas": "SET_YOUR_MAX_VALUE",  

Max gas for queries is configured per-node the app.toml

There are also size limits, such as the maximum transaction size, defined in the config.toml max_tx_bytes var. This applies to all transactions, including code upload.

VM computational costs are tuned to be very cheap. Not free, but, for typical financial products, it's unlikely to be the culprit for runaway gas costs

Smart queries are generally cheap themselves, but incur the same contract loading cost as the initial query (60k or so). Therefore they are, in practice, quite expensive. This can be avoided by voting to "pin" hot contracts, but generally speaking, use raw queries where possible.

Optimization

Generally speaking, costs from most expensive to most cheap:

  1. Contract Load (happens when you smart-query a contract that isn't pinned) - roughly 60,000 SDK units
  2. Storage (see above - both the frequency of read/write actions and the size of data matter). Can easily add up to thousands of SDK units for simple usage
  3. Computation - relatively cheap, tuned to be about 1000 VM instructions per 1 sdk unit.

In practical terms, if you're optimizing for gas, consider the following:

  1. More monolothic contracts to avoid cross-contract queries
  2. Raw queries instead of smart queries
  3. Think very carefully about storage access patterns
  4. Cache storage lookups that stay consistent through a call in memory
  5. Don't worry about simple typical code like iterating over a Vec or cloning a String. It' relatively negligible. Heavy computation like calculating a hash can take around 100 gas units (still not worrisome, in most cases)

RPC / GRPC

See the registry at https://github.com/cosmos/chain-registry/tree/master (testnets are a subdir: https://github.com/cosmos/chain-registry/tree/master/testnets)

Each chain has a JSON config that specifies some RPC/GRPC endpoints.

Websockets might exist on the rpc node with wss:// and /websocket at the end, but YMMV

Data

For native tokens you can get the oracle prices from the LCD, you can select a specific block height adding ?height=BLOCKNUMBER, for CW20 tokens it is a bit more complex, you have to query the contract

Decode data

Use base64 and protobuf. Example:

echo "CrgBCrABCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKFAQosdGVycmExcDAyNDJnZXdweDhwY3I4aG5wNTd5bG16ZWY2bWtwNjY0eXN4dzQSLHRlcnJhMWc3ampqa3Q1dXZramV5aHA4ZWNkejRlNGh2dG44M3N1ZDN0bWgyGid7ImV4ZWN1dGVfc3RyYXRlZ3kiOnsic3RyYXRlZ3lfaWQiOjI2fX0Ym6rpAhJqClEKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDAgrw7ozay5jSmwGydzr3hZ57XKLZhheZ+C2AIAIjBLoSBAoCCH8YuHUSFQoOCgR1dXNkEgY1NjM1NzMQ3KjlARpAqjHfsg1dT8Lg9SeqgOC50m55Hmu1dY4heKdbV09oGp0Jg1LLLcsNbhcgFjicZ23frMdLpysjIF4Xe65qiQZ35Q==" | base64 -D | protoc --decode_raw

Database dumps

Available via some third-party providers, e.g. https://quicksync.io/networks/osmosis.html

From app.toml:

# default: the last 100 states are kept in addition to every 500th state; pruning at 10 block intervals
# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
# everything: all saved states will be deleted, storing only the current state; pruning at 10 block intervals
# custom: allow pruning options to be manually specified through 'pruning-keep-recent', 'pruning-keep-every', and 'pruning-interval'
pruning = "default"

Supported types

For storage and messaging, these types cannot be used:

  • HashMap
  • HashSet
  • rand
  • usize
  • floats

In-memory, some of these types are fine (todo - check if they're all okay for purely in-memory?)

IBC

The following uses juno/osmosis testnets and the Go relayer as examples, but the concepts are tool-agnostic

One-time setup

1. Add the chain config for each chain

rly chains add --file ./juno-testnet.json juno-testnet
rly chains add --file ./osmosis-testnet.json osmosis-testnet

Here's an example config file for Juno testnet:

{
    "type": "cosmos",
    "value": {
      "key": "default",
      "chain-id": "uni-6",
      "rpc-addr": "https://rpc.uni.junonetwork.io:443",
      "account-prefix": "juno",
      "keyring-backend": "test",
      "gas-adjustment": 1.5,
      "gas-prices": "0.0026ujunox",
      "debug": true,
      "timeout": "20s",
      "output-format": "json",
      "sign-mode": "direct"
    }
}

2. Add a wallet for each chain

rly keys restore juno-testnet default "[MNEMONIC]"
rly keys restore osmosis-testnet default "[MNEMONIC]"

Sanity check that network config is right and wallets have gas tokens

rly q balance juno-testnet
rly q balance osmosis-testnet

3. Create a "path"

This is a go-relayer config thing, not general IBC. We decide the path name, which is “foo” here. Note that here it uses the ChainId, not the network names from above. foo (or whatever path name you choose) will be used in more commands later, instead of referencing each chain every time.

rly paths new uni-6 osmo-test-5 foo

4. Create "clients"

I believe it needs to be made from each side (using the path name from above)

rly transact client juno-testnet osmosis-testnet foo --debug --override
rly transact client osmosis-testnet juno-testnet foo --debug --override

5. Create a "connection"

rly transact connection foo --debug --override

Whenever your port changes

1. Create a "channel"

Decide the channel "version" (just some string we make up that the protocol should be aware of for sanity checking and compatibility). Here it will be "bar-001". This version can stay consistent as you create channels, it's just a way to make sure both sides of the channel understand eachother.

rly transact channel foo --src-port [wasm.somejunoaddress] --dst-port [wasm.someosmosisaddress] --order unordered --version bar-001 --debug --override

That's it!

Start relaying

rly start foo --debug

How to get a port?

This is assigned automatically when you upload a contract with the required IBC handlers, and the port can be queried by looking up the contract info, e.g. with a cosmjs client.getContract() call.

Packet lifecycle for contracts

There are more steps involved here, but TL;DR:

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_channel_connect(deps: DepsMut, env: Env, msg: IbcChannelConnectMsg) -> Result<IbcBasicResponse> {
  // store msg.channel() in state
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_packet_receive(deps: DepsMut, env: Env, msg: IbcPacketReceiveMsg) -> Result<IbcReceiveResponse> {
  // handle incoming packets
}

// write an IBC packet to my channel
let channel_id = self
    .get_ibc_channel(storage)?
    .endpoint
    .channel_id;

response.add_message(IbcMsg::SendPacket {
    channel_id,
    data: to_binary(&msg)?,
    // default timeout of two minutes.
    timeout: IbcTimeout::with_timestamp(self.env.block.time.plus_seconds(TIMEOUT_SECONDS)),
});

Some references

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment