#35793 [SC-High] `src-20.burn` should use "==" instead of ">="
Submitted on Oct 8th 2024 at 14:05:18 UTC by @jasonxiale for IOP | Swaylend
Report ID: #35793
Report Type: Smart Contract
Report severity: High
Target: https://github.com/Swaylend/swaylend-monorepo/blob/develop/contracts/src-20/src/main.sw
Impacts:
Block stuffing
Description
Brief/Intro
While burning tokens, `src-20.burn` checks msg_amount() >= amount, and updates `total_supply` as `storage.total_supply.read() - amount` in main.sw#L159-L160, and afterwards, `amount` of token will be burned in main.sw#L162
The issue is that
The totalSupply is capped by 1_000_000_000_000_000_000u64, which means there will be at most 1_000_000_000_000_000_000u64 amount of token
In `src-20.burn`, the `storage.total_supply` is subtracted by `amount`, and `amount` of tokens will be burnt, which means `amount` is less than `msg_amount()`, the rest of token will be left in the `src-20` contract.
And because `src-20` contract doesn't have any ABI to transfer the token out, the token will be stucked in the contract
Vulnerability Details
As shown in the following code: ```Rust 149 #[payable] 150 #[storage(read, write)] 151 fn burn(sub_id: SubId, amount: u64) { 152 require(sub_id == DEFAULT_SUB_ID, "incorrect-sub-id"); 153 require(msg_amount() >= amount, "incorrect-amount-provided"); <<<--- Here "==" should be used 154 require( 155 msg_asset_id() == AssetId::default(), 156 "incorrect-asset-provided", 157 ); 158 159 let new_supply = storage.total_supply.read() - amount; 160 storage.total_supply.write(new_supply); 161 162 burn(DEFAULT_SUB_ID, amount); 163 164 TotalSupplyEvent::new(AssetId::default(), new_supply, msg_sender().unwrap()) 165 .log(); 166 } ```
Impact Details
So please consider in a worst situation:
The erc-20 owner mints `MAX_SUPPLY(1_000_000_000_000_000_000u64)` amount of token to Alice
Alice calls `erc-20.burn` with `amount` as `0`, so all the token will be transferred to `erc-20` contract, and will be stucked.
The erc-20 owner can't mint any token more, because the `total_supply` has reached its MAX_SUPPLY
References
Add any relevant links to documentation or code
Proof of Concept
Proof of Concept
Please create a folder with path `swaylend-monorepo/contracts/src-20/tests`, and add the following code into `swaylend-monorepo/contracts/src-20/tests/harness.rs`
And run ```bash cargo test burn_token -- --nocapture ... running 1 test mint wallet1 1000000000000000000 amount of token wallet_1 balance: 1000000000000000000 wallet1 burn 1000000000000000000 amount of token wallet_1 balance: 0 mint wallet1 1 amount of token test burn_token ... ok
```
As shown in the POC, the owner(wallet_0) first mints wallet_1 `1000000000000000000` amount of token, then wallet_1 calls `src-20.burn` with `amount` as 0, and then if the owner tries to mint 1 amount of token, the tx will revert
```Rust use fuels::{prelude::*, types::ContractId}; use fuels::types::{Address, AssetId, Bits256, Bytes32, Identity}; use sha2::{Digest, Sha256};
// Load abi from json abigen!( Contract( name = "SingleAsset", abi = "/opt/swaylend-monorepo/contracts/src-20/out/debug/src-20-abi.json" ) );
#[tokio::test] async fn burn_token() -> Result<()> { let max_supply = 1_000_000_000_000_000_000u64; let mut wallets = launch_custom_provider_and_get_wallets( WalletsConfig::new( Some(5), Some(1), Some(1_000_000_000), /* Amount per coin */ ), None, None, ) .await .unwrap(); let wallet_0 = wallets.pop().unwrap(); let wallet_1 = wallets.pop().unwrap();
}
pub(crate) fn get_asset_id(sub_id: Bytes32, contract: ContractId) -> AssetId { let mut hasher = Sha256::new(); hasher.update(*contract); hasher.update(*sub_id); AssetId::new(*Bytes32::from(<[u8; 32]>::from(hasher.finalize()))) } pub(crate) async fn get_wallet_balance(wallet: &WalletUnlocked, asset: &AssetId) -> u64 { wallet.get_asset_balance(asset).await.unwrap() } ```