1use anyhow::{anyhow, Result};
2use ethers::{
3 abi::AbiEncode,
4 prelude::*,
5 types::transaction::{
6 eip2718::TypedTransaction,
7 eip712::{EIP712Domain, Eip712DomainType, TypedData},
8 },
9};
10use serde_json::{json, Value};
11use std::collections::BTreeMap;
12use std::sync::Arc;
13use tracing::error;
14
15abigen!(Token, "public/ABI/Token.json");
17
18abigen!(Vesting, "public/ABI/Vesting.json");
20
21abigen!(Epoch, "public/ABI/Epoch.json");
23
24abigen!(Prover, "public/ABI/Prover.json");
26
27abigen!(Stake, "public/ABI/Stake.json");
29
30abigen!(Task, "public/ABI/Task.json");
32
33abigen!(Reward, "public/ABI/Reward.json");
35
36abigen!(Controller, "public/ABI/Controller.json");
38
39abigen!(AAWallet, "public/others/Wallet.json");
41
42const NETWORKS_ADDRESS: &str = include_str!("../public/networks.json");
43
44pub fn contract_address(network: &str, name: &str) -> Result<(Address, u64)> {
45 let addresses: Value =
46 serde_json::from_str(NETWORKS_ADDRESS).map_err(|_| anyhow!("networks.json is invalid"))?;
47
48 let address: Address = addresses[network][name]["address"]
49 .as_str()
50 .ok_or(anyhow!("contract address is invalid 1"))?
51 .parse()
52 .map_err(|_| anyhow!("contract address is invalid 2"))?;
53
54 let start: i64 = addresses[network][name]["startBlock"]
55 .as_i64()
56 .ok_or(anyhow!("contract block is invalid"))?;
57
58 Ok((address, start as u64))
59}
60
61pub fn pozk_metrics_url(network: &str) -> Result<String> {
62 match network {
63 "localhost" | "testnet" => Ok("https://pozk-metrics.zypher.dev".to_owned()),
64 "mainnet" => Ok("https://pozk-metrics.zypher.network".to_owned()),
65 _ => Err(anyhow!("Invalid network")),
66 }
67}
68
69pub fn pozk_rpc_url(network: &str) -> Result<String> {
70 match network {
71 "localhost" => Ok("http://localhost:8545".to_owned()),
72 "testnet" => Ok("https://rpc.zypher.network".to_owned()),
73 "mainnet" => Ok("https://rpc.zypher.network".to_owned()),
74 _ => Err(anyhow!("Invalid network")),
75 }
76}
77
78pub fn pozk_zero_gas_url(network: &str) -> Result<String> {
79 match network {
80 "testnet" => Ok("https://gas.zypher.network".to_owned()),
81 "mainnet" => Ok("https://gas.zypher.network".to_owned()),
82 _ => Err(anyhow!("Invalid network")),
83 }
84}
85
86pub type DefaultProvider = Provider<Http>;
87
88pub fn new_providers(rpcs: &[String]) -> Vec<Arc<DefaultProvider>> {
89 let mut providers = vec![];
90 for rpc in rpcs {
91 if let Ok(p) = Provider::<Http>::try_from(rpc) {
92 providers.push(Arc::new(p))
93 }
94 }
95 providers
96}
97
98pub type DefaultSigner = SignerMiddleware<Arc<Provider<Http>>, LocalWallet>;
99
100pub async fn new_signer(
101 provider: Arc<DefaultProvider>,
102 wallet: LocalWallet,
103) -> Result<Arc<DefaultSigner>> {
104 let signer = SignerMiddleware::new_with_provider_chain(provider, wallet).await?;
105 Ok(Arc::new(signer))
106}
107
108pub async fn check_zero_gas(uri: &str, controller: Address) -> Result<()> {
109 let res = reqwest::get(format!("{}/api/balanceof/{:?}", uri, controller))
110 .await?
111 .json::<Value>()
112 .await?;
113 if let Some(amount) = res.pointer("/data/amount") {
114 if amount != "0x0" {
115 return Ok(());
116 }
117 }
118
119 Err(anyhow!("No permission"))
120}
121
122pub async fn create_zero_gas(uri: &str, controller: Address) -> Result<Address> {
123 let client = reqwest::Client::new();
124 let data = json!({
125 "owner": format!("{:?}", controller)
126 });
127 let res = client
128 .post(format!("{}/api/create", uri))
129 .json(&data)
130 .send()
131 .await?
132 .json::<Value>()
133 .await?;
134 if let Some(aa) = res.pointer("/data/wallet") {
135 Ok(aa.as_str().unwrap_or("").parse::<Address>()?)
136 } else {
137 Err(anyhow!("Invalid response"))
138 }
139}
140
141pub async fn zero_gas<S: Signer>(
142 uri: &str,
143 tx: TypedTransaction,
144 chain: u64,
145 aa: Address,
146 nonce: u64,
147 wallet: &S,
148) -> Result<Option<String>> {
149 let owner = format!("{:?}", wallet.address());
150 let from = format!("{:?}", aa);
151
152 let to = format!("{:?}", tx.to_addr().unwrap());
153 let value = tx.value().unwrap_or(&U256::zero()).encode_hex();
154 let data = format!("0x{}", hex::encode(tx.data().unwrap().to_vec()));
155
156 let tdata = generate_eip712_data(chain, nonce, &from, &to, &value, &data);
157 let sign = wallet
158 .sign_typed_data(&tdata)
159 .await
160 .map_err(|_| anyhow!("Invalid typed data"))?;
161 let mut r_bytes = [0u8; 32];
162 sign.r.to_big_endian(&mut r_bytes);
163 let mut s_bytes = [0u8; 32];
164 sign.s.to_big_endian(&mut s_bytes);
165
166 let v = sign.v;
167 let r = format!("0x{}", hex::encode(&r_bytes));
168 let s = format!("0x{}", hex::encode(&s_bytes));
169
170 let client = reqwest::Client::new();
171 let data = json!({
172 "wallet": from,
173 "to": to,
174 "data": data,
175 "value": value,
176 "v": v,
177 "r": r,
178 "s": s,
179 "owner": owner,
180 });
181 let res = client
182 .post(format!("{}/api/functioncall", uri))
183 .json(&data)
184 .send()
185 .await?
186 .json::<Value>()
187 .await?;
188
189 if let Some(hash) = res.pointer("/data/tx_hash") {
190 return Ok(Some(hash.as_str().unwrap_or("").to_owned()));
191 }
192
193 if let Some(err) = res.pointer("/msg") {
194 error!("[Utils] 0 gas chain error: {}", err);
195 Ok(None)
196 } else {
197 Err(anyhow!("Something wrong with 0 gas service"))
198 }
199}
200
201const DOMAIN: &str = "Zytron";
204const FUNCTION_CALL_TIP: &str =
205 "You are agreeing to this single transaction to be executed on the chain.";
206
207#[inline]
208fn eip712_type(name: &str, t: &str) -> Eip712DomainType {
209 Eip712DomainType {
210 name: name.to_owned(),
211 r#type: t.to_owned(),
212 }
213}
214
215fn generate_eip712_data(
216 chain: u64,
217 nonce: u64,
218 from: &str,
219 to: &str,
220 value: &str,
221 data: &str,
222) -> TypedData {
223 let mut types = BTreeMap::new();
224 types.insert(
225 "EIP712Domain".to_owned(),
226 vec![
227 eip712_type("name", "string"),
228 eip712_type("chainId", "uint256"),
229 ],
230 );
231 types.insert(
232 "Message".to_owned(),
233 vec![
234 eip712_type("tip", "string"),
235 eip712_type("nonce", "uint256"),
236 eip712_type("from", "address"),
237 eip712_type("to", "address"),
238 eip712_type("value", "uint256"),
239 eip712_type("data", "bytes"),
240 ],
241 );
242
243 let mut message = BTreeMap::new();
244 message.insert("tip".to_owned(), FUNCTION_CALL_TIP.into());
245 message.insert("nonce".to_owned(), nonce.into());
246 message.insert("from".to_owned(), from.into());
247 message.insert("to".to_owned(), to.into());
248 message.insert("value".to_owned(), value.into());
249 message.insert("data".to_owned(), data.into());
250
251 TypedData {
252 types,
253 message,
254 domain: EIP712Domain {
255 name: Some(DOMAIN.to_owned()),
256 chain_id: Some(chain.into()),
257 ..Default::default()
258 },
259 primary_type: "Message".to_owned(),
260 }
261}
262
263const PROXY_LIST_ACCOUNTS: [H160; 1] = [H160([0u8; 20])];
264
265pub fn check_task_proxy_list(signer: &Address) -> bool {
266 PROXY_LIST_ACCOUNTS.contains(signer)
267}
268
269#[tokio::test]
270async fn test_zero_gas() {
271 let uri = "https://gas.zypher.network";
272 let account: Address = "0x5Ef51c9f449DB7Be2f0c636C6C137e65B8B96B9B"
273 .parse()
274 .unwrap();
275 let wallet: Address = "0x4e3111334ba387ddf000966cde24db35245fdc59"
276 .parse()
277 .unwrap();
278 assert!(check_zero_gas(uri, account).await.is_ok());
279 assert_eq!(create_zero_gas(uri, account).await.unwrap(), wallet);
280
281 let account2: Address = "0x1Ef51c9f449DB7Be2f0c636C6C137e65B8B96B9B"
282 .parse()
283 .unwrap();
284 assert!(check_zero_gas(uri, account2).await.is_err());
285}