Skip to content

Commit

Permalink
feature: working on possibility of checking attestation
Browse files Browse the repository at this point in the history
  • Loading branch information
scx1332 committed Apr 30, 2024
1 parent 6ea3407 commit 36725e1
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 19 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,8 @@ Be nicer to endpoint rate limit every 2 seconds
```
cargo run -- account-balance --interval 2.0 -c polygon -a 0x75be52afd54a13b6c98490b4db495aa79b609d58,0x7caac644722316101807e0d55f838f7851a97031,0x52a258ed593c793251a89bfd36cae158ee9fc4f8,0x04e2dc96afecdf72221882e1cee039cab4d443e0,0xa32a0edc623d86e623f58e7c4174023a80a67ddf,0x7cb53b925a79fb15c348fcfd9abcf2287854d33a,0x8cf88c473b6cb40b8d37cdd93e6c8118c14a6e60,0xa96d3f3e177687fb0b5f990d5c4000923b49430b,0x92fb36230b50a87a39ba3237c996caf5a39b230b,0x0c4d7a995aa9846ef25e1a347a8711c8b534b5a6,0x698076ae39e7e44bcd2bbe15f0486c8d44bb4e6f
```


# Example attestation

http://127.0.0.1:8080/erc20/api/attestation/sepolia/0x1d542735c2be213e64e3eff427efad256d27ac1dc9fabb6e5507d2e89cdd1717
6 changes: 6 additions & 0 deletions crates/erc20_payment_lib/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ pub fn encode_get_attestation(
contract_encode(&EAS_CONTRACT_TEMPLATE, "getAttestation", (uid, ))
}

pub fn encode_get_schema(
uid: H256,
) -> Result<Vec<u8>, web3::ethabi::Error> {
contract_encode(&SCHEMA_REGISTRY_TEMPLATE, "getSchema", (uid, ))
}

