mod convertible_value;
pub mod event;
use std::fmt::{Debug, Formatter};
use anyhow::{anyhow, Context, Result};
use contract_transcode::ContractMessageTranscoder;
pub use convertible_value::ConvertibleValue;
use log::info;
use pallet_contracts_primitives::ContractExecResult;
use crate::{
connections::TxInfo,
contract_transcode::Value,
pallets::contract::{ContractCallArgs, ContractRpc, ContractsUserApi},
sp_weights::weight_v2::Weight,
AccountId, Balance, ConnectionApi, SignedConnectionApi, TxStatus,
};
pub const DEFAULT_MAX_GAS: u64 = 250_000_000_000u64;
pub const DEFAULT_MAX_PROOF_SIZE: u64 = 250_000_000_000u64;
pub struct ContractInstance {
address: AccountId,
transcoder: ContractMessageTranscoder,
max_gas_override: Option<u64>,
max_proof_size_override: Option<u64>,
}
impl ContractInstance {
pub fn new(address: AccountId, metadata_path: &str) -> Result<Self> {
Ok(Self {
address,
transcoder: ContractMessageTranscoder::load(metadata_path)?,
max_gas_override: None,
max_proof_size_override: None,
})
}
pub fn override_gas_limit(&mut self, limit_override: Option<u64>) {
self.max_gas_override = limit_override;
}
pub fn override_proof_size_limit(&mut self, limit_override: Option<u64>) {
self.max_proof_size_override = limit_override;
}
pub fn address(&self) -> &AccountId {
&self.address
}
pub async fn contract_read0<
T: TryFrom<ConvertibleValue, Error = anyhow::Error>,
C: ConnectionApi,
>(
&self,
conn: &C,
message: &str,
) -> Result<T> {
self.contract_read::<String, T, C>(conn, message, &[]).await
}
pub async fn contract_read<
S: AsRef<str> + Debug,
T: TryFrom<ConvertibleValue, Error = anyhow::Error>,
C: ConnectionApi,
>(
&self,
conn: &C,
message: &str,
args: &[S],
) -> Result<T> {
self.contract_read_as(conn, message, args, self.address.clone())
.await
}
pub async fn contract_read_as<
S: AsRef<str> + Debug,
T: TryFrom<ConvertibleValue, Error = anyhow::Error>,
C: ConnectionApi,
>(
&self,
conn: &C,
message: &str,
args: &[S],
sender: AccountId,
) -> Result<T> {
let result = self
.dry_run(conn, message, args, sender)
.await?
.result
.map_err(|e| anyhow!("Contract exec failed {:?}", e))?;
let decoded = self.decode(message, result.data)?;
ConvertibleValue(decoded).try_into()?
}
pub async fn contract_exec0<C: SignedConnectionApi>(
&self,
conn: &C,
message: &str,
) -> Result<TxInfo> {
self.contract_exec::<C, String>(conn, message, &[]).await
}
pub async fn contract_exec<C: SignedConnectionApi, S: AsRef<str> + Debug>(
&self,
conn: &C,
message: &str,
args: &[S],
) -> Result<TxInfo> {
self.contract_exec_value::<C, S>(conn, message, args, 0)
.await
}
pub async fn contract_exec_value0<C: SignedConnectionApi>(
&self,
conn: &C,
message: &str,
value: Balance,
) -> Result<TxInfo> {
self.contract_exec_value::<C, String>(conn, message, &[], value)
.await
}
pub async fn contract_exec_value<C: SignedConnectionApi, S: AsRef<str> + Debug>(
&self,
conn: &C,
message: &str,
args: &[S],
value: Balance,
) -> Result<TxInfo> {
let dry_run_result = self
.dry_run(conn, message, args, conn.account_id().clone())
.await?;
let data = self.encode(message, args)?;
conn.call(
self.address.clone(),
value,
Weight {
ref_time: dry_run_result.gas_required.ref_time(),
proof_size: dry_run_result.gas_required.proof_size(),
},
None,
data,
TxStatus::Finalized,
)
.await
}
fn encode<S: AsRef<str> + Debug>(&self, message: &str, args: &[S]) -> Result<Vec<u8>> {
self.transcoder.encode(message, args)
}
fn decode(&self, message: &str, data: Vec<u8>) -> Result<Value> {
self.transcoder.decode_return(message, &mut data.as_slice())
}
async fn dry_run<S: AsRef<str> + Debug, C: ConnectionApi>(
&self,
conn: &C,
message: &str,
args: &[S],
sender: AccountId,
) -> Result<ContractExecResult<Balance>> {
let payload = self.encode(message, args)?;
let args = ContractCallArgs {
origin: sender,
dest: self.address.clone(),
value: 0,
gas_limit: None,
input_data: payload,
storage_deposit_limit: None,
};
let contract_read_result = conn
.call_and_get(args)
.await
.context("RPC request error - there may be more info in node logs.")?;
if !contract_read_result.debug_message.is_empty() {
info!(
target: "aleph_client::contract",
"Dry-run debug messages: {:?}",
core::str::from_utf8(&contract_read_result.debug_message)
.unwrap_or("<Invalid UTF8>")
.split('\n')
.filter(|m| !m.is_empty())
.collect::<Vec<_>>()
);
}
if let Ok(res) = &contract_read_result.result {
if res.did_revert() {
return Err(anyhow!("Dry-run call reverted"));
}
}
Ok(contract_read_result)
}
}
impl Debug for ContractInstance {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContractInstance")
.field("address", &self.address)
.finish()
}
}