-
Notifications
You must be signed in to change notification settings - Fork 278
/
Struct-deletion.sol
115 lines (94 loc) · 3.45 KB
/
Struct-deletion.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
/*
Name: Struct Deletion Oversight
Description:
Incomplete struct deletion leaves residual data.
If you delete a struct containing a mapping, the mapping won't be deleted.
The bug arises because Solidity's delete keyword does not reset the storage to its
initial state but rather performs a partial reset.
When delete myStructs[structId] is called,
it only resets the id at mappingId to its default value 0,
but the other flags in the mapping remain unchanged. Therefore,
if the struct is deleted without deleting the mapping inside,
the remaining flags will persist in storage.
Mitigation:
To fix this bug, you should delete the mapping inside the struct before deleting the struct itself.
REF:
https://twitter.com/1nf0s3cpt/status/1676836264245592065
https://docs.soliditylang.org/en/develop/types.html
*/
contract ContractTest is Test {
StructDeletionBug StructDeletionBugContract;
FixedStructDeletion FixedStructDeletionContract;
function setUp() public {
StructDeletionBugContract = new StructDeletionBug();
FixedStructDeletionContract = new FixedStructDeletion();
}
function testStructDeletion() public {
StructDeletionBugContract.addStruct(10, 10);
StructDeletionBugContract.getStruct(10, 10);
StructDeletionBugContract.deleteStruct(10);
StructDeletionBugContract.getStruct(10, 10);
}
function testFixedStructDeletion() public {
FixedStructDeletionContract.addStruct(10, 10);
FixedStructDeletionContract.getStruct(10, 10);
FixedStructDeletionContract.deleteStruct(10);
FixedStructDeletionContract.getStruct(10, 10);
}
receive() external payable {}
}
contract StructDeletionBug {
struct MyStruct {
uint256 id;
mapping(uint256 => bool) flags;
}
mapping(uint256 => MyStruct) public myStructs;
function addStruct(uint256 structId, uint256 flagKeys) public {
MyStruct storage newStruct = myStructs[structId];
newStruct.id = structId;
newStruct.flags[flagKeys] = true;
}
function getStruct(
uint256 structId,
uint256 flagKeys
) public view returns (uint256, bool) {
MyStruct storage myStruct = myStructs[structId];
bool keys = myStruct.flags[flagKeys];
return (myStruct.id, keys);
}
function deleteStruct(uint256 structId) public {
MyStruct storage myStruct = myStructs[structId];
delete myStructs[structId];
}
}
contract FixedStructDeletion {
struct MyStruct {
uint256 id;
mapping(uint256 => bool) flags;
}
mapping(uint256 => MyStruct) public myStructs;
function addStruct(uint256 structId, uint256 flagKeys) public {
MyStruct storage newStruct = myStructs[structId];
newStruct.id = structId;
newStruct.flags[flagKeys] = true;
}
function getStruct(
uint256 structId,
uint256 flagKeys
) public view returns (uint256, bool) {
MyStruct storage myStruct = myStructs[structId];
bool keys = myStruct.flags[flagKeys];
return (myStruct.id, keys);
}
function deleteStruct(uint256 structId) public {
MyStruct storage myStruct = myStructs[structId];
// Check if all flags are deleted, then delete the mapping
for (uint256 i = 0; i < 15; i++) {
delete myStruct.flags[i];
}
delete myStructs[structId];
}
}