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

fix: normalize transferred tokens by decimals #159

Merged
merged 17 commits into from
Jan 9, 2025
3 changes: 2 additions & 1 deletion evm/src/omni-bridge/contracts/BridgeTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ library BridgeTypes {
string token,
string name,
string symbol,
uint8 decimals
uint8 decimals,
uint8 originDecimals
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need originDecimals? Since we have token we can look it up on Near

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little bit complicated to retrieve the origin decimals due to async specific of the cross calls on Near, so by adding this we can reduce the number of callbacks on Near side

);

event LogMetadata(
Expand Down
35 changes: 31 additions & 4 deletions evm/src/omni-bridge/contracts/OmniBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,26 @@ contract OmniBridge is
_grantRole(PAUSABLE_ADMIN_ROLE, _msgSender());
}

function addCustomToken(string calldata nearTokenId, address tokenAddress, address customMinter) external onlyRole(DEFAULT_ADMIN_ROLE) {
function addCustomToken(string calldata nearTokenId, address tokenAddress, address customMinter, uint8 originDecimals) external onlyRole(DEFAULT_ADMIN_ROLE) {
isBridgeToken[tokenAddress] = true;
ethToNearToken[tokenAddress] = nearTokenId;
nearToEthToken[nearTokenId] = tokenAddress;
customMinters[tokenAddress] = customMinter;

string memory name = IERC20Metadata(tokenAddress).name();
string memory symbol = IERC20Metadata(tokenAddress).symbol();
uint8 decimals = IERC20Metadata(tokenAddress).decimals();

deployTokenExtension(nearTokenId, tokenAddress, decimals, originDecimals);

emit BridgeTypes.DeployToken(
tokenAddress,
nearTokenId,
name,
symbol,
decimals,
originDecimals
);
}

function removeCustomToken(address tokenAddress) external onlyRole(DEFAULT_ADMIN_ROLE) {
Expand All @@ -88,6 +103,7 @@ contract OmniBridge is
}

require(!isBridgeToken[nearToEthToken[metadata.token]], "ERR_TOKEN_EXIST");
uint8 decimals = _normalizeDecimals(metadata.decimals);

address bridgeTokenProxy = address(
new ERC1967Proxy(
Expand All @@ -96,18 +112,19 @@ contract OmniBridge is
BridgeToken.initialize.selector,
metadata.name,
metadata.symbol,
metadata.decimals
decimals
)
)
);

deployTokenExtension(metadata.token, bridgeTokenProxy);
deployTokenExtension(metadata.token, bridgeTokenProxy, decimals, metadata.decimals);

emit BridgeTypes.DeployToken(
bridgeTokenProxy,
metadata.token,
metadata.name,
metadata.symbol,
decimals,
metadata.decimals
);

Expand All @@ -118,7 +135,7 @@ contract OmniBridge is
return bridgeTokenProxy;
}

function deployTokenExtension(string memory token, address tokenAddress) internal virtual {}
function deployTokenExtension(string memory token, address tokenAddress, uint8 decimals, uint8 originDecimals) internal virtual {}

function setMetadata(
string calldata token,
Expand Down Expand Up @@ -283,6 +300,16 @@ contract OmniBridge is
proxy.upgradeToAndCall(implementation, bytes(""));
}

function _normalizeDecimals(
uint8 decimals
) internal pure returns (uint8) {
uint8 maxAllowedDecimals = 18;
if (decimals > maxAllowedDecimals) {
return maxAllowedDecimals;
}
return decimals;
}

function _authorizeUpgrade(
address newImplementation
) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
Expand Down
6 changes: 4 additions & 2 deletions evm/src/omni-bridge/contracts/OmniBridgeWormhole.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ contract OmniBridgeWormhole is OmniBridge {
_consistencyLevel = consistencyLevel;
}

