Skip to content

Commit 302938c

Browse files
enriquesouzagupnik
andauthored
Support for signing wasm/MsgInstantiateContract transactions (#4368)
* my tracked commit * MsgInstantiateContract * revert formatting * test * Tests fixes and broadcast * cargo fmt * remove unused --------- Co-authored-by: gupnik <[email protected]>
1 parent bb6c286 commit 302938c

File tree

6 files changed

+225
-7
lines changed

6 files changed

+225
-7
lines changed

rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,12 @@ message MsgExecuteContract {
1717
// Gap in field numbering is intentional!
1818
repeated cosmos.base.v1beta1.Coin funds = 5;
1919
}
20+
21+
message MsgInstantiateContract {
22+
string sender = 1;
23+
string admin = 2; // optional
24+
uint64 code_id = 3;
25+
string label = 4;
26+
bytes msg = 5; // JSON
27+
repeated cosmos.base.v1beta1.Coin init_funds = 6;
28+
}

rust/tw_cosmos_sdk/src/modules/tx_builder.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ where
237237
},
238238
MessageEnum::None => SigningError::err(SigningErrorType::Error_invalid_params)
239239
.context("No TX message provided"),
240+
MessageEnum::wasm_instantiate_contract_message(ref generic) => {
241+
Self::wasm_instantiate_contract_generic_msg_from_proto(coin, generic)
242+
},
240243
}
241244
}
242245

@@ -601,6 +604,50 @@ where
601604
Ok(msg.into_boxed())
602605
}
603606

607+
pub fn wasm_instantiate_contract_generic_msg_from_proto(
608+
_coin: &dyn CoinContext,
609+
generic: &Proto::mod_Message::WasmInstantiateContract<'_>,
610+
) -> SigningResult<CosmosMessageBox> {
611+
use crate::transaction::message::wasm_message_instantiate_contract::{
612+
InstantiateMsg, WasmInstantiateContractMessage,
613+
};
614+
615+
let funds = generic
616+
.init_funds
617+
.iter()
618+
.map(Self::coin_from_proto)
619+
.collect::<SigningResult<_>>()?;
620+
621+
let sender = Address::from_str(&generic.sender)
622+
.into_tw()
623+
.context("Invalid sender address")?;
624+
625+
let admin = if generic.admin.is_empty() {
626+
None
627+
} else {
628+
Some(
629+
Address::from_str(&generic.admin)
630+
.into_tw()
631+
.context("Invalid admin address")?,
632+
)
633+
};
634+
635+
let msg = WasmInstantiateContractMessage {
636+
sender,
637+
admin,
638+
code_id: generic.code_id,
639+
label: generic.label.to_string(),
640+
msg: InstantiateMsg::Json(
641+
serde_json::from_slice(&generic.msg)
642+
.into_tw()
643+
.context("Invalid JSON in msg")?,
644+
),
645+
funds,
646+
};
647+
648+
Ok(msg.into_boxed())
649+
}
650+
604651
pub fn thorchain_send_msg_from_proto(
605652
_coin: &dyn CoinContext,
606653
send: &Proto::mod_Message::THORChainSend<'_>,

rust/tw_cosmos_sdk/src/transaction/message/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod stride_message;
1818
pub mod terra_wasm_message;
1919
pub mod thorchain_message;
2020
pub mod wasm_message;
21+
pub mod wasm_message_instantiate_contract;
2122

2223
pub type ProtobufMessage = google::protobuf::Any<'static>;
2324
pub type CosmosMessageBox = Box<dyn CosmosMessage>;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
//
3+
// Copyright © 2017 Trust Wallet.
4+
5+
use crate::address::CosmosAddress;
6+
use crate::modules::serializer::protobuf_serializer::build_coin;
7+
use crate::proto::cosmwasm;
8+
use crate::transaction::message::{CosmosMessage, JsonMessage, ProtobufMessage};
9+
use crate::transaction::Coin;
10+
use serde::Serialize;
11+
use serde_json::{json, Value as Json};
12+
use tw_coin_entry::error::prelude::*;
13+
use tw_memory::Data;
14+
use tw_proto::to_any;
15+
16+
const DEFAULT_INSTANTIATE_JSON_MSG_TYPE: &str = "wasm/MsgInstantiateContract";
17+
18+
#[derive(Clone, Serialize)]
19+
#[serde(untagged)]
20+
pub enum InstantiateMsg {
21+
/// Either a regular string or a stringified JSON object.
22+
String(String),
23+
/// JSON object with a type.
24+
Json(Json),
25+
}
26+
27+
impl InstantiateMsg {
28+
/// Tries to convert [`InstantiateMsg::String`] to [`InstantiateMsg::Json`], otherwise returns the same object.
29+
pub fn try_to_json(&self) -> InstantiateMsg {
30+
if let InstantiateMsg::String(s) = self {
31+
if let Ok(json) = serde_json::from_str(s) {
32+
return InstantiateMsg::Json(json);
33+
}
34+
}
35+
self.clone()
36+
}
37+
38+
/// Creates an [`InstantiateMsg`] from a serializable payload.
39+
pub fn json<Payload: Serialize>(payload: Payload) -> SigningResult<InstantiateMsg> {
40+
let payload = serde_json::to_value(payload)
41+
.tw_err(SigningErrorType::Error_internal)
42+
.context("Error serializing message payload to JSON")?;
43+
Ok(InstantiateMsg::Json(payload))
44+
}
45+
46+
/// Converts the message to bytes.
47+
pub fn to_bytes(&self) -> Data {
48+
match self {
49+
InstantiateMsg::String(ref s) => s.as_bytes().to_vec(),
50+
InstantiateMsg::Json(ref j) => j.to_string().as_bytes().to_vec(),
51+
}
52+
}
53+
}
54+
55+
/// This message is used for instantiating a new CosmWasm contract.
56+
#[derive(Serialize)]
57+
pub struct WasmInstantiateContractMessage<Address: CosmosAddress> {
58+
pub sender: Address,
59+
/// (Optional) The address with permission to perform migrations.
60+
/// If no admin is provided, this field will be an empty string in the protobuf.
61+
pub admin: Option<Address>,
62+
/// The Code ID referencing the stored contract code.
63+
pub code_id: u64,
64+
/// A human-readable label for the contract instance.
65+
pub label: String,
66+
/// A JSON-encoded initialization message.
67+
pub msg: InstantiateMsg,
68+
/// A list of coins to be sent along with the instantiation.
69+
pub funds: Vec<Coin>,
70+
}
71+
72+
impl<Address: CosmosAddress> CosmosMessage for WasmInstantiateContractMessage<Address> {
73+
fn to_proto(&self) -> SigningResult<ProtobufMessage> {
74+
let proto_msg = cosmwasm::wasm::v1::MsgInstantiateContract {
75+
sender: self.sender.to_string().into(),
76+
admin: self
77+
.admin
78+
.as_ref()
79+
.map_or("".into(), |admin| admin.to_string().into()),
80+
code_id: self.code_id,
81+
label: self.label.clone().into(),
82+
msg: self.msg.to_bytes().into(),
83+
// Use "init_funds" here, matching the protobuf definition.
84+
init_funds: self.funds.iter().map(build_coin).collect(),
85+
};
86+
Ok(to_any(&proto_msg))
87+
}
88+
89+
fn to_json(&self) -> SigningResult<JsonMessage> {
90+
let value = json!({
91+
"sender": self.sender,
92+
"admin": self.admin,
93+
"code_id": self.code_id,
94+
"label": self.label,
95+
"msg": self.msg.try_to_json(),
96+
"init_funds": self.funds,
97+
});
98+
Ok(JsonMessage {
99+
msg_type: DEFAULT_INSTANTIATE_JSON_MSG_TYPE.to_string(),
100+
value,
101+
})
102+
}
103+
}

rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::borrow::Cow;
77
use tw_coin_entry::test_utils::test_context::TestCoinContext;
88
use tw_cosmos_sdk::context::StandardCosmosContext;
99
use tw_cosmos_sdk::modules::tx_builder::TxBuilder;
10-
use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message};
10+
use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_fee_none, make_message};
1111
use tw_cosmos_sdk::test_utils::sign_utils::{test_sign_json, test_sign_protobuf, TestInput};
1212
use tw_encoding::base64::{self, STANDARD};
1313
use tw_encoding::hex::DecodeHex;
@@ -16,6 +16,13 @@ use tw_number::U256;
1616
use tw_proto::Cosmos::Proto;
1717
use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum;
1818

19+
fn account_593_private_key() -> Cow<'static, [u8]> {
20+
"7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"
21+
.decode_hex()
22+
.unwrap()
23+
.into()
24+
}
25+
1926
fn account_336_private_key() -> Cow<'static, [u8]> {
2027
"37f0af5bc20adb6832d39368a15492cd1e9e0cc1556d4317a5f75f9ccdf525ee"
2128
.decode_hex()
@@ -124,6 +131,47 @@ fn test_wasm_execute_generic_with_coins() {
124131
});
125132
}
126133

