Skip to content

Commit

Permalink
feat(torii): implement graphql for erc
Browse files Browse the repository at this point in the history
commit-id:10465a00
  • Loading branch information
lambda-0x committed Sep 19, 2024
1 parent 12df212 commit 397004b
Show file tree
Hide file tree
Showing 16 changed files with 415 additions and 10 deletions.
2 changes: 2 additions & 0 deletions crates/torii/core/src/sql/erc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ impl Sql {
.fetch_one(&self.pool)
.await?;

// TODO(opt): we dont need to make rpc call for each token id for erc721, metadata for all
// tokens is same is same for a specific contract
if !token_exists {
register_erc721_token_metadata(
contract_address,
Expand Down
8 changes: 4 additions & 4 deletions crates/torii/core/src/sql/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ pub fn felts_to_sql_string(felts: &[Felt]) -> String {
+ FELT_DELIMITER
}

pub(crate) fn felt_to_sql_string(felt: &Felt) -> String {
pub fn felt_to_sql_string(felt: &Felt) -> String {
format!("{:#x}", felt)
}

pub(crate) fn felt_and_u256_to_sql_string(felt: &Felt, u256: &U256) -> String {
pub fn felt_and_u256_to_sql_string(felt: &Felt, u256: &U256) -> String {

Check warning on line 15 in crates/torii/core/src/sql/utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/sql/utils.rs#L15

Added line #L15 was not covered by tests
format!("{}:{}", felt_to_sql_string(felt), u256_to_sql_string(u256))
}

pub(crate) fn u256_to_sql_string(u256: &U256) -> String {
pub fn u256_to_sql_string(u256: &U256) -> String {

Check warning on line 19 in crates/torii/core/src/sql/utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/sql/utils.rs#L19

Added line #L19 was not covered by tests
format!("{:#064x}", u256)
}

pub(crate) fn sql_string_to_u256(sql_string: &str) -> U256 {
pub fn sql_string_to_u256(sql_string: &str) -> U256 {

Check warning on line 23 in crates/torii/core/src/sql/utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/sql/utils.rs#L23

Added line #L23 was not covered by tests
let sql_string = sql_string.strip_prefix("0x").unwrap_or(sql_string);
U256::from(crypto_bigint::U256::from_be_hex(sql_string))
}
2 changes: 1 addition & 1 deletion crates/torii/graphql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde.workspace = true
serde_json.workspace = true
sozo-ops.workspace = true
sqlx.workspace = true
starknet-crypto.workspace = true
strum.workspace = true
strum_macros.workspace = true
thiserror.workspace = true
Expand All @@ -46,5 +47,4 @@ dojo-world.workspace = true
katana-runner.workspace = true
scarb.workspace = true
serial_test = "2.0.0"
starknet-crypto.workspace = true
starknet.workspace = true
7 changes: 7 additions & 0 deletions crates/torii/graphql/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub const QUERY_TYPE_NAME: &str = "World__Query";
pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription";
pub const MODEL_ORDER_TYPE_NAME: &str = "World__ModelOrder";
pub const MODEL_ORDER_FIELD_TYPE_NAME: &str = "World__ModelOrderField";
pub const ERC_BALANCE_TYPE_NAME: &str = "ERC__Balance";
pub const ERC_TRANSFER_TYPE_NAME: &str = "ERC__Transfer";
pub const ERC_TOKEN_TYPE_NAME: &str = "ERC__Token";

// objects' single and plural names
pub const ENTITY_NAMES: (&str, &str) = ("entity", "entities");
Expand All @@ -45,6 +48,10 @@ pub const METADATA_NAMES: (&str, &str) = ("metadata", "metadatas");
pub const TRANSACTION_NAMES: (&str, &str) = ("transaction", "transactions");
pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", "");

pub const ERC_BALANCE_NAME: (&str, &str) = ("ercBalance", "");
pub const ERC_TOKEN_NAME: (&str, &str) = ("ercToken", "");
pub const ERC_TRANSFER_NAME: (&str, &str) = ("ercTransfer", "");

// misc
pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection";
pub const ORDER_ASC: &str = "ASC";
Expand Down
2 changes: 2 additions & 0 deletions crates/torii/graphql/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub enum ExtractError {
NotList(String),
#[error("Not a string: {0}")]
NotString(String),
#[error("Not a felt: {0}")]
NotFelt(String),
#[error("Not a number: {0}")]
NotNumber(String),
}
25 changes: 24 additions & 1 deletion crates/torii/graphql/src/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use async_graphql::Name;
use dojo_types::primitive::Primitive;
use lazy_static::lazy_static;

use crate::constants::{CONTENT_TYPE_NAME, SOCIAL_TYPE_NAME};
use crate::constants::{CONTENT_TYPE_NAME, ERC_TOKEN_TYPE_NAME, SOCIAL_TYPE_NAME};
use crate::types::{GraphqlType, TypeData, TypeMapping};

lazy_static! {
Expand Down Expand Up @@ -144,4 +144,27 @@ lazy_static! {
TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string()))
),
]);

pub static ref ERC_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([
(Name::new("balance"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("type"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("token_metadata"), TypeData::Simple(TypeRef::named(ERC_TOKEN_TYPE_NAME))),
]);

pub static ref ERC_TRANSFER_TYPE_MAPPING: TypeMapping = IndexMap::from([
(Name::new("from"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("to"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("amount"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("type"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("executed_at"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("token_metadata"), TypeData::Simple(TypeRef::named(ERC_TOKEN_TYPE_NAME))),
]);

pub static ref ERC_TOKEN_TYPE_MAPPING: TypeMapping = IndexMap::from([
(Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("symbol"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("token_id"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("decimals"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("contract_address"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
]);
}
143 changes: 143 additions & 0 deletions crates/torii/graphql/src/object/erc/erc_balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef};
use async_graphql::{Name, Value};
use convert_case::{Case, Casing};
use serde::Deserialize;
use sqlx::{FromRow, Pool, Sqlite, SqliteConnection};
use starknet_crypto::Felt;
use torii_core::sql::utils::felt_to_sql_string;
use tracing::warn;

use crate::constants::{ERC_BALANCE_NAME, ERC_BALANCE_TYPE_NAME};
use crate::mapping::ERC_BALANCE_TYPE_MAPPING;
use crate::object::{BasicObject, ResolvableObject};
use crate::types::{TypeMapping, ValueMapping};
use crate::utils::extract;

#[derive(Debug)]
pub struct ErcBalanceObject;

impl BasicObject for ErcBalanceObject {
fn name(&self) -> (&str, &str) {
ERC_BALANCE_NAME
}

fn type_name(&self) -> &str {
ERC_BALANCE_TYPE_NAME
}

fn type_mapping(&self) -> &TypeMapping {
&ERC_BALANCE_TYPE_MAPPING
}
}

impl ResolvableObject for ErcBalanceObject {
fn resolvers(&self) -> Vec<Field> {
let account_address = "account_address";
let argument = InputValue::new(
account_address.to_case(Case::Camel),
TypeRef::named_nn(TypeRef::STRING),
);

let field = Field::new(self.name().0, TypeRef::named_list(self.type_name()), move |ctx| {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let address = extract::<Felt>(
ctx.args.as_index_map(),
&account_address.to_case(Case::Camel),
)?;

Check warning on line 47 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L42-L47

Added lines #L42 - L47 were not covered by tests

let erc_balances = fetch_erc_balances(&mut conn, address).await?;

Check warning on line 49 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L49

Added line #L49 was not covered by tests

Ok(Some(Value::List(erc_balances)))
})

Check warning on line 52 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L51-L52

Added lines #L51 - L52 were not covered by tests
})
.argument(argument);
vec![field]
}
}

async fn fetch_erc_balances(
conn: &mut SqliteConnection,
address: Felt,
) -> sqlx::Result<Vec<Value>> {
let query = "SELECT t.contract_address, t.name, t.symbol, t.decimals, b.balance, b.token_id, \
c.contract_type
FROM balances b
JOIN tokens t ON b.token_id = t.id
JOIN contracts c ON t.contract_address = c.contract_address
WHERE b.account_address = ?";

Check warning on line 68 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L59-L68

Added lines #L59 - L68 were not covered by tests

let rows = sqlx::query(query).bind(felt_to_sql_string(&address)).fetch_all(conn).await?;

Check warning on line 70 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L70

Added line #L70 was not covered by tests

let mut erc_balances = Vec::new();

Check warning on line 72 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L72

Added line #L72 was not covered by tests

for row in rows {
let row = BalanceQueryResultRaw::from_row(&row)?;

Check warning on line 75 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L74-L75

Added lines #L74 - L75 were not covered by tests

let balance_value = match row.contract_type.as_str() {
"ERC20" | "Erc20" | "erc20" => {
let token_metadata = Value::Object(ValueMapping::from([
(Name::new("name"), Value::String(row.name)),
(Name::new("symbol"), Value::String(row.symbol)),
// for erc20 there is no token_id
(Name::new("token_id"), Value::Null),
(Name::new("decimals"), Value::String(row.decimals.to_string())),
(Name::new("contract_address"), Value::String(row.contract_address.clone())),
]));

Value::Object(ValueMapping::from([
(Name::new("balance"), Value::String(row.balance)),
(Name::new("type"), Value::String(row.contract_type)),
(Name::new("token_metadata"), token_metadata),
]))

Check warning on line 92 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L77-L92

Added lines #L77 - L92 were not covered by tests
}
"ERC721" | "Erc721" | "erc721" => {

Check warning on line 94 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L94

Added line #L94 was not covered by tests
// contract_address:token_id
let token_id = row.token_id.split(':').collect::<Vec<&str>>();
assert!(token_id.len() == 2);

Check warning on line 97 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L96-L97

Added lines #L96 - L97 were not covered by tests

let token_metadata = Value::Object(ValueMapping::from([
(Name::new("contract_address"), Value::String(row.contract_address.clone())),
(Name::new("name"), Value::String(row.name)),
(Name::new("symbol"), Value::String(row.symbol)),
(Name::new("token_id"), Value::String(row.token_id)),
(Name::new("decimals"), Value::String(row.decimals.to_string())),
]));

Value::Object(ValueMapping::from([
(Name::new("balance"), Value::String(row.balance)),
(Name::new("type"), Value::String(row.contract_type)),
(Name::new("token_metadata"), token_metadata),
]))

Check warning on line 111 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L99-L111

Added lines #L99 - L111 were not covered by tests
}
_ => {
warn!("Unknown contract type: {}", row.contract_type);
continue;

Check warning on line 115 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L114-L115

Added lines #L114 - L115 were not covered by tests
}
};

erc_balances.push(balance_value);

Check warning on line 119 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L119

Added line #L119 was not covered by tests
}

Ok(erc_balances)
}

Check warning on line 123 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L122-L123

Added lines #L122 - L123 were not covered by tests

// TODO: This would be required when subscriptions are needed
// impl ErcBalanceObject {
// pub fn value_mapping(entity: ErcBalance) -> ValueMapping {
// IndexMap::from([
// ])
// }
// }

#[derive(FromRow, Deserialize, Debug, Clone)]

Check warning on line 133 in crates/torii/graphql/src/object/erc/erc_balance.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_balance.rs#L133

Added line #L133 was not covered by tests
#[serde(rename_all = "camelCase")]
struct BalanceQueryResultRaw {
pub contract_address: String,
pub name: String,
pub symbol: String,
pub decimals: u8,
pub token_id: String,
pub balance: String,
pub contract_type: String,
}
21 changes: 21 additions & 0 deletions crates/torii/graphql/src/object/erc/erc_token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::constants::{ERC_TOKEN_NAME, ERC_TOKEN_TYPE_NAME};
use crate::mapping::ERC_TOKEN_TYPE_MAPPING;
use crate::object::BasicObject;
use crate::types::TypeMapping;

#[derive(Debug)]
pub struct ErcTokenObject;

impl BasicObject for ErcTokenObject {
fn name(&self) -> (&str, &str) {
ERC_TOKEN_NAME
}

Check warning on line 12 in crates/torii/graphql/src/object/erc/erc_token.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/graphql/src/object/erc/erc_token.rs#L10-L12

Added lines #L10 - L12 were not covered by tests

fn type_name(&self) -> &str {
ERC_TOKEN_TYPE_NAME
}

fn type_mapping(&self) -> &TypeMapping {
&ERC_TOKEN_TYPE_MAPPING
}
}
Loading

0 comments on commit 397004b

Please sign in to comment.