Skip to content

Commit

Permalink
Merge pull request #360 from CosmWasm/350-extend-contract-data
Browse files Browse the repository at this point in the history
Extend `ContractData` in multi-test
  • Loading branch information
hashedone authored Jul 29, 2021
2 parents dae72a3 + bb62fce commit 8c506d6
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 59 deletions.
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
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

0 comments on commit 8c506d6

Please sign in to comment.