Skip to content

Instantly share code, notes, and snippets.

@CyberHoward
Last active August 31, 2022 08:24
Show Gist options
  • Save CyberHoward/fa1ea030581a7e513fabc01ed8907843 to your computer and use it in GitHub Desktop.
Save CyberHoward/fa1ea030581a7e513fabc01ed8907843 to your computer and use it in GitHub Desktop.
[ Proof of Concept ] Environment-generic test and deployment scripting for CosmWasm contracts in Rust.
#![allow(unused)]
use std::marker::PhantomData;
/// Goal: Create a generic interface to perform integration tests and live tests/deployments with minimal syntax.
///
/// Why: This would give developers the ability to easily interact with their contracts in different environments without much syntax changes.
/// It also speeds up development by replacing time-consuming and error-prone bash/typescript deployment scripts and it would speed up
/// the testing process as the contract calls are much more concise and easy to read.
use serde::Serialize;
/// An instance of a contract. Contains references to the execution environment (chain) and a local state (state)
/// The state is used to store contract addresses/code-ids
struct Contract<Chain: TxHandler, E: Serialize>
where
TxResponse<Chain>: IndexResponse,
{
// Name of the contract, used to retrieve addr/code-id
pub name: String,
// chain object that handles tx execution and queries.
chain: Chain,
// A pointer to a state object to store contract addresses/code-ids
state: Box<dyn StateInterface>,
// Indicate the type of executemsg
_exec: PhantomData<E>,
}
/// Expose chain and state function to call them on the contract
impl<Chain: TxHandler, E: Serialize> Contract<Chain, E>
where
TxResponse<Chain>: IndexResponse,
{
fn new(name: &str, chain: Chain, state: Box<dyn StateInterface>) -> Self {
Contract {
name: name.to_string(),
chain,
state,
_exec: PhantomData,
}
}
fn execute(&self, msg: &E) -> TxResponse<Chain> {
self.chain.execute(msg)
}
fn address(&self) -> String {
self.state.get_addr(&self.name)
}
fn store_address(&self, address: &str) {
self.state.store_addr(address)
}
}
struct MockResponse {}
struct RealResponse {}
#[derive(Debug, Clone)]
struct MockChain;
#[derive(Debug, Clone)]
struct RealChain;
trait StateInterface {
fn get_addr(&self, key: &str) -> String;
fn store_addr(&self, address: &str);
}
struct RealChainStore {}
impl StateInterface for RealChainStore {
fn get_addr(&self, _key: &str) -> String {
"real".to_string()
}
fn store_addr(&self, address: &str) {
// store the address in a local JSON file
()
}
}
struct MockChainStore {}
impl StateInterface for MockChainStore {
fn get_addr(&self, _key: &str) -> String {
"test".to_string()
}
fn store_addr(&self, address: &str) {
// store the address in a custom heap-allocated store-object
()
}
}
// Function to index data returned by transactions which are applicable to both AppResponse (mock env) and TxResponse (live env)
trait IndexResponse {
fn events(&self) -> String {
"index_events_here".to_string()
}
}
impl IndexResponse for MockResponse {}
impl IndexResponse for RealResponse {}
type TxResponse<Chain> = <Chain as TxHandler>::Out;
// Functions that are callable on the cosmwasm chain/mock
trait TxHandler {
type Out;
fn execute<E: Serialize>(&self, msg: &E) -> Self::Out;
}
// Execute on the test chain, returns test response type
impl TxHandler for MockChain {
type Out = MockResponse;
fn execute<E: Serialize>(&self, msg: &E) -> Self::Out {
MockResponse {}
}
}
// Execute on the real chain, returns tx response
impl TxHandler for RealChain {
type Out = RealResponse;
fn execute<E: Serialize>(&self, msg: &E) -> Self::Out {
RealResponse {}
}
}
// Execute on the real chain, returns tx response
impl<Chain: TxHandler, E: Serialize> TxHandler for Contract<Chain, E>
where
TxResponse<Chain>: IndexResponse,
{
type Out = RealResponse;
fn execute<R>(&self, msg: &R) -> Self::Out {
RealResponse {}
}
}
// Msg that'd be implemented in a different crate and imported.
#[derive(Debug, Serialize)]
enum ExecuteMsg {
Send(),
}
// Some other message that you're using
#[derive(Debug, Serialize)]
enum NotCw20Msg {
SomeMsg(),
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////// This is what the user would interact with /////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// Implement your contract as a type
type Cw20<Chain> = Contract<Chain, ExecuteMsg>;
// Implement some function that can only be executed on a real chain.
impl Cw20<RealChain> {
pub fn do_something_on_real_chain(&self) -> String {
let out = self.execute(&ExecuteMsg::Send());
let new_address = out.events();
new_address
}
}
// Implement some function that can only be executed on a mock chain.
impl Cw20<MockChain> {
pub fn do_something_on_mock_chain(&self) -> String {
let out = self.execute(&ExecuteMsg::Send());
let new_address = out.events();
new_address
}
}
// Implement generic functionality to use in real and testing environments!
impl<Chain: TxHandler> Cw20<Chain>
where
TxResponse<Chain>: IndexResponse,
{
pub fn send(&self) -> TxResponse<Chain> {
self.execute(&ExecuteMsg::Send())
}
pub fn do_crazy_stuff(&self) {
// Execute the contract
// Throws a compile error because NotCw20Msg can't be executed on the Cw20 contract!!
// self.execute(NotCw20Msg::SomeMsg());
// execute something
let resp = self.execute(&ExecuteMsg::Send());
// Get information that both test results and live results returns like events!
let events = resp.events();
// Get an address from state.
let some_addr = self.address();
// Store something to state
// Could be a heap-allocated state-store (test store) or a json file with your deployment details (real store)!
self.store_address("terra1...oeuc");
}
}
/// This is what a user would use in his deployment / testing environment
fn main() {
// message we want to execute on a cw20 contract
let msg = ExecuteMsg::Send();
// Using test environment
let state = MockChainStore {};
let chain = MockChain {};
let my_token = Cw20::new("my_test_token", chain, Box::new(state));
// Returns u16 (MockResponse)
let exec_result = my_token.execute(&msg);
// Runs on the test chain
my_token.do_crazy_stuff();
my_token.do_something_on_mock_chain();
// Using live environment
let state = RealChainStore {};
let chain = RealChain {};
// identical to the above!
let my_token = Cw20::new("astro", chain, Box::new(state));
// Returns u32 (RealResponse)
let exec_result = my_token.execute(&msg);
// Also runs on the real chain!!
my_token.do_crazy_stuff();
// Compile error because using RealChain
// my_token.do_something_on_test_chain();
my_token.do_something_on_real_chain();
let contract_address = my_token.address();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment