1use crate::{Error, Result, Sr25519Signer, Wallet};
36use serde::{Deserialize, Serialize};
37use subxt::{OnlineClient, PolkadotConfig};
38use tracing::{debug, info};
39
40pub type ContractAddress = [u8; 32];
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ContractMetadata {
46 pub spec: ContractSpec,
48 pub storage: StorageLayout,
50 pub types: Vec<TypeDef>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ContractSpec {
57 pub constructors: Vec<ConstructorSpec>,
59 pub messages: Vec<MessageSpec>,
61 pub events: Vec<EventSpec>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ConstructorSpec {
68 pub label: String,
70 pub selector: [u8; 4],
72 pub args: Vec<MessageArg>,
74 pub docs: Vec<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct MessageSpec {
81 pub label: String,
83 pub selector: [u8; 4],
85 pub args: Vec<MessageArg>,
87 pub return_type: Option<TypeRef>,
89 pub mutates: bool,
91 pub payable: bool,
93 pub docs: Vec<String>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MessageArg {
100 pub label: String,
102 pub type_ref: TypeRef,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct EventSpec {
109 pub label: String,
111 pub args: Vec<EventArg>,
113 pub docs: Vec<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct EventArg {
120 pub label: String,
122 pub type_ref: TypeRef,
124 pub indexed: bool,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct StorageLayout {
131 pub root: LayoutKey,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct LayoutKey {
138 pub key: String,
140 pub ty: u32,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct TypeRef {
147 pub ty: u32,
149 pub display_name: Vec<String>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct TypeDef {
156 pub id: u32,
158 pub path: Vec<String>,
160 pub params: Vec<TypeParam>,
162 pub def: TypeDefVariant,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct TypeParam {
169 pub name: String,
171 pub ty: Option<u32>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177#[serde(tag = "type")]
178pub enum TypeDefVariant {
179 Composite { fields: Vec<Field> },
180 Variant { variants: Vec<Variant> },
181 Sequence { type_param: u32 },
182 Array { len: u32, type_param: u32 },
183 Tuple { fields: Vec<u32> },
184 Primitive { primitive: String },
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct Field {
190 pub name: Option<String>,
192 pub ty: u32,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct Variant {
199 pub name: String,
201 pub fields: Vec<Field>,
203 pub index: u8,
205}
206
207#[derive(Debug, Clone, Copy, parity_scale_codec::Encode, parity_scale_codec::Decode)]
209pub struct GasLimit {
210 pub ref_time: u64,
212 pub proof_size: u64,
214}
215
216impl GasLimit {
217 pub fn new(ref_time: u64, proof_size: u64) -> Self {
219 Self {
220 ref_time,
221 proof_size,
222 }
223 }
224
225 pub fn default_call() -> Self {
227 Self {
228 ref_time: 1_000_000_000_000, proof_size: 3_145_728, }
231 }
232
233 pub fn default_deploy() -> Self {
235 Self {
236 ref_time: 5_000_000_000_000, proof_size: 10_485_760, }
239 }
240}
241
242#[derive(Debug, Clone, Copy)]
244pub enum StorageDepositLimit {
245 NoLimit,
247 Limited(u128),
249}
250
251#[allow(dead_code)]
253pub struct ContractCallBuilder {
254 contract_address: ContractAddress,
255 selector: [u8; 4],
256 args: Vec<u8>,
257 gas_limit: GasLimit,
258 storage_deposit: StorageDepositLimit,
259 value: u128,
260}
261
262impl ContractCallBuilder {
263 pub fn new(contract_address: ContractAddress, selector: [u8; 4]) -> Self {
265 Self {
266 contract_address,
267 selector,
268 args: Vec::new(),
269 gas_limit: GasLimit::default_call(),
270 storage_deposit: StorageDepositLimit::NoLimit,
271 value: 0,
272 }
273 }
274
275 pub fn args(mut self, args: &[u8]) -> Self {
277 self.args = args.to_vec();
278 self
279 }
280
281 pub fn gas_limit(mut self, limit: GasLimit) -> Self {
283 self.gas_limit = limit;
284 self
285 }
286
287 pub fn storage_deposit(mut self, limit: StorageDepositLimit) -> Self {
289 self.storage_deposit = limit;
290 self
291 }
292
293 pub fn value(mut self, value: u128) -> Self {
295 self.value = value;
296 self
297 }
298
299 pub fn build_call_data(&self) -> Vec<u8> {
301 let mut call_data = Vec::new();
302 call_data.extend_from_slice(&self.selector);
303 call_data.extend_from_slice(&self.args);
304 call_data
305 }
306}
307
308pub struct ContractClient {
310 client: OnlineClient<PolkadotConfig>,
311 address: ContractAddress,
312 metadata: Option<ContractMetadata>,
313}
314
315impl ContractClient {
316 pub fn new(client: OnlineClient<PolkadotConfig>, address: ContractAddress) -> Self {
318 Self {
319 client,
320 address,
321 metadata: None,
322 }
323 }
324
325 pub fn with_metadata(
327 client: OnlineClient<PolkadotConfig>,
328 address: ContractAddress,
329 metadata: ContractMetadata,
330 ) -> Self {
331 Self {
332 client,
333 address,
334 metadata: Some(metadata),
335 }
336 }
337
338 pub async fn deploy(
354 client: OnlineClient<PolkadotConfig>,
355 wasm_code: Vec<u8>,
356 metadata: ContractMetadata,
357 constructor_name: &str,
358 constructor_args: &[u8],
359 wallet: &Wallet,
360 salt: Option<Vec<u8>>,
361 ) -> Result<Self> {
362 info!("Deploying contract with constructor: {}", constructor_name);
363
364 let constructor = metadata
366 .spec
367 .constructors
368 .iter()
369 .find(|c| c.label == constructor_name)
370 .ok_or_else(|| {
371 Error::Transaction(format!("Constructor '{}' not found", constructor_name))
372 })?;
373
374 let mut call_data = Vec::new();
376 call_data.extend_from_slice(&constructor.selector);
377 call_data.extend_from_slice(constructor_args);
378
379 let salt = salt.unwrap_or_else(|| vec![0u8; 32]);
381
382 let gas_limit = GasLimit::default_deploy();
384 let storage_deposit = StorageDepositLimit::NoLimit;
385
386 let instantiate_call = subxt::dynamic::tx(
387 "Contracts",
388 "instantiate",
389 vec![
390 subxt::dynamic::Value::u128(0), Self::encode_gas_limit(&gas_limit)?,
392 Self::encode_storage_deposit(&storage_deposit)?,
393 subxt::dynamic::Value::from_bytes(&wasm_code),
394 subxt::dynamic::Value::from_bytes(&call_data),
395 subxt::dynamic::Value::from_bytes(&salt),
396 ],
397 );
398
399 let pair = wallet
401 .sr25519_pair()
402 .ok_or_else(|| Error::Transaction("Wallet does not have SR25519 key".to_string()))?;
403
404 let signer = Sr25519Signer::new(pair.clone());
405
406 let mut progress = client
407 .tx()
408 .sign_and_submit_then_watch_default(&instantiate_call, &signer)
409 .await
410 .map_err(|e| {
411 Error::Transaction(format!("Failed to submit deploy transaction: {}", e))
412 })?;
413
414 while let Some(event) = progress.next().await {
416 let event = event
417 .map_err(|e| Error::Transaction(format!("Deploy transaction error: {}", e)))?;
418
419 if let Some(finalized) = event.as_finalized() {
420 info!("Contract deployment finalized");
421
422 let events = finalized
424 .fetch_events()
425 .await
426 .map_err(|e| Error::Transaction(format!("Failed to get events: {}", e)))?;
427
428 for evt in events.iter() {
429 let evt = evt.map_err(|e| {
430 Error::Transaction(format!("Failed to decode event: {}", e))
431 })?;
432
433 if evt.pallet_name() == "Contracts" && evt.variant_name() == "Instantiated" {
435 debug!("Contract instantiated event found");
438
439 let field_bytes = evt.field_bytes();
443
444 if field_bytes.len() >= 64 {
448 let mut contract_address = [0u8; 32];
449 contract_address.copy_from_slice(&field_bytes[32..64]);
451 return Ok(Self::with_metadata(client, contract_address, metadata));
452 } else {
453 return Err(Error::Transaction(format!(
454 "Contract event data has unexpected length: {}",
455 field_bytes.len()
456 )));
457 }
458 }
459 }
460
461 return Err(Error::Transaction(
462 "Contract deployment succeeded but address not found in events".to_string(),
463 ));
464 }
465 }
466
467 Err(Error::Transaction(
468 "Deploy transaction stream ended without finalization".to_string(),
469 ))
470 }
471
472 pub async fn call(&self, method_name: &str, args: &[u8], wallet: &Wallet) -> Result<String> {
484 info!("Calling contract method: {}", method_name);
485
486 let message = if let Some(ref metadata) = self.metadata {
488 metadata
489 .spec
490 .messages
491 .iter()
492 .find(|m| m.label == method_name)
493 .ok_or_else(|| Error::Transaction(format!("Method '{}' not found", method_name)))?
494 } else {
495 return Err(Error::Transaction(
496 "Contract metadata not available".to_string(),
497 ));
498 };
499
500 let mut call_data = Vec::new();
502 call_data.extend_from_slice(&message.selector);
503 call_data.extend_from_slice(args);
504
505 let gas_limit = GasLimit::default_call();
507 let storage_deposit = StorageDepositLimit::NoLimit;
508
509 let call_tx = subxt::dynamic::tx(
510 "Contracts",
511 "call",
512 vec![
513 subxt::dynamic::Value::from_bytes(self.address),
514 subxt::dynamic::Value::u128(0), Self::encode_gas_limit(&gas_limit)?,
516 Self::encode_storage_deposit(&storage_deposit)?,
517 subxt::dynamic::Value::from_bytes(&call_data),
518 ],
519 );
520
521 let pair = wallet
523 .sr25519_pair()
524 .ok_or_else(|| Error::Transaction("Wallet does not have SR25519 key".to_string()))?;
525
526 let signer = Sr25519Signer::new(pair.clone());
527
528 let mut progress = self
529 .client
530 .tx()
531 .sign_and_submit_then_watch_default(&call_tx, &signer)
532 .await
533 .map_err(|e| Error::Transaction(format!("Failed to submit call transaction: {}", e)))?;
534
535 while let Some(event) = progress.next().await {
537 let event =
538 event.map_err(|e| Error::Transaction(format!("Call transaction error: {}", e)))?;
539
540 if let Some(finalized) = event.as_finalized() {
541 let tx_hash = format!("0x{}", hex::encode(finalized.extrinsic_hash()));
542 info!("Contract call finalized: {}", tx_hash);
543
544 finalized
545 .wait_for_success()
546 .await
547 .map_err(|e| Error::Transaction(format!("Contract call failed: {}", e)))?;
548
549 return Ok(tx_hash);
550 }
551 }
552
553 Err(Error::Transaction(
554 "Call transaction stream ended without finalization".to_string(),
555 ))
556 }
557
558 pub async fn read(&self, method_name: &str, args: &[u8], caller: &[u8; 32]) -> Result<Vec<u8>> {
594 debug!("Reading contract state: {}", method_name);
595
596 let message = if let Some(ref metadata) = self.metadata {
598 metadata
599 .spec
600 .messages
601 .iter()
602 .find(|m| m.label == method_name)
603 .ok_or_else(|| Error::Transaction(format!("Method '{}' not found", method_name)))?
604 } else {
605 return Err(Error::Transaction(
606 "Contract metadata not available".to_string(),
607 ));
608 };
609
610 let mut call_data = Vec::new();
612 call_data.extend_from_slice(&message.selector);
613 call_data.extend_from_slice(args);
614
615 use parity_scale_codec::Encode;
618
619 let gas_limit = GasLimit::default_call();
620 let value: u128 = 0; let storage_deposit_limit: Option<u128> = None; let mut encoded_params = Vec::new();
625 caller.encode_to(&mut encoded_params); self.address.encode_to(&mut encoded_params); value.encode_to(&mut encoded_params); gas_limit.encode_to(&mut encoded_params); storage_deposit_limit.encode_to(&mut encoded_params); call_data.encode_to(&mut encoded_params); let result_bytes = self
634 .client
635 .backend()
636 .call(
637 "ContractsApi_call",
638 Some(&encoded_params),
639 self.client
640 .backend()
641 .latest_finalized_block_ref()
642 .await?
643 .hash(),
644 )
645 .await
646 .map_err(|e| Error::Transaction(format!("ContractsApi_call failed: {}", e)))?;
647
648 let decoded_result = Self::decode_contract_result(&result_bytes)?;
656
657 Ok(decoded_result)
658 }
659
660 fn decode_contract_result(bytes: &[u8]) -> Result<Vec<u8>> {
662 use parity_scale_codec::Decode;
663
664 let mut input = bytes;
674
675 let _gas_consumed_ref_time = u64::decode(&mut input).map_err(|e| {
677 Error::Transaction(format!("Failed to decode gas_consumed.ref_time: {}", e))
678 })?;
679 let _gas_consumed_proof_size = u64::decode(&mut input).map_err(|e| {
680 Error::Transaction(format!("Failed to decode gas_consumed.proof_size: {}", e))
681 })?;
682
683 let _gas_required_ref_time = u64::decode(&mut input).map_err(|e| {
685 Error::Transaction(format!("Failed to decode gas_required.ref_time: {}", e))
686 })?;
687 let _gas_required_proof_size = u64::decode(&mut input).map_err(|e| {
688 Error::Transaction(format!("Failed to decode gas_required.proof_size: {}", e))
689 })?;
690
691 let storage_deposit_variant = u8::decode(&mut input).map_err(|e| {
694 Error::Transaction(format!("Failed to decode storage_deposit variant: {}", e))
695 })?;
696 if storage_deposit_variant <= 1 {
697 let _deposit_amount = u128::decode(&mut input).map_err(|e| {
699 Error::Transaction(format!("Failed to decode storage_deposit amount: {}", e))
700 })?;
701 }
702
703 let debug_msg = Vec::<u8>::decode(&mut input)
705 .map_err(|e| Error::Transaction(format!("Failed to decode debug_message: {}", e)))?;
706
707 if !debug_msg.is_empty() {
708 debug!(
709 "Contract debug message: {}",
710 String::from_utf8_lossy(&debug_msg)
711 );
712 }
713
714 let result_variant = u8::decode(&mut input)
716 .map_err(|e| Error::Transaction(format!("Failed to decode result variant: {}", e)))?;
717
718 if result_variant == 0 {
719 let _flags = u32::decode(&mut input)
722 .map_err(|e| Error::Transaction(format!("Failed to decode flags: {}", e)))?;
723
724 let data = Vec::<u8>::decode(&mut input)
725 .map_err(|e| Error::Transaction(format!("Failed to decode return data: {}", e)))?;
726
727 Ok(data)
728 } else {
729 Err(Error::Transaction(
731 "Contract execution failed with DispatchError".to_string(),
732 ))
733 }
734 }
735
736 pub fn address(&self) -> &ContractAddress {
738 &self.address
739 }
740
741 pub fn metadata(&self) -> Option<&ContractMetadata> {
743 self.metadata.as_ref()
744 }
745
746 fn encode_gas_limit(limit: &GasLimit) -> Result<subxt::dynamic::Value> {
749 Ok(subxt::dynamic::Value::named_composite([
750 (
751 "ref_time",
752 subxt::dynamic::Value::u128(limit.ref_time as u128),
753 ),
754 (
755 "proof_size",
756 subxt::dynamic::Value::u128(limit.proof_size as u128),
757 ),
758 ]))
759 }
760
761 fn encode_storage_deposit(limit: &StorageDepositLimit) -> Result<subxt::dynamic::Value> {
762 match limit {
763 StorageDepositLimit::NoLimit => {
764 Ok(subxt::dynamic::Value::unnamed_variant("None", vec![]))
765 }
766 StorageDepositLimit::Limited(amount) => Ok(subxt::dynamic::Value::unnamed_variant(
767 "Some",
768 vec![subxt::dynamic::Value::u128(*amount)],
769 )),
770 }
771 }
772}
773
774pub fn parse_metadata(json: &str) -> Result<ContractMetadata> {
776 serde_json::from_str(json)
777 .map_err(|e| Error::Metadata(format!("Failed to parse contract metadata: {}", e)))
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783
784 #[test]
785 fn test_gas_limit() {
786 let limit = GasLimit::default_call();
787 assert!(limit.ref_time > 0);
788 assert!(limit.proof_size > 0);
789 }
790
791 #[test]
792 fn test_contract_call_builder() {
793 let address = [1u8; 32];
794 let selector = [0x12, 0x34, 0x56, 0x78];
795
796 let builder = ContractCallBuilder::new(address, selector)
797 .args(&[1, 2, 3])
798 .value(1000);
799
800 let call_data = builder.build_call_data();
801 assert_eq!(&call_data[0..4], &selector);
802 assert_eq!(&call_data[4..], &[1, 2, 3]);
803 }
804}