Skip to content

Commit ee76be0

Browse files
authored
Merge pull request #793 from CosmWasm/system-entry-point
System entry point
2 parents 1de7a27 + 2a59717 commit ee76be0

File tree

14 files changed

+249
-12
lines changed

14 files changed

+249
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ and this project adheres to
3939
different implementation) ([#692], [#711], [#714])
4040
- cosmwasm-std: Added new `WasmMsg::Migrate` variant that allows one contract
4141
(eg. multisig) be the admin and migrate another contract ([#768])
42+
- cosmwasm-std: Added optional `system` entry point that can only be called by
43+
native (blockchain) modules to expose admin functionality if desired. ([#793])
4244

4345
[#692]: https://github.com/CosmWasm/cosmwasm/issues/692
4446
[#706]: https://github.com/CosmWasm/cosmwasm/pull/706
@@ -47,6 +49,7 @@ and this project adheres to
4749
[#714]: https://github.com/CosmWasm/cosmwasm/pull/714
4850
[#716]: https://github.com/CosmWasm/cosmwasm/pull/716
4951
[#768]: https://github.com/CosmWasm/cosmwasm/pull/768
52+
[#793]: https://github.com/CosmWasm/cosmwasm/pull/793
5053

5154
### Changed
5255

contracts/hackatom/examples/schema.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use std::fs::create_dir_all;
44
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
55
use cosmwasm_std::BalanceResponse;
66

7-
use hackatom::contract::{HandleMsg, InitMsg, MigrateMsg, QueryMsg, State, VerifierResponse};
7+
use hackatom::contract::{
8+
HandleMsg, InitMsg, MigrateMsg, QueryMsg, State, SystemMsg, VerifierResponse,
9+
};
810

911
fn main() {
1012
let mut out_dir = current_dir().unwrap();
@@ -15,6 +17,7 @@ fn main() {
1517
export_schema(&schema_for!(InitMsg), &out_dir);
1618
export_schema(&schema_for!(HandleMsg), &out_dir);
1719
export_schema(&schema_for!(MigrateMsg), &out_dir);
20+
export_schema(&schema_for!(SystemMsg), &out_dir);
1821
export_schema(&schema_for!(QueryMsg), &out_dir);
1922
export_schema(&schema_for!(State), &out_dir);
2023
export_schema(&schema_for!(VerifierResponse), &out_dir);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "SystemMsg",
4+
"description": "SystemMsg is only expose for internal sdk modules to call. This is showing how we can expose \"admin\" functionality than can not be called by external users or contracts, but only trusted (native/Go) code in the blockchain",
5+
"anyOf": [
6+
{
7+
"type": "object",
8+
"required": [
9+
"StealFunds"
10+
],
11+
"properties": {
12+
"StealFunds": {
13+
"type": "object",
14+
"required": [
15+
"amount",
16+
"recipient"
17+
],
18+
"properties": {
19+
"amount": {
20+
"type": "array",
21+
"items": {
22+
"$ref": "#/definitions/Coin"
23+
}
24+
},
25+
"recipient": {
26+
"$ref": "#/definitions/HumanAddr"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
],
33+
"definitions": {
34+
"Coin": {
35+
"type": "object",
36+
"required": [
37+
"amount",
38+
"denom"
39+
],
40+
"properties": {
41+
"amount": {
42+
"$ref": "#/definitions/Uint128"
43+
},
44+
"denom": {
45+
"type": "string"
46+
}
47+
}
48+
},
49+
"HumanAddr": {
50+
"type": "string"
51+
},
52+
"Uint128": {
53+
"type": "string"
54+
}
55+
}
56+
}

contracts/hackatom/src/contract.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize};
55
use sha2::{Digest, Sha256};
66

77
use cosmwasm_std::{
8-
from_slice, to_binary, to_vec, AllBalanceResponse, Api, BankMsg, Binary, CanonicalAddr, Deps,
9-
DepsMut, Env, HumanAddr, MessageInfo, QueryRequest, QueryResponse, Response, StdError,
10-
StdResult, WasmQuery,
8+
entry_point, from_slice, to_binary, to_vec, AllBalanceResponse, Api, BankMsg, Binary,
9+
CanonicalAddr, Coin, Deps, DepsMut, Env, HumanAddr, MessageInfo, QueryRequest, QueryResponse,
10+
Response, StdError, StdResult, WasmQuery,
1111
};
1212

1313
use crate::errors::HackError;
@@ -30,6 +30,17 @@ pub struct MigrateMsg {
3030
pub verifier: HumanAddr,
3131
}
3232

33+
/// SystemMsg is only expose for internal sdk modules to call.
34+
/// This is showing how we can expose "admin" functionality than can not be called by
35+
/// external users or contracts, but only trusted (native/Go) code in the blockchain
36+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
37+
pub enum SystemMsg {
38+
StealFunds {
39+
recipient: HumanAddr,
40+
amount: Vec<Coin>,
41+
},
42+
}
43+
3344
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
3445
pub struct State {
3546
pub verifier: CanonicalAddr,
@@ -122,6 +133,21 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, Ha
122133
Ok(Response::default())
123134
}
124135

136+
#[entry_point]
137+
pub fn system(_deps: DepsMut, _env: Env, msg: SystemMsg) -> Result<Response, HackError> {
138+
match msg {
139+
SystemMsg::StealFunds { recipient, amount } => {
140+
let msg = BankMsg::Send {
141+
to_address: recipient,
142+
amount,
143+
};
144+
let mut response = Response::default();
145+
response.add_message(msg);
146+
Ok(response)
147+
}
148+
}
149+
}
150+
125151
pub fn handle(
126152
deps: DepsMut,
127153
env: Env,
@@ -433,6 +459,34 @@ mod tests {
433459
assert_eq!(query_response.verifier, new_verifier);
434460
}
435461

462+
#[test]
463+
fn system_can_steal_tokens() {
464+
let mut deps = mock_dependencies(&[]);
465+
466+
let verifier = HumanAddr::from("verifies");
467+
let beneficiary = HumanAddr::from("benefits");
468+
let creator = HumanAddr::from("creator");
469+
let msg = InitMsg {
470+
verifier: verifier.clone(),
471+
beneficiary,
472+
};
473+
let info = mock_info(creator.as_str(), &[]);
474+
let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
475+
assert_eq!(0, res.messages.len());
476+
477+
// system takes any tax it wants
478+
let to_address = HumanAddr::from("community-pool");
479+
let amount = coins(700, "gold");
480+
let sys_msg = SystemMsg::StealFunds {
481+
recipient: to_address.clone(),
482+
amount: amount.clone(),
483+
};
484+
let res = system(deps.as_mut(), mock_env(), sys_msg.clone()).unwrap();
485+
assert_eq!(1, res.messages.len());
486+
let msg = res.messages.get(0).expect("no message");
487+
assert_eq!(msg, &BankMsg::Send { to_address, amount }.into(),);
488+
}
489+
436490
#[test]
437491
fn querier_callbacks_work() {
438492
let rich_addr = HumanAddr::from("foobar");

contracts/hackatom/tests/integration.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ use cosmwasm_vm::{
2525
call_handle, from_slice,
2626
testing::{
2727
handle, init, migrate, mock_env, mock_info, mock_instance, mock_instance_with_balances,
28-
query, test_io, MOCK_CONTRACT_ADDR,
28+
query, system, test_io, MOCK_CONTRACT_ADDR,
2929
},
3030
BackendApi, Storage, VmError,
3131
};
3232

33-
use hackatom::contract::{HandleMsg, InitMsg, MigrateMsg, QueryMsg, State, CONFIG_KEY};
33+
use hackatom::contract::{HandleMsg, InitMsg, MigrateMsg, QueryMsg, State, SystemMsg, CONFIG_KEY};
3434

3535
static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/hackatom.wasm");
3636

@@ -145,6 +145,34 @@ fn migrate_verifier() {
145145
);
146146
}
147147

148+
#[test]
149+
fn system_can_steal_tokens() {
150+
let mut deps = mock_instance(WASM, &[]);
151+
152+
let verifier = HumanAddr::from("verifies");
153+
let beneficiary = HumanAddr::from("benefits");
154+
let creator = HumanAddr::from("creator");
155+
let msg = InitMsg {
156+
verifier: verifier.clone(),
157+
beneficiary,
158+
};
159+
let info = mock_info(creator.as_str(), &[]);
160+
let res: Response = init(&mut deps, mock_env(), info, msg).unwrap();
161+
assert_eq!(0, res.messages.len());
162+
163+
// system takes any tax it wants
164+
let to_address = HumanAddr::from("community-pool");
165+
let amount = coins(700, "gold");
166+
let sys_msg = SystemMsg::StealFunds {
167+
recipient: to_address.clone(),
168+
amount: amount.clone(),
169+
};
170+
let res: Response = system(&mut deps, mock_env(), sys_msg.clone()).unwrap();
171+
assert_eq!(1, res.messages.len());
172+
let msg = res.messages.get(0).expect("no message");
173+
assert_eq!(msg, &BankMsg::Send { to_address, amount }.into(),);
174+
}
175+
148176
#[test]
149177
fn querier_callbacks_work() {
150178
let rich_addr = HumanAddr::from("foobar");

packages/std/src/exports.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ where
136136
release_buffer(v) as u32
137137
}
138138

139+
/// do_system should be wrapped in an external "C" export, containing a contract-specific function as arg
140+
///
141+
/// - `M`: message type for request
142+
/// - `C`: custom response message type (see CosmosMsg)
143+
/// - `E`: error type for responses
144+
pub fn do_system<M, C, E>(
145+
system_fn: &dyn Fn(DepsMut, Env, M) -> Result<Response<C>, E>,
146+
env_ptr: u32,
147+
msg_ptr: u32,
148+
) -> u32
149+
where
150+
M: DeserializeOwned + JsonSchema,
151+
C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema,
152+
E: ToString,
153+
{
154+
let res = _do_system(system_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
155+
let v = to_vec(&res).unwrap();
156+
release_buffer(v) as u32
157+
}
158+
139159
/// do_query should be wrapped in an external "C" export, containing a contract-specific function as arg
140160
///
141161
/// - `M`: message type for request
@@ -220,6 +240,26 @@ where
220240
migrate_fn(deps.as_mut(), env, msg).into()
221241
}
222242

243+
fn _do_system<M, C, E>(
244+
system_fn: &dyn Fn(DepsMut, Env, M) -> Result<Response<C>, E>,
245+
env_ptr: *mut Region,
246+
msg_ptr: *mut Region,
247+
) -> ContractResult<Response<C>>
248+
where
249+
M: DeserializeOwned + JsonSchema,
250+
C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema,
251+
E: ToString,
252+
{
253+
let env: Vec<u8> = unsafe { consume_region(env_ptr) };
254+
let msg: Vec<u8> = unsafe { consume_region(msg_ptr) };
255+
256+
let env: Env = try_into_contract_result!(from_slice(&env));
257+
let msg: M = try_into_contract_result!(from_slice(&msg));
258+
259+
let mut deps = make_dependencies();
260+
system_fn(deps.as_mut(), env, msg).into()
261+
}
262+
223263
fn _do_query<M, E>(
224264
query_fn: &dyn Fn(Deps, Env, M) -> Result<QueryResponse, E>,
225265
env_ptr: *mut Region,

packages/std/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ mod imports;
6262
mod memory; // Used by exports and imports only. This assumes pointers are 32 bit long, which makes it untestable on dev machines.
6363

6464
#[cfg(target_arch = "wasm32")]
65-
pub use crate::exports::{do_handle, do_init, do_migrate, do_query};
65+
pub use crate::exports::{do_handle, do_init, do_migrate, do_query, do_system};
6666
#[cfg(target_arch = "wasm32")]
6767
pub use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage};
6868

packages/vm/src/calls.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::serde::{from_slice, to_vec};
1414
const MAX_LENGTH_INIT: usize = 100_000;
1515
const MAX_LENGTH_HANDLE: usize = 100_000;
1616
const MAX_LENGTH_MIGRATE: usize = 100_000;
17+
const MAX_LENGTH_SYSTEM: usize = 100_000;
1718
const MAX_LENGTH_QUERY: usize = 100_000;
1819

1920
pub fn call_init<A, S, Q, U>(
@@ -71,6 +72,23 @@ where
7172
Ok(result)
7273
}
7374

75+
pub fn call_system<A, S, Q, U>(
76+
instance: &mut Instance<A, S, Q>,
77+
env: &Env,
78+
msg: &[u8],
79+
) -> VmResult<ContractResult<Response<U>>>
80+
where
81+
A: BackendApi + 'static,
82+
S: Storage + 'static,
83+
Q: Querier + 'static,
84+
U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq,
85+
{
86+
let env = to_vec(env)?;
87+
let data = call_system_raw(instance, &env, msg)?;
88+
let result: ContractResult<Response<U>> = from_slice(&data)?;
89+
Ok(result)
90+
}
91+
7492
pub fn call_query<A, S, Q>(
7593
instance: &mut Instance<A, S, Q>,
7694
env: &Env,
@@ -144,6 +162,22 @@ where
144162
call_raw(instance, "migrate", &[env, msg], MAX_LENGTH_MIGRATE)
145163
}
146164

165+
/// Calls Wasm export "system" and returns raw data from the contract.
166+
/// The result is length limited to prevent abuse but otherwise unchecked.
167+
pub fn call_system_raw<A, S, Q>(
168+
instance: &mut Instance<A, S, Q>,
169+
env: &[u8],
170+
msg: &[u8],
171+
) -> VmResult<Vec<u8>>
172+
where
173+
A: BackendApi + 'static,
174+
S: Storage + 'static,
175+
Q: Querier + 'static,
176+
{
177+
instance.set_storage_readonly(false);
178+
call_raw(instance, "system", &[env, msg], MAX_LENGTH_SYSTEM)
179+
}
180+
147181
/// Calls Wasm export "query" and returns raw data from the contract.
148182
/// The result is length limited to prevent abuse but otherwise unchecked.
149183
pub fn call_query_raw<A, S, Q>(

packages/vm/src/instance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ mod singlepass_tests {
811811
.unwrap();
812812

813813
let handle_used = gas_before_handle - instance.get_gas_left();
814-
assert_eq!(handle_used, 165757);
814+
assert_eq!(handle_used, 165794);
815815
}
816816

817817
#[test]

packages/vm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub use crate::backend::{
2929
pub use crate::cache::{AnalysisReport, Cache, CacheOptions, Stats};
3030
pub use crate::calls::{
3131
call_handle, call_handle_raw, call_init, call_init_raw, call_migrate, call_migrate_raw,
32-
call_query, call_query_raw,
32+
call_query, call_query_raw, call_system, call_system_raw,
3333
};
3434
pub use crate::checksum::Checksum;
3535

packages/vm/src/static_analysis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ mod tests {
7373
false
7474
}
7575
});
76-
assert_eq!(exported_functions.count(), 7); // 6 required export plus "migrate"
76+
assert_eq!(exported_functions.count(), 8); // 6 required export plus "migrate" and "system"
7777

7878
let exported_memories = module
7979
.export_section()

0 commit comments

Comments
 (0)