HackWasm Workshop Starter, Checkpoints and Instructions

Overview

Osmosis Swaprouter Worskhop

This is a workshop for building an Osmosis Swap Router CosmWasm contract.

The original contract repository is located here: https://github.com/osmosis-labs/swaprouter

Original authors:

What Is This

A contract that allows to swap an exact amount of tokens for a minimum of another token, similar to swapping a token on the trade screen GUI. While anyone can trade, only the contract owner can pre-define a swap route. Most importantly, traders are able to specify the maximum slippage percentage to avoid having large trades resulting in significant price fluctuations.

Why Do We Need This

This contract can be used by other client contracts such as:

  • Dollar-cost-average (DCA)
  • Portfolio balancing
  • Simulating limit orders
  • Trading strategies
  • Lending protocols

and many many others.

Workshop Goals

  • Understanding CosmWasm Fundamentals.
  • Getting familiar with reply message.
  • Utilizing osmosis-rust
  • Interacting with the Osmosis chain in CosmWasm.
  • Learning more about swaps and TWAP.

Prerequisites: Rust, Contract Environment, Beaker, and Osmosis

Before begining, you must set up Rust, your contract environment, Beaker, and Osmosis with one of the two following options:

Option 1: Automatic Setup

Start the installer with the following command, choose LocalOsmosis (option 3), and follow the prompts:

