instantiate.rs 13 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of cargo-contract.
//
// cargo-contract is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// cargo-contract is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.

use super::{
18
19
    display_contract_exec_result,
    display_events,
20
    dry_run_error_details,
21
22
23
24
25
26
27
28
29
30
    parse_balance,
    runtime_api::api,
    wait_for_success_and_handle_error,
    Balance,
    CodeHash,
    ContractAccount,
    ContractMessageTranscoder,
    ExtrinsicOpts,
    PairSigner,
    RuntimeApi,
31
    RuntimeDispatchError,
32
33
    EXEC_RESULT_MAX_KEY_COL_WIDTH,
};
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
use crate::{
    name_value_println,
    util::decode_hex,
    Verbosity,
};
use anyhow::{
    anyhow,
    Context,
    Result,
};
use jsonrpsee::{
    core::client::ClientT,
    rpc_params,
    ws_client::WsClientBuilder,
};
49
50
51
52
use pallet_contracts_primitives::{
    ContractResult,
    InstantiateReturnValue,
};
53
use serde::Serialize;
54
55
56
57
use sp_core::{
    crypto::Ss58Codec,
    Bytes,
};
58
59
use std::{
    fs,
60
61
62
63
    path::{
        Path,
        PathBuf,
    },
64
    result,
65
66
67
68
69
70
};
use subxt::{
    rpc::NumberOrHex,
    ClientBuilder,
    Config,
    DefaultConfig,
71
72
};

73
74
75
76
type ContractInstantiateResult = ContractResult<
    result::Result<InstantiateReturnValue<ContractAccount>, RuntimeDispatchError>,
    Balance,
>;
77

78
#[derive(Debug, clap::Args)]
79
pub struct InstantiateCommand {
80
    /// Path to Wasm contract code, defaults to `./target/ink/<name>.wasm`.
81
82
    /// Use to instantiate contracts which have not yet been uploaded.
    /// If the contract has already been uploaded use `--code-hash` instead.
83
    #[clap(parse(from_os_str))]
84
85
86
87
    wasm_path: Option<PathBuf>,
    /// The hash of the smart contract code already uploaded to the chain.
    /// If the contract has not already been uploaded use `--wasm-path` or run the `upload` command
    /// first.
88
    #[clap(long, parse(try_from_str = parse_code_hash))]
89
90
    code_hash: Option<<DefaultConfig as Config>::Hash>,
    /// The name of the contract constructor to call
91
    #[clap(name = "constructor", long, default_value = "new")]
92
93
    constructor: String,
    /// The constructor arguments, encoded as strings
94
    #[clap(long, multiple_values = true)]
95
    args: Vec<String>,
96
    #[clap(flatten)]
97
98
    extrinsic_opts: ExtrinsicOpts,
    /// Transfers an initial balance to the instantiated contract
99
    #[clap(name = "value", long, default_value = "0", parse(try_from_str = parse_balance))]
100
101
    value: Balance,
    /// Maximum amount of gas to be used for this command
102
    #[clap(name = "gas", long, default_value = "50000000000")]
103
104
105
    gas_limit: u64,
    /// A salt used in the address derivation of the new contract. Use to create multiple instances
    /// of the same contract code from the same account.
106
    #[clap(long, parse(try_from_str = parse_hex_bytes))]
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
    salt: Option<Bytes>,
}

/// Parse a hex encoded 32 byte hash. Returns error if not exactly 32 bytes.
fn parse_code_hash(input: &str) -> Result<<DefaultConfig as Config>::Hash> {
    let bytes = decode_hex(input)?;
    if bytes.len() != 32 {
        anyhow::bail!("Code hash should be 32 bytes in length")
    }
    let mut arr = [0u8; 32];
    arr.copy_from_slice(&bytes);
    Ok(arr.into())
}

/// Parse hex encoded bytes.
fn parse_hex_bytes(input: &str) -> Result<Bytes> {
    let bytes = decode_hex(input)?;
    Ok(bytes.into())
}

