Critical differences in behavior between mintable erc20s and xc20s

The motivation for this forum post is a recent event that allowed a user to withdraw more USDC than they should have been able to from Moonbeam due to a design difference between burnable ERC-20s and Mintable XC-20s.

First some context: Moonbeam, as a fully EVM compatible environment, supports standard ERC-20s contracts such as OpenZeppelin’s ERC20Burnable.sol. Moonbeam also has a unique asset type called XC-20s. XC-20s are Substrate native assets stored in the Assets Pallet that are exposed into the Moonbeam EVM with an ERC-20 compatible interface as a precompile contract.

There are different types of XC-20s:

External XC-20s: assets from remote parachains which can be moved via XCM to Moonbeam and then accessed from the Moonbeam EVM via an ERC-20 interface, called external XC-20s.

Mintable XC-20s: a less common type of XC-20 that is created and destroyed on Moonbeam, and which implements minting and burning functions. This type of XC-20 is needed for assets which are created and managed on Moonbeam but that need to be able to move to other parachain destinations.

Note, the process for creating Mintable XC-20s is likely to change in the future to allow regular ERC-20s in the Moonbeam EVM to be XCM transferable to other parachain destinations, thus removing the need for the current precompile based Mintable XC-20s.

The USDC issue referenced above specifically had to do with Mintable XC-20s, and more specifically the behavior of the XC-20 burn function.

The burn function is not a standard part of the ERC-20 interface, and different implementations exist. There are 2 different behaviors for the burn function in question here. For ERC-20s based on ERC20Burnable.sol, the behavior is that calls to burn will check that the caller has a sufficient balance for the amount of burn being requested and fail if that condition is not met. In Substrate, the Pallet Assets behavior is that calling burn with a value higher than the current balance will result in a burn of the amount available, and returning success. The burn implementation in Mintable XC-20s simply exposes the existing Substrate burn function, and does not behave like ERC20Burnable.sol.

This difference in behavior is what led to the incorrect USDC withdrawal. There was a smart contract that called the XC-20 burn function but expected the ERC20Burnable.sol behavior, when in fact the behavior implemented was the Substrate based behavior. A user requested the burn of a larger amount of USDC than they owned, and the contract incorrectly assumed the full requested amount was burned, whereas only a much smaller amount was actually destroyed.

The correct implementation is that contracts calling the XC-20 burn function must explicitly check that the caller has the balance available to burn before calling the burn function.

Moonbeam devs need to be aware of this important difference in behavior between OpenZeppelin based burnable erc20s and the burn behavior of Mintable XC-20s.

Additionally, it is also important to note that Mintable XC-20 will behave differently if the total supply is over 2^128 (without decimals), the mint will be successful but the total supply will be (2^128)-1. This is unlikely to happen for traditional tokens as they are not meant to reach such high numbers.


Very technical but interesting, thank you for this explanation :+1:t3::blush:

Very usefull and well explained, many thanks.