Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: possibility to set metadata for erc20 contracts #837

Merged
merged 3 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Added functions for setting and getting metadata of ERC-20 contracts deployed with `deploy_erc20_token` transaction
by [@aleksuss]. ([#837])

[#837]: https://github.com/aurora-is-near/aurora-engine/pull/837

## [3.0.0] 2023-08-28

### Fixes
Expand Down
22 changes: 16 additions & 6 deletions engine-standalone-storage/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,24 @@ pub fn parse_transaction_kind(
TransactionKind::SetKeyManager(args)
}
TransactionKindTag::AddRelayerKey => {
let args =
aurora_engine::parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?;
let args = parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?;
TransactionKind::AddRelayerKey(args)
}
TransactionKindTag::RemoveRelayerKey => {
let args =
aurora_engine::parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?;
let args = parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?;
TransactionKind::RemoveRelayerKey(args)
}
TransactionKindTag::StartHashchain => {
let args =
aurora_engine::parameters::StartHashchainArgs::try_from_slice(&bytes).map_err(f)?;
let args = parameters::StartHashchainArgs::try_from_slice(&bytes).map_err(f)?;
TransactionKind::StartHashchain(args)
}
TransactionKindTag::SetErc20Metadata => {
let args: parameters::SetErc20MetadataArgs =
serde_json::from_slice(&bytes).map_err(|e| {
ParseTransactionKindError::failed_deserialization(tx_kind_tag, Some(e))
})?;
TransactionKind::SetErc20Metadata(args)
}
TransactionKindTag::Unknown => {
return Err(ParseTransactionKindError::UnknownMethodName {
name: method_name.into(),
Expand Down Expand Up @@ -609,6 +613,12 @@ fn non_submit_execute<I: IO + Copy>(
TransactionKind::StartHashchain(_) => {
contract_methods::admin::start_hashchain(io, env)?;

None
}
TransactionKind::SetErc20Metadata(_) => {
let mut handler = crate::promise::NoScheduler { promise_data };
contract_methods::connector::set_erc20_metadata(io, env, &mut handler)?;

None
}
};
Expand Down
12 changes: 12 additions & 0 deletions engine-standalone-storage/src/sync/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ pub enum TransactionKind {
/// Remove the relayer public function call access key
RemoveRelayerKey(parameters::RelayerKeyArgs),
StartHashchain(parameters::StartHashchainArgs),
/// Set metadata of ERC-20 contract.
SetErc20Metadata(parameters::SetErc20MetadataArgs),
/// Sentinel kind for cases where a NEAR receipt caused a
/// change in Aurora state, but we failed to parse the Action.
Unknown,
Expand Down Expand Up @@ -374,6 +376,7 @@ impl TransactionKind {
Self::AddRelayerKey(_) => Self::no_evm_execution("add_relayer_key"),
Self::RemoveRelayerKey(_) => Self::no_evm_execution("remove_relayer_key"),
Self::StartHashchain(_) => Self::no_evm_execution("start_hashchain"),
Self::SetErc20Metadata(_) => Self::no_evm_execution("set_erc20_metadata"),
}
}

Expand Down Expand Up @@ -484,6 +487,8 @@ pub enum TransactionKindTag {
RemoveRelayerKey,
#[strum(serialize = "start_hashchain")]
StartHashchain,
#[strum(serialize = "set_erc20_metadata")]
SetErc20Metadata,
Unknown,
}

Expand Down Expand Up @@ -532,6 +537,7 @@ impl TransactionKind {
args.try_to_vec().unwrap_or_default()
}
Self::StartHashchain(args) => args.try_to_vec().unwrap_or_default(),
Self::SetErc20Metadata(args) => serde_json::to_vec(args).unwrap_or_default(),
}
}
}
Expand Down Expand Up @@ -575,6 +581,7 @@ impl From<&TransactionKind> for TransactionKindTag {
TransactionKind::AddRelayerKey(_) => Self::AddRelayerKey,
TransactionKind::RemoveRelayerKey(_) => Self::RemoveRelayerKey,
TransactionKind::StartHashchain(_) => Self::StartHashchain,
TransactionKind::SetErc20Metadata(_) => Self::SetErc20Metadata,
TransactionKind::Unknown => Self::Unknown,
}
}
Expand Down Expand Up @@ -755,6 +762,7 @@ enum BorshableTransactionKind<'a> {
AddRelayerKey(Cow<'a, parameters::RelayerKeyArgs>),
RemoveRelayerKey(Cow<'a, parameters::RelayerKeyArgs>),
StartHashchain(Cow<'a, parameters::StartHashchainArgs>),
SetErc20Metadata(Cow<'a, parameters::SetErc20MetadataArgs>),
}

impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> {
Expand Down Expand Up @@ -807,6 +815,7 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> {
TransactionKind::AddRelayerKey(x) => Self::AddRelayerKey(Cow::Borrowed(x)),
TransactionKind::RemoveRelayerKey(x) => Self::RemoveRelayerKey(Cow::Borrowed(x)),
TransactionKind::StartHashchain(x) => Self::StartHashchain(Cow::Borrowed(x)),
TransactionKind::SetErc20Metadata(x) => Self::SetErc20Metadata(Cow::Borrowed(x)),
}
}
}
Expand Down Expand Up @@ -880,6 +889,9 @@ impl<'a> TryFrom<BorshableTransactionKind<'a>> for TransactionKind {
Ok(Self::RemoveRelayerKey(x.into_owned()))
}
BorshableTransactionKind::StartHashchain(x) => Ok(Self::StartHashchain(x.into_owned())),
BorshableTransactionKind::SetErc20Metadata(x) => {
Ok(Self::SetErc20Metadata(x.into_owned()))
}
}
}
}
47 changes: 47 additions & 0 deletions engine-tests/src/tests/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::utils::{
use aurora_engine::engine::EngineErrorKind;
use aurora_engine::parameters::TransactionStatus;
use aurora_engine_sdk as sdk;
use aurora_engine_types::parameters::connector::{Erc20Metadata, SetErc20MetadataArgs};
use bstr::ByteSlice;
use libsecp256k1::SecretKey;

Expand Down Expand Up @@ -237,6 +238,52 @@ fn deploy_erc_20_out_of_gas() {
);
}

#[test]
fn test_erc20_get_and_set_metadata() {
let mut runner = utils::deploy_runner();
let erc20_address = runner.deploy_erc20_token("token");
let caller = runner.aurora_account_id.clone();
let result = runner.one_shot().call(
"get_erc20_metadata",
&caller,
erc20_address.as_bytes().to_vec(),
);

assert!(result.is_ok());

let metadata: Erc20Metadata =
serde_json::from_slice(&result.unwrap().return_data.as_value().unwrap()).unwrap();
assert_eq!(metadata, Erc20Metadata::default());

let new_metadata = Erc20Metadata {
name: "USD Token".to_string(),
symbol: "USDT".to_string(),
decimals: 20,
};

let result = runner.call(
"set_erc20_metadata",
&caller,
serde_json::to_vec(&SetErc20MetadataArgs {
erc20_address,
erc20_metadata: new_metadata.clone(),
})
.unwrap(),
);
assert!(result.is_ok());

let result = runner.one_shot().call(
"get_erc20_metadata",
&caller,
erc20_address.as_bytes().to_vec(),
);
assert!(result.is_ok());

let metadata: Erc20Metadata =
serde_json::from_slice(&result.unwrap().return_data.as_value().unwrap()).unwrap();
assert_eq!(metadata, new_metadata);
}

fn get_address_erc20_balance(
runner: &utils::AuroraRunner,
signer: &Signer,
Expand Down
8 changes: 5 additions & 3 deletions engine-tests/src/tests/sanity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,10 +681,12 @@ fn test_num_wasm_functions() {
let module = walrus::ModuleConfig::default()
.parse(runner.code.code())
.unwrap();
let num_functions = module.funcs.iter().count();
let expected_number = 1460;
let actual_number = module.funcs.iter().count();

assert!(
num_functions <= 1445,
"{num_functions} is not less than 1445",
actual_number <= expected_number,
"{actual_number} is not less than {expected_number}",
);
}

Expand Down
30 changes: 30 additions & 0 deletions engine-types/src/parameters/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,36 @@ impl rlp::Encodable for LogEntry {
}
}

/// Parameters for `set_erc20_metadata` function.
#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct SetErc20MetadataArgs {
/// Address of the ERC-20 contract.
pub erc20_address: Address,
/// Metadata of the ERC-20 contract.
pub erc20_metadata: Erc20Metadata,
}

