Last active
August 31, 2022 08:24
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![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