use super::{
log_info,
sr25519,
ContractExecResult,
ContractInstantiateResult,
Keypair,
};
use ink_env::Environment;
use core::marker::PhantomData;
use pallet_contracts::CodeUploadResult;
use sp_core::H256;
use subxt::{
backend::{
legacy::LegacyRpcMethods,
rpc::RpcClient,
},
blocks::ExtrinsicEvents,
config::{
DefaultExtrinsicParams,
DefaultExtrinsicParamsBuilder,
ExtrinsicParams,
},
ext::scale_encode,
tx::{
Signer,
TxStatus,
},
utils::MultiAddress,
OnlineClient,
};
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Debug,
Default,
scale::Encode,
scale::Decode,
scale::MaxEncodedLen,
scale_encode::EncodeAsType,
serde::Serialize,
serde::Deserialize,
)]
#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
pub struct Weight {
#[codec(compact)]
ref_time: u64,
#[codec(compact)]
proof_size: u64,
}
impl From<sp_weights::Weight> for Weight {
fn from(weight: sp_weights::Weight) -> Self {
Self {
ref_time: weight.ref_time(),
proof_size: weight.proof_size(),
}
}
}
impl From<Weight> for sp_weights::Weight {
fn from(weight: Weight) -> Self {
sp_weights::Weight::from_parts(weight.ref_time, weight.proof_size)
}
}
#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
pub struct InstantiateWithCode<E: Environment> {
#[codec(compact)]
value: E::Balance,
gas_limit: Weight,
storage_deposit_limit: Option<E::Balance>,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
}
#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
pub struct Call<E: Environment> {
dest: MultiAddress<E::AccountId, ()>,
#[codec(compact)]
value: E::Balance,
gas_limit: Weight,
storage_deposit_limit: Option<E::Balance>,
data: Vec<u8>,
}
#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
pub struct Transfer<E: Environment, C: subxt::Config> {
dest: subxt::utils::Static<C::Address>,
#[codec(compact)]
value: E::Balance,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
serde::Serialize,
scale::Decode,
scale::Encode,
scale_encode::EncodeAsType,
)]
#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
pub enum Determinism {
Enforced,
Relaxed,
}
#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
pub struct RemoveCode<E: Environment> {
code_hash: E::Hash,
}
#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
pub struct UploadCode<E: Environment> {
code: Vec<u8>,
storage_deposit_limit: Option<E::Balance>,
determinism: Determinism,
}
#[derive(serde::Serialize, scale::Encode)]
#[serde(rename_all = "camelCase")]
struct RpcInstantiateRequest<C: subxt::Config, E: Environment> {
origin: C::AccountId,
value: E::Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<E::Balance>,
code: Code,
data: Vec<u8>,
salt: Vec<u8>,
}
#[derive(serde::Serialize, scale::Encode)]
#[serde(rename_all = "camelCase")]
struct RpcCodeUploadRequest<C: subxt::Config, E: Environment>
where
E::Balance: serde::Serialize,
{
origin: C::AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<E::Balance>,
determinism: Determinism,
}
#[derive(serde::Serialize, scale::Encode)]
#[serde(rename_all = "camelCase")]
struct RpcCallRequest<C: subxt::Config, E: Environment> {
origin: C::AccountId,
dest: E::AccountId,
value: E::Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<E::Balance>,
input_data: Vec<u8>,
}
#[derive(serde::Serialize, scale::Encode)]
#[serde(rename_all = "camelCase")]
enum Code {
Upload(Vec<u8>),
#[allow(unused)]
Existing(H256),
}
pub struct ContractsApi<C: subxt::Config, E: Environment> {
pub rpc: LegacyRpcMethods<C>,
pub client: OnlineClient<C>,
_phantom: PhantomData<fn() -> (C, E)>,
}
impl<C, E> ContractsApi<C, E>
where
C: subxt::Config,
C::AccountId: From<sr25519::PublicKey> + serde::de::DeserializeOwned + scale::Codec,
C::Address: From<sr25519::PublicKey>,
C::Signature: From<sr25519::Signature>,
<C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
E: Environment,
E::Balance: scale::HasCompact + serde::Serialize,
{
pub async fn new(rpc: RpcClient) -> Result<Self, subxt::Error> {
let client = OnlineClient::<C>::from_rpc_client(rpc.clone()).await?;
let rpc = LegacyRpcMethods::<C>::new(rpc);
Ok(Self {
rpc,
client,
_phantom: Default::default(),
})
}
pub async fn try_transfer_balance(
&self,
origin: &Keypair,
dest: C::AccountId,
value: E::Balance,
) -> Result<(), subxt::Error> {
let call = subxt::tx::Payload::new(
"Balances",
"transfer_allow_death",
Transfer::<E, C> {
dest: subxt::utils::Static(dest.into()),
value,
},
)
.unvalidated();
let _ = self.submit_extrinsic(&call, origin).await;
Ok(())
}
pub async fn instantiate_with_code_dry_run(
&self,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
signer: &Keypair,
) -> ContractInstantiateResult<E::AccountId, E::Balance, ()> {
let code = Code::Upload(code);
let call_request = RpcInstantiateRequest::<C, E> {
origin: Signer::<C>::account_id(signer),
value,
gas_limit: None,
storage_deposit_limit,
code,
data,
salt,
};
let func = "ContractsApi_instantiate";
let params = scale::Encode::encode(&call_request);
let bytes = self
.rpc
.state_call(func, Some(¶ms), None)
.await
.unwrap_or_else(|err| {
panic!("error on ws request `contracts_instantiate`: {err:?}");
});
scale::Decode::decode(&mut bytes.as_ref()).unwrap_or_else(|err| {
panic!("decoding ContractInstantiateResult failed: {err}")
})
}
pub async fn submit_extrinsic<Call>(
&self,
call: &Call,
signer: &Keypair,
) -> ExtrinsicEvents<C>
where
Call: subxt::tx::TxPayload,
{
let account_id = <Keypair as Signer<C>>::account_id(signer);
let account_nonce =
self.get_account_nonce(&account_id)
.await
.unwrap_or_else(|err| {
panic!("error calling `get_account_nonce`: {err:?}");
});
let params = DefaultExtrinsicParamsBuilder::new()
.nonce(account_nonce)
.build();
let mut tx = self
.client
.tx()
.create_signed_offline(call, signer, params.into())
.unwrap_or_else(|err| {
panic!("error on call `create_signed_with_nonce`: {err:?}");
})
.submit_and_watch()
.await
.inspect(|tx_progress| {
log_info(&format!(
"signed and submitted tx with hash {:?}",
tx_progress.extrinsic_hash()
));
})
.unwrap_or_else(|err| {
panic!("error on call `submit_and_watch`: {err:?}");
});
while let Some(status) = tx.next().await {
match status.unwrap_or_else(|err| {
panic!("error subscribing to tx status: {err:?}");
}) {
TxStatus::InBestBlock(tx_in_block)
| TxStatus::InFinalizedBlock(tx_in_block) => {
return tx_in_block.fetch_events().await.unwrap_or_else(|err| {
panic!("error on call `fetch_events`: {err:?}");
})
}
TxStatus::Error { message } => {
panic!("TxStatus::Error: {message:?}");
}
TxStatus::Invalid { message } => {
panic!("TxStatus::Invalid: {message:?}");
}
TxStatus::Dropped { message } => {
panic!("TxStatus::Dropped: {message:?}");
}
_ => continue,
}
}
panic!("Error waiting for tx status")
}
pub async fn best_block(&self) -> C::Hash {
self.rpc
.chain_get_block_hash(None)
.await
.unwrap_or_else(|err| {
panic!("error on call `chain_get_block_hash`: {err:?}");
})
.unwrap_or_else(|| {
panic!("error on call `chain_get_block_hash`: no best block found");
})
}
async fn get_account_nonce(
&self,
account_id: &C::AccountId,
) -> Result<u64, subxt::Error> {
let best_block = self.best_block().await;
let account_nonce = self
.client
.blocks()
.at(best_block)
.await?
.account_nonce(account_id)
.await?;
Ok(account_nonce)
}
#[allow(clippy::too_many_arguments)]
pub async fn instantiate_with_code(
&self,
value: E::Balance,
gas_limit: Weight,
storage_deposit_limit: Option<E::Balance>,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
signer: &Keypair,
) -> ExtrinsicEvents<C> {
let call = subxt::tx::Payload::new(
"Contracts",
"instantiate_with_code",
InstantiateWithCode::<E> {
value,
gas_limit,
storage_deposit_limit,
code,
data,
salt,
},
)
.unvalidated();
self.submit_extrinsic(&call, signer).await
}
pub async fn upload_dry_run(
&self,
signer: &Keypair,
code: Vec<u8>,
storage_deposit_limit: Option<E::Balance>,
) -> CodeUploadResult<E::Hash, E::Balance> {
let call_request = RpcCodeUploadRequest::<C, E> {
origin: Signer::<C>::account_id(signer),
code,
storage_deposit_limit,
determinism: Determinism::Enforced,
};
let func = "ContractsApi_upload_code";
let params = scale::Encode::encode(&call_request);
let bytes = self
.rpc
.state_call(func, Some(¶ms), None)
.await
.unwrap_or_else(|err| {
panic!("error on ws request `upload_code`: {err:?}");
});
scale::Decode::decode(&mut bytes.as_ref())
.unwrap_or_else(|err| panic!("decoding CodeUploadResult failed: {err}"))
}
pub async fn upload(
&self,
signer: &Keypair,
code: Vec<u8>,
storage_deposit_limit: Option<E::Balance>,
) -> ExtrinsicEvents<C> {
let call = subxt::tx::Payload::new(
"Contracts",
"upload_code",
UploadCode::<E> {
code,
storage_deposit_limit,
determinism: Determinism::Enforced,
},
)
.unvalidated();
self.submit_extrinsic(&call, signer).await
}
pub async fn remove_code(
&self,
signer: &Keypair,
code_hash: E::Hash,
) -> ExtrinsicEvents<C> {
let call = subxt::tx::Payload::new(
"Contracts",
"remove_code",
RemoveCode::<E> { code_hash },
)
.unvalidated();
self.submit_extrinsic(&call, signer).await
}
pub async fn call_dry_run(
&self,
origin: C::AccountId,
dest: E::AccountId,
input_data: Vec<u8>,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
) -> ContractExecResult<E::Balance, ()> {
let call_request = RpcCallRequest::<C, E> {
origin,
dest,
value,
gas_limit: None,
storage_deposit_limit,
input_data,
};
let func = "ContractsApi_call";
let params = scale::Encode::encode(&call_request);
let bytes = self
.rpc
.state_call(func, Some(¶ms), None)
.await
.unwrap_or_else(|err| {
panic!("error on ws request `contracts_call`: {err:?}");
});
scale::Decode::decode(&mut bytes.as_ref())
.unwrap_or_else(|err| panic!("decoding ContractExecResult failed: {err}"))
}
pub async fn call(
&self,
contract: MultiAddress<E::AccountId, ()>,
value: E::Balance,
gas_limit: Weight,
storage_deposit_limit: Option<E::Balance>,
data: Vec<u8>,
signer: &Keypair,
) -> ExtrinsicEvents<C> {
let call = subxt::tx::Payload::new(
"Contracts",
"call",
Call::<E> {
dest: contract,
value,
gas_limit,
storage_deposit_limit,
data,
},
)
.unvalidated();
self.submit_extrinsic(&call, signer).await
}
pub async fn runtime_call<'a>(
&self,
signer: &Keypair,
pallet_name: &'a str,
call_name: &'a str,
call_data: Vec<subxt::dynamic::Value>,
) -> ExtrinsicEvents<C> {
let call = subxt::dynamic::tx(pallet_name, call_name, call_data);
self.submit_extrinsic(&call, signer).await
}
}