impl InstantiateCommand {
    /// Instantiate a contract stored at the supplied code hash.
    /// Returns the account id of the instantiated contract if successful.
    ///
    /// Creates an extrinsic with the `Contracts::instantiate` Call, submits via RPC, then waits for
    /// the `ContractsEvent::Instantiated` event.
    pub fn run(&self) -> Result<()> {
        let (crate_metadata, contract_metadata) =
            super::load_metadata(self.extrinsic_opts.manifest_path.as_ref())?;
        let transcoder = ContractMessageTranscoder::new(&contract_metadata);
        let data = transcoder.encode(&self.constructor, &self.args)?;
        let signer = super::pair_signer(self.extrinsic_opts.signer()?);
139
        let url = self.extrinsic_opts.url_to_string();
140
141
142
143
144
145
146
147
148
149
        let verbosity = self.extrinsic_opts.verbosity()?;

        fn load_code(wasm_path: &Path) -> Result<Code> {
            log::info!("Contract code path: {}", wasm_path.display());
            let code = fs::read(&wasm_path)
                .context(format!("Failed to read from {}", wasm_path.display()))?;
            Ok(Code::Upload(code.into()))
        }

        let code = match (self.wasm_path.as_ref(), self.code_hash.as_ref()) {
150
151
152
153
154
            (Some(_), Some(_)) => {
                Err(anyhow!(
                    "Specify either `--wasm-path` or `--code-hash` but not both"
                ))
            }
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
            (Some(wasm_path), None) => load_code(wasm_path),
            (None, None) => {
                // default to the target contract wasm in the current project,
                // inferred via the crate metadata.
                load_code(&crate_metadata.dest_wasm)
            }
            (None, Some(code_hash)) => Ok(Code::Existing(*code_hash)),
        }?;
        let salt = self.salt.clone().unwrap_or_else(|| Bytes(Vec::new()));

        let args = InstantiateArgs {
            value: self.value,
            gas_limit: self.gas_limit,
            storage_deposit_limit: self.extrinsic_opts.storage_deposit_limit,
            data,
            salt,
        };

        let exec = Exec {
            args,
            url,
            verbosity,
            signer,
            transcoder,
        };

181
182
183
        async_std::task::block_on(async move {
            exec.exec(code, self.extrinsic_opts.dry_run).await
        })
184
185
186
187
188
189
190
191
192
193
194
195
196
197
    }
}

struct InstantiateArgs {
    value: super::Balance,
    gas_limit: u64,
    storage_deposit_limit: Option<Balance>,
    data: Vec<u8>,
    salt: Bytes,
}

pub struct Exec<'a> {
    args: InstantiateArgs,
    verbosity: Verbosity,
198
    url: String,
199
200
201
202
203
204
205
    signer: PairSigner,
    transcoder: ContractMessageTranscoder<'a>,
}

impl<'a> Exec<'a> {
    async fn subxt_api(&self) -> Result<RuntimeApi> {
        let api = ClientBuilder::new()
206
            .set_url(&self.url)
207
208
209
210
211
212
213
            .build()
            .await?
            .to_runtime_api::<RuntimeApi>();
        Ok(api)
    }

    async fn exec(&self, code: Code, dry_run: bool) -> Result<()> {
214
        log::debug!("instantiate data {:?}", self.args.data);
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        if dry_run {
            let result = self.instantiate_dry_run(code).await?;
            match result.result {
                Ok(ref ret_val) => {
                    name_value_println!(
                        "Result",
                        String::from("Success!"),
                        EXEC_RESULT_MAX_KEY_COL_WIDTH
                    );
                    name_value_println!(
                        "Contract",
                        ret_val.account_id.to_ss58check(),
                        EXEC_RESULT_MAX_KEY_COL_WIDTH
                    );
                    name_value_println!(
                        "Reverted",
                        format!("{:?}", ret_val.result.did_revert()),
                        EXEC_RESULT_MAX_KEY_COL_WIDTH
                    );
                    name_value_println!(
                        "Data",
                        format!("{:?}", ret_val.result.data),
                        EXEC_RESULT_MAX_KEY_COL_WIDTH
                    );
                }
240
241
242
243
                Err(ref err) => {
                    let err =
                        dry_run_error_details(&self.subxt_api().await?, err).await?;
                    name_value_println!("Result", err, EXEC_RESULT_MAX_KEY_COL_WIDTH);
244
245
246
                }
            }
            display_contract_exec_result(&result)?;
247
            return Ok(())
248
249
250
251
        }

        match code {
            Code::Upload(code) => {
252
253
                let (code_hash, contract_account) =
                    self.instantiate_with_code(code).await?;
254
255
256
                if let Some(code_hash) = code_hash {
                    name_value_println!("Code hash", format!("{:?}", code_hash));
                }
257
258
259
260
261
262
263
264
265
266
                name_value_println!("Contract", contract_account.to_ss58check());
            }
            Code::Existing(code_hash) => {
                let contract_account = self.instantiate(code_hash).await?;
                name_value_println!("Contract", contract_account.to_ss58check());
            }
        }
        Ok(())
    }

267
268
269
    async fn instantiate_with_code(
        &self,
        code: Bytes,
270
    ) -> Result<(Option<CodeHash>, ContractAccount)> {
271
272
273
274
275
276
277
278
279
280
281
        let api = self.subxt_api().await?;
        let tx_progress = api
            .tx()
            .contracts()
            .instantiate_with_code(
                self.args.value,
                self.args.gas_limit,
                self.args.storage_deposit_limit,
                code.to_vec(),
                self.args.data.clone(),
                self.args.salt.0.clone(),
282
            )?
Andrew Jones's avatar
Andrew Jones committed
283
            .sign_and_submit_then_watch_default(&self.signer)
284
285
286
287
288
289
290
291
            .await?;

        let result = wait_for_success_and_handle_error(tx_progress).await?;

        let metadata = api.client.metadata();

        display_events(&result, &self.transcoder, metadata, &self.verbosity)?;

292
293
        // The CodeStored event is only raised if the contract has not already been uploaded.
        let code_hash = result
294
            .find_first::<api::contracts::events::CodeStored>()?
295
296
            .map(|code_stored| code_stored.code_hash);

297
        let instantiated = result
298
            .find_first::<api::contracts::events::Instantiated>()?
Andrew Jones's avatar
Andrew Jones committed
299
            .ok_or_else(|| anyhow!("Failed to find Instantiated event"))?;
300

301
        Ok((code_hash, instantiated.contract))
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    }

