diff --git a/Cargo.toml b/Cargo.toml index 86cdca639..3dde7ba07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ lto = true opt-level = 3 [dependencies] +base64 = { version = "0.13.0", default-features = false, features = ["alloc"] } blake2 = { git = "https://github.com/near/near-blake2.git", version = "0.9.1", default-features = false } borsh = { version = "0.8.2", default-features = false } bn = { package = "aurora-bn", git = "https://github.com/aurora-is-near/aurora-bn.git", default-features = false } @@ -80,7 +81,6 @@ rand = "0.7.3" criterion = "0.3.4" git2 = "0.13" lazy-static-include = "3.1.1" -base64 = "0.13.0" [features] default = ["sha2", "std"] diff --git a/etc/state-migration-test/Cargo.lock b/etc/state-migration-test/Cargo.lock index afcdb50df..eaf655a5e 100644 --- a/etc/state-migration-test/Cargo.lock +++ b/etc/state-migration-test/Cargo.lock @@ -34,6 +34,7 @@ name = "aurora-engine" version = "1.5.0" dependencies = [ "aurora-bn", + "base64", "blake2", "borsh", "byte-slice-cast", @@ -67,6 +68,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "beef" version = "0.5.0" diff --git a/src/connector.rs b/src/connector.rs index 143b09bcf..2a80229af 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -82,6 +82,7 @@ impl EthConnectorContract { let contract_data = Self::set_contract_data(SetContractDataCallArgs { prover_account: args.prover_account, eth_custodian_address: args.eth_custodian_address, + metadata: args.metadata, }); let current_account_id = sdk::current_account_id(); @@ -118,6 +119,11 @@ impl EthConnectorContract { &contract_data, ); + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::FungibleTokenMetadata), + &args.metadata, + ); + contract_data } @@ -616,6 +622,14 @@ impl EthConnectorContract { pub fn set_paused_flags(&mut self, args: PauseEthConnectorCallArgs) { self.set_paused(args.paused_mask); } + + /// Return metdata + pub fn get_metadata() -> Option { + sdk::read_storage(&Self::get_contract_key( + &EthConnectorStorageId::FungibleTokenMetadata, + )) + .and_then(|data| FungibleTokenMetadata::try_from_slice(&data).ok()) + } } impl AdminControlled for EthConnectorContract { diff --git a/src/fungible_token.rs b/src/fungible_token.rs index 00ff7b5c0..de57c10cb 100644 --- a/src/fungible_token.rs +++ b/src/fungible_token.rs @@ -1,5 +1,7 @@ +use crate::json::JsonValue; #[cfg(feature = "log")] use crate::prelude::format; +use crate::prelude::BTreeMap; use crate::types::*; use borsh::{BorshDeserialize, BorshSerialize}; use { @@ -27,6 +29,67 @@ pub struct FungibleToken { pub account_storage_usage: StorageUsage, } +#[derive(BorshDeserialize, BorshSerialize, Clone)] +pub struct FungibleTokenMetadata { + pub spec: String, + pub name: String, + pub symbol: String, + pub icon: Option, + pub reference: Option, + pub reference_hash: Option<[u8; 32]>, + pub decimals: u8, +} + +impl Default for FungibleTokenMetadata { + fn default() -> Self { + Self { + spec: "ft-1.0.0".to_string(), + name: "Ether".to_string(), + symbol: "ETH".to_string(), + icon: Some("data:image/png;base64,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".to_string()), + reference: None, + reference_hash: None, + decimals: 18, + } + } +} + +impl From for JsonValue { + fn from(metadata: FungibleTokenMetadata) -> Self { + let mut kvs = BTreeMap::new(); + kvs.insert("spec".to_string(), JsonValue::String(metadata.spec)); + kvs.insert("name".to_string(), JsonValue::String(metadata.name)); + kvs.insert("symbol".to_string(), JsonValue::String(metadata.symbol)); + kvs.insert( + "icon".to_string(), + metadata + .icon + .map(JsonValue::String) + .unwrap_or(JsonValue::Null), + ); + kvs.insert( + "reference".to_string(), + metadata + .reference + .map(JsonValue::String) + .unwrap_or(JsonValue::Null), + ); + kvs.insert( + "reference_hash".to_string(), + metadata + .reference_hash + .map(|hash| JsonValue::String(base64::encode(hash))) + .unwrap_or(JsonValue::Null), + ); + kvs.insert( + "decimals".to_string(), + JsonValue::U64(metadata.decimals as u64), + ); + + JsonValue::Object(kvs) + } +} + impl FungibleToken { pub fn new() -> Self { Self::default() diff --git a/src/json.rs b/src/json.rs index 39d68ce88..242699d73 100644 --- a/src/json.rs +++ b/src/json.rs @@ -5,6 +5,7 @@ use rjson::{Array, Null, Object, Value}; #[cfg(test)] use std::collections::BTreeMap; +#[derive(PartialEq)] pub enum JsonValue { Null, F64(f64), diff --git a/src/lib.rs b/src/lib.rs index 2f7842ecd..f6cdba375 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ mod contract { use crate::connector::EthConnectorContract; use crate::engine::{Engine, EngineState, GasPaymentError}; + use crate::fungible_token::FungibleTokenMetadata; #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; use crate::parameters::{ @@ -181,8 +182,7 @@ mod contract { /// code. #[no_mangle] pub extern "C" fn state_migration() { - // This function is purposely left empty because we do not have any state migration - // to do. + // TODO: currently we don't have migrations } /// @@ -637,6 +637,14 @@ mod contract { ); } + #[no_mangle] + pub extern "C" fn ft_metadata() { + let metadata: FungibleTokenMetadata = + EthConnectorContract::get_metadata().unwrap_or_default(); + let json_data = crate::json::JsonValue::from(metadata); + sdk::return_output(json_data.to_string().as_bytes()) + } + /// Due to the design change to stop minting and burning bridged ETH tokens /// (see https://github.com/aurora-is-near/aurora-engine/pull/133 ), /// there is currently an incorrect number of tokens in the Aurora NEP-141 token diff --git a/src/parameters.rs b/src/parameters.rs index b91a8f23b..214d3d541 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -1,5 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use crate::fungible_token::FungibleTokenMetadata; use crate::prelude::{String, Vec}; use crate::types::{AccountId, Balance, RawAddress, RawH256, RawU256}; use crate::{ @@ -266,7 +267,7 @@ pub struct StorageBalance { impl StorageBalance { pub fn to_json_bytes(&self) -> Vec { crate::prelude::format!( - "{{\"total\": \"{}\", \"available\": \"{}\",}}", + "{{\"total\": \"{}\", \"available\": \"{}\"}}", self.total.to_string(), self.available.to_string() ) @@ -316,6 +317,7 @@ pub struct FinishDepositEthCallArgs { pub struct InitCallArgs { pub prover_account: AccountId, pub eth_custodian_address: AccountId, + pub metadata: FungibleTokenMetadata, } /// Eth-connector Set contract data call args diff --git a/src/storage.rs b/src/storage.rs index 52af685e9..6d8132fbf 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -30,6 +30,7 @@ pub enum EthConnectorStorageId { UsedEvent = 0x2, PausedMask = 0x3, StatisticsAuroraAccountsCounter = 0x4, + FungibleTokenMetadata = 0x5, } /// We can't use const generic over Enum, but we can do it over integral type diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index ea0cf0fce..c8e8fc2aa 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -12,7 +12,7 @@ use primitive_types::U256; use rlp::RlpStream; use secp256k1::{self, Message, PublicKey, SecretKey}; -use crate::fungible_token::FungibleToken; +use crate::fungible_token::{FungibleToken, FungibleTokenMetadata}; use crate::parameters::{InitCallArgs, NewCallArgs, SubmitResult, TransactionStatus}; use crate::prelude::Address; use crate::storage; @@ -381,6 +381,7 @@ pub(crate) fn deploy_evm() -> AuroraRunner { let args = InitCallArgs { prover_account: "prover.near".to_string(), eth_custodian_address: "d045f7e19B2488924B97F9c145b5E51D0D895A65".to_string(), + metadata: FungibleTokenMetadata::default(), }; let (_, maybe_error) = runner.call( "new_eth_connector", diff --git a/src/tests/eth_connector.rs b/src/tests/eth_connector.rs index 1f8994d60..2c65e70d5 100644 --- a/src/tests/eth_connector.rs +++ b/src/tests/eth_connector.rs @@ -2,6 +2,7 @@ use crate::admin_controlled::{PausedMask, ERR_PAUSED}; use crate::connector::{ ERR_NOT_ENOUGH_BALANCE_FOR_FEE, PAUSE_DEPOSIT, PAUSE_WITHDRAW, UNPAUSE_ALL, }; +use crate::fungible_token::FungibleTokenMetadata; use crate::parameters::{ InitCallArgs, NewCallArgs, RegisterRelayerCallArgs, WithdrawCallArgs, WithdrawResult, }; @@ -71,6 +72,7 @@ fn init_contract( &InitCallArgs { prover_account: PROVER_ACCOUNT.into(), eth_custodian_address: custodian_address.into(), + metadata: FungibleTokenMetadata::default(), } .try_to_vec() .unwrap(), diff --git a/src/tests/sanity.rs b/src/tests/sanity.rs index fec285b7a..669cea74d 100644 --- a/src/tests/sanity.rs +++ b/src/tests/sanity.rs @@ -1,3 +1,4 @@ +use crate::fungible_token::FungibleTokenMetadata; use crate::parameters::{SubmitResult, TransactionStatus}; use crate::prelude::{Address, U256}; use crate::test_utils; @@ -318,6 +319,25 @@ fn test_block_hash_contract() { test_utils::panic_on_fail(result.status); } +#[test] +fn test_ft_metadata() { + let mut runner = test_utils::deploy_evm(); + + let (maybe_outcome, maybe_error) = runner.call( + "ft_metadata", + runner.context.signer_account_id.clone(), + Vec::new(), + ); + assert!(maybe_error.is_none()); + let outcome = maybe_outcome.unwrap(); + let json_value = crate::json::parse_json(&outcome.return_data.as_value().unwrap()).unwrap(); + + assert_eq!( + json_value, + crate::json::JsonValue::from(FungibleTokenMetadata::default()) + ); +} + #[cfg(feature = "testnet-test")] #[test] fn test_balance_evm_and_nep_141() { diff --git a/src/tests/state_migration.rs b/src/tests/state_migration.rs index 6eb65ae71..fd055cd52 100644 --- a/src/tests/state_migration.rs +++ b/src/tests/state_migration.rs @@ -53,6 +53,7 @@ pub fn deploy_evm() -> AuroraAccount { let init_args = InitCallArgs { prover_account, eth_custodian_address: "d045f7e19B2488924B97F9c145b5E51D0D895A65".to_string(), + metadata: Default::default(), }; contract_account .call(