Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bounded values

A common situation developers find themselves in is their contract needs to take a value that must been within a certain bound.

For example, a fee rate should be within the range of 0~1. It doesn't make sense to charge more than 100% fee. Whenever a fee rate is provided, the contract needs to verify it's within the bounds, throwing error if not:

#![allow(unused)]
fn main() {
#[grug::derive(Serde)]
struct InstantiateMsg {
    pub fee_rate: Udec256,
}

#[grug::export]
fn instantiate(ctx: MutableCtx, msg: InstantiateMsg) -> anyhow::Result<Response> {
    ensure!(
        Udec256::ZERO <= fee_rate && fee_rate < Udec256::ONE,
        "fee rate is out of bounds"
    );

    Ok(Response::new())
}
}

We call this an imperative approach for working with bounded values.

The problem with this is that the declaration and validation of fee_rate are in two places, often in two separate files. Sometimes developers simply forget to do the validation.

Instead, Grug encourages a declarative approach. We declare the valid range of a value at the time we define it, utilizing the Bounded type and Bounds trait:

#![allow(unused)]
fn main() {
use grug::{Bounded, Bounds};
use std::ops::Bound;

struct FeeRateBounds;

impl Bounds<Udec256> for FeeRateBounds {
    const MIN: Bound<Udec256> = Bound::Inclusive(Udec256::ZERO);
    const MAX: Bound<Udec256> = Bound::Exclusive(Udec256::ONE);
}

type FeeRate = Bounded<Udec256, FeeRateBounds>;

#[grug::derive(Serde)]
struct InstantiateMsg {
    pub fee_rate: FeeRate,
}

#[grug::export]
fn instantiate(ctx: MutableCtx, msg: InstantiateMsg) -> anyhow::Result<Response> {
    // No need to validate the fee rate here.
    // Its bounds are already verified when `msg` is deserialized!

    Ok(Response::new())
}
}