Skip to content

Commit

Permalink
Simplify adding tokens and add compatibility with burn_fee (#6102)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Jul 24, 2024
1 parent 4d02fa2 commit 487eaeb
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 97 deletions.
4 changes: 4 additions & 0 deletions backend/canisters/registry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [unreleased]

### Changed

- Simplify adding tokens and add compatibility with `burn_fee` ([#6102](https://github.com/open-chat-labs/open-chat/pull/6102))

## [[2.0.1193](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1193-registry)] - 2024-06-06

### Fixed
Expand Down
46 changes: 21 additions & 25 deletions backend/canisters/registry/impl/src/jobs/check_for_token_updates.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::metadata_helper::MetadataHelper;
use crate::{mutate_state, read_state};
use ic_cdk::api::call::RejectionCode;
use icrc_ledger_types::icrc::generic_metadata_value::MetadataValue;
use std::time::Duration;
use tracing::error;
use types::CanisterId;
use utils::canister_timers::run_now_then_interval;
use utils::time::HOUR_IN_MS;
Expand All @@ -22,34 +23,29 @@ async fn run_async() {

async fn check_for_token_updates(ledger_canister_id: CanisterId) -> Result<(), (RejectionCode, String)> {
let metadata = icrc_ledger_canister_c2c_client::icrc1_metadata(ledger_canister_id).await?;
let metadata_helper = match MetadataHelper::try_parse(metadata) {
Ok(h) => h,
Err(reason) => {
let error = format!("Token metadata is incomplete: {reason}");
error!(%ledger_canister_id, error);
return Err((RejectionCode::Unknown, error));
}
};

mutate_state(|state| {
if let Some(token) = state.data.tokens.get(ledger_canister_id).cloned() {
let mut args = registry_canister::update_token::Args::new(ledger_canister_id);
for (name, value) in metadata {
match name.as_str() {
"icrc1:logo" => {
if let MetadataValue::Text(logo) = value {
if logo != token.logo {
args.logo = Some(logo);
}
}
}
"icrc1:name" => {
if let MetadataValue::Text(name) = value {
if name != token.name {
args.name = Some(name);
}
}
}
"icrc1:symbol" => {
if let MetadataValue::Text(symbol) = value {
if symbol != token.symbol {
args.symbol = Some(symbol);
}
}
}
_ => {}
if *metadata_helper.name() != token.name {
args.name = Some(metadata_helper.name().to_string());
}

if *metadata_helper.symbol() != token.symbol {
args.symbol = Some(metadata_helper.symbol().to_string());
}

if let Some(logo) = metadata_helper.logo().cloned() {
if logo != token.logo {
args.logo = Some(logo);
}
}

Expand Down
1 change: 1 addition & 0 deletions backend/canisters/registry/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod guards;
mod jobs;
mod lifecycle;
mod memory;
mod metadata_helper;
mod model;
mod queries;
mod updates;
Expand Down
74 changes: 74 additions & 0 deletions backend/canisters/registry/impl/src/metadata_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use icrc_ledger_types::icrc::generic_metadata_value::MetadataValue;

pub struct MetadataHelper {
name: String,
symbol: String,
decimals: u8,
fee: u128,
logo: Option<String>,
is_icrc1_compatible: bool,
}

impl MetadataHelper {
pub fn try_parse(metadata: Vec<(String, MetadataValue)>) -> Result<MetadataHelper, String> {
let mut name = None;
let mut symbol = None;
let mut decimals = None;
let mut fee = None;
let mut burn_fee = None;
let mut logo = None;
let mut is_icrc1_compatible = true;

for (key, value) in metadata {
match (key.as_str(), value) {
("icrc1:name", MetadataValue::Text(s)) => name = Some(s),
("icrc1:symbol", MetadataValue::Text(s)) => symbol = Some(s),
("icrc1:decimals", MetadataValue::Nat(n)) => decimals = u8::try_from(n.0).ok(),
("icrc1:fee", MetadataValue::Nat(n)) => fee = u128::try_from(n.0).ok(),
("icrc1:burn_fee", MetadataValue::Nat(n)) => burn_fee = u128::try_from(n.0).ok(),
("icrc1:logo", MetadataValue::Text(s)) => logo = Some(s),
("icrc1:transfer_fee_rate" | "icrc1:burn_fee_rate", _) => is_icrc1_compatible = false,
_ => {}
}
}

match (name, symbol, decimals, fee) {
(Some(n), Some(s), Some(d), Some(f)) => Ok(MetadataHelper {
name: n,
symbol: s,
decimals: d,
fee: f + burn_fee.unwrap_or_default(),
logo,
is_icrc1_compatible,
}),
(None, ..) => Err("Name not found".to_string()),
(_, None, ..) => Err("Symbol not found".to_string()),
(.., None, _) => Err("Decimals not found".to_string()),
(.., None) => Err("Fee not found".to_string()),
}
}

pub fn name(&self) -> &str {
&self.name
}

pub fn symbol(&self) -> &str {
&self.symbol
}

pub fn decimals(&self) -> u8 {
self.decimals
}

pub fn fee(&self) -> u128 {
self.fee
}

pub fn logo(&self) -> Option<&String> {
self.logo.as_ref()
}

pub fn is_icrc1_compatible(&self) -> bool {
self.is_icrc1_compatible
}
}
118 changes: 46 additions & 72 deletions backend/canisters/registry/impl/src/updates/add_token.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use crate::guards::caller_is_governance_principal;
use crate::metadata_helper::MetadataHelper;
use crate::mutate_state;
use canister_api_macros::proposal;
use canister_tracing_macros::trace;
use futures::try_join;
use ic_cdk::api::call::RejectionCode;
use icrc_ledger_types::icrc::generic_metadata_value::MetadataValue;
use registry_canister::add_token::{Response::*, *};
use registry_canister::NervousSystemDetails;
use tracing::{error, info};
Expand Down Expand Up @@ -49,7 +47,12 @@ async fn add_token_impl(
Err(error) => return InternalError(format!("{error:?}")),
};

if !check_icrc1_compatibility(&metadata) {
let metadata_helper = match MetadataHelper::try_parse(metadata) {
Ok(h) => h,
Err(reason) => return InvalidRequest(format!("Token metadata is incomplete: {reason}")),
};

if !metadata_helper.is_icrc1_compatible() {
return InvalidRequest("Token is not compatible with the ICRC1 standard".to_string());
}

Expand All @@ -65,46 +68,46 @@ async fn add_token_impl(
}
};

match try_join!(
icrc_ledger_canister_c2c_client::icrc1_name(ledger_canister_id),
icrc_ledger_canister_c2c_client::icrc1_symbol(ledger_canister_id),
icrc_ledger_canister_c2c_client::icrc1_decimals(ledger_canister_id),
icrc_ledger_canister_c2c_client::icrc1_fee(ledger_canister_id),
icrc_ledger_canister_c2c_client::icrc1_supported_standards(ledger_canister_id),
get_logo(logo, metadata, nervous_system.as_ref().map(|ns| ns.logo.clone())),
) {
Ok((.., logo)) if logo.is_none() => {
let error = "Failed to find logo for token";
error!(%ledger_canister_id, error);
InternalError(error.to_string())
}
Ok((name, symbol, decimals, fee, standards, logo)) => mutate_state(|state| {
let now = state.env.now();
let standards = standards.into_iter().map(|r| r.name).collect();
if let Some(logo) = metadata_helper
.logo()
.cloned()
.or(logo)
.or(nervous_system.as_ref().map(|ns| ns.logo.clone()))
{
match icrc_ledger_canister_c2c_client::icrc1_supported_standards(ledger_canister_id).await {
Ok(standards) => mutate_state(|state| {
let now = state.env.now();
let standards = standards.into_iter().map(|r| r.name).collect();

if let Some(ns) = nervous_system {
state.data.nervous_systems.add(ns, now);
}
if state.data.tokens.add(
ledger_canister_id,
name.clone(),
symbol,
decimals,
fee.0.try_into().unwrap(),
logo.unwrap(),
info_url,
how_to_buy_url,
transaction_url_format,
standards,
now,
) {
info!(name, %ledger_canister_id, "Token added");
Success
} else {
AlreadyAdded
}
}),
Err(error) => InternalError(format!("{error:?}")),
if let Some(ns) = nervous_system {
state.data.nervous_systems.add(ns, now);
}
let name = metadata_helper.name().to_string();
if state.data.tokens.add(
ledger_canister_id,
name.clone(),
metadata_helper.symbol().to_string(),
metadata_helper.decimals(),
metadata_helper.fee(),
logo,
info_url,
how_to_buy_url,
transaction_url_format,
standards,
now,
) {
info!(name, %ledger_canister_id, "Token added");
Success
} else {
AlreadyAdded
}
}),
Err(error) => InternalError(format!("{error:?}")),
}
} else {
let error = "Failed to find logo for token";
error!(%ledger_canister_id, error);
InternalError(error.to_string())
}
}

Expand Down Expand Up @@ -157,32 +160,3 @@ fn extract_urls(
transaction_url_format,
})
}

async fn get_logo(
logo: Option<String>,
metadata: Vec<(String, MetadataValue)>,
governance_logo: Option<String>,
) -> Result<Option<String>, (RejectionCode, String)> {
let metadata_logo = metadata.into_iter().find(|(k, _)| k == "icrc1:logo").and_then(|(_, v)| {
if let MetadataValue::Text(t) = v {
Some(t)
} else {
None
}
});

Ok(metadata_logo.or(logo).or(governance_logo))
}

fn check_icrc1_compatibility(metadata: &[(String, MetadataValue)]) -> bool {
for (k, v) in metadata {
if k == "icrc1:transfer_fee_rate" || k == "icrc1:burn_fee" || k == "icrc1:burn_fee_rate" {
match v {
MetadataValue::Nat(x) if *x > 0u128 => return false,
MetadataValue::Int(x) if *x > 0i128 => return false,
_ => {}
}
}
}
true
}

0 comments on commit 487eaeb

Please sign in to comment.