/// Metadata of ERC-20 contract.
#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Erc20Metadata {
/// Name of the token.
pub name: String,
/// Symbol of the token.
pub symbol: String,
/// Number of decimals.
pub decimals: u8,
}

impl Default for Erc20Metadata {
fn default() -> Self {
Self {
name: "Empty".to_string(),
symbol: "EMPTY".to_string(),
decimals: 0,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
18 changes: 14 additions & 4 deletions engine-types/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,21 @@ pub type RawH256 = [u8; 32]; // Unformatted binary data of fixed length.

pub type StorageUsage = u64;

/// Selector to call mint function in ERC 20 contract
///
/// `keccak("mint(address,uint256)".as_bytes())[..4];`
#[allow(dead_code)]
/// Selector to call `mint` function in ERC 20 contract.
/// `keccak(b"mint(address,uint256)")[..4];`
pub const ERC20_MINT_SELECTOR: &[u8] = &[64, 193, 15, 25];
/// Selector to call `setMetadata` function in ERC-20 contact.
/// `keccak(b"setMetadata(string,string,uint8)")[..4];`
pub const ERC20_SET_METADATA_SELECTOR: &[u8] = &[55, 210, 194, 244];
/// Selector to call `name` function in ERC-20 contact.
/// `keccak(b"name()")[..4];`
pub const ERC20_NAME_SELECTOR: &[u8] = &[6, 253, 222, 3];
/// Selector to call `symbol` function in ERC-20 contact.
/// `keccak(b"symbol()")[..4];`
pub const ERC20_SYMBOL_SELECTOR: &[u8] = &[149, 216, 155, 65];
/// Selector to call `digits` function in ERC-20 contact.
/// `keccak(b"digits()")[..4];`
pub const ERC20_DIGITS_SELECTOR: &[u8] = &[49, 60, 229, 103];

#[derive(Debug)]
pub enum AddressValidationError {
Expand Down
52 changes: 50 additions & 2 deletions engine/src/contract_methods/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use aurora_engine_types::{
parameters::{
connector::{
InitCallArgs, NEP141FtOnTransferArgs, ResolveTransferCallArgs, SetContractDataCallArgs,
StorageDepositCallArgs, StorageWithdrawCallArgs, TransferCallArgs,
TransferCallCallArgs,
SetErc20MetadataArgs, StorageDepositCallArgs, StorageWithdrawCallArgs,
TransferCallArgs, TransferCallCallArgs,
},
engine::{
errors::ParseTypeFromJsonError, DeployErc20TokenArgs, PauseEthConnectorCallArgs,
Expand Down Expand Up @@ -404,3 +404,51 @@ pub fn set_paused_flags<I: IO + Copy, E: Env>(io: I, env: &E) -> Result<(), Cont
Ok(())
})
}

#[named]
pub fn set_erc20_metadata<I: IO + Copy, E: Env, H: PromiseHandler>(
io: I,
env: &E,
handler: &mut H,
) -> Result<SubmitResult, ContractError> {
with_hashchain(io, env, function_name!(), |io| {
let state = state::get_state(&io)?;
require_running(&state)?;
// TODO: Define special role for this transaction. Potentially via multisig?
aleksuss marked this conversation as resolved.
Show resolved Hide resolved
let is_private = env.assert_private_call();
if is_private.is_err() {
require_owner_only(&state, &env.predecessor_account_id())?;
}

let args: SetErc20MetadataArgs = serde_json::from_slice(&io.read_input().to_vec())
.map_err(Into::<ParseTypeFromJsonError>::into)?;
let current_account_id = env.current_account_id();
let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state(
state,
predecessor_address(&env.predecessor_account_id()),
current_account_id,
io,
env,
);
let result = engine.set_erc20_metadata(args.erc20_address, args.erc20_metadata, handler)?;

Ok(result)
})
}

pub fn get_erc20_metadata<I: IO + Copy, E: Env>(mut io: I, env: &E) -> Result<(), ContractError> {
let erc20_address = io.read_input_arr20().map(Address::from_array)?;
let state = state::get_state(&io)?;
let current_account_id = env.current_account_id();
let engine: Engine<_, E, AuroraModExp> = Engine::new_with_state(
state,
predecessor_address(&env.predecessor_account_id()),
current_account_id,
io,
env,
);
let metadata = engine.get_erc20_metadata(erc20_address)?;

io.return_output(&serde_json::to_vec(&metadata).map_err(|_| errors::ERR_SERIALIZE)?);
Ok(())
}
8 changes: 4 additions & 4 deletions engine/src/contract_methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl<T: AsRef<[u8]> + Send + Sync + 'static> From<T> for ContractError {

/// This type is structurally the same as `ContractError`, but
/// importantly `ContractError` implements `From<T: AsRef<[u8]>>`
/// for easy usage in the this module's function implementations, while
/// for easy usage in this module's function implementations, while
/// `ErrorMessage` implements `AsRef<[u8]>` for compatibility with
/// `sdk_unwrap`.
pub struct ErrorMessage {
Expand All @@ -60,22 +60,22 @@ impl AsRef<[u8]> for ErrorMessage {
}
}

fn require_running(state: &crate::state::EngineState) -> Result<(), ContractError> {
fn require_running(state: &state::EngineState) -> Result<(), ContractError> {
if state.is_paused {
return Err(errors::ERR_PAUSED.into());
}
Ok(())
}

fn require_paused(state: &crate::state::EngineState) -> Result<(), ContractError> {
fn require_paused(state: &state::EngineState) -> Result<(), ContractError> {
if !state.is_paused {
return Err(errors::ERR_RUNNING.into());
}
Ok(())
}

fn require_owner_only(
state: &crate::state::EngineState,
state: &state::EngineState,
predecessor_account_id: &AccountId,
) -> Result<(), ContractError> {
if &state.owner_id != predecessor_account_id {
Expand Down
Loading
Loading