pub fn encode_erc20_balance_of(address: Address) -> Result<Vec<u8>, web3::ethabi::Error> {
contract_encode(&ERC20_CONTRACT_TEMPLATE, "balanceOf", (address,))
}
Expand Down
65 changes: 60 additions & 5 deletions crates/erc20_payment_lib/src/eth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::contracts::{encode_erc20_allowance, encode_erc20_balance_of, encode_get_attestation, encode_get_deposit_details};
use crate::contracts::{encode_erc20_allowance, encode_erc20_balance_of, encode_get_attestation, encode_get_deposit_details, encode_get_schema};
use crate::error::*;
use crate::{err_create, err_custom_create, err_from};
use erc20_payment_lib_common::utils::{datetime_from_u256_timestamp, datetime_from_u256_with_option, U256ConvExt};
Expand Down Expand Up @@ -100,9 +100,63 @@ pub fn nonce_from_deposit_id(deposit_id: U256) -> u64 {
u64::from_be_bytes(slice[24..32].try_into().unwrap())
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct AttestationSchema {
pub uid: H256,
pub resolver: Address,
pub revocable: bool,
pub schema: String,
}

pub async fn get_schema_details(
web3: Arc<Web3RpcPool>,
uid: H256,
eas_schema_contract_address: Address,
) -> Result<crate::eth::AttestationSchema, PaymentError> {
let res = web3
.eth_call(
CallRequest {
to: Some(eas_schema_contract_address),
data: Some(encode_get_schema(uid).unwrap().into()),
..Default::default()
},
None,
)
.await
.map_err(err_from!())?;

let decoded = ethabi::decode(
&[
ethabi::ParamType::Tuple(
vec![
ethabi::ParamType::FixedBytes(32),
ethabi::ParamType::Address,
ethabi::ParamType::Bool,
ethabi::ParamType::String
]
)
],
&res.0
).map_err(|err|err_custom_create!(
"Failed to decode attestation view from bytes, check if proper contract and contract method is called: {}",
err
))?;

let decoded = decoded[0].clone().into_tuple().unwrap();
log::info!("Decoded attestation: {:?}", decoded);
let schema = AttestationSchema {
uid: H256::from_slice(decoded[0].clone().into_fixed_bytes().unwrap().as_slice()),
resolver: decoded[1].clone().into_address().unwrap(),
revocable: decoded[2].clone().into_bool().unwrap(),
schema: decoded[3].clone().into_string().unwrap()
};

Ok(schema)
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Attestation {
pub id: H256,
pub uid: H256,
pub schema: H256,
pub time: DateTime<Utc>,
pub expiration_time: Option<DateTime<Utc>>,
Expand Down Expand Up @@ -131,6 +185,7 @@ pub async fn get_attestation_details(
.await
.map_err(err_from!())?;


let decoded = ethabi::decode(
&[
ethabi::ParamType::Tuple(
Expand All @@ -143,7 +198,7 @@ pub async fn get_attestation_details(
ethabi::ParamType::FixedBytes(32),
ethabi::ParamType::Address,
ethabi::ParamType::Address,
ethabi::ParamType::Uint(64),
ethabi::ParamType::Bool,
ethabi::ParamType::Bytes
]
)
Expand All @@ -157,15 +212,15 @@ pub async fn get_attestation_details(
let decoded = decoded[0].clone().into_tuple().unwrap();
log::info!("Decoded attestation: {:?}", decoded);
let attestation = Attestation {
id: H256::from_slice(decoded[0].clone().into_fixed_bytes().unwrap().as_slice()),
uid: H256::from_slice(decoded[0].clone().into_fixed_bytes().unwrap().as_slice()),
schema: H256::from_slice(decoded[1].clone().into_fixed_bytes().unwrap().as_slice()),
time: datetime_from_u256_with_option(decoded[2].clone().into_uint().unwrap()).ok_or(err_custom_create!("Attestation timestamp out of range"))?,
expiration_time: datetime_from_u256_with_option(decoded[3].clone().into_uint().unwrap()),
revocation_time: datetime_from_u256_with_option(decoded[4].clone().into_uint().unwrap()),
refUID: H256::from_slice(decoded[5].clone().into_fixed_bytes().unwrap().as_slice()),
recipient: decoded[6].clone().into_address().unwrap(),
attester: decoded[7].clone().into_address().unwrap(),
revocable: decoded[8].clone().into_uint().unwrap().as_u64() != 0,
revocable: decoded[8].clone().into_bool().unwrap(),
data: Bytes::from(decoded[9].clone().into_bytes().unwrap())
};

Expand Down
173 changes: 170 additions & 3 deletions crates/erc20_payment_lib/src/server/web.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::eth::get_balance;
use crate::eth::{Attestation, AttestationSchema, get_attestation_details, get_balance, get_schema_details};
use crate::runtime::{PaymentRuntime, SharedState, TransferArgs, TransferType};
use crate::server::ws::event_stream_websocket_endpoint;
use crate::setup::{ChainSetup, PaymentSetup};
Expand All @@ -13,7 +13,7 @@ use chrono::{DateTime, Utc};
use erc20_payment_lib_common::model::DepositId;
use erc20_payment_lib_common::ops::*;
use erc20_payment_lib_common::utils::datetime_from_u256_timestamp;
use erc20_payment_lib_common::{export_metrics_to_prometheus, FaucetData};
use erc20_payment_lib_common::{err_custom_create, export_metrics_to_prometheus, FaucetData};
use erc20_rpc_pool::VerifyEndpointResult;
use serde::{Deserialize, Serialize};
use serde_json::json;
Expand All @@ -22,8 +22,10 @@ use std::collections::BTreeMap;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use actix_web::error::ErrorBadRequest;
use tokio::sync::Mutex;
use web3::types::{Address, BlockId, BlockNumber, U256};
use web3::ethabi;
use web3::types::{Address, BlockId, BlockNumber, H256, U256};

pub struct ServerData {
pub shared_state: Arc<std::sync::Mutex<SharedState>>,
Expand Down Expand Up @@ -1220,6 +1222,170 @@ pub async fn faucet(data: Data<Box<ServerData>>, req: HttpRequest) -> impl Respo
}))
}

#[derive(Debug, Serialize)]
struct AttestationItemInfo {
name: String,
#[serde(rename = "type")]
typ: String,
value: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct AttestationCheckResult {
chain_id: u64,
chain: String,
attestation: Attestation,
schema: AttestationSchema,
params: Vec<AttestationItemInfo>,
}

pub async fn check_attestation(data: Data<Box<ServerData>>, req: HttpRequest) -> actix_web::Result<web::Json<AttestationCheckResult>>
{
let my_data = data.shared_state.lock().unwrap();


let attestation_uid = req.match_info().get("uid").unwrap_or("");
let chain_name = req.match_info().get("chain").unwrap_or("");
let chain: &ChainSetup = data
.payment_setup
.chain_setup
.iter().find(
|(_, chain)| chain.network == chain_name
).ok_or(
actix_web::error::ErrorBadRequest(format!("No config found for network {}", chain_name))
)?.1;

let web3 = data.payment_setup.get_provider(
chain.chain_id
).map_err(
|e| ErrorBadRequest(format!("Failed to get provider: {}", e))
)?;

let decoded_bytes = match hex::decode(attestation_uid.replace("0x", "")) {
Ok(bytes) => bytes,
Err(e) => {
return Err(ErrorBadRequest(format!(
"Failed to decode attestation id: {}",
e
)));
}
};

let contract = chain.eas_contract_settings.clone().ok_or(
ErrorBadRequest(format!("No contract settings found for chain {}", chain_name))
)?;

let schema_contract = chain.eas_schema_registry_settings.clone().ok_or(
ErrorBadRequest(format!("No schema contract settings found for chain {}", chain_name))
)?;

let uid = ethabi::Bytes::from(decoded_bytes);

let uid = if uid.len() != 32 {
return Err(ErrorBadRequest(format!(
"Invalid attestation id length: {}, expected 32",
uid.len()
)));
} else {
H256::from_slice(uid.as_slice())
};
log::info!("Querying attestation contract: {:#x}", contract.address);



let attestation = match get_attestation_details(
web3.clone(),
uid,
contract.address,
).await {
Ok(attestation) => {
attestation
}
Err(e) => {
log::error!("Failed to get attestation details: {}", e);
return Err(ErrorBadRequest(format!(
"Failed to get attestation details: {}",
e
)));
}
};

let attestation_schema = match get_schema_details(
web3,
attestation.schema,
schema_contract.address,
).await {
Ok(attestation_schema) => {
attestation_schema
}
Err(e) => {
log::error!("Failed to get attestation details: {}", e);
return Err(ErrorBadRequest(format!(
"Failed to get attestation details: {}",
e
)));
}
};


log::info!("Querying schema contract: {:#x}", schema_contract.address);


println!("attestation: {}", serde_json::to_string_pretty(&attestation).map_err(
|e| ErrorBadRequest(format!("Failed to serialize attestation details: {}", e))
)?);

println!("schema: {}", serde_json::to_string_pretty(&attestation_schema).map_err(
|e| ErrorBadRequest(format!("Failed to serialize attestation details: {}", e))
)?);

let items = attestation_schema.schema.split(",").into_iter().collect::<Vec<&str>>();
log::debug!("There are {} items in the schema", items.len());
let mut param_types = Vec::new();
let mut param_names = Vec::new();

for item in items {
let items2 = item.trim().split(" ").into_iter().collect::<Vec<&str>>();
if items2.len() != 2 {
log::error!("Invalid item in schema: {}", item);
return Err(ErrorBadRequest(format!("Invalid item in schema: {}", item)));
}
let item_type = items2[0].trim();
let item_name = items2[1].trim();

log::debug!("Item name: {}, Item type: {}", item_name, item_type);
let param_type = ethabi::param_type::Reader::read(item_type).map_err(
|e| ErrorBadRequest(format!("Failed to read param type: {}", e))
)?;
param_types.push(param_type);
param_names.push(item_name);
}

let decoded_tokens = ethabi::decode(&param_types, &attestation.data.0).map_err(
|e| ErrorBadRequest(format!("Failed to decode attestation data: {}", e))
)?;

let mut decoded_items = Vec::new();
for ((token, token_name), token_type) in decoded_tokens.iter().zip(param_names.iter()).zip(param_types.iter()) {
println!("Token {}: {}", token_name, token);
decoded_items.push(AttestationItemInfo {
name: token_name.to_string(),
typ: token_type.to_string(),
value: token.to_string(),
});
}

return Ok(web::Json(AttestationCheckResult {
chain_id: chain.chain_id as u64,
chain: chain_name.to_string(),
attestation,
schema: attestation_schema,
params: decoded_items,
}))
}


pub fn runtime_web_scope(
scope: Scope,
server_data: Data<Box<ServerData>>,
Expand All @@ -1231,6 +1397,7 @@ pub fn runtime_web_scope(
let api_scope = Scope::new("/api");
let mut api_scope = api_scope
.app_data(server_data)
.route("/attestation/{chain}/{uid}", web::get().to(check_attestation))
.route("/allowances", web::get().to(allowances))
.route("/balance/{account}/{chain}", web::get().to(account_balance))
.route("/rpc_pool", web::get().to(rpc_pool))
Expand Down
Loading

0 comments on commit 36725e1

Please sign in to comment.