134+
#[test]
135+
fn test_wasm_instantiate_contract() {
136+
let coin = TestCoinContext::default()
137+
.with_public_key_type(PublicKeyType::Secp256k1)
138+
.with_hrp("thor");
139+
140+
let execute_msg = r#"{"foo":"bar"}"#;
141+
142+
let contract = Proto::mod_Message::WasmInstantiateContract {
143+
sender: "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r".into(),
144+
admin: "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r".into(),
145+
code_id: 1,
146+
label: "Contract".into(),
147+
msg: Cow::Borrowed(execute_msg.as_bytes()),
148+
init_funds: vec![],
149+
..Default::default()
150+
};
151+
152+
let input = Proto::SigningInput {
153+
account_number: 593,
154+
chain_id: "thorchain-1".into(),
155+
sequence: 24,
156+
fee: Some(make_fee_none(200_000)),
157+
private_key: account_593_private_key(),
158+
mode: Proto::BroadcastMode::SYNC,
159+
messages: vec![make_message(
160+
MessageEnum::wasm_instantiate_contract_message(contract),
161+
)],
162+
..Default::default()
163+
};
164+
165+
// https://thornode.ninerealms.com/cosmos/tx/v1beta1/txs/45CECF3858D1B4C2D49A48C83082D6F018A1E06CA68B1E76F7FF86D5CEC67255
166+
test_sign_protobuf::<StandardCosmosContext>(TestInput {
167+
coin: &coin,
168+
input: input.clone(),
169+
tx: r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CqQBCqEBCigvY29zbXdhc20ud2FzbS52MS5Nc2dJbnN0YW50aWF0ZUNvbnRyYWN0EnUKK3Rob3IxejUzd3dlN21kNmNld3o5c3F3cXpuMGFhdnBhdW4wZ3cwZXhuMnISK3Rob3IxejUzd3dlN21kNmNld3o5c3F3cXpuMGFhdnBhdW4wZ3cwZXhuMnIYASIIQ29udHJhY3QqDXsiZm9vIjoiYmFyIn0SWApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3EgQKAggBGBgSBBDAmgwaQBJNYdvq5b6xOY6tZDgJo7ueYCjs4XKPwg3Np10E6T6CLO6W7SsYKh2GiSrwHvzSA5ragedrQ+h9HwaTIGTLmOE="}"#,
170+
signature: "124d61dbeae5beb1398ead643809a3bb9e6028ece1728fc20dcda75d04e93e822cee96ed2b182a1d86892af01efcd2039ada81e76b43e87d1f06932064cb98e1",
171+
signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"Ek1h2+rlvrE5jq1kOAmju55gKOzhco/CDc2nXQTpPoIs7pbtKxgqHYaJKvAe/NIDmtqB52tD6H0fBpMgZMuY4Q=="}]"#
172+
});
173+
}
174+
127175
/// TerraV2 Transfer
128176
#[test]
129177
fn test_wasm_execute_transfer() {

src/proto/Cosmos.proto

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ message Message {
232232
repeated Amount coins = 5;
233233
}
234234

235+
// MsgInstantiateContract defines a message for instantiating a new CosmWasm contract.
236+
message WasmInstantiateContract {
237+
string sender = 1;
238+
string admin = 2;
239+
uint64 code_id = 3;
240+
string label = 4;
241+
bytes msg = 5;
242+
repeated Amount init_funds = 6;
243+
}
244+
235245
message RawJSON {
236246
string type = 1;
237247
string value = 2;
@@ -284,7 +294,6 @@ message Message {
284294
REDELEGATE = 3;
285295
}
286296

287-
288297
// cosmos-sdk/MsgGrant
289298
message AuthGrant {
290299
string granter = 1;
@@ -318,8 +327,8 @@ message Message {
318327

319328
// cosmos-sdk/MsgVote defines a message to cast a vote.
320329
message MsgVote {
321-
uint64 proposal_id = 1;
322-
string voter = 2;
330+
uint64 proposal_id = 1;
331+
string voter = 2;
323332
VoteOption option = 3;
324333
}
325334

@@ -353,20 +362,21 @@ message Message {
353362
WasmExecuteContractSend wasm_execute_contract_send_message = 14;
354363
WasmExecuteContractGeneric wasm_execute_contract_generic = 15;
355364
SignDirect sign_direct_message = 16;
356-
AuthGrant auth_grant = 17;
365+
AuthGrant auth_grant = 17;
357366
AuthRevoke auth_revoke = 18;
358367
SetWithdrawAddress set_withdraw_address_message = 19;
359368
MsgVote msg_vote = 20;
360369
MsgStrideLiquidStakingStake msg_stride_liquid_staking_stake = 21;
361370
MsgStrideLiquidStakingRedeem msg_stride_liquid_staking_redeem = 22;
362371
THORChainDeposit thorchain_deposit_message = 23;
372+
WasmInstantiateContract wasm_instantiate_contract_message = 24;
363373
}
364374
}
365375

366376
// Options for transaction encoding: JSON (Amino, older) or Protobuf.
367377
enum SigningMode {
368-
JSON = 0; // JSON format, Pre-Stargate
369-
Protobuf = 1; // Protobuf-serialized (binary), Stargate
378+
JSON = 0; // JSON format, Pre-Stargate
379+
Protobuf = 1; // Protobuf-serialized (binary), Stargate
370380
}
371381

372382
enum TxHasher {

0 commit comments

Comments
 (0)