Skip to content

Commit ba54358

Browse files
committed
Disallow delete on types containing nested mappings.
1 parent d247cd5 commit ba54358

9 files changed

+53
-134
lines changed

Changelog.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
### 0.9.0 (unreleased)
22

33
Breaking changes:
4-
* `error` is now a keyword that can only be used for defining errors.
4+
* Disallow ``.pop()`` on arrays containing nested mappings.
5+
* Disallow ``delete`` on types that contain nested mappings.
56
* Inline Assembly: Consider functions, function parameters and return variables for shadowing checks.
7+
* ``error`` is now a keyword that can only be used for defining errors.
68

79

810
### 0.8.8 (unreleased)

docs/security-considerations.rst

+4-40
Original file line numberDiff line numberDiff line change
@@ -284,48 +284,12 @@ key-value data structure that does not keep track of the keys that were
284284
assigned a non-zero value. Because of that, cleaning a mapping without extra
285285
information about the written keys is not possible.
286286
If a ``mapping`` is used as the base type of a dynamic storage array, deleting
287-
or popping the array will have no effect over the ``mapping`` elements. The
288-
same happens, for example, if a ``mapping`` is used as the type of a member
289-
field of a ``struct`` that is the base type of a dynamic storage array. The
290-
``mapping`` is also ignored in assignments of structs or arrays containing a
287+
or popping the array is not possible.
288+
The same is true, for example, if a ``mapping`` is used as the type of a member
289+
field of a ``struct`` that is the base type of a dynamic storage array.
290+
It is also not possible to assign new values to structs or arrays containing a
291291
``mapping``.
292292

293-
.. code-block:: solidity
294-
295-
// SPDX-License-Identifier: GPL-3.0
296-
pragma solidity >=0.6.0 <0.9.0;
297-
298-
contract Map {
299-
mapping (uint => uint)[] array;
300-
301-
function allocate(uint _newMaps) public {
302-
for (uint i = 0; i < _newMaps; i++)
303-
array.push();
304-
}
305-
306-
function writeMap(uint _map, uint _key, uint _value) public {
307-
array[_map][_key] = _value;
308-
}
309-
310-
function readMap(uint _map, uint _key) public view returns (uint) {
311-
return array[_map][_key];
312-
}
313-
314-
function eraseMaps() public {
315-
delete array;
316-
}
317-
}
318-
319-
Consider the example above and the following sequence of calls: ``allocate(10)``,
320-
``writeMap(4, 128, 256)``.
321-
At this point, calling ``readMap(4, 128)`` returns 256.
322-
If we call ``eraseMaps``, the length of state variable ``array`` is zeroed, but
323-
since its ``mapping`` elements cannot be zeroed, their information stays alive
324-
in the contract's storage.
325-
After deleting ``array``, calling ``allocate(5)`` allows us to access
326-
``array[4]`` again, and calling ``readMap(4, 128)`` returns 256 even without
327-
another call to ``writeMap``.
328-
329293
If your ``mapping`` information must be deleted, consider using a library similar to
330294
`iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_,
331295
allowing you to traverse the keys and delete their values in the appropriate ``mapping``.

libsolidity/analysis/TypeChecker.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,13 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
16131613
_operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure;
16141614
_operation.annotation().isLValue = false;
16151615

1616+
if (op == Token::Delete && subExprType->nameable() && subExprType->containsNestedMapping())
1617+
m_errorReporter.fatalTypeError(
1618+
2811_error,
1619+
_operation.location(),
1620+
"Type containing a (nested) mapping cannot be deleted."
1621+
);
1622+
16161623
return false;
16171624
}
16181625

@@ -2839,6 +2846,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
28392846
);
28402847

28412848
if (
2849+
funType->kind() == FunctionType::Kind::ArrayPop &&
2850+
exprType->containsNestedMapping()
2851+
)
2852+
m_errorReporter.typeError(
2853+
6298_error,
2854+
_memberAccess.location(),
2855+
"Storage arrays with nested mappings do not support .pop()."
2856+
);
2857+
else if (
28422858
funType->kind() == FunctionType::Kind::ArrayPush &&
28432859
arguments.value().numArguments() != 0 &&
28442860
exprType->containsNestedMapping()

test/libsolidity/semanticTests/storage/mappings_array2d_pop_delete.sol

-41
This file was deleted.

test/libsolidity/semanticTests/storage/mappings_array_pop_delete.sol

-36
This file was deleted.

test/libsolidity/semanticTests/structs/delete_struct.sol

-16
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,17 @@ contract test {
22
struct topStruct {
33
nestedStruct nstr;
44
uint topValue;
5-
mapping (uint => uint) topMapping;
65
}
76
uint toDelete;
87
topStruct str;
98
struct nestedStruct {
109
uint nestedValue;
11-
mapping (uint => bool) nestedMapping;
1210
}
1311
constructor() {
1412
toDelete = 5;
1513
str.topValue = 1;
16-
str.topMapping[0] = 1;
17-
str.topMapping[1] = 2;
1814

1915
str.nstr.nestedValue = 2;
20-
str.nstr.nestedMapping[0] = true;
21-
str.nstr.nestedMapping[1] = false;
2216
delete str;
2317
delete toDelete;
2418
}
@@ -31,20 +25,10 @@ contract test {
3125
function getNestedValue() public returns(uint nestedValue){
3226
nestedValue = str.nstr.nestedValue;
3327
}
34-
function getTopMapping(uint index) public returns(uint ret) {
35-
ret = str.topMapping[index];
36-
}
37-
function getNestedMapping(uint index) public returns(bool ret) {
38-
return str.nstr.nestedMapping[index];
39-
}
4028
}
4129
// ====
4230
// compileViaYul: also
4331
// ----
4432
// getToDelete() -> 0
4533
// getTopValue() -> 0
4634
// getNestedValue() -> 0 #mapping values should be the same#
47-
// getTopMapping(uint256): 0 -> 1
48-
// getTopMapping(uint256): 1 -> 2
49-
// getNestedMapping(uint256): 0 -> true
50-
// getNestedMapping(uint256): 1 -> false

test/libsolidity/syntaxTests/array/pop/storage_with_mapping_pop.sol

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ contract C {
66
}
77
}
88
// ----
9+
// TypeError 6298: (109-118): Storage arrays with nested mappings do not support .pop().
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
contract D {
2+
struct Test {
3+
mapping(address => uint) balances;
4+
}
5+
6+
Test test;
7+
8+
constructor()
9+
{
10+
test = Test();
11+
}
12+
}
13+
// ----
14+
// TypeError 9214: (102-106): Types in storage containing (nested) mappings cannot be assigned to.
15+
// TypeError 9515: (109-115): Struct containing a (nested) mapping cannot be constructed.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
contract D {
2+
struct Test {
3+
mapping(address => uint) balances;
4+
}
5+
6+
Test test;
7+
8+
constructor()
9+
{
10+
delete test;
11+
}
12+
}
13+
// ----
14+
// TypeError 2811: (102-113): Type containing a (nested) mapping cannot be deleted.

0 commit comments

Comments
 (0)