Struct with Mapping Deletion
Overview
- Severity: Medium
- Confidence: High
- Affected Versions: < 0.7.0
What is the Struct with Mapping Deletion vulnerability?
In Solidity, as in several other programming languages, structs can be used to organize and store data. If a struct contains a mapping as a member, the delete
keyword will not clear the associated mapping, so care must be taken if this is the intent of the developer.
Further reading: Solidity Documentation: Structs
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract Membership {
struct MemberProfile {
string name;
uint256 joinDate;
mapping(uint256 => bool) monthlyDuesPaid; // Mapping: month => paid/not
}
mapping(address => MemberProfile) public memberProfiles;
// Function to add or update a member's profile
function setMemberProfile(address member, string memory name, uint256 joinDate) public {
memberProfiles[member].name = name;
memberProfiles[member].joinDate = joinDate;
}
// Record a monthly dues payment
function recordDuesPayment(address member, uint256 month) public {
memberProfiles[member].monthlyDuesPaid[month] = true;
}
// Incorrectly attempting to reset a member's profile using `delete`
function removeMember(address member) public {
// Developer expects this to clear the member's profile and dues records
delete memberProfiles[member];
}
}
In the example above, contract Membership
represents logic for managing members as part of some broader decentralized application. The contract defines a struct MemberProfile
that contains a mapping member to track dues paid for membership in the organization. The function removeMember()
attempts to clear this data using the delete
keyword, which will not actually clear the mapping data. If the mapping data were used in some other context in the decentralized application, e.g. to confirm eligibility for some benefit, they could still potentially be used for a member that was removed.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract MembershipUpdated {
struct MemberProfile {
string name;
uint256 joinDate;
}
mapping(address => MemberProfile) public memberProfiles;
// Dues payment now tracked in a distinct mapping
mapping(address => mapping(uint256 => bool)) monthlyDuesPaid;
// Function to add or update a member's profile
function setMemberProfile(address member, string memory name, uint256 joinDate) public {
memberProfiles[member].name = name;
memberProfiles[member].joinDate = joinDate;
}
// Record a monthly dues payment
function recordDuesPayment(address member, uint256 month) public {
monthlyDuesPaid[member][month] = true;
}
// Delete the struct that no longer contains a mapping
function removeMember(address member) public {
delete memberProfiles[member];
}
}
In the revised example above, contract MembershipUpdated
has changed the manner in which member information and dues payments are stored: dues payment info is now in a separate mapping monthlyDuesPaid
, and the MemberProfile
struct no longer contains a mapping, so the delete
keyword in function removeMember()
will now delete the struct data.