-
Notifications
You must be signed in to change notification settings - Fork 136
/
declare.rs
186 lines (168 loc) · 6.59 KB
/
declare.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use anyhow::{anyhow, Context, Result};
use camino::Utf8PathBuf;
use cast::helpers::{response_structs::DeclareResponse, scarb_utils::get_scarb_manifest};
use cast::{handle_rpc_error, handle_wait_for_tx};
use clap::Args;
use serde::Deserialize;
use starknet::accounts::AccountError::Provider;
use starknet::accounts::ConnectedAccount;
use starknet::core::types::FieldElement;
use starknet::{
accounts::{Account, SingleOwnerAccount},
core::types::contract::{CompiledClass, SierraClass},
providers::jsonrpc::{HttpTransport, JsonRpcClient},
signers::LocalWallet,
};
use std::process::{Command, Stdio};
use std::sync::Arc;
#[allow(dead_code)]
#[derive(Deserialize)]
struct ScarbStarknetArtifacts {
version: u32,
contracts: Vec<ScarbStarknetContract>,
}
#[allow(dead_code)]
#[derive(Deserialize)]
struct ScarbStarknetContract {
id: String,
package_name: String,
contract_name: String,
artifacts: ScarbStarknetContractArtifact,
}
#[allow(dead_code)]
#[derive(Deserialize)]
struct ScarbStarknetContractArtifact {
sierra: Option<Utf8PathBuf>,
casm: Option<Utf8PathBuf>,
}
#[derive(Args)]
#[command(about = "Declare a contract to starknet", long_about = None)]
pub struct Declare {
/// Contract name
#[clap(short = 'c', long = "contract-name")]
pub contract: String,
/// Max fee for the transaction. If not provided, max fee will be automatically estimated
#[clap(short, long)]
pub max_fee: Option<FieldElement>,
}
#[allow(clippy::too_many_lines)]
pub async fn declare(
contract_name: &str,
max_fee: Option<FieldElement>,
account: &mut SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
path_to_scarb_toml: &Option<Utf8PathBuf>,
wait: bool,
) -> Result<DeclareResponse> {
let contract_name: String = contract_name.to_string();
let manifest_path = match path_to_scarb_toml.clone() {
Some(path) => path,
None => get_scarb_manifest().context("Failed to obtain manifest path from scarb")?,
};
which::which("scarb")
.context("Cannot find `scarb` binary in PATH. Make sure you have Scarb installed https://github.com/software-mansion/scarb")?;
let command_result = Command::new("scarb")
.arg("--manifest-path")
.arg(&manifest_path)
.arg("--release")
.arg("build")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.context("Failed to start building contracts with Scarb")?;
let result_code = command_result
.status
.code()
.context("Failed to obtain status code from scarb build")?;
let result_msg = String::from_utf8(command_result.stdout)?;
if result_code != 0 {
anyhow::bail!(
"Scarb build returned non-zero exit code: {} - error message: {}",
result_code,
result_msg
);
}
let metadata = scarb_metadata::MetadataCommand::new()
.inherit_stderr()
.exec()
.context("Failed to obtain scarb metadata")?;
// TODO #41 improve handling starknet artifacts
let compiled_directory = metadata
.target_dir
.map(|path| path.join("release"))
.ok_or(anyhow!("Failed to obtain path to compiled contracts"))?;
let mut paths = compiled_directory
.read_dir()
.context("Failed to read ./target/release, scarb build probably failed")?;
let starknet_artifacts = paths
.find_map(|path| {
path.ok().and_then(|entry| {
let name = entry.file_name().into_string().ok()?;
name.contains("starknet_artifacts").then_some(entry.path())
})
})
.ok_or(anyhow!("Failed to find starknet_artifacts.json file"))?;
let starknet_artifacts = std::fs::read_to_string(&starknet_artifacts)
.with_context(|| format!("Failed to read {starknet_artifacts:?} contents"))?;
let starknet_artifacts: ScarbStarknetArtifacts =
serde_json::from_str(starknet_artifacts.as_str())
.context("Failed to parse starknet_artifacts.json contents")?;
let sierra_path = starknet_artifacts
.contracts
.iter()
.find_map(|contract| {
if contract.contract_name == contract_name {
return contract.artifacts.sierra.clone();
}
None
})
.ok_or(anyhow!("Cannot find sierra artifact {contract_name} in starknet_artifacts.json - please make sure sierra is set to 'true' under your [[target.starknet-contract]] field in Scarb.toml"))?;
let casm_path = starknet_artifacts
.contracts
.iter()
.find_map(|contract| {
if contract.contract_name == contract_name {
return contract.artifacts.casm.clone();
}
None
})
.ok_or(anyhow!("Cannot find casm artifact {contract_name} in starknet_artifacts.json - please make sure casm is set to 'true' under your [[target.starknet-contract]] field in Scarb.toml"))?;
let sierra_contract_path = &compiled_directory.join(sierra_path);
let casm_contract_path = &compiled_directory.join(casm_path);
let contract_definition: SierraClass = {
let file_contents = std::fs::read(sierra_contract_path.clone())
.with_context(|| format!("Failed to read contract file: {sierra_contract_path}"))?;
serde_json::from_slice(&file_contents).with_context(|| {
format!("Failed to parse contract definition: {sierra_contract_path}")
})?
};
let casm_contract_definition: CompiledClass = {
let file_contents = std::fs::read(casm_contract_path.clone())
.with_context(|| format!("Failed to read contract file: {casm_contract_path}"))?;
serde_json::from_slice(&file_contents)
.with_context(|| format!("Failed to parse contract definition: {casm_contract_path}"))?
};
let casm_class_hash = casm_contract_definition.class_hash()?;
let declaration = account.declare(Arc::new(contract_definition.flatten()?), casm_class_hash);
let execution = if let Some(max_fee) = max_fee {
declaration.max_fee(max_fee)
} else {
declaration
};
let declared = execution.send().await;
match declared {
Ok(result) => {
handle_wait_for_tx(
account.provider(),
result.transaction_hash,
DeclareResponse {
class_hash: result.class_hash,
transaction_hash: result.transaction_hash,
},
wait,
)
.await
}
Err(Provider(error)) => handle_rpc_error(error),
_ => Err(anyhow!("Unknown RPC error")),
}
}