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
to0.025uwasm
- set
- edit
~/.wasmd/config/client.toml
- set
chain-id
tolocalwasmd
- set
- edit
~/.wasmd/config/genesis.json
- change
stake
toustake
- change
- edit
~/.wasmd/config/config.toml
- under Consensus Configuration: change all the
timeout_
stuff to200ms
for faster local dev block time
- under Consensus Configuration: change all the
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
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)deps.api.debug()
contract messages will now appear in the wasmd console
Some tips for how it all fits together: CosmWasm/cosmwasm#746 (comment)
SETUP
- in wasmd go.mod
replace( github.com/CosmWasm/wasmvm => path/to/my/wasmvm)
- in local
wasmvm
edit libwasmvm's Cargo.toml to point at a localcosmwasm-vm
viapath = ...
- generally point at local crates as-needed (e.g. a local
cosmwasm-std
, localcw-storage-plus
, etc.)
ITERATIVE DEV
- Make changes in local cosmwasm-vm
- recompile the
libwasmvm
(make build-rust
inwasmvm
) - 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);
});
-
Cosm costs for storage read/write here: https://github.com/cosmos/cosmos-sdk/blob/0360c3d87f6fb19c6d0bb509fb9b340b48bbe670/store/types/gas.go#L232
-
Wasmd costs for contract loading, events, messages, execution etc. are here: https://github.com/CosmWasm/wasmd/blob/9050b5fa4e21f7abb03e5eb46130d735fb47f353/x/wasm/types/gas_register.go#L13
-
Cosm limits here: https://github.com/CosmWasm/cosmwasm/blob/5220ed7aee3245d624e1756e7acdb03dfc820f74/packages/vm/src/imports.rs#L30
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.
Generally speaking, costs from most expensive to most cheap:
- Contract Load (happens when you smart-query a contract that isn't pinned) - roughly 60,000 SDK units
- 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
- 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:
- More monolothic contracts to avoid cross-contract queries
- Raw queries instead of smart queries
- Think very carefully about storage access patterns
- Cache storage lookups that stay consistent through a call in memory
- 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)
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
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
Use base64 and protobuf. Example:
echo "CrgBCrABCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKFAQosdGVycmExcDAyNDJnZXdweDhwY3I4aG5wNTd5bG16ZWY2bWtwNjY0eXN4dzQSLHRlcnJhMWc3ampqa3Q1dXZramV5aHA4ZWNkejRlNGh2dG44M3N1ZDN0bWgyGid7ImV4ZWN1dGVfc3RyYXRlZ3kiOnsic3RyYXRlZ3lfaWQiOjI2fX0Ym6rpAhJqClEKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDAgrw7ozay5jSmwGydzr3hZ57XKLZhheZ+C2AIAIjBLoSBAoCCH8YuHUSFQoOCgR1dXNkEgY1NjM1NzMQ3KjlARpAqjHfsg1dT8Lg9SeqgOC50m55Hmu1dY4heKdbV09oGp0Jg1LLLcsNbhcgFjicZ23frMdLpysjIF4Xe65qiQZ35Q==" | base64 -D | protoc --decode_raw
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"
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?)
The following uses juno/osmosis testnets and the Go relayer as examples, but the concepts are tool-agnostic
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"
}
}
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
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
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
rly transact connection foo --debug --override
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!
rly start foo --debug
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.
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)),
});
- IBC explanation / spec: https://github.com/CosmWasm/cosmwasm/blob/main/IBC.md
- IBC standards: https://github.com/cosmos/ibc#interchain-standards
- Simple example w/ comments: https://github.com/0xekez/cw-ibc-example
- Another example: https://github.com/public-awesome/ics721