Envío de tokens

Esta sección muestra un contrato inteligente diseñado para enviar tokens nativos de blockchain a un destinatario especificado por el remitente original en el mensaje de ejecución.

Explicación

send_tokens

Una vez que se ejecuta el caso de uso principal de esta función (que en este contexto es nula), se adjunta un mensaje bancario para que el contrato actúe. Vale la pena señalar que el contrato se convierte en el firmante de la transacción, no en el remitente que la inicia.

// contract.rs
pub fn send_tokens(
    _deps: DepsMut,
    amount: Uint128,
    denom: String,
    to: Addr
) -> Result<Response, ContractError> {
    
    /* Sending tokens is managed via the response of this function.
       A developer crafts a BankMsg to transmit tokens to a specified address using the native token.
       The function will fail if the smart contract lacks sufficient tokens.
       If any error surfaces prior to the response's generation, funds won't be transmitted. */
    
    Ok(Response::new()
        .add_attribute("action", "send")
        .add_message(BankMsg::Send {
            to_address: to.into_string(),
            amount: vec![Coin{denom, amount}]
        })
    )
}

Pruebas de integración

// integration_tests.rs
fn balance() {
    let (mut app, cw_template_contract) = proper_instantiate();
    let msg = ExecuteMsg::SendTokens {
        amount: Uint128::new(10),
        denom: "token".to_string(),
        to: Addr::unchecked("receiver")
    };
    let funds_sent = Coin::new(10u128, "token".to_string());
    let cosmos_msg = cw_template_contract.call(msg, funds_sent).unwrap();
    app.execute(Addr::unchecked(USER), cosmos_msg).unwrap();
    let balance = app.wrap().query_balance("receiver", "token").unwrap();
    assert_eq!(balance.amount, Uint128::new(10));
    assert_eq!(balance.denom, "token");
}

Ejemplo

Para enviar tokens con CosmWasm, puede crear los siguientes archivos: lib.rs contract.rs msg.rs error.rs state.rs helpers.rs Integration_tests.rs

lib.rs

pub mod contract;
mod error;
pub mod msg;
pub mod state;
pub mod integration_tests;
pub mod helpers;
pub use crate::error::ContractError;

contract.rs

#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    _deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    _msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    Ok(Response::new()
        .add_attribute("method", "instantiate"))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::SendTokens {amount, denom, to} => execute::send_tokens(deps, amount, denom, to),
    }
}
pub mod execute {
    use cosmwasm_std::{Uint128, Addr, BankMsg, Coin};
    use super::*;
    pub fn send_tokens(_deps: DepsMut, amount: Uint128, denom: String, to: Addr) -> Result<Response, ContractError> {
        Ok(Response::new().add_attribute("action", "increment")
        /* Sending tokens is part of the response of a function
        Developer creates a BankMsg to send tokens to an address with a specific native token
        Will fail if smart contract does not have this much tokens initially  */
        .add_message(BankMsg::Send { to_address: to.into_string(), amount: vec![Coin{denom, amount}] }))
    }
   
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
    }
}
pub mod query {
   
}
#[cfg(test)]
mod tests {
}

msg.rs

use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Uint128, Addr, Coin};
#[cw_serde]
pub struct InstantiateMsg {}
#[cw_serde]
pub enum ExecuteMsg {
    SendTokens {amount: Uint128, denom: String, to: Addr}
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
}
#[cw_serde]
pub struct BalanceResponse {
    pub amount: Coin,
}

error.rs

use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
    #[error("{0}")]
    Std(#[from] StdError),
    #[error("Unauthorized")]
    Unauthorized {},
    // Add any other custom errors you like here.
    // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.
}

state.rs

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::Addr;
use cw_storage_plus::Item;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct State {
    pub count: i32,
    pub owner: Addr,
}
pub const STATE: Item<State> = Item::new("state");

helpers.rs

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{
    to_binary, Addr, CosmosMsg, StdResult, WasmMsg, Coin,
};
use crate::msg::{ExecuteMsg};
/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers
/// for working with this.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct CwTemplateContract(pub Addr);
impl CwTemplateContract {
    pub fn addr(&self) -> Addr {
        self.0.clone()
    }
    pub fn call<T: Into<ExecuteMsg>>(&self, msg: T, funds: Coin) -> StdResult<CosmosMsg> {
        let msg = to_binary(&msg.into())?;
        Ok(WasmMsg::Execute {
            contract_addr: self.addr().into(),
            msg,
            funds: vec![funds],
        }
        .into())
    }
   
}

integration_tests.rs

#[cfg(test)]
mod tests {
    use crate::helpers::CwTemplateContract;
    use crate::msg::InstantiateMsg;
    use cosmwasm_std::{Addr, Coin, Empty, Uint128};
    use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor};
    pub fn contract_template() -> Box<dyn Contract<Empty>> {
        let contract = ContractWrapper::new(
            crate::contract::execute,
            crate::contract::instantiate,
            crate::contract::query,
        );
        Box::new(contract)
    }
    const USER: &str = "USER";
    const ADMIN: &str = "ADMIN";
    const NATIVE_DENOM: &str = "token";
    fn mock_app() -> App {
        AppBuilder::new().build(|router, _, storage| {
            router
                .bank
                .init_balance(
                    storage,
                    &Addr::unchecked(USER),
                    vec![Coin {
                        denom: NATIVE_DENOM.to_string(),
                        amount: Uint128::new(10),
                    }],
                )
                .unwrap();
        })
    }
    fn proper_instantiate() -> (App, CwTemplateContract) {
        let mut app = mock_app();
        let cw_template_id = app.store_code(contract_template());
        let msg = InstantiateMsg {};
        let cw_template_contract_addr = app
            .instantiate_contract(
                cw_template_id,
                Addr::unchecked(ADMIN),
                &msg,
                &[],
                "test",
                None,
            )
            .unwrap();
        let cw_template_contract = CwTemplateContract(cw_template_contract_addr);
        (app, cw_template_contract)
    }
    mod count {
        use super::*;
        use crate::msg::ExecuteMsg;
        #[test]
        fn balance() {
            let (mut app, cw_template_contract) = proper_instantiate();
            let msg = ExecuteMsg::SendTokens { amount: Uint128::new(10), denom: "token".to_string(), to: Addr::unchecked("receiver") } ;
            let funds_sent = Coin::new(10u128, "token".to_string());
            let cosmos_msg = cw_template_contract.call(msg, funds_sent).unwrap();
            app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); 
            let balance = app.wrap().query_balance("receiver", "token").unwrap();
            assert_eq!(balance.amount, Uint128::new(10));
            assert_eq!(balance.denom, "token");
            
        }
    }
}

Last updated