function deployTokenExtension(string memory token, address tokenAddress) internal override {
function deployTokenExtension(string memory token, address tokenAddress, uint8 decimals, uint8 originDecimals) internal override {
bytes memory payload = bytes.concat(
bytes1(uint8(MessageType.DeployToken)),
Borsh.encodeString(token),
bytes1(omniBridgeChainId),
Borsh.encodeAddress(tokenAddress)
Borsh.encodeAddress(tokenAddress),
bytes1(decimals),
bytes1(originDecimals)
);
_wormhole.publishMessage{value: msg.value}(
wormholeNonce,
Expand Down
55 changes: 50 additions & 5 deletions near/omni-bridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use omni_types::{
BasicMetadata, ChainKind, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress,
PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee,
};
use storage::{TransferMessageStorage, TransferMessageStorageValue, NEP141_DEPOSIT};
use storage::{Decimals, TransferMessageStorage, TransferMessageStorageValue, NEP141_DEPOSIT};

mod errors;
mod storage;
Expand Down Expand Up @@ -70,6 +70,7 @@ enum StorageKey {
TokenDeployerAccounts,
DeployedTokens,
DestinationNonces,
TokenDecimals,
}

#[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)]
Expand Down Expand Up @@ -164,6 +165,7 @@ pub struct Contract {
pub finalised_transfers: LookupSet<TransferId>,
pub token_id_to_address: LookupMap<(ChainKind, AccountId), OmniAddress>,
pub token_address_to_id: LookupMap<OmniAddress, AccountId>,
pub token_decimals: LookupMap<OmniAddress, Decimals>,
pub deployed_tokens: LookupSet<AccountId>,
pub token_deployer_accounts: LookupMap<ChainKind, AccountId>,
pub mpc_signer: AccountId,
Expand Down Expand Up @@ -248,6 +250,7 @@ impl Contract {
finalised_transfers: LookupSet::new(StorageKey::FinalisedTransfers),
token_id_to_address: LookupMap::new(StorageKey::TokenIdToAddress),
token_address_to_id: LookupMap::new(StorageKey::TokenAddressToId),
token_decimals: LookupMap::new(StorageKey::TokenDecimals),
deployed_tokens: LookupSet::new(StorageKey::DeployedTokens),
token_deployer_accounts: LookupMap::new(StorageKey::TokenDeployerAccounts),
mpc_signer,
Expand Down Expand Up @@ -398,12 +401,18 @@ impl Contract {
)
.unwrap_or_else(|| env::panic_str("ERR_FAILED_TO_GET_TOKEN_ADDRESS"));

let decimals = self.token_decimals.get(&token_address);
let amount_to_transfer = Self::normalize_amount(
transfer_message.amount.0 - transfer_message.fee.fee.0,
decimals,
);

let transfer_payload = TransferMessagePayload {
prefix: PayloadType::TransferMessage,
destination_nonce: transfer_message.destination_nonce,
transfer_id,
token_address,
amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0),
amount: U128(amount_to_transfer),
recipient: transfer_message.recipient,
fee_recipient,
};
Expand Down Expand Up @@ -495,14 +504,19 @@ impl Contract {
"Unknown factory"
);

let decimals = self.token_decimals.get(&init_transfer.token);
karim-en marked this conversation as resolved.
Show resolved Hide resolved

let destination_nonce =
self.get_next_destination_nonce(init_transfer.recipient.get_chain());
let transfer_message = TransferMessage {
origin_nonce: init_transfer.origin_nonce,
token: init_transfer.token,
amount: init_transfer.amount,
amount: Self::de_normalize_amount(init_transfer.amount.0, decimals).into(),
recipient: init_transfer.recipient,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

denormalize would be one word

fee: init_transfer.fee,
fee: Fee {
fee: Self::de_normalize_amount(init_transfer.fee.fee.0, decimals).into(),
native_fee: init_transfer.fee.native_fee,
},
sender: init_transfer.sender,
msg: init_transfer.msg,
destination_nonce,
Expand Down Expand Up @@ -564,7 +578,11 @@ impl Contract {
);

let message = self.remove_transfer_message(fin_transfer.transfer_id);
let fee = message.amount.0 - fin_transfer.amount.0;
let de_normalized_amount = Self::de_normalize_amount(
fin_transfer.amount.0,
self.token_decimals.get(&message.token),
olga24912 marked this conversation as resolved.
Show resolved Hide resolved
);
let fee = message.amount.0 - de_normalized_amount;

if message.fee.native_fee.0 != 0 {
if message.get_origin_chain() == ChainKind::Near {
Expand Down Expand Up @@ -774,6 +792,15 @@ impl Contract {
);
self.token_address_to_id
.insert(&deploy_token.token_address, &deploy_token.token);

self.token_decimals.insert(
&deploy_token.token_address,
&Decimals {
decimals: deploy_token.decimals,
origin_decimals: deploy_token.origin_decimals,
},
);

let required_deposit = env::storage_byte_cost()
.saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into());

Expand Down Expand Up @@ -1192,4 +1219,22 @@ impl Contract {
Promise::new(account_id).transfer(amount);
}
}

fn de_normalize_amount(amount: u128, decimals: Option<Decimals>) -> u128 {
if let Some(decimals) = decimals {
let diff_decimals: u128 = (decimals.origin_decimals - decimals.decimals).into();
amount * 10 ^ diff_decimals
} else {
amount
}
}

fn normalize_amount(amount: u128, decimals: Option<Decimals>) -> u128 {
if let Some(decimals) = decimals {
let diff_decimals: u128 = (decimals.origin_decimals - decimals.decimals).into();
amount / 10 ^ diff_decimals
} else {
amount
}
}
}
6 changes: 6 additions & 0 deletions near/omni-bridge/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ impl TransferMessageStorage {
}
}

#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Decimals {
pub decimals: u8,
pub origin_decimals: u8,
}

#[near_bindgen]
impl Contract {
#[payable]
Expand Down
4 changes: 4 additions & 0 deletions near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ struct DeployTokenWh {
payload_type: ProofKind,
token: String,
token_address: OmniAddress,
decimals: u8,
origin_decimals: u8,
}

#[derive(Debug, BorshDeserialize)]
Expand Down Expand Up @@ -215,6 +217,8 @@ impl TryInto<DeployTokenMessage> for ParsedVAA {
Ok(DeployTokenMessage {
token: parsed_payload.token.parse().map_err(stringify)?,
token_address: parsed_payload.token_address.clone(),
decimals: parsed_payload.decimals,
origin_decimals: parsed_payload.origin_decimals,
emitter_address: OmniAddress::new_from_slice(
parsed_payload.token_address.get_chain(),
&self.emitter_address,
Expand Down
13 changes: 8 additions & 5 deletions near/omni-types/src/evm/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ sol! {
string token,
string name,
string symbol,
uint8 decimals
uint8 decimals,
uint8 originDecimals
);

event LogMetadata(
Expand Down Expand Up @@ -128,15 +129,17 @@ impl TryFromLog<Log<DeployToken>> for DeployTokenMessage {
}

Ok(DeployTokenMessage {
emitter_address: OmniAddress::new_from_evm_address(
chain_kind,
H160(event.address.into()),
)?,
token: event.data.token.parse().map_err(stringify)?,
token_address: OmniAddress::new_from_evm_address(
chain_kind,
H160(event.data.tokenAddress.into()),
)?,
decimals: event.data.decimals,
origin_decimals: event.data.originDecimals,
emitter_address: OmniAddress::new_from_evm_address(
chain_kind,
H160(event.address.into()),
)?,
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions near/omni-types/src/prover_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct FinTransferMessage {
pub struct DeployTokenMessage {
pub token: AccountId,
pub token_address: OmniAddress,
pub decimals: u8,
pub origin_decimals: u8,
pub emitter_address: OmniAddress,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ pub const USED_NONCES_ACCOUNT_SIZE: u32 = 8 + (USED_NONCES_PER_ACCOUNT + 7) / 8;

#[constant]
pub const SOLANA_OMNI_BRIDGE_CHAIN_ID: u8 = 2;

#[constant]
pub const MAX_ALLOWED_DECIMALS: u8 = 9;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::{AUTHORITY_SEED, WRAPPED_MINT_SEED};
use crate::constants::{AUTHORITY_SEED, MAX_ALLOWED_DECIMALS, WRAPPED_MINT_SEED};
use crate::instructions::wormhole_cpi::*;
use crate::state::message::SignedPayload;
use crate::state::message::{
Expand All @@ -25,7 +25,7 @@ pub struct DeployToken<'info> {
payer = wormhole.payer,
seeds = [WRAPPED_MINT_SEED, data.payload.token.as_bytes().as_ref()],
bump,
mint::decimals = data.payload.decimals,
mint::decimals = std::cmp::min(MAX_ALLOWED_DECIMALS, data.payload.decimals),
mint::authority = authority,
)]
pub mint: Box<Account<'info, Mint>>,
Expand All @@ -49,9 +49,11 @@ pub struct DeployToken<'info> {
}

impl<'info> DeployToken<'info> {
pub fn initialize_token_metadata(&self, metadata: DeployTokenPayload) -> Result<()> {
pub fn initialize_token_metadata(&self, mut metadata: DeployTokenPayload) -> Result<()> {
let bump = &[self.wormhole.config.bumps.authority];
let signer_seeds = &[&[AUTHORITY_SEED, bump][..]];
let origin_decimals = metadata.decimals;
metadata.decimals = std::cmp::min(MAX_ALLOWED_DECIMALS, metadata.decimals);

let cpi_accounts = CreateMetadataAccountsV3 {
payer: self.wormhole.payer.to_account_info(),
Expand Down Expand Up @@ -86,6 +88,8 @@ impl<'info> DeployToken<'info> {
let payload = DeployTokenResponse {
token: metadata.token,
solana_mint: self.mint.key(),
decimals: metadata.decimals,
origin_decimals,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

.serialize_for_near(())?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ impl Payload for DeployTokenPayload {
pub struct DeployTokenResponse {
pub token: String,
pub solana_mint: Pubkey,
pub decimals: u8,
pub origin_decimals: u8,
}

impl Payload for DeployTokenResponse {
Expand Down
Loading