I agree with the specificity and probably we should add it as a SHOULD (in case the token doesn’t have specific errors as the ERC20 template by OZ has) not replace the specific errors for insufficient balances (outside the frozen one). However I’m still having some issues with the naming. “not available” (“unavailable”) suggests a temporary condition like a frozen amount not allowing you to transfer a determined amount while “insufficient” suggest more of a general lack of funds. Indeed if you try to transfer more tokens than the unfrozen one, you technically have a “sufficient” balance, but not an “available” one to be used. What do you think about this argument ? What about changing to “unavailable” instead ?
I used ERC20InsufficientBalance
to refer to the error as ERC-6093 defines it and OZ implements it, but I meant the underlying error. A phrasing in the ERC-7943 definition could be:
/// @notice Error reverted when a transfer is attempted from `user` with an `amount` less or equal than its balance, but greater than its unfrozen balance.
/// @param user The address holding the tokens.
/// @param tokenId The ID of the token being transferred.
/// @param amount The amount being transferred.
/// @param unfrozenBalance The amount of tokens that are unfrozen and available to transfer.
error ErrorName(address user, uint256 tokenId, uint256 amount, uint256 unfrozenBalance);
In terms of error naming, I believe “insufficient balance” and “unavailable amount” are synonyms in this context, although the latter has a more temporal component. I slightly prefer “insufficient balance” due to its clarity and ongoing use in ERC-6093, but I see both as valid options.
As I see it, there are 2 ways we can go with this error:
- Define and specify the error to throw when a user tries to transfer more tokens than they have unfrozen. Either
ERC7943InsufficientUnfrozenBalance
orERC7943FrozenAmount
. - Define a broad and miscellaneous error to throw when the user tries to transfer more tokens than they have available, either due to being frozen, or locked by custom compliance implementation logic.
ERC7943UnavailableAmount
.
To me, achieving specificity is key, and we should go with the first, keeping additional errors to be defined in extensions, either custom or standardized in other ERCs.
I agree in using isTransferAllowed, we might end up doing the error of forcing a setPaused and have integrators being forced to integrate this to be compliant with this EIP even if they don’t need it.
We might need more context from other industry integrators, but I wouldn’t discard the feature yet. Most security tokens I’ve seen include it.
For
forceTransfer
:I think the standard should still say that the function
SHOULD
skip freezing validations ( I believe you proposed a “MUST” but I’m curious to know why. If the function actually skips that, then I agree with your suggestion of unfreezing first (and emit an event) before doing the transfer, so that the transfer event doesn’t emit an amount of unavailable tokens. However, the integrators might decide to NOT skip freezing validations in aforceTransfer
. I am thinking of use cases where there’s an automated penalty system that forcefully transfer tokens out without skipping the freezing status (that might have a higher priority over the penalty system).
forceTransfer
being a permissioned function, is there a case where you may not want to unfreeze tokens to forcefully transfer them out of a wallet and revert? I don’t visualize it.
One thing that concerns me is that, if we don’t impose that forceTransfer
unfreezes tokens (only if the transfer cannot be fully satisfied with the unfrozen balance), the tokens must be first unfrozen and then forcefully transferred, adding the requirement to execute those calls in batch to avoid being front-run on the forceful transfer by the user.
I see the benefit of imposing the forceTransfer
implementation in that we don’t end up with a method that behaves differently in different tokens with a core functionality such as token freezing. Either we need to extend the ForcedTransfer
event to specify which tokens were frozen and which unfrozen, or we need to impose the implementation (MUST
), if we want integrators to accurately index frozen and baseline balances.
Additional flexibility can be obtained with custom batched actions.
For
burn
:I actually think is dangerous to allow a
burn
to unfreeze. Theburn
function is meant to be either public (anyone can burn) or restricted. In the former case, burning would actually circumvent the freezing status imposed by an authorized party. This would easily translate into frozen assets being on purpose burnt down to avoid any reusage of those by the freezing authority.
We have a different vision of who’s allowed to call burn
.
I don’t see token holders being able to burn their shares of a company, or other underlying securities. I see token burning always happening from the permissioned side.
If we add permissioned mint
and burn
functions to the standard, I’d go the same way than with the forceTransfer
definition: impose token unfreezing.
If we don’t, I see the benefit of specifying each expected behaviour:
- Burning CAN run validation to prohibit burning more assets than the ones available (difference between balance and frozen amount). For example, in public burning functions.
- Burning CAN bypass freezing validation, in which case it MUST update the freezing status if required, emitting a
Frozen
event before theTransfer
event. For example, in permissioned burning functions.