1use anyhow::{
2 bail,
3 Context,
4};
5use frame_support::{
6 __private::BasicExternalities,
7 pallet_prelude::Weight,
8 traits::fungible::Inspect,
9};
10use migration::v13;
11use pallet_contracts::{
12 migration,
13 Code,
14 CollectEvents,
15 Config,
16 ContractResult,
17 DebugInfo,
18 Determinism,
19 ExecReturnValue,
20};
21use sp_core::{
22 crypto::AccountId32,
23 storage::Storage,
24 H256,
25};
26use sp_runtime::{
27 DispatchError,
28 ModuleError,
29};
30use std::{
31 fmt::{
32 Display,
33 Formatter,
34 },
35 fs,
36 path::PathBuf,
37};
38use v13::ContractInfoOf;
39
40use payload::PayloadCrafter;
41
42use crate::{
43 cli::{
44 config::Configuration,
45 ziggy::ZiggyConfig,
46 },
47 contract::{
48 custom::preferences::{
49 DevelopperPreferences,
50 Preferences,
51 },
52 payload,
53 runtime::{
54 AccountId,
55 Contracts,
56 Runtime,
57 },
58 },
59 cover::trace::CoverageTrace,
60 instrumenter::instrumentation::Instrumenter,
61 ResultOf,
62};
63
64pub type BalanceOf<T> =
65 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
66
67pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
68pub type EventRecord = frame_system::EventRecord<
69 <Runtime as frame_system::Config>::RuntimeEvent,
70 <Runtime as frame_system::Config>::Hash,
71>;
72pub type ContractResponse =
73 ContractResult<Result<ExecReturnValue, DispatchError>, u128, EventRecord>;
74
75#[derive(Clone, scale_info::TypeInfo)]
76pub struct FullContractResponse(pub ContractResponse);
77
78impl Display for FullContractResponse {
79 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80 write!(
81 f,
82 "{}",
83 self.clone()
84 .debug_message()
85 .remove_cov_from_trace()
86 .replace("\n", " ")
87 )
88 }
89}
90impl FullContractResponse {
91 pub fn get_response(&self) -> &ContractResponse {
92 &self.0
93 }
94
95 pub fn from_contract_result(
96 c: ContractResult<Result<ExecReturnValue, DispatchError>, u128, EventRecord>,
97 ) -> Self {
98 Self(c)
99 }
100 pub fn result(&self) -> &Result<ExecReturnValue, DispatchError> {
101 &self.0.result
102 }
103
104 pub fn failed(&self) -> bool {
105 self.0.result.is_err()
106 }
107
108 pub fn get(&self) -> &ContractResponse {
109 &self.0
110 }
111
112 pub fn debug_message(self) -> CoverageTrace {
113 CoverageTrace::from(self.0.debug_message)
114 }
115 pub fn is_trapped(&self) -> bool {
116 if let Err(DispatchError::Module(ModuleError { message, .. })) = &self.0.result {
117 if *message == Some("ContractTrapped") {
118 return true;
119 }
120 }
121 false
122 }
123}
124
125#[derive(Clone)]
126pub struct ContractSetup {
127 pub genesis: Storage,
128 pub contract_address: AccountIdOf<Runtime>,
129 pub json_specs: String,
130 pub path_to_specs: PathBuf,
131}
132
133impl ContractSetup {
134 pub const DEFAULT_GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024);
135 pub const DEFAULT_DEPLOYER: AccountId32 = AccountId32::new([1u8; 32]);
136
137 pub fn initialize_wasm(config: ZiggyConfig) -> ResultOf<Self> {
140 let finder = Instrumenter::new(config.clone())
141 .find()
142 .context("Couldn't execute `find` for this current config")?;
143 let wasm_bytes = fs::read(&finder.wasm_path)
144 .context(format!("Couldn't read WASM from {:?}", finder.wasm_path))?;
145
146 let conf = config.config();
147 let contract_address: &AccountIdOf<Runtime> = conf.deployer_address();
148 if conf.verbose {
149 println!("🛠️Initializing contract address from the origin: {contract_address:?}");
150 }
151
152 let path_to_specs = finder.specs_path;
153 let json_specs = fs::read_to_string(&path_to_specs).context(format!(
154 "Couldn't read JSON from {path_to_specs:?}. \
155 Check that you have a WASM AND JSON file in your target/ directory"
156 ))?;
157
158 let genesis: (Storage, AccountIdOf<Runtime>) = {
159 let storage = <Preferences as DevelopperPreferences>::runtime_storage();
160
161 let mut chain = BasicExternalities::new(storage.clone());
162 let address = chain.execute_with(|| {
163 let _ = <Preferences as DevelopperPreferences>::on_contract_initialize(); if conf.verbose {
166 println!("📤 Starting upload of WASM bytes by: {contract_address:?}");
167 }
168
169 let code_hash = Self::upload(&wasm_bytes, contract_address)?;
170
171 let new_contract_address: AccountIdOf<Runtime> = Self::instantiate(&json_specs, code_hash, contract_address, conf)
172 .context("Can't fetch the contract address because of incorrect instantiation")?;
173
174 if !ContractInfoOf::<Runtime>::contains_key(&new_contract_address) {
176 bail!(
177 "Contract instantiation failed!
178 This error is likely due to a misconfigured constructor payload in the configuration file.
179 Please ensure the correct payload for the constructor (selector + parameters) is provided, just as you would for a regular deployment. You can use the `constructor_payload` field inside the TOML configuration file for this purpose.
180 To generate your payload, please use `cargo contract`, for instance
181 ```sh
182 cargo contract encode --message \"new\" --args 1234 1337 \"0x8bb565d32618e40e8b9991c00d05b52a89ddbed0c7d9103be5610ab8a713fc67\" \"0x2a18c7d454ba9cc46f97fff2f048db136d975fb1401e75c09ed03050864bcd19\" \"0xbf0108f5882aee2e97f84f054c1645c1598499e9dfcf179e367e4d41c3130ee8\" -- target/ink/multi_contract_caller.\
183 \
184 Encoded data: 9BAE9D5E...3130EE8\
185 ```"
186 );
187 }
188 Ok(new_contract_address)
189 })?;
190
191 (chain.into_storages(), address)
192 };
193
194 Ok(Self {
195 genesis: genesis.0,
196 contract_address: genesis.1,
197 json_specs,
198 path_to_specs,
199 })
200 }
201
202 pub fn call(
204 self,
205 payload: &[u8],
206 who: u8,
207 transfer_value: BalanceOf<Runtime>,
208 config: Configuration,
209 ) -> FullContractResponse {
210 FullContractResponse::from_contract_result(Contracts::bare_call(
211 AccountId32::new([who; 32]),
212 self.contract_address,
213 transfer_value,
214 config.default_gas_limit.unwrap_or(Self::DEFAULT_GAS_LIMIT),
215 Configuration::parse_balance(&config.storage_deposit_limit),
216 payload.to_owned(),
217 DebugInfo::UnsafeDebug,
218 CollectEvents::UnsafeCollect,
219 Determinism::Enforced,
220 ))
221 }
222
223 pub fn upload(wasm_bytes: &[u8], who: &AccountId) -> ResultOf<H256> {
224 let upload_result = Contracts::bare_upload_code(
225 who.clone(),
226 Vec::from(wasm_bytes),
227 None,
228 Determinism::Enforced,
229 );
230 match upload_result {
231 Ok(upload_info) => {
232 let hash = upload_info.code_hash;
233 println!("✅ Upload successful. Code hash: {hash}",);
234 Ok(hash)
235 }
236 Err(e) => {
237 bail!("❌ Upload failed for: {who:?} with error: {e:?}");
238 }
239 }
240 }
241
242 pub fn instantiate(
243 json_specs: &str,
244 code_hash: H256,
245 who: &AccountId,
246 config: &Configuration,
247 ) -> ResultOf<AccountIdOf<Runtime>> {
248 let data: Vec<u8> = if let Some(payload) = &config.constructor_payload {
249 hex::decode(payload.replace(" ", ""))
250 .context("Impossible to hex-decode this. Check your config file")?
251 } else {
252 PayloadCrafter::extract_constructor(json_specs)
253 .context("Couldn't extract the constructor from the JSON specs")?
254 .into()
255 };
256
257 let initial_value = Configuration::parse_balance(&config.instantiate_initial_value);
258
259 let instantiate = Contracts::bare_instantiate(
260 who.clone(),
261 initial_value.unwrap_or(0),
262 config.default_gas_limit.unwrap_or_default(),
263 None,
264 Code::Existing(code_hash),
265 data,
266 vec![],
267 DebugInfo::UnsafeDebug,
268 CollectEvents::UnsafeCollect,
269 );
270
271 match instantiate.result {
272 Ok(contract_info) => {
273 println!("🔍 Instantiated the contract, contract's account is {who:?}");
274 Ok(contract_info.account_id)
275 }
276 Err(e) => {
277 let debug = String::from_utf8_lossy(instantiate.debug_message.as_ref());
278 bail!("❌ Failed to instantiate the contract, double check your `constructor_payload` please ({e:?}). Details : {debug}");
279 }
280 }
281 }
282}