Deletion on Mapping
id: LS05L
title: Deletion on Mapping
baseSeverity: L
category: storage
language: solidity
blockchain: [ethereum]
impact: Inconsistent state, gas griefing, or minor DoS
status: draft
complexity: low
attack_vector: internal
mitigation_difficulty: easy
versions: [">=0.5.0", "<latest"]
cwe: CWE-704
swc: SWC-135
π Description
- In Solidity, deleting a mapping entry that contains a struct via delete mapping[key] resets all fields of the struct to their default values (e.g., 0, false, address(0)).
- However, this may not fully remove logical relationships or presence flags stored outside the struct or within it.
- If other parts of the contract depend on the structβs non-zero fields to check validity, deletion can create ambiguous or exploitable states.
- Common issues include:
- Incorrectly assuming that delete removes the mapping key itself (it doesn'tβit resets the value to the default struct).
- Reusing struct fields as logic flags (e.g., if (user.exists) or if (user.balance > 0)).
- Leaving residual entries in iteration logic or enumerable mappings.
π¨ Vulnerable Code
pragma solidity ^0.8.0;
contract StructDelete {
struct User {
uint256 balance;
bool isActive;
}
mapping(address => User) public users;
function removeUser(address user) external {
delete users[user]; // β Sets values to 0/false, but key still "exists"
}
function isRegistered(address user) external view returns (bool) {
return users[user].isActive; // Will return false even for old users
}
}
π§ͺ Exploit Scenario
- Contract tracks user registration with a struct: {balance, isActive}.
- delete users[alice] is called after she withdraws funds.
- Her entry remains in users, but all fields are zeroed.
- A re-registration check like require(!users[alice].isActive) passes, but other logic (e.g., referral counters, index mapping) still refers to her address.
- This causes accounting mismatches, unintended rewards, or logic corruption.
Assumptions:
- Mapping keys are reused or iterated externally.
- Struct field values are used as logical flags.
β Fixed Code
pragma solidity ^0.8.0;
contract SafeStructDelete {
struct User {
uint256 balance;
bool isActive;
}
mapping(address => User) public users;
mapping(address => bool) public exists;
function addUser(address user) external {
require(!exists[user], "Already registered");
users[user] = User(0, true);
exists[user] = true;
}
function removeUser(address user) external {
delete users[user];
exists[user] = false;
}
function isRegistered(address user) external view returns (bool) {
return exists[user]; // β
Clear presence check
}
}
π§ Contextual Severity
- context: "Default"
severity: L
reasoning: "Logical confusion, but unlikely to cause major loss."
- context: "Governance or staking-based system"
severity: M
reasoning: "Can be abused to reclaim rewards or bypass eligibility checks."
- context: "Private use contract with full control"
severity: I
reasoning: "Has no impact under full admin control and no logic tied to deletion."
π‘οΈ Prevention
Primary Defenses
- Never assume delete mapping[key] removes the key completelyβit only resets the value.
- Always track existence explicitly if needed for logic control or external iteration.
Additional Safeguards
- Use a parallel presence-tracking mapping (e.g., exists[address]).
- For enumerable mappings, explicitly manage entry lists and indexes.
Detection Methods
- Search for use of delete mapping[key] in structs without presence flags.
- Check for functions relying on struct field values to infer mapping key existence.
- Tools: Slither (struct-default-check, logic-deletion), manual logic review
π°οΈ Historical Exploits
- Name: Akropolis Struct Deletion Bug
- Date: 2020
- Loss: ~$2M
- Post-mortem: Link to post-mortem
π Further Reading
- SWC-135: Incorrect Authorization Logic
- Solidity Docs β delete on Mappings
- Trail of Bits: Struct Deletion Pitfalls
- OpenZeppelin: Enumerable Mapping Caveats
β Vulnerability Report
id: LS05L
title: Deletion on Mapping
severity: L
score:
impact: 3
exploitability: 3
reachability: 3
complexity: 2
detectability: 3
finalScore: 3.0
π Justifications & Analysis
- Impact: Logic can break due to relying on default values to imply non-existence.
- Exploitability: Attackers can re-register, claim rewards, or bypass eligibility checks.
- Reachability: Common in user mappings, NFT registries, or game state tracking.
- Complexity: Easy to introduce; based on misunderstanding of delete.
- Detectability: Not obvious unless mapping usage and logic are closely reviewed.