contract_extrinsics/
lib.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17mod balance;
18mod call;
19mod contract_artifacts;
20mod contract_info;
21mod contract_storage;
22mod env_check;
23mod error;
24pub mod events;
25pub mod extrinsic_calls;
26pub mod extrinsic_opts;
27mod instantiate;
28pub mod pallet_contracts_primitives;
29mod remove;
30mod rpc;
31pub mod upload;
32
33#[cfg(test)]
34mod contract_storage_tests;
35
36#[cfg(test)]
37#[cfg(feature = "integration-tests")]
38mod integration_tests;
39
40use env_check::compare_node_env_with_contract;
41
42use anyhow::Result;
43use contract_build::{
44    CrateMetadata,
45    Verbosity,
46    DEFAULT_KEY_COL_WIDTH,
47};
48use scale::{
49    Decode,
50    Encode,
51};
52use subxt::{
53    backend::legacy::LegacyRpcMethods,
54    blocks,
55    config::{
56        DefaultExtrinsicParams,
57        DefaultExtrinsicParamsBuilder,
58        ExtrinsicParams,
59    },
60    tx,
61    Config,
62    OnlineClient,
63};
64
65pub use balance::{
66    BalanceVariant,
67    TokenMetadata,
68};
69pub use call::{
70    CallCommandBuilder,
71    CallExec,
72};
73pub use contract_artifacts::ContractArtifacts;
74pub use contract_info::{
75    fetch_all_contracts,
76    fetch_contract_info,
77    fetch_wasm_code,
78    ContractInfo,
79    TrieId,
80};
81use contract_metadata::ContractMetadata;
82pub use contract_storage::{
83    ContractStorage,
84    ContractStorageCell,
85    ContractStorageLayout,
86    ContractStorageRpc,
87};
88pub use contract_transcode::ContractMessageTranscoder;
89pub use error::{
90    ErrorVariant,
91    GenericError,
92};
93pub use events::DisplayEvents;
94pub use extrinsic_opts::ExtrinsicOptsBuilder;
95pub use instantiate::{
96    Code,
97    InstantiateArgs,
98    InstantiateCommandBuilder,
99    InstantiateDryRunResult,
100    InstantiateExec,
101    InstantiateExecResult,
102};
103pub use remove::{
104    RemoveCommandBuilder,
105    RemoveExec,
106    RemoveResult,
107};
108
109pub use upload::{
110    UploadCommandBuilder,
111    UploadExec,
112    UploadResult,
113};
114
115pub use rpc::{
116    RawParams,
117    RpcRequest,
118};
119
120/// The Wasm code of a contract.
121#[derive(Debug, Clone)]
122pub struct WasmCode(Vec<u8>);
123
124impl WasmCode {
125    /// The hash of the contract code: uniquely identifies the contract code on-chain.
126    pub fn code_hash(&self) -> [u8; 32] {
127        contract_build::code_hash(&self.0)
128    }
129}
130
131/// Wait for the transaction to be included successfully into a block.
132///
133/// # Errors
134///
135/// If a runtime Module error occurs, this will only display the pallet and error indices.
136/// Dynamic lookups of the actual error will be available once the following issue is
137/// resolved: <https://github.com/paritytech/subxt/issues/443>.
138///
139/// # Finality
140///
141/// Currently this will report success once the transaction is included in a block. In the
142/// future there could be a flag to wait for finality before reporting success.
143async fn submit_extrinsic<C, Call, Signer>(
144    client: &OnlineClient<C>,
145    rpc: &LegacyRpcMethods<C>,
146    call: &Call,
147    signer: &Signer,
148) -> core::result::Result<blocks::ExtrinsicEvents<C>, subxt::Error>
149where
150    C: Config,
151    Call: tx::Payload,
152    Signer: tx::Signer<C>,
153    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
154        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
155{
156    let account_id = Signer::account_id(signer);
157    let account_nonce = get_account_nonce(client, rpc, &account_id).await?;
158
159    let params = DefaultExtrinsicParamsBuilder::new()
160        .nonce(account_nonce)
161        .build();
162    let mut tx = client
163        .tx()
164        .create_signed_offline(call, signer, params.into())?
165        .submit_and_watch()
166        .await?;
167
168    // Below we use the low level API to replicate the `wait_for_in_block` behaviour which
169    // was removed in subxt 0.33.0. See https://github.com/paritytech/subxt/pull/1237.
170    //
171    // We require this because we use `substrate-contracts-node` as our development node,
172    // which does not currently support finality, so we just want to wait until it is
173    // included in a block.
174    use subxt::error::{
175        RpcError,
176        TransactionError,
177    };
178    use tx::TxStatus;
179
180    while let Some(status) = tx.next().await {
181        match status? {
182            TxStatus::InBestBlock(tx_in_block)
183            | TxStatus::InFinalizedBlock(tx_in_block) => {
184                let events = tx_in_block.wait_for_success().await?;
185                return Ok(events)
186            }
187            TxStatus::Error { message } => {
188                return Err(TransactionError::Error(message).into())
189            }
190            TxStatus::Invalid { message } => {
191                return Err(TransactionError::Invalid(message).into())
192            }
193            TxStatus::Dropped { message } => {
194                return Err(TransactionError::Dropped(message).into())
195            }
196            _ => continue,
197        }
198    }
199    Err(RpcError::SubscriptionDropped.into())
200}
201
202/// Return the account nonce at the *best* block for an account ID.
203async fn get_account_nonce<C>(
204    client: &OnlineClient<C>,
205    rpc: &LegacyRpcMethods<C>,
206    account_id: &C::AccountId,
207) -> core::result::Result<u64, subxt::Error>
208where
209    C: Config,
210{
211    let best_block = rpc
212        .chain_get_block_hash(None)
213        .await?
214        .ok_or(subxt::Error::Other("Best block not found".into()))?;
215    let account_nonce = client
216        .blocks()
217        .at(best_block)
218        .await?
219        .account_nonce(account_id)
220        .await?;
221    Ok(account_nonce)
222}
223
224async fn state_call<C, A: Encode, R: Decode>(
225    rpc: &LegacyRpcMethods<C>,
226    func: &str,
227    args: A,
228) -> Result<R>
229where
230    C: Config,
231{
232    let params = args.encode();
233    let bytes = rpc.state_call(func, Some(&params), None).await?;
234    Ok(R::decode(&mut bytes.as_ref())?)
235}
236
237/// Fetch the hash of the *best* block (included but not guaranteed to be finalized).
238async fn get_best_block<C>(
239    rpc: &LegacyRpcMethods<C>,
240) -> core::result::Result<C::Hash, subxt::Error>
241where
242    C: Config,
243{
244    rpc.chain_get_block_hash(None)
245        .await?
246        .ok_or(subxt::Error::Other("Best block not found".into()))
247}
248
249fn check_env_types<C>(
250    client: &OnlineClient<C>,
251    transcoder: &ContractMessageTranscoder,
252    verbosity: &Verbosity,
253) -> Result<()>
254where
255    C: Config,
256{
257    compare_node_env_with_contract(
258        client.metadata().types(),
259        transcoder.metadata(),
260        verbosity,
261    )
262}
263
264// Converts a Url into a String representation without excluding the default port.
265pub fn url_to_string(url: &url::Url) -> String {
266    match (url.port(), url.port_or_known_default()) {
267        (None, Some(port)) => {
268            format!(
269                "{}:{port}{}",
270                &url[..url::Position::AfterHost],
271                &url[url::Position::BeforePath..]
272            )
273            .to_string()
274        }
275        _ => url.to_string(),
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn url_to_string_works() {
285        // with custom port
286        let url = url::Url::parse("ws://127.0.0.1:9944").unwrap();
287        assert_eq!(url_to_string(&url), "ws://127.0.0.1:9944/");
288
289        // with default port
290        let url = url::Url::parse("wss://127.0.0.1:443").unwrap();
291        assert_eq!(url_to_string(&url), "wss://127.0.0.1:443/");
292
293        // with default port and path
294        let url = url::Url::parse("wss://127.0.0.1:443/test/1").unwrap();
295        assert_eq!(url_to_string(&url), "wss://127.0.0.1:443/test/1");
296
297        // with default port and domain
298        let url = url::Url::parse("wss://test.io:443").unwrap();
299        assert_eq!(url_to_string(&url), "wss://test.io:443/");
300
301        // with default port, domain and path
302        let url = url::Url::parse("wss://test.io/test/1").unwrap();
303        assert_eq!(url_to_string(&url), "wss://test.io:443/test/1");
304    }
305}