diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 0f4e89de1..db61cc4a1 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -274,10 +274,19 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) void Compiler::registerStateVariables(ContractDefinition const& _contract) { + vector variables; for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) for (ASTPointer const& variable: contract->getStateVariables()) if (!variable->isConstant()) - m_context.addStateVariable(*variable); + variables.push_back(variable.get()); + TypePointers types; + for (auto variable: variables) + types.push_back(variable->getType()); + StorageOffsets offsets; + offsets.computeOffsets(types); + for (size_t index = 0; index < variables.size(); ++index) + if (auto const* offset = offsets.getOffset(index)) + m_context.addStateVariable(*variables[index], offset->first, offset->second); } void Compiler::initializeStateVariables(ContractDefinition const& _contract) diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index f2bb1de20..7cade367c 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -37,15 +37,13 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio m_magicGlobals.insert(&_declaration); } -void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) +void CompilerContext::addStateVariable( + VariableDeclaration const& _declaration, + u256 const& _storageOffset, + unsigned _byteOffset +) { - m_stateVariables[&_declaration] = m_stateVariablesSize; - bigint newSize = bigint(m_stateVariablesSize) + _declaration.getType()->getStorageSize(); - if (newSize >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() - << errinfo_comment("State variable does not fit in storage.") - << errinfo_sourceLocation(_declaration.getLocation())); - m_stateVariablesSize = u256(newSize); + m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); } void CompilerContext::startFunction(Declaration const& _function) @@ -170,7 +168,7 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const return m_asm.deposit() - _offset - 1; } -u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const +pair CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const { auto it = m_stateVariables.find(&_declaration); solAssert(it != m_stateVariables.end(), "Variable not found in storage."); diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index 76923a77a..87f90d4c4 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -42,7 +43,7 @@ class CompilerContext { public: void addMagicGlobal(MagicVariableDeclaration const& _declaration); - void addStateVariable(VariableDeclaration const& _declaration); + void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void removeVariable(VariableDeclaration const& _declaration); void addAndInitializeVariable(VariableDeclaration const& _declaration); @@ -82,7 +83,7 @@ public: /// Converts an offset relative to the current stack height to a value that can be used later /// with baseToCurrentStackOffset to point to the same stack element. unsigned currentToBaseStackOffset(unsigned _offset) const; - u256 getStorageLocationOfVariable(Declaration const& _declaration) const; + std::pair getStorageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } @@ -144,10 +145,8 @@ private: std::set m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. std::map m_compiledContracts; - /// Size of the state variables, offset of next variable to be added. - u256 m_stateVariablesSize = 0; /// Storage offsets of state variables - std::map m_stateVariables; + std::map> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). std::map m_localVariables; /// Labels pointing to the entry points of functions. diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 5fc8bd8f2..511254fa5 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -199,7 +199,6 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } - unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 6f5331294..a0a688bbd 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -67,9 +67,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); // retrieve the position of the variable - m_context << m_context.getStorageLocationOfVariable(_varDecl); - TypePointer returnType = _varDecl.getType(); + auto const& location = m_context.getStorageLocationOfVariable(_varDecl); + m_context << location.first; + TypePointer returnType = _varDecl.getType(); for (TypePointer const& paramType: paramTypes) { // move offset to memory @@ -81,7 +82,6 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& returnType = dynamic_cast(*returnType).getValueType(); } - m_context << u256(0); // @todo unsigned retSizeOnStack = 0; solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); if (StructType const* structType = dynamic_cast(returnType.get())) @@ -93,21 +93,20 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { if (types[i]->getCategory() == Type::Category::Mapping) continue; - m_context - << eth::Instruction::DUP2 << structType->getStorageOffsetOfMember(names[i]) - << eth::Instruction::ADD; - m_context << u256(0); //@todo + pair const& offsets = structType->getStorageOffsetsOfMember(names[i]); + m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second); StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); - m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; + m_context << eth::Instruction::SWAP1; retSizeOnStack += types[i]->getSizeOnStack(); } - m_context << eth::Instruction::POP << eth::Instruction::POP; + m_context << eth::Instruction::POP; } else { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + m_context << u256(location.second); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); retSizeOnStack = returnType->getSizeOnStack(); } @@ -666,10 +665,9 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) case Type::Category::Struct: { StructType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); - m_context << eth::Instruction::POP; //@todo - m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; - //@todo - m_context << u256(0); + m_context << eth::Instruction::POP; // structs always align to new slot + pair const& offsets = type.getStorageOffsetsOfMember(member); + m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second); setLValueToStorageItem(_memberAccess); break; } diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index 8e618e87f..234072bce 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -77,7 +77,8 @@ void StackVariable::setToZero(SourceLocation const& _location, bool) const StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): StorageItem(_compilerContext, *_declaration.getType()) { - m_context << m_context.getStorageLocationOfVariable(_declaration) << u256(0); + auto const& location = m_context.getStorageLocationOfVariable(_declaration); + m_context << location.first << u256(location.second); } StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): @@ -86,7 +87,6 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): if (m_dataType.isValueType()) { solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); - //@todo the meaning of getStorageSize() probably changes solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size."); } } @@ -98,12 +98,18 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const return; // no distinction between value and reference for non-value types if (!_remove) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - m_context - << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 - << u256(2) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; - //@todo higher order bits might be dirty. Is this bad? - //@todo this does not work for types that are left-aligned on the stack. - // make those types right-aligned? + if (m_dataType.getStorageBytes() == 32) + m_context << eth::Instruction::POP << eth::Instruction::SLOAD; + else + { + m_context + << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 + << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (m_dataType.getCategory() == Type::Category::FixedBytes) + m_context << (u256(0x1) << (256 - 8 * m_dataType.getStorageBytes())) << eth::Instruction::MUL; + else + m_context << ((u256(0x1) << (8 * m_dataType.getStorageBytes())) - 1) << eth::Instruction::AND; + } } void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const @@ -111,12 +117,43 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack: value storage_key storage_offset if (m_dataType.isValueType()) { - //@todo OR the value into the storage like it is done for ByteArrayElement - m_context - << u256(2) << eth::Instruction::EXP << eth::Instruction::DUP3 << eth::Instruction::MUL - << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - if (_move) + solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType.getStorageBytes() == 32) + { + // offset should be zero m_context << eth::Instruction::POP; + if (!_move) + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + m_context << eth::Instruction::SSTORE; + } + else + { + // OR the value into the other values in the storage slot + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: value storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: value storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1) + << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: value storage_ref multiplier cleared_value + m_context + << eth::Instruction::SWAP1 << eth::Instruction::DUP4; + // stack: value storage_ref cleared_value multiplier value + if (m_dataType.getCategory() == Type::Category::FixedBytes) + m_context + << (u256(0x1) << (256 - 8 * dynamic_cast(m_dataType).getNumBytes())) + << eth::Instruction::SWAP1 << eth::Instruction::DIV; + m_context << eth::Instruction::MUL << eth::Instruction::OR; + // stack: value storage_ref updated_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + if (_move) + m_context << eth::Instruction::POP; + } } else { @@ -134,28 +171,31 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc else if (m_dataType.getCategory() == Type::Category::Struct) { // stack layout: source_ref source_offset target_ref target_offset + // note that we have structs, so offsets should be zero and are ignored auto const& structType = dynamic_cast(m_dataType); solAssert(structType == _sourceType, "Struct assignment with conversion."); for (auto const& member: structType.getMembers()) { - //@todo actually use offsets // assign each member that is not a mapping TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP5 << eth::Instruction::DUP2 << eth::Instruction::ADD; - m_context << u256(0); // zero offset - // stack: source_ref source_off target_ref target_off member_offset source_member_ref source_member_off + pair const& offsets = structType.getStorageOffsetsOfMember(member.first); + m_context + << offsets.first << u256(offsets.second) + << eth::Instruction::DUP6 << eth::Instruction::DUP3 + << eth::Instruction::ADD << eth::Instruction::DUP2; + // stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_member_ref source_member_off StorageItem(m_context, *memberType).retrieveValue(_location, true); // stack: source_ref source_off target_ref target_off member_offset source_value... - solAssert(2 + memberType->getSizeOnStack() <= 16, "Stack too deep."); - m_context << eth::dupInstruction(3 + memberType->getSizeOnStack()) - << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; - // stack: source_ref source_off target_ref target_off member_offset source_value... target_member_ref - m_context << u256(0); // zero offset + solAssert(4 + memberType->getSizeOnStack() <= 16, "Stack too deep."); + m_context + << eth::dupInstruction(4 + memberType->getSizeOnStack()) + << eth::dupInstruction(3 + memberType->getSizeOnStack()) << eth::Instruction::ADD + << eth::dupInstruction(2 + memberType->getSizeOnStack()); + // stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_value... target_member_ref target_member_byte_off StorageItem(m_context, *memberType).storeValue(*memberType, _location, true); - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } if (_move) m_context @@ -185,6 +225,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const else if (m_dataType.getCategory() == Type::Category::Struct) { // stack layout: storage_key storage_offset + // @todo this can be improved for packed types auto const& structType = dynamic_cast(m_dataType); for (auto const& member: structType.getMembers()) { @@ -192,11 +233,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - // @todo actually use offset + pair const& offsets = structType.getStorageOffsetsOfMember(member.first); m_context - << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP3 << eth::Instruction::ADD; - m_context << u256(0); + << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD + << u256(offsets.second); StorageItem(m_context, *memberType).setToZero(); } if (_removeReference) @@ -208,7 +248,28 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const // @todo actually use offset if (!_removeReference) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - m_context << eth::Instruction::POP << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + if (m_dataType.getStorageBytes() == 32) + { + // offset should be zero + m_context + << eth::Instruction::POP << u256(0) + << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } + else + { + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1) + << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: storage_ref cleared_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } } } diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 500ae9b05..147eff1ec 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -35,54 +35,72 @@ namespace dev namespace solidity { -std::pair const* MemberList::getMemberStorageOffset(string const& _name) const +void StorageOffsets::computeOffsets(TypePointers const& _types) { - if (!m_storageOffsets) + bigint slotOffset = 0; + unsigned byteOffset = 0; + map> offsets; + for (size_t i = 0; i < _types.size(); ++i) { - bigint slotOffset = 0; - unsigned byteOffset = 0; - map> offsets; - for (auto const& nameAndType: m_memberTypes) + TypePointer const& type = _types[i]; + if (!type->canBeStored()) + continue; + if (byteOffset + type->getStorageBytes() > 32) { - TypePointer const& type = nameAndType.second; - if (!type->canBeStored()) - continue; - if (byteOffset + type->getStorageBytes() > 32) - { - // would overflow, go to next slot - ++slotOffset; - byteOffset = 0; - } - if (slotOffset >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); - offsets[nameAndType.first] = make_pair(u256(slotOffset), byteOffset); - solAssert(type->getStorageSize() >= 1, "Invalid storage size."); - if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32) - byteOffset += type->getStorageBytes(); - else - { - slotOffset += type->getStorageSize(); - byteOffset = 0; - } - } - if (byteOffset > 0) + // would overflow, go to next slot ++slotOffset; + byteOffset = 0; + } if (slotOffset >= bigint(1) << 256) BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); - m_storageSize = u256(slotOffset); - m_storageOffsets.reset(new decltype(offsets)(move(offsets))); + offsets[i] = make_pair(u256(slotOffset), byteOffset); + solAssert(type->getStorageSize() >= 1, "Invalid storage size."); + if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32) + byteOffset += type->getStorageBytes(); + else + { + slotOffset += type->getStorageSize(); + byteOffset = 0; + } } - if (m_storageOffsets->count(_name)) - return &((*m_storageOffsets)[_name]); + if (byteOffset > 0) + ++slotOffset; + if (slotOffset >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); + m_storageSize = u256(slotOffset); + swap(m_offsets, offsets); +} + +pair const* StorageOffsets::getOffset(size_t _index) const +{ + if (m_offsets.count(_index)) + return &m_offsets.at(_index); else return nullptr; } +std::pair const* MemberList::getMemberStorageOffset(string const& _name) const +{ + if (!m_storageOffsets) + { + TypePointers memberTypes; + memberTypes.reserve(m_memberTypes.size()); + for (auto const& nameAndType: m_memberTypes) + memberTypes.push_back(nameAndType.second); + m_storageOffsets.reset(new StorageOffsets()); + m_storageOffsets->computeOffsets(memberTypes); + } + for (size_t index = 0; index < m_memberTypes.size(); ++index) + if (m_memberTypes[index].first == _name) + return m_storageOffsets->getOffset(index); + return nullptr; +} + u256 const& MemberList::getStorageSize() const { // trigger lazy computation getMemberStorageOffset(""); - return m_storageSize; + return m_storageOffsets->getStorageSize(); } TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) @@ -830,18 +848,11 @@ MemberList const& StructType::getMembers() const return *m_members; } -u256 StructType::getStorageOffsetOfMember(string const& _name) const +pair const& StructType::getStorageOffsetsOfMember(string const& _name) const { - - //@todo cache member offset? - u256 offset; - for (ASTPointer const& variable: m_struct.getMembers()) - { - if (variable->getName() == _name) - return offset; - offset += variable->getType()->getStorageSize(); - } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); + auto const* offsets = getMembers().getMemberStorageOffset(_name); + solAssert(offsets, "Storage offset of non-existing member requested."); + return *offsets; } TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 5e4970780..cc7a0e243 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -43,6 +43,26 @@ using TypePointer = std::shared_ptr; using FunctionTypePointer = std::shared_ptr; using TypePointers = std::vector; + +/** + * Helper class to compute storage offsets of members of structs and contracts. + */ +class StorageOffsets +{ +public: + /// Resets the StorageOffsets objects and determines the position in storage for each + /// of the elements of @a _types. + void computeOffsets(TypePointers const& _types); + /// @returns the offset of the given member, might be null if the member is not part of storage. + std::pair const* getOffset(size_t _index) const; + /// @returns the total number of slots occupied by all members. + u256 const& getStorageSize() const { return m_storageSize; } + +private: + u256 m_storageSize; + std::map> m_offsets; +}; + /** * List of members of a type. */ @@ -71,8 +91,7 @@ public: private: MemberMap m_memberTypes; - mutable u256 m_storageSize = 0; - mutable std::unique_ptr>> m_storageOffsets; + mutable std::unique_ptr m_storageOffsets; }; /** @@ -411,7 +430,7 @@ public: virtual MemberList const& getMembers() const override; - u256 getStorageOffsetOfMember(std::string const& _name) const; + std::pair const& getStorageOffsetsOfMember(std::string const& _name) const; private: StructDefinition const& m_struct; diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index de6f43b77..a07b78872 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -991,18 +991,20 @@ BOOST_AUTO_TEST_CASE(multiple_elementary_accessors) BOOST_AUTO_TEST_CASE(complex_accessors) { - char const* sourceCode = "contract test {\n" - " mapping(uint256 => bytes4) public to_string_map;\n" - " mapping(uint256 => bool) public to_bool_map;\n" - " mapping(uint256 => uint256) public to_uint_map;\n" - " mapping(uint256 => mapping(uint256 => uint256)) public to_multiple_map;\n" - " function test() {\n" - " to_string_map[42] = \"24\";\n" - " to_bool_map[42] = false;\n" - " to_uint_map[42] = 12;\n" - " to_multiple_map[42][23] = 31;\n" - " }\n" - "}\n"; + char const* sourceCode = R"( + contract test { + mapping(uint256 => bytes4) public to_string_map; + mapping(uint256 => bool) public to_bool_map; + mapping(uint256 => uint256) public to_uint_map; + mapping(uint256 => mapping(uint256 => uint256)) public to_multiple_map; + function test() { + to_string_map[42] = "24"; + to_bool_map[42] = false; + to_uint_map[42] = 12; + to_multiple_map[42][23] = 31; + } + } + )"; compileAndRun(sourceCode); BOOST_CHECK(callContractFunction("to_string_map(uint256)", 42) == encodeArgs("24")); BOOST_CHECK(callContractFunction("to_bool_map(uint256)", 42) == encodeArgs(false)); @@ -3269,6 +3271,130 @@ BOOST_AUTO_TEST_CASE(constant_variables) })"; compileAndRun(sourceCode); } + +BOOST_AUTO_TEST_CASE(packed_storage_structs_uint) +{ + char const* sourceCode = R"( + contract C { + struct str { uint8 a; uint16 b; uint248 c; } + str data; + function test() returns (uint) { + data.a = 2; + if (data.a != 2) return 2; + data.b = 0xabcd; + if (data.b != 0xabcd) return 3; + data.c = 0x1234567890; + if (data.c != 0x1234567890) return 4; + if (data.a != 2) return 5; + data.a = 8; + if (data.a != 8) return 6; + if (data.b != 0xabcd) return 7; + data.b = 0xdcab; + if (data.b != 0xdcab) return 8; + if (data.c != 0x1234567890) return 9; + data.c = 0x9876543210; + if (data.c != 0x9876543210) return 10; + return 1; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(packed_storage_structs_enum) +{ + char const* sourceCode = R"( + contract C { + enum small { A, B, C, D } + enum larger { A, B, C, D, E} + struct str { small a; small b; larger c; larger d; } + str data; + function test() returns (uint) { + data.a = small.B; + if (data.a != small.B) return 2; + data.b = small.C; + if (data.b != small.C) return 3; + data.c = larger.D; + if (data.c != larger.D) return 4; + if (data.a != small.B) return 5; + data.a = small.C; + if (data.a != small.C) return 6; + if (data.b != small.C) return 7; + data.b = small.D; + if (data.b != small.D) return 8; + if (data.c != larger.D) return 9; + data.c = larger.B; + if (data.c != larger.B) return 10; + return 1; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(packed_storage_structs_bytes) +{ + char const* sourceCode = R"( + contract C { + struct s1 { byte a; byte b; bytes10 c; bytes9 d; bytes10 e; } + struct s2 { byte a; s1 inner; byte b; byte c; } + byte x; + s2 data; + byte y; + function test() returns (bool) { + x = 1; + data.a = 2; + data.inner.a = 3; + data.inner.b = 4; + data.inner.c = "1234567890"; + data.inner.d = "123456789"; + data.inner.e = "abcdefghij"; + data.b = 5; + data.c = 6; + y = 7; + return x == 1 && data.a == 2 && data.inner.a == 3 && data.inner.b == 4 && + data.inner.c == "1234567890" && data.inner.d == "123456789" && + data.inner.e == "abcdefghij" && data.b == 5 && data.c == 6 && y == 7; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(packed_storage_structs_delete) +{ + char const* sourceCode = R"( + contract C { + struct str { uint8 a; uint16 b; uint8 c; } + uint8 x; + uint16 y; + str data; + function test() returns (uint) { + x = 1; + y = 2; + data.a = 2; + data.b = 0xabcd; + data.c = 0xfa; + if (x != 1 || y != 2 || data.a != 2 || data.b != 0xabcd || data.c != 0xfa) + return 2; + delete y; + delete data.b; + if (x != 1 || y != 0 || data.a != 2 || data.b != 0 || data.c != 0xfa) + return 3; + delete x; + delete data; + return 1; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(1)); + BOOST_CHECK(m_state.storage(m_contractAddress).empty()); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityOptimizer.cpp b/test/SolidityOptimizer.cpp index 161d1f898..85b77d210 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(unused_expressions) data; } })"; - compileBothVersions(29, sourceCode); + compileBothVersions(36, sourceCode); compareVersions("f()"); }