bash <(curl -sL https://get.osmosis.zone/run)

Option 2: Manual Setup

Rust

Install Rust using rustup with the following command and follow the prompts:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Contact Environment

Set 'stable' as the default release channel:

rustup default stable

Add WASM as the compilation target:

rustup target add wasm32-unknown-unknown

Install the following packages to generate the contract:

cargo install cargo-generate --features vendored-openssl
cargo install cargo-run-scrip

Beaker

Install Beaker with the following command:

cargo install -f beaker

Osmosis

Setup v12.x Osmosis

cd $HOME
git clone https://github.com/osmosis-labs/osmosis.git
cd $HOME/osmosis
git checkout v12.x
make install
source ~/.profile

CosmWasm Fundamentals

Checkpoints

We will over the development of the contract from scratch. The following checkpoints (git branches) are available to jump to any specific point in the workshop.

FAQ

  • How do I add a dependency to my contract?

    • Update contract's Cargo.toml
    • Example
  • What versions of osmosis_std and osmosis_rust should I use?

    • osmosis-std = {git = "https://github.com/osmosis-labs/osmosis-rust", branch = "osmosis-v12-rc2"}
    • osmosis-testing = {git = "https://github.com/osmosis-labs/osmosis-rust", branch = "main"}
  • What version of Beaker should I use?

  • What is the version of Osmosis/LocalOsmosis?

  • How to improve debugging experience with osmosis-testing?

    • To print additional debug messages to the console when executing osmosis-testing tests, you can use deps.api.debug(< msg >) in your contract code. For example:
twap_price = twap_price - twap_price.mul(percentage);
deps.api.debug(&format!(
    "twap_price minus {percentage_impact}%: {twap_price}"
));
  • What extension do I need for the seamless development experience?
    • Only rust-analyzer is required. Please make sure that the deprecated Rust extension is not installed

0. Setup and Contract Boilerplate

Goals:

  • Have a foundational structure of the CosmWasm contract generated with Beaker.
  • Understand the anatomy of a smart contract.
  • Learn about the core architecture and security benefits.

Generate a new CosmWasm Project with Beaker

First, generate the swaprouter workspace with the following command:

beaker new swaprouter-workshop

Select the minimal template. Now generate a contract inside the workspace.

cd swaprouter-workshop
beaker wasm new swaprouter

Open workspace root in your editor of choice

You should observe the following directory structure:

tree
.
├── Beaker.toml
├── Cargo.lock
├── Cargo.toml
├── README.md
├── contracts
│   └── swaprouter
│       ├── Cargo.toml
│       ├── LICENSE
│       ├── NOTICE
│       ├── README.md
│       └── src
│           ├── bin
│           │   └── schema.rs
│           ├── contract.rs
│           ├── error.rs
│           ├── lib.rs
│           ├── msg.rs
│           └── state.rs
└── ts
    └── ...

Anatomy of a CosmWasm Contract

Let's go over the Rust files that we care about:

  • contract.rs

Defines entrypoints of the smart contract. There are 3 main entrypoints that we will be interacting with today:

  1. instantiate

Once the contract's code is uploaded to the chain, it needs to be instantiated to initialize state. This entypoint takes a respective InstantiateMsg and executes it. It is the first message that is run for the contract, and it can only be run once.

  1. execute

After a contract is initialized with the InstantiateMsg we can continue running other defined execute messages. This entrypoint takes these execute messages and propagates them to the relevant entrypoint.

  1. query

There are times when clients need to know the state of the contract. This entrypoint takes relevant messages so that users can query the state of the contract.

  1. reply

Due to the architecture necessary to protect against re-entrancy attacks (to be discussed later), it was originally impossible to receive replies from the messages executes from within the contract.

This entrypoint was later introduced to help with receiving replies. Code in the reply entrypoint is comparable to a callback function running after some asyncronous logic is executed.

For example, in our workshop we are going to send a message to the Osmosis chain to swap some tokens. We will need to know how many tokens we will receive in return.

Therefore, we will issue a swap message from a relevant ExecuteMsg and then receive the result of the swap in the reply entrypoint.

CosmWasm enables this functionality by wrapping a CosmosMsg (swap message) into a submessage. Submessage has a cache context so if it fails, it can rollback any changes that were made earleir and fail the whole transaction. There are certain edge cases where the submessages do not fail depending on the kind of the reply handler so please read this CosmWasm SEMANTICS page if you are interested to learn more.

Key points to note from the additional reading:

Submessages (and their replies) are all executed before any messages. They also follow the depth first rules as with messages. Here is a simple example. Contract A returns submessages S1 and S2, and message M1. Submessage S1 returns message N1. The order will be: S1, N1, reply(S1), S2, reply(S2), M1.

We do not drop reply data. If execute returns data: Some(b"try 1") and reply returns `data: Some(b"try 2"), we return , reply will overwrite the data from execute.

There are other entrypoints such as migrate that are outside of scope of this workshop.

  • error.rs

This file defines the error types that can be returned by the contract. We will be defining custom errors later in the workshop.

  • msg.rs

Here, we are going to define all of the messages that our smart contract will support. Each entrypoint discussed earlier has its own set of messages.

  • state.rs

Defines the state of the smart contract. In this file, we will be defining the storage layout for persising any information across contract calls.

Security Benefits

  • Private internal state

    • In contrast to cosmos-sdk, where each module has read and write access to another module's store
  • Serialized execution

    • Comparable to an automatic mutex over the contract code. Protects from re-entrancy attacks.
  • Atomic Executiion

    • If a submessage fails, the whole transaction fails and the state is rolled back.

A lot of these benefits are guaranteed by the "Actor Framework" architecture employed by CosmWasm.

So what is this? The Actor is a single instantiation of a contract, which can perform several actions. When the actor finishes his job, he prepares a summary of it, which includes the list of things that have to be done, to complete the whole scheduled task.

KFC analogy:

  • The restaurant (an actor)
  • Me making an order to the cashier of the restaurant (action)
  • The cashier takes an order (execute message) and tells the cook what to make (issue sub-message)
  • The cook notifies cashier once an order is ready (reply message)
  • If the cook does not have ingridients, my entire order is aborted like it never hapened (atomic execution)
  • Things happen in sequence
  • I don't have access to the internal state of the restaurant unless exposed by queries (private internal state).

Sources:

Remove MigrateMsg and migrate Entrypoint

As previously discussed, these are outside of the scope of the workshop so to keep the code clean, please remove the MigrateMsg and migrate entrypoint from the contract.

For reference, the diff of what needs to be removed can be seen in this PR: https://github.com/p0mvn/swaprouter-workshop/pull/11/files

Acceptance Criteria

  • Be able to cargo wasm without any errors.
  • MigrateMsg and migrate entrypoint are removed

1. Complete Instantiate Message and Write Out Stubs

Goals:

  • Finish the implementation of InstantiateMsg and outline the stubs for all other messages.

If you get stuck, see: https://github.com/p0mvn/swaprouter-workshop/tree/checkpoint/1-instantiate-msg-stubs

User Stories

Let's begin by understanding the requirements.

  1. As a contract owner, I would like to have exclusive access to set trading routes so that I can be the only one with privileges of limiting trades to tokens needed by my application

Need:

  • InstantiateMsg that stores the contract owner address

    • Fully implemented in checkpoint this checkpoint - 1
  • ExecuteMsg::SetRoute that can only be called by the contract owner.

    • Fully implemented in checkpoint 2
  • QueryMsg::GetOwner to query the owner of the contract.

  1. As a contract user, I would like to be able to trade only on the pre-defined route so that I can be confident I am only exposed to the trades needed by the application

Need:

  • ExecuteMsg::Swap that can be called by anyone.

    • Fully implemented in checkpoint 4
    • Performs a swap on the pre-defined route.
  • Swap reply message

    • Fully implemented in checkpoint 4
    • ExecuteMsg::Swap requires interacting with the Osmosis chain. So we need to send a swap message to it and receive a reply, all in one transaction.
  1. As a contract user, I would like to be able to trade with maximum slippage so that my large trades do not affect the market too much.

Need:

  • Improve ExecuteMsg::Swap to support a new trade type with max slippage.
    • Fully implemented in checkpoint 5 (latest state of the repository).

Based on the above requirements, let's outline all the logic that we require.

Each QueryMsg and ExecuteMsg need to have a relevant handler function. Therefore, let's proceed by defining them all.

There are 2 ExecuteMsgs - SetRoute and Swap. So, we create a new file contracts/swaprouter/src/execute.rs to define the rough stub handlers for these messages. Note that they will be modified as we progress with the workshop. Also note that when adding a new file, we need to make it a public module to be able to import it from other modules. So we need to add pub mod execute; to contracts/swaprouter/src/lib.rs.

pub fn set_route(
    _input_denom: String,
    _output_denom: String,
    _pool_route: Vec<SwapAmountInRoute>,
) -> Result<Response, ContractError> {
    Ok(Response::default())
}

pub fn swap(
    _input_coin: Coin,
    _output_denom: String,
    _minimum_output_amount: Uint128,
) -> Result<Response, ContractError> {
    Ok(Response::default())
}

Similarly, there are 2 query messages so we define their stub handlers in the new file contracts/swaprouter/src/query.rs. Note that when adding a new file, we need to make it a public module to be able to import it from other modules. So we need to add pub mod query; to contracts/swaprouter/src/lib.rs.

pub fn query_owner() -> StdResult<GetOwnerResponse> {
    Ok(GetOwnerResponse {
        owner: String::default(),
    })
}

pub fn query_route(input_denom: String, output_denom: String) -> StdResult<GetRouteResponse> {
    Ok(GetRouteResponse { pool_route: vec![] })
}

Now, we are ready to outline the actual messages in msg.rs. Note that upon defining them as below, you will get compilation errors due to the need to handle these messages in contract.rs. We will address that right after.

use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute;

/// Message type for `instantiate` entry_point
#[cw_serde]
pub struct InstantiateMsg {
    pub owner: String,
}

/// Message type for `execute` entry_point
#[cw_serde]
pub enum ExecuteMsg {
    SetRoute {
        input_denom: String,
        output_denom: String,
        pool_route: Vec<SwapAmountInRoute>,
    },
    Swap {
        input_coin: Coin,
        output_denom: String,
        minimum_output_amount: Uint128,
    },
}

/// Message type for `query` entry_point
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
    #[returns(GetOwnerResponse)]
    GetOwner {},
    #[returns(GetRouteResponse)]
    GetRoute {
        input_denom: String,
        output_denom: String,
    },
}

#[cw_serde]
pub struct GetOwnerResponse {
    pub owner: String,
}

#[cw_serde]
pub struct GetRouteResponse {
    pub pool_route: Vec<SwapAmountInRoute>,
}

Notice that this does not compile. Part of the problem is that we need to add a dependency osmosis-std to contracts/swaprouter/Cargo.toml:

[dependencies]
...
osmosis-std = {git = "https://github.com/osmosis-labs/osmosis-rust", branch = "osmosis-v12-rc2"}

With the messaged defined and dependency added, we can proceed by connecting them to the handlers in contract.rs:

/// Handling contract execution
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    _deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::SetRoute {
            input_denom,
            output_denom,
            pool_route,
        } => set_route(input_denom, output_denom, pool_route),
        ExecuteMsg::Swap {
            input_coin,
            output_denom,
            minimum_output_amount,
        } => swap(input_coin, output_denom, minimum_output_amount),
    }
}

/// Handling contract query
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::GetOwner {} => to_binary(&query_owner()?),
        QueryMsg::GetRoute { input_denom, output_denom } => to_binary(&query_route(input_denom, output_denom)?),
    }
}

Note that in the previous step we brought in the following struct into the scope:

use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute;

Essentially, this translates directly into the Osmosis proto definition that we use on chain. We import it from the osmosis_std crate of the osmosis-rust repository.

By having these interoperable struct defintions, we will be able to easily call into the Osmosis chain messages to perform a swap later in the workshop.

As the last step for the completion of this checkpoint, we are going to fully implement the InstantiateMsg. For that, we need to define the state of the contract for persisting the contract owner. In state.rs add:

pub const OWNER: Item<Addr> = Item::new("owner");

Go back to contract.rs and implement the instantiate entrypoint:

/// Handling contract instantiation
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    let owner_address = deps.api.addr_validate(&msg.owner)?;

    OWNER.save(deps.storage, &owner_address)?;

    // With `Response` type, it is possible to dispatch message to invoke external logic.
    // See: https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#dispatching-messages
    Ok(Response::new()
        .add_attribute("method", "instantiate")
        .add_attribute("owner", info.sender))
}

Acceptance Criteria

  • Be able to cargo wasm without any errors.

2. Implement Set Route Message

Goals:

  • Fully-functional SetRoute messages. Short intro to osmosis-testing.

If you get stuck, see: https://github.com/p0mvn/swaprouter-workshop/tree/checkpoint/2-set-route-msg

Completing the implementation is as simple as filling in the blank stub of the set_route handler in execute.rs that we created in the previous checkpoint.

Let's begin by defining the function spec to understand what we need to do. In execute.rs:

// set_route sets route for swaps. Only contract owner may execute this message.
// Returns response with attributes on success.
// Errors if:
// - executed by anyone other than the owner
// - invalid pool route given
//
// Example 1 (one-hop):
// OSMO -> ION
// input: OSMO
// OUTPUT: ION
// ROUTE = [ { pool_id: 1, token_out_denom: ION } ]
//
// Example 2 (multi-hop):
// OSMO -> ION -> STAKE
// input: OSMO
// OUTPUT: STAKE
// ROUTE = [ { pool_id: 1, token_out_denom: ION }, { pool_id: 2, token_out_denom: STAKE } ]
pub fn set_route(...) {
    ...
}

So we need to store the route in the contract state. Let's define it in state.rs:

pub const ROUTING_TABLE: Map<(&str, &str), Vec<SwapAmountInRoute>> = Map::new("routing_table");

Now, we are ready to dive into the implementation of set_route:

pub fn set_route(
    deps: DepsMut,
    info: MessageInfo,
    input_denom: String,
    output_denom: String,
    pool_route: Vec<SwapAmountInRoute>,
) -> Result<Response, ContractError> {
    // Make sure that sender is contract owner.
    validate_is_contract_owner(deps.as_ref(), info.sender)?;

    // Validate that pool route is valid.
    validate_pool_route(
        deps.as_ref(),
        input_denom.clone(),
        output_denom.clone(),
        pool_route.clone(),
    )?;

    // Save the route to the routing table.
    ROUTING_TABLE.save(deps.storage, (&input_denom, &output_denom), &pool_route)?;

    // Response of success. 
    Ok(Response::new().add_attribute("action", "set_route"))
}

Implement each helper in the new file contracts/swaprouter/src/helpers.rs: Note that when adding a new file, we need to make it a public module to be able to import it from other modules. So we need to add pub mod helpers; to contracts/swaprouter/src/lib.rs.

// validate_is_contract_owner validates if sender is the contract owner.
// Returns success if sender is the owner, error otherwise.
pub fn validate_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> {
    let owner = OWNER.load(deps.storage).unwrap();
    if owner != sender {
        Err(ContractError::Unauthorized {})
    } else {
        Ok(())
    }
}

// validate_pool_route validates if the pool route is valid.
// Returns success if it is, error otherwise.
pub fn validate_pool_route(
    deps: Deps,
    input_denom: String,
    output_denom: String,
    pool_route: Vec<SwapAmountInRoute>,
) -> Result<(), ContractError> {
    let mut current_denom_in = input_denom;

    // Iterate over each route
    for route_part in &pool_route {
        // Query liqudity of the pool id specified by the route
        // from the Osmosis chain.
        let liquidity = QueryTotalPoolLiquidityRequest {
            pool_id: route_part.pool_id,
        }
        .query(&deps.querier)?
        .liquidity;

        // If the current denom to swap in does not match any of the denoms
        // in the pool, return error.
        if !liquidity.iter().any(|coin| coin.denom == current_denom_in) {
            return Result::Err(ContractError::InvalidPoolRoute {
                reason: format!(
                    "denom {} is not in pool id {}",
                    current_denom_in, route_part.pool_id
                ),
            });
        }

        // If the denom to swap out does not match any of the denoms in the pool,
        // return error.
        if !liquidity
            .iter()
            .any(|coin| coin.denom == route_part.token_out_denom)
        {
            return Result::Err(ContractError::InvalidPoolRoute {
                reason: format!(
                    "denom {} is not in pool id {}",
                    current_denom_in, route_part.pool_id
                ),
            });
        }

        // The denom to swap in for the next route is the denom
        // out for the current route.
        current_denom_in = route_part.token_out_denom.clone();
    }

    // Make sure the final route output asset is the same as the expected output_denom
    if current_denom_in != output_denom {
        return Result::Err(ContractError::InvalidPoolRoute {
            reason: "last denom doesn't match".to_string(),
        });
    }

    Ok(())
}

Let's now add our new custom error InvaliudPoolRoute to errors.rs:

#[derive(Error, Debug)]
pub enum ContractError {
    ...

    #[error("Invalid Pool Route: {reason:?}")]
    InvalidPoolRoute { reason: String },
}

Now, we are ready to test this message with osmosis-testing. We omit listing details in this README. However, there are 2 relevant files on the checkpoint 2 branch:

Essentially, osmosis-testing spins up an actual test Osmosis application in the background. That allows us to realistically check that all of the messages are functioning as expected contrary to the original cw_multitest approach that forces users to define mocks.

Make sure to add osmosis-testing to contracts/swaprouter/Cargo.toml:

[dev-dependencies]
...
osmosis-testing = {git = "https://github.com/osmosis-labs/osmosis-rust", branch = "main"}

With these files and osmosis_testing added to your Cargo.toml, you can run:

  • cargo wasm to build the contract
  • cargo test to run the osmosis tests

Acceptance Criteria

  • Be able to cargo wasm without any errors.
  • Be able to cargo test without any errors.

3. Implement Queries

Goals:

  • Implement queries and write up basic test for InstantiateMsg by utilizing the queries.

If you get stuck, see: https://github.com/p0mvn/swaprouter-workshop/tree/checkpoint/3-queries

Let's beging by going to the earlier created query.rs where we have 2 stubs.

  • query_owner - needs to return the owner from the state
// query_owner returns contracr owner. Returns error on storage failure.
pub fn query_owner(deps: Deps) -> StdResult<GetOwnerResponse> {
    let owner = OWNER.load(deps.storage)?;
    Ok(GetOwnerResponse {
        owner: owner.into_string(),
    })
}
  • query_route - returns query route from the state given denoms.
// query_route returns query route for given
// input and output denoms.
// Returns error on any storage failure.
pub fn query_route(
    deps: Deps,
    input_denom: String,
    output_denom: String,
) -> StdResult<GetRouteResponse> {
    let route = ROUTING_TABLE.load(deps.storage, (&input_denom, &output_denom))?;
    Ok(GetRouteResponse { pool_route: route })
}

Query implementations are now completed.

Let'go back to contract.rs and write a basic unit test for the InstantiateMsg by utilizing query_owner. At the bottom of the file add:

#[cfg(test)]
mod tests {
    use crate::msg::GetOwnerResponse;

    use super::*;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR};
    use cosmwasm_std::{coins, from_binary};

    #[test]
    fn instantiate_works() {
        let mut deps = mock_dependencies();

        let msg = InstantiateMsg {
            owner: String::from(MOCK_CONTRACT_ADDR),
        };
        let info = mock_info("creator", &coins(1000, "earth"));

        let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
        assert_eq!(0, res.messages.len());

        // make sure that the owner was set correctly.
        let res = query(deps.as_ref(), mock_env(), QueryMsg::GetOwner {}).unwrap();
        let value: GetOwnerResponse = from_binary(&res).unwrap();
        assert_eq!(MOCK_CONTRACT_ADDR, value.owner);
    }
}

Acceptance Criteria

  • Be able to cargo wasm without any errors.
  • Be able to cargo test without any errors.

4. Implement Basic Swap Message

Goals:

  • Understand the semantics of the single-asset swap in Osmosis
  • Implement ExecuteMsg::Swap that performs a basic SwapExactAmountIn swap (w/o slippage)
  • Imp
  • Learn how to interact with the Osmosis chain from CosmWasm

If you get stuck, see: https://github.com/p0mvn/swaprouter-workshop/blob/checkpoint/4-swap-msg

Swap Message Semantics

Before we proceed with the implementation, let's take a step back and understand the semantics of the swap that we are going to work with.

As it stands today, Osmosis is a constant product function AMM based on the balancer design.

We are going to focus on the SwapExactAmountIn swap. The semantics of this swap are as follows:

Swap an exact amount of tokens for a minimum of another token.

We are going to issue this message to the Osmosis chain.

Again, osmosis_std's proto bindings are going to help us with that.

Implementation

As discussed previously, we will need to utilize the reply entrypoint and CosmWasm submessages in order to receive the swap reply from the Osmosis chain, all trigerred by ExecuteMsg::Swap.

  • ExecuteMsg::Swap

Similarly to ExecuteMsg::SetRoute, let's begin by understanding what we want our swap handler to do.

// swap initiates an Osmosis swap message of the input_coin to at least
// minimum_output_token of another coin. Wraps the message into
// CosmWasm swap message to receive reply from the respective entrypoint.
// Returns error if:
// - funds sent in by the initiator do no match the input_coin.
// - fails to generate the message.
pub fn swap(...) {
    // check if the sender has funds equal to input coind

    // generate the Osmosis swap message

    // persist some intermediate state to later receive in the reply entrypoint

    // return success with added swap submessage.
    // this submessage will get propagated to the reply entrypoint.
}

Awesome! Note that we will need some intermediary state persisted between the swap caller and the reply entrypoint. This is because the Osmosis chain does not return all of the inputs to the swap message.

So, in state.rs. we add:

#[cw_serde]
pub struct SwapMsgReplyState {
    pub original_sender: Addr,
    pub swap_msg: MsgSwapExactAmountIn,
}

...
// SWAP_REPLY_STATES persists data from swap message creation until the reply receipt.
pub const SWAP_REPLY_STATES: Map<u64, SwapMsgReplyState> = Map::new("swap_reply_states");

Let's now translate the swam handler comments into code in execute.rs

pub fn swap(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    input_coin: Coin,
    minimum_output_token: Coin,
) -> Result<Response, ContractError> {
    if !has_coins(&info.funds, &input_coin) {
        return Err(ContractError::InsufficientFunds {});
    }

    // generate the swap message using osmosis-rust (osmosis_std).
    let swap_msg = generate_swap_msg(
        deps.as_ref(),
        env.contract.address,
        input_coin,
        minimum_output_token,
    )?;

    // save intermediate state for reply
    SWAP_REPLY_STATES.save(
        deps.storage,
        SWAP_REPLY_ID,
        &SwapMsgReplyState {
            original_sender: info.sender,
            swap_msg: swap_msg.clone(),
        },
    )?;

    Ok(Response::new()
        .add_attribute("action", "swap")
        // add sub message with reply on success. See reply entrypoint for the continuation of the flow.
        .add_submessage(SubMsg::reply_on_success(swap_msg, SWAP_REPLY_ID))) // SWAP_REPLY_ID is defined in `contract.rs` in future steps
}

While has_coins is a helper that can be imported from cosmwasm_std, we need to define generate_swap_msg ourselves in helpers.rs:

// generate_swap_msg generates and returns an Osmosis
// MsgSwapExactAmountIn with sender, input token and min_output_token.
// Returns error if there is no supported route
// between input_token and min_output_token.
pub fn generate_swap_msg(
    deps: Deps,
    sender: Addr,
    input_token: Coin,
    min_output_token: Coin,
) -> Result<MsgSwapExactAmountIn, ContractError> {
    // get trade route
    let route = ROUTING_TABLE.load(deps.storage, (&input_token.denom, &min_output_token.denom))?;

    Ok(MsgSwapExactAmountIn {
        sender: sender.into_string(),
        routes: route,
        token_in: Some(input_token.into()),
        token_out_min_amount: min_output_token.amount.to_string(),
    })
}
  • Implement Reply Entrypoint

Hopefully, by now you have a good understanding of why we need the reply entrypoint. Now, we are going to discuss the implementation.

While in our contract, we only have one reply message, for bigger contracts, it is well possible to have several. Therefore, the first thing that we need to do is check that we receive the desired reply message and propagate it to the appropriate handler. In contract.rs:

// Msg Reply IDs
pub const SWAP_REPLY_ID: u64 = 1u64;

...

/// Handling submessage reply.
/// For more info on submessage and reply, see https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#submessages
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
    if msg.id == SWAP_REPLY_ID {
        // get intermediate swap reply state. Error if not found.
        let swap_msg_state = SWAP_REPLY_STATES.load(deps.storage, msg.id)?;

        // prune intermedate state
        SWAP_REPLY_STATES.remove(deps.storage, msg.id);

        // call reply function to handle the swap return
        handle_swap_reply(msg, swap_msg_state)
    } else {
        Ok(Response::new())
    }
}

Right below the reply function, we define handle_swap_reply handler. Let's begin with the stub and comments.

// handle_swap_reply deserializes the response from Osmosis chain
// If the response is successful and swap is complete, send
// the swapped token to the original user who initiated the swap.
// Otherwise, return contract error.
pub fn handle_swap_reply(
    msg: Reply,
    swap_msg_reply_state: SwapMsgReplyState,
) -> Result<Response, ContractError> {
    // If response can be deserialized

    //    1. Retrieve swapped amount from deserizlized response
    
    //    2. Retrieve swapped denom from reply state we created earleir

    //    3. Send swapped token to original sender from contract.

    //    4. Return success with attributes

    // if cannot deserialize
    //     return error
}

By translating comments into code, we get:

pub fn handle_swap_reply(
    msg: Reply,
    swap_msg_reply_state: SwapMsgReplyState,
) -> Result<Response, ContractError> {
    if let SubMsgResult::Ok(SubMsgResponse { data: Some(b), .. }) = msg.result {
        // Unwrap and deserialize message response.
        let res: MsgSwapExactAmountInResponse = b.try_into().map_err(ContractError::Std)?;

        // Retrieve swapped amount.
        let amount = Uint128::from_str(&res.token_out_amount)?;

        // Retrieve swapped denom from reply state.
        let send_denom = &swap_msg_reply_state
            .swap_msg
            .routes
            .last()
            .unwrap()
            .token_out_denom;

        // Send the swapped token from contract to the original
        // user who initiated the swap.
        let bank_msg = BankMsg::Send {
            to_address: swap_msg_reply_state.original_sender.into_string(),
            amount: coins(amount.u128(), send_denom),
        };

        // Success response.
        return Ok(Response::new()
            .add_message(bank_msg)
            // This attribute should be present in the reply events.
            .add_attribute("token_out_amount", amount));
    }

    Err(ContractError::FailedSwap {
        reason: msg.result.unwrap_err(),
    })
}

Checkpoint 4 should now be complete. At this point, we have all the core functionality layed out. The remaining logic is adding slippage limit functionality to the contract.

Acceptance Criteria

  • Be able to cargo wasm without any errors.
  • Be able to cargo test without any errors.

5. Final Result: Swap with Maximum Slippage Percentage

Goals:

  • Understand and utilize TWAP.
  • Implement swap with the max slippage.

If you get stuck, see: https://github.com/p0mvn/swaprouter-workshop/tree/main

What is TWAP

TWAP - Time Weighted Average Price. It is a price feed that provides smart contracts with prices that are manipulation resistant.

The details are outside of scope of this tutorial but, basically, we don't want to use real-time prices for security reasons. Instead, we use the time weighted prices that are more difficult to manipulate.

More information about this can be found here

What Are Slippage and Price Impact

Slippage refers to the change in price caused by the broad market movements.

On the other hand, price impact is how much your sell/buy directly impacts the liquidity pool.

Technically, we are working with price impact throughout the contract. However, if we look at this abstraction from the user's perspective, they only care about the difference in price that they are getting broadly. Therefore, we call it slippage.

For some of the applications such as collateralized loans, it is common to require to execute large trades. To avoid impacting the market, these applications might want to set a maximum price impact (slippage) ratio.

Implementation

Implementation is about refactoring our ExecuteMsg::Swap to now support an additional trade swap type.

Since this change is a large refactor, it might be easier to refer to the diff in this pull-request

Let's represent the additional swap type by updating the message in msg.rs:

#[cw_serde]
pub enum ExecuteMsg {
    ...
    Swap {
        input_coin: Coin,
        output_denom: String,
-        minimum_output_amount: Uint128,
+        swap_type: SwapType,
    },
}

+#[cw_serde]
+pub enum SwapType {
+    MaxSlippagePercentage(Decimal),
+    MinOutputAmount(Uint128),
+}

There are more updates that we will need to do in contracts.rs that are omitted from the guide. Again, refer to this pull-request for details.

In the meantime, we will move directly to updating the swap handler in execute.rs:

pub fn swap(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    input_coin: Coin,
-    minimum_output_token: Coin,
+    output_denom: String,
+    swap_type: SwapType,
) -> Result<Response, ContractError> {
    if !has_coins(&info.funds, &input_coin) {
        return Err(ContractError::InsufficientFunds {});
    }

+    // get minimum output coin from swap type.
+    let minimum_output_token = match swap_type {
+        SwapType::MaxSlippagePercentage(percentage) => calculate_min_output_from_twap(
+            deps.as_ref(),
+            input_coin.clone(),
+            output_denom,
+            env.block.time,
+            percentage,
+        )?,
+        SwapType::MinOutputAmount(minimum_output_amount) => {
+            coin(minimum_output_amount.u128(), output_denom)
+        }
+    };

    ...
}

Lastly, let's implement calculate_min_output_from_twap function in helpers.rs for the new swap type:

pub fn calculate_min_output_from_twap(
    deps: Deps,
    input_token: Coin,
    output_denom: String,
    now: Timestamp,
    percentage_impact: Decimal,
) -> Result<Coin, ContractError> {
    // get trade route
    let route = ROUTING_TABLE.load(deps.storage, (&input_token.denom, &output_denom))?;
    if route.is_empty() {
        return Err(ContractError::InvalidPoolRoute {
            reason: format!("No route foung for {} -> {output_denom}", input_token.denom),
        });
    }

    let percentage = percentage_impact.div(Uint128::new(100));

    let mut twap_price: Decimal = Decimal::one();

    // When swapping from input to output, we need to quote the price in the input token
    // For example when selling uosmo to buy uion:
    // price of <out> is X<in> (i.e.: price of uion is X uosmo)
    let mut quote_denom = input_token.denom;

    let start_time = now.minus_seconds(1);
    let start_time = OsmosisTimestamp {
        seconds: start_time.seconds() as i64,
        nanos: 0_i32,
    };

    for route_part in route {
        deps.api.debug(&format!("route part: {route_part:?}"));

        let twap = TwapQuerier::new(&deps.querier)
            .arithmetic_twap_to_now(
                route_part.pool_id,
                route_part.token_out_denom.clone(), // base_asset
                quote_denom.clone(),                // quote_asset
                Some(start_time.clone()),
            )?
            .arithmetic_twap;

        deps.api.debug(&format!("twap = {twap}"));

        let current_twap: Decimal = twap.parse().map_err(|_e| ContractError::CustomError {
            val: "Invalid twap value received from the chain".to_string(),
        })?;

        twap_price = twap_price.checked_mul(current_twap.into()).map_err(|_e| {
            ContractError::CustomError {
                val: format!("Invalid value for twap price: {twap_price} * {twap}"),
            }
        })?;

        // the current output is the input for the next route_part
        quote_denom = route_part.token_out_denom;
    }

    twap_price = twap_price - twap_price.mul(percentage);
    deps.api.debug(&format!(
        "twap_price minus {percentage_impact}%: {twap_price}"
    ));

    let min_out: Uint128 = input_token.amount.mul(twap_price);
    deps.api.debug(&format!("min: {min_out}"));

    Ok(Coin::new(min_out.into(), output_denom))
}

Now, we are ready to test this message with osmosis-testing. We omit listing details in this README. However, there are 2 relevant files on the checkpoint 2 branch:

Acceptance Criteria

  • Be able to cargo wasm without any errors.
  • Be able to cargo test without any errors.
  • Be able to deploy and manually test the contract with Beaker (discussed in the next section)

Deployment

Finally, let's upload our contract and test it with LocalOsmosis and Beaker.

Start LocalOsmosis

LocalOsmosis is a tool to spin up a local test Osmosis chain. It is located inside the Osmosis repository and can be interacted with by Makefile commands.

cd $HOME/osmosis
git checkout v12.x

# Build localnet Osmosis image.
make localnet-build

# Start LocalOsmosis with test state.
make localnet-start-with-state

# Add test account keys to your keyring.
make localnet-keys

# To stop LocalOsmosis
make localnet-stop 

# To reset the state (only call when chain is stopped)
# Warning: deletes all the progress deployed to chain so far.
make localnet-clean

Interact with the contract on LocalOsmosis

Beaker

Deploy to LocalOsmosis

The following commands builds the contract as a wasm file, uploads it to LocalOsmosis and runs InstantiateMsg.

# Deploy swaprouter contract. Owner is the address of an account that we created above.
# N.B. Label is needed in case you happen to want to make a change and redeploy a new version of the contract.
# Since contracts are immutable, you would increase the label by 1 and redeploy to operate on a new version.
beaker wasm deploy swaprouter --signer-account test1 --no-wasm-opt --raw '{ "owner": "osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks" }' --label 1

Note: beaker wasm deploy does the following behind the scenes:

  • builds the wasm contract file
  • stores it on chain
  • instantiates the contract

Issue ExecuteMsg::SetRoute

beaker wasm execute swaprouter --raw '{ "set_route": { "input_denom": "stake", "output_denom": "uion", "pool_route": [ { "pool_id": 1, "token_out_denom": "uosmo" }, { "pool_id": 2, "token_out_denom": "uion" } ] } }' --signer-account test1 --label 1

Use Beaker to issue ExecuteMsg::Swap with SwapType::MinOutputAmount

beaker wasm execute swaprouter --raw '{"swap": { "input_coin": { "amount": "50", "denom": "stake" }, "output_denom": "uion", "swap_type": { "min_output_amount": "1" } } }' --signer-account test1 --label 1 --gas "100000uosmo" --gas-limit 10000000 --funds "50stake"

Use Beaker to issue ExecuteMsg::Swap with SwapType::MaxSlippagePercentage

beaker wasm execute swaprouter --raw '{"swap": { "input_coin": { "amount": "1000", "denom": "stake" }, "output_denom": "uion", "swap_type": { "max_slippage_percentage": "20" } } }' --signer-account test1 --label 1 --gas "100000uosmo" --gas-limit 25000000 --funds "1000stake"
Comments
  • generate swap comment (backport #19)

    generate swap comment (backport #19)

    This is an automatic backport of pull request #19 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • readd ts-sdk (backport #9)

    readd ts-sdk (backport #9)

    This is an automatic backport of pull request #9 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • readd ts-sdk (backport #9)

    readd ts-sdk (backport #9)

    This is an automatic backport of pull request #9 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • readd ts-sdk (backport #9)

    readd ts-sdk (backport #9)

    This is an automatic backport of pull request #9 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • remove migrate message (backport #10)

    remove migrate message (backport #10)

    This is an automatic backport of pull request #10 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • remove migrate message (backport #10)

    remove migrate message (backport #10)

    This is an automatic backport of pull request #10 done by Mergify. Cherry-pick of 16611fe6d6dbfff4ffff6bc8653227ccda75ea46 has failed:

    On branch mergify/bp/checkpoint/1-instantiate-msg-stubs/pr-10
    Your branch is up to date with 'origin/checkpoint/1-instantiate-msg-stubs'.
    
    You are currently cherry-picking commit 16611fe.
      (fix conflicts and run "git cherry-pick --continue")
      (use "git cherry-pick --skip" to skip this patch)
      (use "git cherry-pick --abort" to cancel the cherry-pick operation)
    
    Changes to be committed:
    	modified:   contracts/swaprouter/src/bin/schema.rs
    	modified:   contracts/swaprouter/src/msg.rs
    
    Unmerged paths:
      (use "git add <file>..." to mark resolution)
    	both modified:   contracts/swaprouter/src/contract.rs
    
    

    To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • remove migrate message (backport #10)

    remove migrate message (backport #10)

    This is an automatic backport of pull request #10 done by Mergify. Cherry-pick of 16611fe6d6dbfff4ffff6bc8653227ccda75ea46 has failed:

    On branch mergify/bp/checkpoint/0-setup-boilerplate/pr-10
    Your branch is up to date with 'origin/checkpoint/0-setup-boilerplate'.
    
    You are currently cherry-picking commit 16611fe.
      (fix conflicts and run "git cherry-pick --continue")
      (use "git cherry-pick --skip" to skip this patch)
      (use "git cherry-pick --abort" to cancel the cherry-pick operation)
    
    Changes to be committed:
    	modified:   contracts/swaprouter/src/bin/schema.rs
    	modified:   contracts/swaprouter/src/msg.rs
    
    Unmerged paths:
      (use "git add <file>..." to mark resolution)
    	both modified:   contracts/swaprouter/src/contract.rs
    
    

    To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • add inputs to the route query (backport #6)

    add inputs to the route query (backport #6)

    This is an automatic backport of pull request #6 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • cargo config in root (backport #3)

    cargo config in root (backport #3)

    This is an automatic backport of pull request #3 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
  • cargo config in root (backport #3)

    cargo config in root (backport #3)

    This is an automatic backport of pull request #3 done by Mergify.


    Mergify commands and options

    More conditions and actions can be found in the documentation.

    You can also trigger Mergify actions by commenting on this pull request:

    • @Mergifyio refresh will re-evaluate the rules
    • @Mergifyio rebase will rebase this PR on its base branch
    • @Mergifyio update will merge the base branch into this PR
    • @Mergifyio backport <destination> will backport this PR on <destination> branch

    Additionally, on Mergify dashboard you can:

    • look at your merge queues
    • generate the Mergify configuration with the config editor.

    Finally, you can contact us on https://mergify.com

    opened by mergify[bot] 0
Owner
Roman
Roman
Example (with solution) for the Rust Fundamentals Workshop

Workshop Repo: Rust Fundamentals NOTE: This is Work-In-Progress! Please check for updates a day before the workshop. This Github repository contains a

Stefan Baumgartner 5 Jan 31, 2023
An API for getting questions from http://either.io implemented fully in Rust, using reqwest and some regex magic. Provides asynchronous and blocking clients respectively.

eithers_rust An API for getting questions from http://either.io implemented fully in Rust, using reqwest and some regex magic. Provides asynchronous a

null 2 Oct 24, 2021
Safe, efficient, and ergonomic bindings to Wolfram LibraryLink and the Wolfram Language

wolfram-library-link Bindings to the Wolfram LibraryLink interface, making it possible to call Rust code from the Wolfram Language. This library is us

Wolfram Research, Inc. 28 Dec 6, 2022
This blog provides detailed status updates and useful information about Theseus OS and its development

The Theseus OS Blog This blog provides detailed status updates and useful information about Theseus OS and its development. Attribution This blog was

Theseus OS 1 Apr 14, 2022
Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously

Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously. It is intended to suit one's every requirement regarding chat on omegle.

null 1 May 25, 2022
Fast and simple datetime, date, time and duration parsing for rust.

speedate Fast and simple datetime, date, time and duration parsing for rust. speedate is a lax† RFC 3339 date and time parser, in other words, it pars

Samuel Colvin 43 Nov 25, 2022
In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.

Learn Rust What is this? In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang. This is usef

Domagoj Ratko 5 Nov 5, 2022
A tool and library to losslessly join multiple .mp4 files shot with same camera and settings

mp4-merge A tool and library to losslessly join multiple .mp4 files shot with same camera and settings. This is useful to merge multiple files that ar

Gyroflow 7 Jan 2, 2023
A tray application for Windows that gives you push notifications and instant downloads of new posts, messages and stories posted by models you subscribe to on Onlyfans.

OF-notifier A tray application for Windows that gives you push notifications and instant downloads of new posts, messages and stories posted by models

Gentlemen Mercenary 10 Dec 20, 2022
A simpler and 5x faster alternative to HashMap in Rust, which doesn't use hashing and doesn't use heap

At least 5x faster alternative of HashMap, for very small maps. It is also faster than FxHashMap, hashbrown, ArrayMap, and nohash-hasher. The smaller

Yegor Bugayenko 12 Apr 19, 2023
A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32 architectures going step-by-step into the world of reverse engineering Rust from scratch.

FREE Reverse Engineering Self-Study Course HERE Hacking Rust A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32

Kevin Thomas 98 Jun 21, 2023
Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

Larry Fantasy 635 Jan 3, 2023
:crab: Small exercises to get you used to reading and writing Rust code!

rustlings ?? ❤️ Greetings and welcome to rustlings. This project contains small exercises to get you used to reading and writing Rust code. This inclu

The Rust Programming Language 33.1k Jan 2, 2023
A catalogue of Rust design patterns, anti-patterns and idioms

Rust Design Patterns An open source book about design patterns and idioms in the Rust programming language that you can read here. Contributing You ar

null 6.5k Jan 9, 2023
Simple and performant hot-reloading for Rust

reloady Simple, performant hot-reloading for Rust. Requires Rust nightly and only works on Linux for now. installing CLI To install the CLI helper car

Anirudh Balaji 24 Aug 5, 2022
Rust library for hardware accelerated drawing of 2D shapes, images, and text, with an easy to use API.

Speedy2D Hardware-accelerated drawing of shapes, images, and text, with an easy to use API. Speedy2D aims to be: The simplest Rust API for creating a

null 223 Dec 26, 2022
Compare the size of v8 and quickjs

rust_v8_and_quickjs Size debug release quickjs 1,758,728 1,281,104 v8 40,047,648 20,956,816 Chart License rust_v8_and_quickjs is released under the MI

迷渡 16 Dec 23, 2022
Rust, cargo and QEMU setup for multi-architecture OS development.

rust-osdev-jumpstart Rust, cargo and QEMU setup for multi-architecture OS development. Goal This repo should give you a boost in starting a bare-metal

Alister Lee 27 Nov 20, 2022