diff --git a/.changeset/grumpy-cheetahs-study.md b/.changeset/grumpy-cheetahs-study.md new file mode 100644 index 00000000..37074560 --- /dev/null +++ b/.changeset/grumpy-cheetahs-study.md @@ -0,0 +1,6 @@ +--- +'@fuel-bridge/solidity-contracts': patch +'@fuel-bridge/fungible-token': patch +--- + +Use L1 token decimals to determine L2 token decimals diff --git a/packages/fungible-token/bridge-fungible-token/src/main.sw b/packages/fungible-token/bridge-fungible-token/src/main.sw index 70977f7b..1e8e58ae 100644 --- a/packages/fungible-token/bridge-fungible-token/src/main.sw +++ b/packages/fungible-token/bridge-fungible-token/src/main.sw @@ -58,11 +58,10 @@ use utils::{ }; use src_20::SRC20; -const DEFAULT_DECIMALS: u8 = 9u8; +const FUEL_ASSET_DECIMALS: u8 = 9u8; const ZERO_U256 = 0x00u256; configurable { - DECIMALS: u64 = 9u64, BRIDGED_TOKEN_GATEWAY: b256 = 0x00000000000000000000000096c53cd98B7297564716a8f2E1de2C83928Af2fe, } @@ -74,6 +73,7 @@ storage { l1_addresses: StorageMap = StorageMap {}, l1_symbols: StorageMap = StorageMap {}, l1_names: StorageMap = StorageMap {}, + decimals: StorageMap = StorageMap {}, total_assets: u64 = 0, } @@ -203,8 +203,8 @@ impl SRC20 for Contract { #[storage(read)] fn decimals(asset: AssetId) -> Option { - match storage.tokens_minted.get(asset).try_read() { - Some(_) => Some(DECIMALS.try_as_u8().unwrap_or(DEFAULT_DECIMALS)), + match storage.l1_addresses.get(asset).try_read() { + Some(l1_address) => storage.decimals.get(l1_address).try_read(), None => None, } } @@ -329,6 +329,15 @@ fn _process_deposit(message_data: DepositMessage, msg_idx: u64) { storage .l1_addresses .insert(asset_id, message_data.token_address); + if message_data.decimals < FUEL_ASSET_DECIMALS { + storage + .decimals + .insert(message_data.token_address, message_data.decimals); + } else { + storage + .decimals + .insert(message_data.token_address, FUEL_ASSET_DECIMALS); + } }; // mint tokens & update storage mint(sub_id, amount); diff --git a/packages/fungible-token/bridge-fungible-token/tests/functions/message_receiver/mod.rs b/packages/fungible-token/bridge-fungible-token/tests/functions/message_receiver/mod.rs index 11b217a1..9791f04c 100644 --- a/packages/fungible-token/bridge-fungible-token/tests/functions/message_receiver/mod.rs +++ b/packages/fungible-token/bridge-fungible-token/tests/functions/message_receiver/mod.rs @@ -512,12 +512,13 @@ mod success { } #[tokio::test] - async fn register_metadata() { + async fn register_metadata_18_decimals() { let mut wallet = create_wallet(); let configurables: Option = None; let name = "Token".to_string(); let symbol = "TKN".to_string(); + let decimals = 18; let amount: u64 = u64::MAX; let (deposit_message, coin, _) = create_deposit_message( @@ -526,7 +527,232 @@ mod success { FROM, *wallet.address().hash(), U256::from(amount), - BRIDGED_TOKEN_DECIMALS, + decimals, + configurables.clone(), + false, + None, + ) + .await; + + let metadata_message = create_metadata_message( + BRIDGED_TOKEN, + BRIDGED_TOKEN_ID, + &name, + &symbol, + configurables.clone(), + ) + .await; + + let (bridge, utxo_inputs) = setup_environment( + &mut wallet, + vec![coin], + vec![deposit_message, (0, metadata_message)], + None, + None, + configurables, + ) + .await; + + let asset_id = get_asset_id(bridge.contract_id(), BRIDGED_TOKEN); + let provider = wallet.provider().expect("Needs provider"); + + // Relay the deposit message + let tx_id = relay_message_to_contract( + &wallet, + utxo_inputs.message[0].clone(), + utxo_inputs.contract.clone(), + ) + .await; + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + assert!(matches!(tx_status, TxStatus::Success { .. })); + + let l1_address: Bits256 = bridge + .methods() + .asset_to_l1_address(asset_id) + .call() + .await + .unwrap() + .value; + assert_eq!(l1_address, Bits256::from_hex_str(BRIDGED_TOKEN).unwrap()); + + let l2_decimals: u8 = bridge + .methods() + .decimals(asset_id) + .call() + .await + .unwrap() + .value + .unwrap(); + + assert_eq!(l2_decimals, PROXY_TOKEN_DECIMALS as u8); + + // Relay the metadata message + let tx_id = relay_message_to_contract( + &wallet, + utxo_inputs.message[1].clone(), + utxo_inputs.contract.clone(), + ) + .await; + + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + let receipts = tx_status.clone().take_receipts(); + assert!(matches!(tx_status, TxStatus::Success { .. })); + + let metadata_events = bridge + .log_decoder() + .decode_logs_with_type::(&receipts) + .unwrap(); + + assert_eq!(metadata_events.len(), 1); + assert_eq!( + metadata_events[0].token_address, + Bits256::from_hex_str(BRIDGED_TOKEN).unwrap() + ); + + let registered_name = bridge.methods().name(asset_id).call().await.unwrap().value; + + assert_eq!(name, registered_name.unwrap()); + + let registered_symbol = bridge + .methods() + .symbol(asset_id) + .call() + .await + .unwrap() + .value; + + assert_eq!(symbol, registered_symbol.unwrap()); + } + + #[tokio::test] + async fn register_metadata_9_decimals() { + let mut wallet = create_wallet(); + let configurables: Option = None; + + let name = "Token".to_string(); + let symbol = "TKN".to_string(); + let decimals = 9; + + let amount: u64 = u64::MAX; + let (deposit_message, coin, _) = create_deposit_message( + BRIDGED_TOKEN, + BRIDGED_TOKEN_ID, + FROM, + *wallet.address().hash(), + U256::from(amount), + decimals, + configurables.clone(), + false, + None, + ) + .await; + + let metadata_message = create_metadata_message( + BRIDGED_TOKEN, + BRIDGED_TOKEN_ID, + &name, + &symbol, + configurables.clone(), + ) + .await; + + let (bridge, utxo_inputs) = setup_environment( + &mut wallet, + vec![coin], + vec![deposit_message, (0, metadata_message)], + None, + None, + configurables, + ) + .await; + + let asset_id = get_asset_id(bridge.contract_id(), BRIDGED_TOKEN); + let provider = wallet.provider().expect("Needs provider"); + + // Relay the deposit message + let tx_id = relay_message_to_contract( + &wallet, + utxo_inputs.message[0].clone(), + utxo_inputs.contract.clone(), + ) + .await; + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + assert!(matches!(tx_status, TxStatus::Success { .. })); + + let l1_address: Bits256 = bridge + .methods() + .asset_to_l1_address(asset_id) + .call() + .await + .unwrap() + .value; + assert_eq!(l1_address, Bits256::from_hex_str(BRIDGED_TOKEN).unwrap()); + + let l2_decimals: u8 = bridge + .methods() + .decimals(asset_id) + .call() + .await + .unwrap() + .value + .unwrap(); + + assert_eq!(l2_decimals, PROXY_TOKEN_DECIMALS as u8); + + // Relay the metadata message + let tx_id = relay_message_to_contract( + &wallet, + utxo_inputs.message[1].clone(), + utxo_inputs.contract.clone(), + ) + .await; + + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + let receipts = tx_status.clone().take_receipts(); + assert!(matches!(tx_status, TxStatus::Success { .. })); + + let metadata_events = bridge + .log_decoder() + .decode_logs_with_type::(&receipts) + .unwrap(); + + assert_eq!(metadata_events.len(), 1); + assert_eq!( + metadata_events[0].token_address, + Bits256::from_hex_str(BRIDGED_TOKEN).unwrap() + ); + + let registered_name = bridge.methods().name(asset_id).call().await.unwrap().value; + assert_eq!(name, registered_name.unwrap()); + + let registered_symbol = bridge + .methods() + .symbol(asset_id) + .call() + .await + .unwrap() + .value; + + assert_eq!(symbol, registered_symbol.unwrap()); + } + + #[tokio::test] + async fn register_metadata_6_decimals() { + let mut wallet = create_wallet(); + let configurables: Option = None; + + let name = "Token".to_string(); + let symbol = "TKN".to_string(); + let decimals = 6; + + let amount: u64 = u64::MAX; + let (deposit_message, coin, _) = create_deposit_message( + BRIDGED_TOKEN, + BRIDGED_TOKEN_ID, + FROM, + *wallet.address().hash(), + U256::from(amount), + decimals, configurables.clone(), false, None, @@ -574,6 +800,16 @@ mod success { .value; assert_eq!(l1_address, Bits256::from_hex_str(BRIDGED_TOKEN).unwrap()); + let l2_decimals: u8 = bridge + .methods() + .decimals(asset_id) + .call() + .await + .unwrap() + .value + .unwrap(); + assert_eq!(l2_decimals, decimals as u8); + // Relay the metadata message let tx_id = relay_message_to_contract( &wallet, diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 24bb2cbd..3f62dbd4 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -153,7 +153,8 @@ contract FuelERC20GatewayV4 is uint256(0), // token_id = 0 for all erc20 deposits bytes32(uint256(uint160(msg.sender))), to, - l2MintedAmount + l2MintedAmount, + uint256(decimals) ); _deposit(tokenAddress, amount, l2MintedAmount, depositMessage); } @@ -181,6 +182,7 @@ contract FuelERC20GatewayV4 is bytes32(uint256(uint160(msg.sender))), to, l2MintedAmount, + uint256(decimals), data ); _deposit(tokenAddress, amount, l2MintedAmount, depositMessage); diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index ef3485a1..ad844386 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -52,6 +52,7 @@ const MessagePayloadSolidityTypes = [ 'uint256', // depositor EVM address, padded 'uint256', // recipient FuelVM address 'uint256', // l2 amount to be minted + 'uint256', // decimals ]; export function behavesLikeErc20GatewayV4(fixture: () => Promise) { @@ -382,6 +383,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { zeroPadValue(await user.getAddress(), 32), depositTo, depositAmount, + decimals, ]); await expect(tx) .to.emit(fuelMessagePortal, 'SendMessageCalled') @@ -562,6 +564,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { zeroPadValue(await user.getAddress(), 32), depositTo, depositAmount, + decimals, ]); await expect(tx) .to.emit(fuelMessagePortal, 'SendMessageCalled') @@ -763,6 +766,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { zeroPadValue(await user.getAddress(), 32), depositTo, downscaledDepositAmount, + DECIMALS, ]); await expect(tx) .to.emit(fuelMessagePortal, 'SendMessageCalled')