pozk_utils/
networks.rs

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
15// Vesting contract with abi
16abigen!(Token, "public/ABI/Token.json");
17
18// Vesting contract with abi
19abigen!(Vesting, "public/ABI/Vesting.json");
20
21// Epoch contract with abi
22abigen!(Epoch, "public/ABI/Epoch.json");
23
24// Prover contract with abi
25abigen!(Prover, "public/ABI/Prover.json");
26
27// Stake contract with abi
28abigen!(Stake, "public/ABI/Stake.json");
29
30// Task contract with abi
31abigen!(Task, "public/ABI/Task.json");
32
33// Reward contract with abi
34abigen!(Reward, "public/ABI/Reward.json");
35
36// Controller contract with abi
37abigen!(Controller, "public/ABI/Controller.json");
38
39// Zytron standard AA wallet for zero gas
40abigen!(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
201// EIP712Domain(string name,uint256 chainId)
202// Message(string tip,uint256 nonce,address from,address to,uint256 value,bytes data)
203const 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}