- Notifications
You must be signed in to change notification settings - Fork31
Beaker helps streamlining CosmWasm development workflow.
License
Apache-2.0, MIT licenses found
Licenses found
osmosis-labs/beaker
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Beaker is a versatile toolkit that simplifies interactions with CosmWasm smart contracts. It offers project scaffolding, seamless deployment, upgrades, execution, querying, an interactive console, task scripting capabilities and more for a streamlined development experience.
- Installation
- Prerequisites
- Scaffolding your new dapp project
- Your first CosmWasm contract with Beaker
- Deploy contract on LocalOsmosis
- Contract Upgrade
- Signers
- Tasks
- Console
- Typescript SDK Generation
- Frontend
This section is intended to give you an introduction toBeaker
, for more detailed reference, you can find themhere.
- Rust for building cosmwasm contract
- Rustup for dealing with wasm target
- Docker for running wasm
rust-optimizer
and spinning upLocalOsmosis - Node for frontend related stuffs and
beaker-console
- Yarn over NPM, since it will not have package resolving issue and causes weird errors down the road
Beaker is available viacargo which is a rust toolchain. Once cargo is ready on your machine, run:
cargo install -f beaker# `-f` flag for up-to-date version
Nowbeaker
is ready to use!
In the directory you want your project to reside, run:
beaker new counter-dapp
This gives you 2 template options to choose from.For the sake of this tutorial, let's usecounter-example
but you might want to useminimal
option in your real work since it has zero assumption about your dapp logic or frontend.
? 🤷 Which starting template would you like to use? › minimal❯ counter-example
This will generate new directory calledcounter-dapp
which, by default, come fromthis template.
So what's in the template? Let's have a look...
.├── frontend├── contracts├── Cargo.toml├── Beaker.toml├── .gitignore└── .beaker
This should be self explanatory, it's where frontend and contracts are stored.
Withcounter-example
template, it demonstrate how frontend app can access deployed code/contract's info through.beaker
. It does so by symlinking.beaker
into frontend directory, and since states in.beaker
are in json format, javascript code can just import them.
Withminimal
template, this directory does not exist, which means it does not assume your frontend choice. In that case, you might want to checkoutcreate-cosmos-app for scaffolding your frontend or just create one from scratch.
This is where smart contracts are stored. Single workspace can contain multiple contracts.
Withcounter-example
template, this should havecounter
contract pregenerated.
There is aCargo.toml
here which specifiescargo workspace.
[workspace]members = [ 'contracts/*',][profile.release]...
All the crates (rust packages) in contracts directory are included, with unified release profile. With this, when we have to optimize multiple contracts deterministically, we can do that with ease (seeContracts as Workspace Members section in rust-optimizer).
This is our configuration file, you can find more information about ithere.
Last but not least,.beaker
which is the most unusal part. It contains 2 files:
├── state.json└── state.local.json
These 2 files has similar functionality, which are containing beaker related state such asaddress
,code-id
,label
for each contract on each network for later use.
Whilestate.json
is there for mainnet and testnet state.state.local.json
is intended to use locally andbeing gitignored since its state will not make any sense on other's machine.
And I don't think we have to explain about.gitignore
don't we?
We can scaffold new contract using the following command:
cd counter-dappbeaker wasm new<contract_name>
Default template is fromosmosis-labs/cw-minimal-template
Thecw-minimal-template
has no logic in it unlikecw-template
, only the skeleton is provided, which makes it ideal to start writing new contract.
Now your new contract will be avaiable oncontracts/multiplier
.
If you want to use other contract template, you can change the configuration, for example:
# Beaker.toml[wasm]template_repo = "https://github.com/CosmWasm/cw-template"
This step is not required for the rest of the guide sincecounter
contract is already in place, but you can just try it out.
LocalOsmosis, as it's name suggest, is Osmosis for local development. In the upcoming release, Beaker will have more complete integration with LocalOsmosis, it has to be installed and run separately.
You can use the osmosis installer and select option 3:
curl -sL https://get.osmosis.zone/install> i.py&& python3 i.py
Or if you want to use a specific / modified version of LocalOsmosis, you can build from source by
git clone https://github.com/osmosis-labs/osmosis.gitmake localnet-build# build docker imagemake localnet-start# docker-compose up
Now, with LocalOsmosis up and running,counter
contract can be deployed (build + store-code + instantiate) using the following command:
beaker wasm deploy counter --signer-account test1 --no-wasm-opt --raw'{ "count": 0 }'
What's happending here equivalent to the following command sequence:
# build .wasm file# stored in `target/wasm32-unknown-unknown/release/<CONTRACT_NAME>.wasm`# `--no-wasm-opt` is suitable for development, explained belowbeaker wasm build --no-wasm-opt# read .wasm in `target/wasm32-unknown-unknown/release/<CONTRACT_NAME>.wasm` due to `--no-wasm-opt` flag# use `--signer-account test1` which is predefined.# The list of all predefined accounts are here: https://github.com/osmosis-labs/LocalOsmosis#accounts# `code-id` is stored in the beaker state, local by defaultbeaker wasm store-code counter --signer-account test1 --no-wasm-opt# instantiate counter contract# with instantiate msg: '{ "count": 0 }'beaker wasm instanitate counter --signer-account test1 --raw'{ "count": 0 }'
The flag--no-wasm-opt
is skippingrust-optimizer for faster development iteration.
For testnet/mainnet deployment, use:
beaker wasm deploy counter --signer-account<ACCOUNT> --raw'{ "count": 0 }' --network testnetbeaker wasm deploy counter --signer-account<ACCOUNT> --raw'{ "count": 0 }' --network mainnet
Instantiate message can be stored for later use:
mkdir contracts/counter/instantiate-msgsecho'{ "count": 0 }'> contracts/counter/instantiate-msgs/default.jsonbeaker wasm deploy counter --signer-account test1 --no-wasm-opt
You can find references forbeaker wasm
subcommand here.
Contract upgrade in CosmWasm goes through the following steps:
- store new code on to the chain
- broadcast migrate msg, targeting the contract address that wanted to be upgraded with the newly stored code
To make a contract migratable, the contract needs to have proper entrypoint and admin designated.
To create the contract entrypoint for migration, first, defineMigrateMsg
inmsg.rs
, this could have any information you want to pass for migration.
#[derive(Serialize,Deserialize,Clone,Debug,PartialEq,JsonSchema)]pubstructMigrateMsg{}
With MigrateMsg defined we need to updatecontract.rs
. First update the import fromcrate::msg
to includeMigrateMsg
:
usecrate::msg::{CountResponse,ExecuteMsg,InstantiateMsg,QueryMsg,MigrateMsg};
#[cfg_attr(not(feature ="library"), entry_point)]pubfnmigrate(_deps:DepsMut,_env:Env,_msg:MigrateMsg) ->StdResult<Response>{// perform state update or anything neccessary for the migrationOk(Response::default())}
Now deploy the contract with admin assigned
# `--admin signer` use signer address (test1's address in this case) as designated admin# raw address could be passed in as wellbeaker wasm deploy counter --signer-account test1 --no-wasm-opt --raw'{ "count": 0 }' --admin signer
Now try to change the execute logic a bit to see if the upgrade works:
pubfntry_increment(deps:DepsMut) ->Result<Response,ContractError>{STATE.update(deps.storage, |mut state| ->Result<_,ContractError>{ state.count +=1000000000;// 1 -> 1000000000Ok(state)})?;Ok(Response::new().add_attribute("method","try_increment"))}
With admin astest1
, onlytest1
can upgrade the contract
beaker wasm upgrade counter --signer-account test1 --raw'{}' --no-wasm-opt
Similar todeploy
,upgrade
is basiaclly running sequences of commands behind the scene:
beaker wasm build --no-wasm-optbeaker wasm store-code counter --signer-account test1 --no-wasm-optbeaker wasm migrate counter --signer-account test1 --raw'{}'
And, like before,--no-wasm-opt
only means for developement. For mainnet, use:
beaker wasm upgrade counter --signer-account test1 --raw'{}' --network mainnet
Migrate message can be stored for later use:
mkdir contracts/counter/migrate-msgsecho'{}'> contracts/counter/migrate-msgs/default.jsonbeaker wasm upgrade counter --signer-account test1 --no-wasm-opt
You can find more information about their optionshere.
Contract messages can be executed using thebeaker wasm execute
subcommand. For example:
beaker wasm execute counter --raw'{ "increment": {} }' --signer-account test1
You can query contract state by submitting query messages with thebeaker wasm query
command. For example:
beaker wasm query counter --raw'{"get_count": {}}'
Whenever you run command that requires signing transactions, there are 3 options you can reference your private keys:
--signer-account
input of this option refer to the accounts defined in theconfig file, which is not encrypted, so it should be used only for testing--signer-mnemonic
input of this option is the raw mnemonic string to construct a signer--signer-private-key
input of this option is the same as--signer-mnemonic
except it expects base64 encoded private key--signer-keyring
use the OS secure store as backend to securely store your key. To manage them, you can find more informationhere.
Sometimes you want to run a series of commands in a single command. For example, you want to deploy a set of contracts that one contract instantiation depends on another contract. You can do this by defining a task in thetasks
directory.
beaker task new deploy
This will create a new task file intasks/deploy.rhai
.
Task is written inRhai
which is an embedded scripting language in Rust. You can find example of how to write Rhaihere.
Using Rhai as a scripting language makes exposing all existing functionality of Beaker to the task script relatively simple. Currently, all the subcommands ofbeaker wasm
are exposed to the task script. So you can do things like:
let counter_contract = wasm::deploy(merge( #{ signer_account: "test1" contract_name: "counter", msg: #{} }));let counter_proxy_contract = wasm::deploy(merge( #{ signer_account: "test1" contract_name: "counter", msg: #{ counter_contract_address: counter_contract.address } }));
The interface ofwasm::deploy
the same as thebeaker wasm deploy
command. Other functions inwasm
module are also similar to their corresponding subcommands so you can refer to thedocumentation for more information about what is avialable in the script.
Note that additional feature here is thatmsg
can also be passed as an object rather than passing JSON string toraw
.
There are also some additional helper function and macros that are exposed to the task script.
This module provides access to the file system. It is similar to thestd::fs
module in Rust. Morre information about the module can be foundhere. This is how it can be used in the task script:
file = fs::open_file("params.json");
Matching command line arguments passed to the script and returns a map of the arguments
// beaker task run deploy -- --signer test1 --build-flags no_wasm_optlet cli_args = match_args(["signer", "build_flags"]);print(cli_args) // => #{ signer: "test1", "build_flags": "no_wasm_opt" }
Merges 2 objects together. If there are duplicate keys, the value from the second object will be used.
let a = #{ a: 1, b: 2 };let b = #{ b: 3, c: 4 };let merged = merge(a, b);print(merged) // => #{ a: 1, b: 3, c: 4 }
Perform assertion on the given condition. If the condition is false, the script will exit with an error.This is useful for ensuring that the script is running as expected.
@assert(1 == 1); // pass@assert(1 == 2); // fail@assert(1 != 2); // pass@assert(1 != 1); // fail@assert(1 < 2); // pass@assert(1 > 2); // fail@assert(1 <= 2); // pass@assert(1 >= 2); // fail
For more example on how to use task, you can refer to theexample tasks.
After deployed, you can play with the deployed contract using:
beaker console
It might prompt you like the following:
? Project's Typescript SDK seems to be missing, would you like to generate?
Pressenter
to proceed for now, and we will discuss about it in detail in theTypescript SDK Generation section.
This will launch custom node repl, wherecontract
,account
are available.contract
contains deployed contract.account
containspre-defined accounts in localosmosis.
So you can interact with the recently deployed contract like this:
awaitcontract.counter.signer(account.test1).execute({increment:{}});awaitcontract.counter.query({get_count:{}});
You can find avaialable methods for the aforementioned instances here:
You can removecontract
and/oraccount
namespace by changing config.
# Beaker.toml[console]account_namespace = falsecontract_namespace = false
awaitcounter.signer(test1).execute({increment:{}});awaitcounter.query({get_count:{}});
With the Typescript SDK which was previously mentioned, it is used to extend theContract
instance with method generated ftom execute and query messages. For example:
awaitcounter.getCount();sc=counter.signer(test1);// create signing client for `counter` with `test1`awaitsc.increment();awaitsc.getCount();
With this, it's more convenient than the previous interaction method since you can use tab completion for the methods as well.
Beaker console is also allowed to deploy contract, so that you don't another terminal tab to do so.
.deploycounter----signer-accounttest1--raw'{ "count": 999 }'
.build
,.storeCode
,.instantiate
commands are also available and has the same options as Beaker cli command, except that--no-wasm-opt
are in by default since it is being intended to use in the development phase.
.help
to see all avaiable commands.
Apart from that, in the console, you can access Beaker's state, configuration and sdk fromstate
,conf
andsdk
variables accordingly.
Beaker leveragets-codegen to generate typescript client for cosmwasm contract. By default, Beaker's template preparets/sdk
directory where typescript compiler and bundler are setup, so the generated client definition could be used bybeaker-console
, frontend or published as library for others to use.
To generate sdk for contract, run
beaker wasm ts-gen counter# replace `counter` with any of contract name
With this a package is avaiable ints/sdk
with name<project-name>-sdk
which can be used by any node / js / ts project.
The underlying code that actually callsts-codegen
with configuration is located ints/sdk/scripts/codegen.js
.
Let's try addingmultiply
method to our contract and see how this works.
// msg.rs#[derive(Serialize,Deserialize,Clone,Debug,PartialEq,JsonSchema)]#[serde(rename_all ="snake_case")]pubenumExecuteMsg{Increment{},Multiply{times:i32},// [1] add this enum variantReset{count:i32},}
// contract.rs#[cfg_attr(not(feature ="library"), entry_point)]pubfnexecute(deps:DepsMut,_env:Env,info:MessageInfo,msg:ExecuteMsg,) ->Result<Response,ContractError>{match msg{ExecuteMsg::Increment{} =>try_increment(deps),ExecuteMsg::Multiply{ times} =>try_multiply(deps, times),// [2] add this match armExecuteMsg::Reset{ count} =>try_reset(deps, info, count),}}// [3] add this functionfntry_multiply(deps:DepsMut,times:i32) ->Result<Response,ContractError>{STATE.update(deps.storage, |mut state| ->Result<_,ContractError>{ state.count *= times;Ok(state)})?;Ok(Response::new().add_attribute("method","try_multiply"))}
Then redeploy the contract:
beaker wasm deploy counter --signer-account test1 --no-wasm-opt --raw'{ "count": 0 }'
Then regeneratecounter
's client
beaker wasm ts-gen counter
Now we can test it out in thebeaker console
sc=counter.signer(test1);awaitsc.increment();awaitsc.getCount();// => { count: 1 }awaitsc.multiply({times:2});awaitsc.getCount();// => { count: 2 }awaitsc.multiply({times:10});awaitsc.getCount();// => { count: 20 }
sc
is an instance ofCounterContract
which you can find it ints/sdk/src/contracts/CounterContract.ts
.
Beaker project template also come with frontend template. But in order to interact with it you need:
- Keplr installed
- Keplr chain setup for LocalOsmosis
- Add test account to Keplr
- Add account via mnemonic in Keplr.The account
test1
can be added by copy-pastingnotice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius
to the Import account screen on Keplr. It contains 100,000 test OSMOs. - List of test accounts and its mnemonics in LocalOsmosis
- Add account via mnemonic in Keplr.The account
Now we are good to go! Let's dive in
cd frontendyarn&& yarn dev
Then openhttp://localhost:3000/
in the browser.
In frontend directory, you will see that.beaker
is in here. It is actually symlinked to the one in the root so that frontend code can access beaker state.
The crates in this repository are licensed under either of the following licenses, at your discretion.
Apache License Version 2.0 (LICENSE-APACHE or apache.org license link)MIT license (LICENSE-MIT or opensource.org license link)
Unless you explicitly state otherwise, any contribution submitted for inclusion in this library by you shall be dual licensed as above (as defined in the Apache v2 License), without any additional terms or conditions.
About
Beaker helps streamlining CosmWasm development workflow.