diff --git a/backend-rust/.sqlx/query-392672a3e1068f6e9f9610d605b0916ad8a07650c9a238b37a1f1e2b38301364.json b/backend-rust/.sqlx/query-ba6f3f9d78f75d2247fae1677564b63b8b5669eafe6f661861e66ffb5f3ffa23.json similarity index 93% rename from backend-rust/.sqlx/query-392672a3e1068f6e9f9610d605b0916ad8a07650c9a238b37a1f1e2b38301364.json rename to backend-rust/.sqlx/query-ba6f3f9d78f75d2247fae1677564b63b8b5669eafe6f661861e66ffb5f3ffa23.json index 8b8b65c51..176284e3a 100644 --- a/backend-rust/.sqlx/query-392672a3e1068f6e9f9610d605b0916ad8a07650c9a238b37a1f1e2b38301364.json +++ b/backend-rust/.sqlx/query-ba6f3f9d78f75d2247fae1677564b63b8b5669eafe6f661861e66ffb5f3ffa23.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n total_supply as \"raw_total_supply: BigDecimal\",\n token_id,\n contract_index as \"contract_index: i64\",\n contract_sub_index \"contract_sub_index: i64\",\n token_address,\n metadata_url,\n init_transaction_index\n FROM tokens\n WHERE tokens.contract_index = $1 AND tokens.contract_sub_index = $2 AND tokens.token_address = $3", + "query": "SELECT\n total_supply as \"raw_total_supply: BigDecimal\",\n token_id,\n contract_index as \"contract_index: i64\",\n contract_sub_index \"contract_sub_index: i64\",\n token_address,\n metadata_url,\n init_transaction_index\n FROM tokens\n WHERE tokens.contract_index = $1 AND tokens.contract_sub_index = $2 AND tokens.token_id = $3", "describe": { "columns": [ { @@ -56,5 +56,5 @@ false ] }, - "hash": "392672a3e1068f6e9f9610d605b0916ad8a07650c9a238b37a1f1e2b38301364" + "hash": "ba6f3f9d78f75d2247fae1677564b63b8b5669eafe6f661861e66ffb5f3ffa23" } diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 69f486c05..90eff9291 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -410,9 +410,9 @@ CREATE TABLE scheduled_releases ( -- This index is useful for that. CREATE INDEX scheduled_releases_idx ON scheduled_releases (account_index, release_time); --- All CIS2 tokens. A token is added to this table whenever a `CIS2 mint event` is logged for the first --- time by a contract claiming to follow the `CIS2 standard` or a `CIS2 tokenMetadataUpdated event` is logged --- for the first time by a contract claiming to follow the `CIS2 standard`. +-- All CIS2 tokens. A token is added to this table whenever a CIS2 `MintEvent`/`BurnEvent` +-- or `TokenMetadataEvent` is logged for the first time by a contract claiming +-- to follow the `CIS2 standard`. CREATE TABLE tokens ( -- An index/id for the token (row number). index @@ -446,19 +446,22 @@ CREATE TABLE tokens ( token_id TEXT NOT NULL, - -- Accumulated total supply of the token calculated by considering all `mint/burn` events associated + -- Accumulated total supply of the token calculated by considering all `MintEvents` and `BurnEvents` associated -- to the token. If no total supply is specified when inserting a new token in the table, -- the default total supply 0 is used. total_supply NUMERIC NOT NULL DEFAULT 0, - -- Index of the transaction with the first `CIS2 mint event`, `CIS2 burn event` or `CIS2 tokenMetadata event` logged for the token. + -- Index of the transaction with the first CIS2 `MintEvent`, `BurnEvent` or `TokenMetadataEvent` logged for the token. init_transaction_index BIGINT NOT NULL ); +-- We want to find a specific token (this index should be removed once the front-end querries a token by `token_address`). +CREATE INDEX token_idx ON tokens (contract_index, contract_sub_index, token_id); + CREATE OR REPLACE FUNCTION block_added_notify_trigger_function() RETURNS trigger AS $trigger$ DECLARE rec blocks; diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index ecd358278..7e43489bb 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -16,7 +16,6 @@ macro_rules! todo_api { }; } -use crate::indexer::get_token_address; use account_metrics::AccountMetricsQuery; use anyhow::Context as _; use async_graphql::{ @@ -37,7 +36,6 @@ use concordium_rust_sdk::{ }, smart_contracts::ReceiveName, }, - cis2::{ParseTokenIdVecError, TokenId}, id::types as sdk_types, types::AmountFraction, }; @@ -357,10 +355,6 @@ enum ApiError { InvalidContractVersion(#[from] InvalidContractVersionError), #[error("Schema in database should be a valid versioned module schema")] InvalidVersionedModuleSchema(#[from] VersionedSchemaError), - #[error("Invalid token ID: {0}")] - InvalidTokenID(Arc), - #[error("Invalid token address: {0}")] - InvalidTokenAddress(Arc), } impl From for ApiError { @@ -893,16 +887,9 @@ LIMIT 30", // WHERE slot_time > (LOCALTIMESTAMP - $1::interval) ) -> ApiResult { let pool = get_pool(ctx)?; - let token_address = get_token_address( - contract_index.0, - contract_sub_index.0, - &TokenId::from_str(&token_id).map_err(|e| ApiError::InvalidTokenID(e.into()))?, - ) - .map_err(|e| ApiError::InvalidTokenAddress(Arc::new(e)))?; - let token = sqlx::query_as!( Token, - r#"SELECT + r#"SELECT total_supply as "raw_total_supply: BigDecimal", token_id, contract_index as "contract_index: i64", @@ -911,10 +898,10 @@ LIMIT 30", // WHERE slot_time > (LOCALTIMESTAMP - $1::interval) metadata_url, init_transaction_index FROM tokens - WHERE tokens.contract_index = $1 AND tokens.contract_sub_index = $2 AND tokens.token_address = $3"#, + WHERE tokens.contract_index = $1 AND tokens.contract_sub_index = $2 AND tokens.token_id = $3"#, contract_index.0 as i64, contract_sub_index.0 as i64, - token_address + token_id ) .fetch_optional(pool) .await? @@ -1391,7 +1378,7 @@ type Amount = i64; // TODO: should be UnsignedLong in graphQL type Energy = i64; // TODO: should be UnsignedLong in graphQL type DateTime = chrono::DateTime; // TODO check format matches. type ContractIndex = UnsignedLong; // TODO check format. -type BigInteger = Vec; +type BigInteger = BigDecimal; type MetadataUrl = String; #[derive(SimpleObject)] @@ -2425,12 +2412,20 @@ struct AccountToken { contract_index: ContractIndex, contract_sub_index: ContractIndex, token_id: String, - balance: BigInteger, + #[graphql(skip)] + raw_balance: BigInteger, token: Token, account_id: i64, account: Account, } +#[ComplexObject] +impl AccountToken { + async fn balance(&self, ctx: &Context<'_>) -> ApiResult { + Ok(self.raw_balance.to_string()) + } +} + #[derive(SimpleObject)] #[graphql(complex)] struct Token { diff --git a/backend-rust/src/indexer.rs b/backend-rust/src/indexer.rs index 0048835bb..bdf5ea236 100644 --- a/backend-rust/src/indexer.rs +++ b/backend-rust/src/indexer.rs @@ -1960,21 +1960,22 @@ impl PreparedContractInitialized { } } -/// The token name is generated by using a `version byte 2` and concatenating it -/// with the leb128 byte encoding of the contract index and the leb128 byte +/// The token address is generated by using a `version byte 2` and concatenating +/// it with the leb128 byte encoding of the contract index and the leb128 byte /// encoding of the contract subindex concatenated with the token id in bytes. /// Finally the whole byte array is base 58 check encoded. /// https://proposals.concordium.software/CIS/cis-2.html#token-address +/// The token address is a unique identifier used to distinguish tokens across +/// all smart contracts. pub fn get_token_address( contract_index: u64, contract_subindex: u64, token_id: &TokenId, ) -> Result { - // Encode the contract index and subindex as unsigned LEB128. - let mut contract_index_bytes = Vec::new(); + let mut contract_index_bytes = Vec::with_capacity(64); encode_leb128(&mut contract_index_bytes, contract_index)?; - let mut contract_subindex_bytes = Vec::new(); + let mut contract_subindex_bytes = Vec::with_capacity(64); encode_leb128(&mut contract_subindex_bytes, contract_subindex)?; let token_id_bytes = token_id.as_ref(); @@ -1983,7 +1984,7 @@ pub fn get_token_address( 1 + contract_index_bytes.len() + contract_subindex_bytes.len() + token_id_bytes.len(); let mut bytes = Vec::with_capacity(total_length); - // Fill in the byte buffer. + // Fill in the bytes. bytes.push(2); // version byte 2 bytes.extend_from_slice(&contract_index_bytes); bytes.extend_from_slice(&contract_subindex_bytes);