1use crate::utils::{find_up, FoundryConfig};
2use crate::{lookup, Bytes32, Info, EVM};
3use anyhow::{anyhow, Context, Result};
4use serde::Deserialize;
5use std::fs;
6use std::path::PathBuf;
7use zinkc::{Artifact, Compiler, Config, Constructor, InitStorage};
8
9#[derive(Deserialize)]
11struct BytecodeObject {
12 object: String,
13}
14
15#[derive(Deserialize)]
17pub struct FoundryOutput {
18 bytecode: BytecodeObject,
19}
20
21impl FoundryOutput {
22 pub fn bytecode(&self) -> &str {
24 &self.bytecode.object
25 }
26}
27
28#[derive(Default)]
30pub struct Contract {
31 pub dispatcher: bool,
33 pub artifact: Artifact,
35 pub wasm: Vec<u8>,
37 pub constructor: Constructor,
39 pub address: [u8; 20],
41}
42
43impl<T> From<T> for Contract
44where
45 T: AsRef<[u8]>,
46{
47 fn from(wasm: T) -> Self {
48 crate::setup_logger();
49
50 Self {
51 wasm: wasm.as_ref().into(),
52 dispatcher: true,
53 ..Default::default()
54 }
55 }
56}
57
58impl Contract {
59 pub fn find_foundry_outputs() -> Result<Vec<(String, PathBuf, Vec<u8>)>> {
61 let foundry_toml_path = find_up("foundry.toml")?;
63 let foundry_toml_content =
64 fs::read_to_string(&foundry_toml_path).context("Failed to read foundry.toml")?;
65 let foundry_config: FoundryConfig =
66 toml::from_str(&foundry_toml_content).context("Failed to parse foundry.toml")?;
67
68 let out_dir = foundry_config
70 .profile
71 .default
72 .out
73 .unwrap_or_else(|| "out".to_string());
74 let out_path = foundry_toml_path.parent().unwrap().join(&out_dir);
75
76 let mut outputs = Vec::new();
77 for entry in fs::read_dir(&out_path).context("Failed to read Foundry out directory")? {
78 let entry = entry?;
79 let path = entry.path();
80 if path.is_dir() {
81 if path.file_name().and_then(|s| s.to_str()) == Some("build-info") {
83 continue;
84 }
85 for sub_entry in fs::read_dir(&path)? {
87 let sub_entry = sub_entry.context("Failed to read directory entry")?;
88 let sub_path = sub_entry.path();
89 if sub_path.extension().and_then(|s| s.to_str()) == Some("json") {
90 let file_name = sub_path
91 .file_name()
92 .context("Failed to get file name")?
93 .to_str()
94 .context("File name is not valid UTF-8")?;
95 let Some(contract_name) = file_name.strip_suffix(".json") else {
96 continue;
97 };
98 if contract_name
100 .chars()
101 .all(|c| c.is_alphanumeric() || c == '_')
102 {
103 let content = fs::read_to_string(&sub_path)?;
104 let output: FoundryOutput = serde_json::from_str(&content)
105 .context(format!("Failed to parse JSON for {file_name}"))?;
106 let bytecode = hex::decode(output.bytecode().trim_start_matches("0x"))
107 .context("Failed to decode bytecode")?;
108 outputs.push((contract_name.to_string(), sub_path, bytecode));
109 }
110 }
111 }
112 }
113 }
114
115 Ok(outputs)
116 }
117
118 pub fn bytecode(&self) -> Result<Vec<u8>> {
120 let bytecode = self
121 .constructor
122 .finish(self.artifact.runtime_bytecode.clone().into())
123 .map(|v| v.to_vec())?;
124
125 tracing::debug!("runtime bytecode: {}", hex::encode(&bytecode));
126 Ok(bytecode)
127 }
128
129 pub fn construct(&mut self, storage: InitStorage) -> Result<&mut Self> {
132 self.constructor.storage(storage)?;
133 Ok(self)
134 }
135
136 pub fn compile(mut self) -> Result<Self> {
138 let config = Config::default().dispatcher(self.dispatcher);
139 let compiler = Compiler::new(config);
140 self.artifact = compiler.compile(&self.wasm)?;
141
142 tracing::debug!("bytecode: {}", hex::encode(&self.artifact.runtime_bytecode));
144 Ok(self)
145 }
146
147 pub fn deploy<'e>(&mut self) -> Result<EVM<'e>> {
149 let mut evm = EVM::default();
150 let info = evm.deploy(&self.bytecode()?)?;
151
152 self.address.copy_from_slice(&info.address);
153 Ok(evm)
154 }
155
156 pub fn current() -> Result<Self> {
162 Self::search(&lookup::pkg_name()?)
163 }
164
165 pub fn encode<Param>(&self, inputs: impl AsRef<[Param]>) -> Result<Vec<u8>>
167 where
168 Param: Bytes32,
169 {
170 let mut calldata = Vec::new();
171 let mut inputs = inputs.as_ref();
172 if self.dispatcher {
173 if inputs.is_empty() {
174 return Err(anyhow!("no selector provided"));
175 }
176
177 calldata.extend_from_slice(&zabi::selector::parse(&inputs[0].to_vec()));
178 inputs = &inputs[1..];
179 }
180
181 for input in inputs {
182 calldata.extend_from_slice(&input.to_bytes32());
183 }
184
185 tracing::debug!("calldata: {}", hex::encode(&calldata));
186 Ok(calldata)
187 }
188
189 pub fn execute<Param>(&mut self, inputs: impl AsRef<[Param]>) -> Result<Info>
191 where
192 Param: Bytes32,
193 {
194 EVM::interp(&self.artifact.runtime_bytecode, &self.encode(inputs)?)
195 }
196
197 pub fn json_abi(&self) -> Result<String> {
199 serde_json::to_string_pretty(&self.artifact.abi).map_err(Into::into)
200 }
201
202 pub fn pure(mut self) -> Self {
204 self.dispatcher = false;
205 self
206 }
207
208 pub fn search(name: &str) -> Result<Self> {
210 crate::setup_logger();
213 let wasm = lookup::wasm(name)?;
214 zinkc::utils::wasm_opt(&wasm, &wasm)?;
215
216 tracing::debug!("loading contract from {}", wasm.display());
217 Ok(Self::from(fs::read(wasm)?))
218 }
219}