SuperToken._skipSelfMint silently skips minting, breaking custom SuperToken implementations
Summary
The _skipSelfMint transient flag silently skips minting in selfMint(), which breaks contracts that extend SuperToken and expect selfMint() to always mint tokens when called.
Vulnerability Detail
The _skipSelfMint flag is used internally to prevent a mint/burn loop when withdrawing ETH from the yield backend. When set to true, selfMint() silently returns without minting any tokens:
function selfMint(
address account,
uint256 amount,
bytes memory userData
)
external virtual override
onlySelf
{
if (!_skipSelfMint) {
// ... actual minting logic ...
}
}The problem is that contracts extending SuperToken (e.g., custom wrapper contracts) may implement a receive() function that calls selfMint() expecting tokens to be minted. If _skipSelfMint is set, the call succeeds but no tokens are minted, and the accounting gets corrupted.
For example, a custom SuperToken with fee logic:
receive() external payable {
uint256 fees = msg.value * 1 / 10000; // 1 BPS fee
uint256 amount = msg.value - fees;
this.selfMint(msg.sender, amount, "");
totalFees += fees;
}When _skipSelfMint is true, this code collects fees but mints nothing, causing users to lose funds.
Impact
Custom SuperToken implementations that have other logic in their receive() function, such as the example above, will become broken. The current SETH already has this problem, since it emits the event emit TokenUpgraded(msg.sender, msg.value); on receive(), but no tokens are actually minted.
Code Snippet
function selfMint(
address account,
uint256 amount,
bytes memory userData
)
external virtual override
onlySelf
{
if (!_skipSelfMint) {
if (address(_yieldBackend) != address(0)) {
delegateCallChecked(address(_yieldBackend), abi.encodeCall(IYieldBackend.deposit, (amount)));
}
_mint(msg.sender, account, amount, userData.length != 0 /* invokeHook */,
userData.length != 0 /* requireReceptionAck */, userData, new bytes(0));
}
}Tool Used
Manual Review
Recommendation
Should be flagged to any SuperToken implementation, so it makes sure when the skipSelfMint flag is true, all logic in the receive function should be skipped, not just selfMint().