Hi! I am Olesia Bilenka, Smart Contract Auditor at Hacken. I am sharing below some design review points:
- Forced transfer vs freezing checks
// Spec states BOTH simultaneously:
“CAN bypass the freezing validations” // Optional
“MUST bypass checks enforced by canTransfer” // Mandatory
canTransfer MUST validate unfrozen >= amount per spec
If forcedTransfer CAN bypass → implementation-dependent behavior
Two compliant implementations will be incompatible
What the Specification Says:
/// notice Changes the frozen status of amount tokens belonging to account.
/// dev Requires specific authorization. Frozen tokens cannot be transferred.
/// param amount The amount of tokens to freeze.
/// It can be greater than account balance.
function setFrozenTokens(address account, uint256 amount)
external returns(bool result);
Key phrase: “It can be greater than account balance”
The math problem
// Standard calculation for transferable amount:
uint256 unfrozen = balance[account] - frozen[account];
// Scenario:
balance[alice] = 100
frozen[alice] = 200 // Allowed by spec
unfrozen = 100 - 200 = -100 // error
// In Solidity 0.8.0+:
// This REVERTS with underflow panic
// prev solidity - underflow issue
The Core Semantic Problem
What does frozen[account] = 200 mean when balance[account] = 100?
Interpretation 1: Future freeze
Interpretation 2: Maximum freeze limit
Interpretation 3: Proportional freeze
Recommendation:
Option A: Prohibit frozen > balance (simplest)
Option B: Define explicit “future freeze” semantics
- ERC-721 (account, tokenId) Tuple
What the Specification Says:
interface IERC7943NonFungible {
/// @notice Changes the frozen status of tokenId belonging to account.
function setFrozenTokens(address account, uint256 tokenId, bool frozen)
external returns(bool);
/// @notice Checks the frozen status of a specific `tokenId`.
/// @dev It could return true even if account does not hold the token.
function getFrozenTokens(address account, uint256 tokenId)
external view returns (bool);
}
Key phrase: “It could return true even if account does not hold the token”
The Fundamental Question
What happens to freeze status when ownership changes? The spec does not answer this question.
Why This is Architecturally Wrong
NFTs represent unique assets. Freeze semantics should be:
Asset-centric: “Property #123 is frozen” (not “Alice’s property #123”)
Ownership-independent: Freeze status should persist across ownership changes
Global: Anyone checking freeze should get same answer
Current spec creates account-centric freeze:
“Alice’s token #123 is frozen”
But Bob can own the same token #123
No relationship between Alice’s freeze and Bob’s ownership
Recommendations:
Token-centric freeze for NFTs
Alternative: Account-specific freeze
- ERC-1155 Missing Batch Operations
Current Spec (Single Transfer Only):
interface IERC7943MultiToken {
function forcedTransfer(
address from,
address to,
uint256 tokenId,
uint256 amount
) external returns(bool result);
event ForcedTransfer(
address indexed from,
address indexed to,
uint256 indexed tokenId,
uint256 amount
);
}
ERC-1155 standard includes batch operations:
interface IERC1155 {
function safeBatchTransferFrom(
address from,
address to,
uint256 calldata ids,
uint256 calldata amounts,
bytes calldata data
) external;
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] amounts
);
}
Problem 1: Non-atomic seizure
Problem 2: Extreme Gas costs
Recommendation:
ERC-7943 may have equivalent batch operations for forcedTransfer.
- Burning and Freeze Interaction
What the Specification Says:
“Burning MAY be restricted to prevent burning more assets than the unfrozen amount. It MAY burn more assets than the unfrozen amount in permissioned functions, in which case the contract MUST update the frozen status accordingly.”
// Scenario:
balance[alice] = 100
frozen[alice] = 80
unfrozen = 20
// Permissioned burn of 50 tokens:
burn(alice, 50)
// After burn:
balance[alice] = 50
frozen[alice] = ?
Recommendation:
Public burn functions SHOULD only allow burning unfrozen tokens.
Permissioned burn functions MAY burn frozen tokens.
If burning frozen tokens, the frozen amount MUST be reduced.