    async fn instantiate(&self, code_hash: CodeHash) -> Result<ContractAccount> {
        let api = self.subxt_api().await?;
        let tx_progress = api
            .tx()
            .contracts()
            .instantiate(
                self.args.value,
                self.args.gas_limit,
                self.args.storage_deposit_limit,
                code_hash,
                self.args.data.clone(),
                self.args.salt.0.clone(),
316
            )?
Andrew Jones's avatar
Andrew Jones committed
317
            .sign_and_submit_then_watch_default(&self.signer)
318
319
320
321
322
323
324
325
            .await?;

        let result = wait_for_success_and_handle_error(tx_progress).await?;

        let metadata = api.client.metadata();
        display_events(&result, &self.transcoder, metadata, &self.verbosity)?;

        let instantiated = result
326
            .find_first::<api::contracts::events::Instantiated>()?
Andrew Jones's avatar
Andrew Jones committed
327
            .ok_or_else(|| anyhow!("Failed to find Instantiated event"))?;
328
329
330
331
332

        Ok(instantiated.contract)
    }

    async fn instantiate_dry_run(&self, code: Code) -> Result<ContractInstantiateResult> {
333
        let cli = WsClientBuilder::default().build(&self.url).await?;
334
335
336
337
338
339
340
341
342
343
344
345
346
347
        let storage_deposit_limit = self
            .args
            .storage_deposit_limit
            .as_ref()
            .map(|limit| NumberOrHex::Hex((*limit).into()));
        let call_request = InstantiateRequest {
            origin: self.signer.account_id().clone(),
            value: NumberOrHex::Hex(self.args.value.into()),
            gas_limit: NumberOrHex::Number(self.args.gas_limit),
            storage_deposit_limit,
            code,
            data: self.args.data.clone().into(),
            salt: self.args.salt.clone(),
        };
348
        let params = rpc_params![call_request];
349
350
351
352
        let result: ContractInstantiateResult = cli
            .request("contracts_instantiate", params)
            .await
            .context("contracts_instantiate RPC error")?;
353

354
355
356
357
        Ok(result)
    }
}

358
/// A struct that encodes RPC parameters required to instantiate a new smart contract.
359
360
361
362
363
364
365
366
367
368
369
370
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct InstantiateRequest {
    origin: <DefaultConfig as Config>::AccountId,
    value: NumberOrHex,
    gas_limit: NumberOrHex,
    storage_deposit_limit: Option<NumberOrHex>,
    code: Code,
    data: Bytes,
    salt: Bytes,
}

371
/// Reference to an existing code hash or a new Wasm module.
372
373
374
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
enum Code {
375
    /// A Wasm module as raw bytes.
376
    Upload(Bytes),
377
    /// The code hash of an on-chain Wasm blob.
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
    Existing(<DefaultConfig as Config>::Hash),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_code_hash_works() {
        // with 0x prefix
        assert!(parse_code_hash(
            "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
        )
        .is_ok());
        // without 0x prefix
393
394
        assert!(parse_code_hash(
            "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
395
        )
396
        .is_ok())
397
398
    }
}