From e4a52aa2f604406c9009ed532c0960b91cfc5f71 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 21 Feb 2019 18:46:05 +0100 Subject: [PATCH] Allow dynamic types in public mappings --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 8 - libsolidity/codegen/ExpressionCompiler.cpp | 51 +++++-- test/libsolidity/SolidityEndToEndTest.cpp | 143 ++++++++++++++++++ .../mapping/mapping_dynamic_key_public.sol | 1 - 5 files changed, 185 insertions(+), 19 deletions(-) diff --git a/Changelog.md b/Changelog.md index 78006cf0c..583a3305a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.5.5 (unreleased) Language Features: + * Add support for accessors for mappings with string or byte key types * Meta programming: Provide access to the name of contracts via ``type(C).name``. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index c4f42057c..86514cf01 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -511,14 +511,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) ) m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded."); break; - case Type::Category::Mapping: - if (auto mappingType = dynamic_cast(varType.get())) - if ( - mappingType->keyType()->isDynamicallySized() && - _variable.visibility() == Declaration::Visibility::Public - ) - m_errorReporter.typeError(_variable.location(), "Dynamically-sized keys for public mappings are not supported."); - break; default: break; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 69c4b26ec..69e66066a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -106,18 +106,49 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& if (auto mappingType = dynamic_cast(returnType.get())) { solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - solUnimplementedAssert( - !paramTypes[i]->isDynamicallySized(), - "Accessors for mapping with dynamically-sized keys not yet implemented." - ); + // pop offset m_context << Instruction::POP; - // move storage offset to memory. - utils().storeInMemory(32); - // move key to memory. - utils().copyToStackTop(paramTypes.size() - i, 1); - utils().storeInMemory(0); - m_context << u256(64) << u256(0) << Instruction::KECCAK256; + if (paramTypes[i]->isDynamicallySized()) + { + solAssert( + dynamic_cast(*paramTypes[i]).isByteArray(), + "Expected string or byte array for mapping key type" + ); + + // stack: + + // copy key[i] to top. + utils().copyToStackTop(paramTypes.size() - i + 1, 1); + + m_context.appendInlineAssembly(R"({ + let key_len := mload(key_ptr) + // Temp. use the memory after the array data for the slot + // position + let post_data_ptr := add(key_ptr, add(key_len, 0x20)) + let orig_data := mload(post_data_ptr) + mstore(post_data_ptr, slot_pos) + let hash := keccak256(add(key_ptr, 0x20), add(key_len, 0x20)) + mstore(post_data_ptr, orig_data) + slot_pos := hash + })", {"slot_pos", "key_ptr"}); + + m_context << Instruction::POP; + } + else + { + solAssert(paramTypes[i]->isValueType(), "Expected value type for mapping key"); + + // move storage offset to memory. + utils().storeInMemory(32); + + // move key to memory. + utils().copyToStackTop(paramTypes.size() - i, 1); + utils().storeInMemory(0); + m_context << u256(64) << u256(0); + m_context << Instruction::KECCAK256; + } + // push offset m_context << u256(0); returnType = mappingType->valueType(); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a5f8c2743..2a0c493a2 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -51,6 +51,11 @@ namespace test BOOST_FIXTURE_TEST_SUITE(SolidityEndToEndTest, SolidityExecutionFramework) +int constexpr roundTo32(int _num) +{ + return (_num + 31) / 32 * 32; +} + BOOST_AUTO_TEST_CASE(transaction_status) { char const* sourceCode = R"( @@ -8435,6 +8440,144 @@ BOOST_AUTO_TEST_CASE(string_as_mapping_key) ), encodeArgs(u256(7 + i))); } +BOOST_AUTO_TEST_CASE(string_as_public_mapping_key) +{ + char const* sourceCode = R"( + contract Test { + mapping(string => uint) public data; + function set(string memory _s, uint _v) public { data[_s] = _v; } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + vector strings{ + "Hello, World!", + "Hello, World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1111", + "", + "1" + }; + for (unsigned i = 0; i < strings.size(); i++) + ABI_CHECK(callContractFunction( + "set(string,uint256)", + u256(0x40), + u256(7 + i), + u256(strings[i].size()), + strings[i] + ), encodeArgs()); + for (unsigned i = 0; i < strings.size(); i++) + ABI_CHECK(callContractFunction( + "data(string)", + u256(0x20), + u256(strings[i].size()), + strings[i] + ), encodeArgs(u256(7 + i))); +} + +BOOST_AUTO_TEST_CASE(nested_string_as_public_mapping_key) +{ + char const* sourceCode = R"( + contract Test { + mapping(string => mapping(string => uint)) public data; + function set(string memory _s, string memory _s2, uint _v) public { + data[_s][_s2] = _v; } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + vector strings{ + "Hello, World!", + "Hello, World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1111", + "", + "1", + "last one" + }; + for (unsigned i = 0; i + 1 < strings.size(); i++) + ABI_CHECK(callContractFunction( + "set(string,string,uint256)", + u256(0x60), + u256(roundTo32(0x80 + strings[i].size())), + u256(7 + i), + u256(strings[i].size()), + strings[i], + u256(strings[i+1].size()), + strings[i+1] + ), encodeArgs()); + for (unsigned i = 0; i + 1 < strings.size(); i++) + ABI_CHECK(callContractFunction( + "data(string,string)", + u256(0x40), + u256(roundTo32(0x60 + strings[i].size())), + u256(strings[i].size()), + strings[i], + u256(strings[i+1].size()), + strings[i+1] + ), encodeArgs(u256(7 + i))); +} + +BOOST_AUTO_TEST_CASE(nested_mixed_string_as_public_mapping_key) +{ + char const* sourceCode = R"( + contract Test { + mapping(string => + mapping(int => + mapping(address => + mapping(bytes => int)))) public data; + + function set( + string memory _s1, + int _s2, + address _s3, + bytes memory _s4, + int _value + ) public + { + data[_s1][_s2][_s3][_s4] = _value; + } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + + struct Index + { + string s1; + int s2; + int s3; + string s4; + }; + + vector data{ + { "aabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcbc", 4, 23, "efg" }, + { "tiaron", 456, 63245, "908apzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapzapz" }, + { "", 2345, 12934, "665i65i65i65i65i65i65i65i65i65i65i65i65i65i65i65i65i65i5iart" }, + { "¡¿…", 9781, 8148, "" }, + { "ρν♀♀ω₂₃♀", 929608, 303030, "" } + }; + + for (size_t i = 0; i + 1 < data.size(); i++) + ABI_CHECK(callContractFunction( + "set(string,int256,address,bytes,int256)", + u256(0xA0), + u256(data[i].s2), + u256(data[i].s3), + u256(roundTo32(0xC0 + data[i].s1.size())), + u256(i - 3), + u256(data[i].s1.size()), + data[i].s1, + u256(data[i].s4.size()), + data[i].s4 + ), encodeArgs()); + for (size_t i = 0; i + 1 < data.size(); i++) + ABI_CHECK(callContractFunction( + "data(string,int256,address,bytes)", + u256(0x80), + u256(data[i].s2), + u256(data[i].s3), + u256(roundTo32(0xA0 + data[i].s1.size())), + u256(data[i].s1.size()), + data[i].s1, + u256(data[i].s4.size()), + data[i].s4 + ), encodeArgs(u256(i - 3))); +} + BOOST_AUTO_TEST_CASE(accessor_for_state_variable) { char const* sourceCode = R"( diff --git a/test/libsolidity/syntaxTests/types/mapping/mapping_dynamic_key_public.sol b/test/libsolidity/syntaxTests/types/mapping/mapping_dynamic_key_public.sol index 9fb575afd..a70d19aeb 100644 --- a/test/libsolidity/syntaxTests/types/mapping/mapping_dynamic_key_public.sol +++ b/test/libsolidity/syntaxTests/types/mapping/mapping_dynamic_key_public.sol @@ -2,4 +2,3 @@ contract c { mapping(string => uint) public data; } // ---- -// TypeError: (14-49): Dynamically-sized keys for public mappings are not supported.