Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend ContractData in multi-test #360

Merged
merged 5 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion packages/multi-test/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::bank::Bank;
use crate::contracts::Contract;
use crate::executor::{AppResponse, Executor};
use crate::transactions::transactional;
use crate::wasm::{Wasm, WasmKeeper};
use crate::wasm::{ContractData, Wasm, WasmKeeper};

pub fn next_block(block: &mut BlockInfo) {
block.time = block.time.plus_seconds(5);
Expand Down Expand Up @@ -127,6 +127,13 @@ where
self.router.wasm.store_code(code) as u64
}

/// This allows to get `ContractData` for specific contract
pub fn contract_data(&self, address: &Addr) -> Result<ContractData, String> {
self.router
.wasm
.contract_data(self.storage.as_ref(), address)
}

/// Runs arbitrary CosmosMsg in "sudo" mode.
/// This will create a cache before the execution, so no state changes are persisted if this
/// returns an error, but all are persisted on success.
Expand Down Expand Up @@ -365,6 +372,18 @@ mod test {
.instantiate_contract(code_id, owner.clone(), &msg, &coins(23, "eth"), "Payout")
.unwrap();

let contract_data = app.contract_data(&contract_addr).unwrap();
assert_eq!(
contract_data,
ContractData {
code_id: code_id as usize,
creator: owner.clone(),
admin: None,
label: "Payout".to_owned(),
created: app.block_info().height
}
);

// sender funds deducted
let sender = get_balance(&app, &owner);
assert_eq!(sender, vec![coin(20, "btc"), coin(77, "eth")]);
Expand Down
217 changes: 159 additions & 58 deletions packages/multi-test/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ const CONTRACTS: Map<&Addr, ContractData> = Map::new("contracts");

pub const NAMESPACE_WASM: &[u8] = b"wasm";

/// Contract Data is just a code_id that can be used to lookup the actual code from the Router
/// We can add other info here in the future, like admin
/// Contract Data includes information about contract, equivalent of `ContractInfo` in wasmd
/// interface.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
struct ContractData {
pub struct ContractData {
/// Identifier of stored contract code
pub code_id: usize,
}

impl ContractData {
fn new(code_id: usize) -> Self {
ContractData { code_id }
}
/// Address of account who initially instantiated the contract
pub creator: Addr,
/// Optional address of account who can execute migrations
pub admin: Option<Addr>,
/// Metadata passed while contract instantiation
pub label: String,
/// Blockchain height in the moment of instantiating the contract
pub created: u64,
}

pub trait Wasm<C>
Expand Down Expand Up @@ -63,6 +66,9 @@ where
// Add a new contract. Must be done on the base object, when no contracts running
fn store_code(&mut self, code: Box<dyn Contract<C>>) -> usize;

// Helper for querying for specific contract data
fn contract_data(&self, storage: &dyn Storage, address: &Addr) -> Result<ContractData, String>;

/// Admin interface, cannot be called via CosmosMsg
fn sudo(
&self,
Expand Down Expand Up @@ -134,6 +140,10 @@ where
idx
}

fn contract_data(&self, storage: &dyn Storage, address: &Addr) -> Result<ContractData, String> {
self.load_contract(storage, address)
}

fn sudo(
&self,
contract_addr: Addr,
Expand Down Expand Up @@ -237,14 +247,20 @@ where
Ok((contract_addr, res))
}
WasmMsg::Instantiate {
admin: _,
admin,
code_id,
msg,
funds,
label: _,
label,
} => {
let contract_addr =
Addr::unchecked(self.register_contract(storage, code_id as usize)?);
let contract_addr = self.register_contract(
storage,
code_id as usize,
sender.clone(),
admin.map(Addr::unchecked),
label,
block.height,
)?;
// move the cash
self.send(
storage,
Expand Down Expand Up @@ -394,14 +410,24 @@ where
&self,
storage: &mut dyn Storage,
code_id: usize,
creator: Addr,
admin: impl Into<Option<Addr>>,
label: String,
created: u64,
) -> Result<Addr, String> {
let mut wasm_storage = prefixed(storage, NAMESPACE_WASM);

if !self.codes.contains_key(&code_id) {
return Err("Cannot init contract with unregistered code id".to_string());
}
let addr = self.next_address(&wasm_storage);
let info = ContractData::new(code_id);
let info = ContractData {
code_id,
creator,
admin: admin.into(),
label,
created,
};
CONTRACTS
.save(&mut wasm_storage, &addr, &info)
.map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -533,7 +559,11 @@ where
})
}

fn load_contract(&self, storage: &dyn Storage, address: &Addr) -> Result<ContractData, String> {
pub fn load_contract(
&self,
storage: &dyn Storage,
address: &Addr,
) -> Result<ContractData, String> {
CONTRACTS
.load(&prefixed_read(storage, NAMESPACE_WASM), address)
.map_err(|e| e.to_string())
Expand Down Expand Up @@ -647,48 +677,79 @@ mod test {
let block = mock_env().block;
let code_id = keeper.store_code(contract_error());

let mut cache = StorageTransaction::new(&wasm_storage);
transactional(&mut wasm_storage, |cache, _| {
// cannot register contract with unregistered codeId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could actually be in another transactional block.
To ensure it didn't make any changes when it returns an error.

Since they are simpler, we can do more.

keeper.register_contract(
cache,
code_id + 1,
Addr::unchecked("foobar"),
Addr::unchecked("admin"),
"label".to_owned(),
1000,
)
})
.unwrap_err();

let contract_addr = transactional(&mut wasm_storage, |cache, _| {
// we can register a new instance of this code
keeper.register_contract(
cache,
code_id,
Addr::unchecked("foobar"),
Addr::unchecked("admin"),
"label".to_owned(),
1000,
)
})
.unwrap();

// cannot register contract with unregistered codeId
keeper
.register_contract(&mut cache, code_id + 1)
.unwrap_err();
// verify contract data are as expected
let contract_data = keeper.load_contract(&wasm_storage, &contract_addr).unwrap();

// we can register a new instance of this code
let contract_addr = keeper.register_contract(&mut cache, code_id).unwrap();
assert_eq!(
contract_data,
ContractData {
code_id,
creator: Addr::unchecked("foobar"),
admin: Some(Addr::unchecked("admin")),
label: "label".to_owned(),
created: 1000,
}
);

// now, we call this contract and see the error message from the contract
let info = mock_info("foobar", &[]);
let err = keeper
.call_instantiate(
contract_addr,
&mut cache,
let err = transactional(&mut wasm_storage, |cache, _| {
// now, we call this contract and see the error message from the contract
let info = mock_info("foobar", &[]);
keeper.call_instantiate(
contract_addr.clone(),
cache,
&mock_router(),
&block,
info,
b"{}".to_vec(),
)
.unwrap_err();
})
.unwrap_err();

// StdError from contract_error auto-converted to string
assert_eq!(err, "Generic error: Init failed");

// and the error for calling an unregistered contract
let info = mock_info("foobar", &[]);
let err = keeper
.call_instantiate(
let err = transactional(&mut wasm_storage, |cache, _| {
// and the error for calling an unregistered contract
let info = mock_info("foobar", &[]);
keeper.call_instantiate(
Addr::unchecked("unregistered"),
&mut cache,
cache,
&mock_router(),
&block,
info,
b"{}".to_vec(),
)
.unwrap_err();
})
.unwrap_err();

// Default error message from router when not found
assert_eq!(err, "cw_multi_test::wasm::ContractData not found");

// and flush
cache.prepare().commit(&mut wasm_storage);
}

#[test]
Expand All @@ -700,7 +761,16 @@ mod test {
let mut wasm_storage = MockStorage::new();
let mut cache = StorageTransaction::new(&wasm_storage);

let contract_addr = keeper.register_contract(&mut cache, code_id).unwrap();
let contract_addr = keeper
.register_contract(
&mut cache,
code_id,
Addr::unchecked("foobar"),
None,
"label".to_owned(),
1000,
)
.unwrap();

let payout = coin(100, "TGD");

Expand Down Expand Up @@ -795,27 +865,40 @@ mod test {
let code_id = keeper.store_code(contract_payout());

let mut wasm_storage = MockStorage::new();
let mut cache = StorageTransaction::new(&wasm_storage);

// set contract 1 and commit (on router)
let contract1 = keeper.register_contract(&mut cache, code_id).unwrap();
let payout1 = coin(100, "TGD");
let info = mock_info("foobar", &[]);
let init_msg = to_vec(&PayoutInitMessage {
payout: payout1.clone(),

// set contract 1 and commit (on router)
let contract1 = transactional(&mut wasm_storage, |cache, _| {
let contract = keeper
.register_contract(
cache,
code_id,
Addr::unchecked("foobar"),
None,
"".to_string(),
1000,
)
.unwrap();
let info = mock_info("foobar", &[]);
let init_msg = to_vec(&PayoutInitMessage {
payout: payout1.clone(),
})
.unwrap();
keeper
.call_instantiate(
contract.clone(),
cache,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();

Ok(contract)
})
.unwrap();
let _res = keeper
.call_instantiate(
contract1.clone(),
&mut cache,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();
cache.prepare().commit(&mut wasm_storage);

let payout2 = coin(50, "BTC");
let payout3 = coin(1234, "ATOM");
Expand All @@ -825,7 +908,16 @@ mod test {
assert_payout(&keeper, cache, &contract1, &payout1);

// create contract 2 and use it
let contract2 = keeper.register_contract(cache, code_id).unwrap();
let contract2 = keeper
.register_contract(
cache,
code_id,
Addr::unchecked("foobar"),
None,
"".to_owned(),
1000,
)
.unwrap();
let info = mock_info("foobar", &[]);
let init_msg = to_vec(&PayoutInitMessage {
payout: payout2.clone(),
Expand All @@ -849,7 +941,16 @@ mod test {
assert_payout(&keeper, cache2, &contract2, &payout2);

// create a contract on level 2
let contract3 = keeper.register_contract(cache2, code_id).unwrap();
let contract3 = keeper
.register_contract(
cache2,
code_id,
Addr::unchecked("foobar"),
None,
"".to_owned(),
1000,
)
.unwrap();
let info = mock_info("johnny", &[]);
let init_msg = to_vec(&PayoutInitMessage {
payout: payout3.clone(),
Expand Down