diff --git a/libsolidity/ArrayUtils.cpp b/libsolidity/ArrayUtils.cpp index a064b2f57..2596e4afa 100644 --- a/libsolidity/ArrayUtils.cpp +++ b/libsolidity/ArrayUtils.cpp @@ -34,8 +34,10 @@ using namespace solidity; void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const { - // stack layout: [source_ref] target_ref (top) - // need to leave target_ref on the stack at the end + // this copies source to target and also clears target if it was larger + // need to leave "target_ref target_byte_off" on the stack at the end + + // stack layout: [source_ref] [source_byte_off] [source length] target_ref target_byte_off (top) solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); solAssert( _sourceType.getLocation() == ArrayType::Location::CallData || @@ -47,14 +49,20 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType()); Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType()); - // this copies source to target and also clears target if it was larger - // TODO unroll loop for small sizes - // stack: source_ref [source_length] target_ref + bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage; + + // stack: source_ref [source_byte_off] [source_length] target_ref target_byte_off // store target_ref + m_context << eth::Instruction::POP; //@todo for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) m_context << eth::swapInstruction(i); + // stack: target_ref source_ref [source_byte_off] [source_length] + if (sourceIsStorage) + m_context << eth::Instruction::POP; //@todo + // stack: target_ref source_ref [source_length] + // retrieve source length if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) retrieveLength(_sourceType); // otherwise, length is already there // stack: target_ref source_ref source_length @@ -73,6 +81,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + m_context << u256(0); //@todo return; } // compute hashes (data positions) @@ -109,32 +118,37 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // copy if (sourceBaseType->getCategory() == Type::Category::Array) { - m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; + //@todo + m_context << eth::Instruction::DUP3; + if (sourceIsStorage) + m_context << u256(0); + m_context << eth::dupInstruction(sourceIsStorage ? 4 : 3) << u256(0); copyArrayToStorage( dynamic_cast(*targetBaseType), dynamic_cast(*sourceBaseType) ); - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } else { m_context << eth::Instruction::DUP3; if (_sourceType.getLocation() == ArrayType::Location::Storage) + { + m_context << u256(0); StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } else if (sourceBaseType->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); else solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString()); solAssert(2 + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep."); - m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()); + m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()) << u256(0); StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); } // increment source m_context << eth::Instruction::SWAP2 - << (_sourceType.getLocation() == ArrayType::Location::Storage ? - sourceBaseType->getStorageSize() : - sourceBaseType->getCalldataEncodedSize()) + << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize()) << eth::Instruction::ADD << eth::Instruction::SWAP2; // increment target @@ -147,40 +161,48 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << copyLoopEnd; // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP; // stack: target_ref target_data_end target_data_pos_updated clearStorageLoop(*targetBaseType); m_context << eth::Instruction::POP; + m_context << u256(0); //@todo } void ArrayUtils::clearArray(ArrayType const& _type) const { + unsigned stackHeightStart = m_context.getStackHeight(); solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); if (_type.isDynamicallySized()) + { + m_context << eth::Instruction::POP; // remove byte offset clearDynamicArray(_type); + } else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value { solAssert(!_type.isByteArray(), ""); for (unsigned i = 1; i < _type.getLength(); ++i) { StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); + m_context << eth::Instruction::SWAP1; m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; } StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); } else { solAssert(!_type.isByteArray(), ""); - m_context - << eth::Instruction::DUP1 << u256(_type.getLength()) - << u256(_type.getBaseType()->getStorageSize()) - << eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1; + m_context << eth::Instruction::SWAP1; + m_context << eth::Instruction::DUP1 << _type.getLength(); + convertLengthToSize(_type); + m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; clearStorageLoop(*_type.getBaseType()); - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } + solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } void ArrayUtils::clearDynamicArray(ArrayType const& _type) const @@ -188,6 +210,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.isDynamicallySized(), ""); + unsigned stackHeightStart = m_context.getStackHeight(); // fetch length m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // set length to zero @@ -197,7 +220,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const // compute data positions m_context << eth::Instruction::SWAP1; CompilerUtils(m_context).computeHashStatic(); - // stack: len data_pos (len is in slots for byte array and in items for other arrays) + // stack: len data_pos m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD << eth::Instruction::SWAP1; // stack: data_pos_end data_pos @@ -207,6 +230,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const clearStorageLoop(*_type.getBaseType()); // cleanup m_context << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); } void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const @@ -214,6 +238,7 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.isDynamicallySized(), ""); + unsigned stackHeightStart = m_context.getStackHeight(); eth::AssemblyItem resizeEnd = m_context.newTag(); // stack: ref new_length @@ -249,10 +274,12 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const m_context << resizeEnd; // cleanup m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } void ArrayUtils::clearStorageLoop(Type const& _type) const { + unsigned stackHeightStart = m_context.getStackHeight(); if (_type.getCategory() == Type::Category::Mapping) { m_context << eth::Instruction::POP; @@ -267,13 +294,16 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const eth::AssemblyItem zeroLoopEnd = m_context.newTag(); m_context.appendConditionalJumpTo(zeroLoopEnd); // delete + m_context << u256(0); //@todo StorageItem(m_context, _type).setToZero(SourceLocation(), false); + m_context << eth::Instruction::POP; // increment m_context << u256(1) << eth::Instruction::ADD; m_context.appendJumpTo(loopStart); // cleanup m_context << zeroLoopEnd; m_context << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); } void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const diff --git a/libsolidity/ArrayUtils.h b/libsolidity/ArrayUtils.h index 31cca8173..c7b05b6d3 100644 --- a/libsolidity/ArrayUtils.h +++ b/libsolidity/ArrayUtils.h @@ -41,19 +41,19 @@ public: /// Copies an array to an array in storage. The arrays can be of different types only if /// their storage representation is the same. - /// Stack pre: [source_reference] target_reference - /// Stack post: target_reference + /// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset + /// Stack post: target_reference target_byte_offset void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; /// Clears the given dynamic or static array. - /// Stack pre: reference + /// Stack pre: storage_ref storage_byte_offset /// Stack post: void clearArray(ArrayType const& _type) const; /// Clears the length and data elements of the array referenced on the stack. - /// Stack pre: reference + /// Stack pre: reference (excludes byte offset) /// Stack post: void clearDynamicArray(ArrayType const& _type) const; /// Changes the size of a dynamic array and clears the tail if it is shortened. - /// Stack pre: reference new_length + /// Stack pre: reference (excludes byte offset) new_length /// Stack post: void resizeDynamicArray(ArrayType const& _type) const; /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). @@ -67,7 +67,7 @@ public: void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const; /// Retrieves the length (number of elements) of the array ref on the stack. This also /// works for statically-sized arrays. - /// Stack pre: reference + /// Stack pre: reference (excludes byte offset for dynamic storage arrays) /// Stack post: reference length void retrieveLength(ArrayType const& _arrayType) const; 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 e517e384d..511254fa5 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -93,6 +93,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound else { solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented."); + m_context << eth::Instruction::POP; //@todo m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes // jump to end if length is zero @@ -198,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 92fd70437..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 @@ -90,9 +91,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& // struct for (size_t i = 0; i < names.size(); ++i) { - m_context << eth::Instruction::DUP1 - << structType->getStorageOffsetOfMember(names[i]) - << eth::Instruction::ADD; + if (types[i]->getCategory() == Type::Category::Mapping) + continue; + 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::SWAP1; @@ -104,6 +106,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + m_context << u256(location.second); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); retSizeOnStack = returnType->getSizeOnStack(); } @@ -280,23 +283,24 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::Dec: // -- (pre- or postfix) solAssert(!!m_currentLValue, "LValue not retrieved."); m_currentLValue->retrieveValue(_unaryOperation.getLocation()); - solAssert(m_currentLValue->sizeOnStack() <= 1, "Not implemented."); if (!_unaryOperation.isPrefixOperation()) { - if (m_currentLValue->sizeOnStack() == 1) - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - else - m_context << eth::Instruction::DUP1; + // store value for later + solAssert(_unaryOperation.getType()->getSizeOnStack() == 1, "Stack size != 1 not implemented."); + m_context << eth::Instruction::DUP1; + if (m_currentLValue->sizeOnStack() > 0) + for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); } m_context << u256(1); if (_unaryOperation.getOperator() == Token::Inc) m_context << eth::Instruction::ADD; else - m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap - // Stack for prefix: [ref] (*ref)+-1 - // Stack for postfix: *ref [ref] (*ref)+-1 - if (m_currentLValue->sizeOnStack() == 1) - m_context << eth::Instruction::SWAP1; + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // Stack for prefix: [ref...] (*ref)+-1 + // Stack for postfix: *ref [ref...] (*ref)+-1 + for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); m_currentLValue->storeValue( *_unaryOperation.getType(), _unaryOperation.getLocation(), !_unaryOperation.isPrefixOperation()); @@ -661,7 +665,9 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) case Type::Category::Struct: { StructType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); - m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; + 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; } @@ -729,20 +735,22 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) Type const& baseType = *_indexAccess.getBaseExpression().getType(); if (baseType.getCategory() == Type::Category::Mapping) { + // storage byte offset is ignored for mappings, it should be zero. + m_context << eth::Instruction::POP; + // stack: storage_base_ref Type const& keyType = *dynamic_cast(baseType).getKeyType(); - m_context << u256(0); + m_context << u256(0); // memory position solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); - solAssert(baseType.getSizeOnStack() == 1, - "Unexpected: Not exactly one stack slot taken by subscriptable expression."); m_context << eth::Instruction::SWAP1; appendTypeMoveToMemory(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; + m_context << u256(0); setLValueToStorageItem( _indexAccess); } else if (baseType.getCategory() == Type::Category::Array) { - // stack layout: [] + // stack layout: [storage_byte_offset] [] ArrayType const& arrayType = dynamic_cast(baseType); solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); ArrayType::Location location = arrayType.getLocation(); @@ -758,9 +766,11 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) else if (location == ArrayType::Location::CallData) // length is stored on the stack m_context << eth::Instruction::SWAP1; + else if (location == ArrayType::Location::Storage) + m_context << eth::Instruction::DUP3 << load; else m_context << eth::Instruction::DUP2 << load; - // stack: + // stack: [storage_byte_offset] // check out-of-bounds access m_context << eth::Instruction::DUP2 << eth::Instruction::LT; eth::AssemblyItem legalAccess = m_context.appendConditionalJump(); @@ -768,7 +778,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) m_context << eth::Instruction::STOP; m_context << legalAccess; - // stack: + // stack: [storage_byte_offset] if (arrayType.isByteArray()) // byte array is packed differently, especially in storage switch (location) @@ -776,14 +786,15 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) case ArrayType::Location::Storage: // byte array index storage lvalue on stack (goal): // = - m_context << u256(32) << eth::Instruction::SWAP2; + m_context << u256(32) << eth::Instruction::SWAP3; CompilerUtils(m_context).computeHashStatic(); - // stack: 32 index data_ref + // stack: 32 storage_byte_offset index data_ref m_context - << eth::Instruction::DUP3 << eth::Instruction::DUP3 + << eth::Instruction::DUP4 << eth::Instruction::DUP3 << eth::Instruction::DIV << eth::Instruction::ADD - // stack: 32 index (data_ref + index / 32) - << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 << eth::Instruction::MOD; + // stack: 32 storage_byte_offset index (data_ref + index / 32) + << eth::Instruction::SWAP3 << eth::Instruction::SWAP2 + << eth::Instruction::POP << eth::Instruction::MOD; setLValue(_indexAccess); break; case ArrayType::Location::CallData: @@ -797,6 +808,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) } else { + // stack: [storage_byte_offset] + if (location == ArrayType::Location::Storage) + //@todo use byte offset, remove it for now + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; u256 elementSize = location == ArrayType::Location::Storage ? arrayType.getBaseType()->getStorageSize() : @@ -822,6 +837,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false); break; case ArrayType::Location::Storage: + m_context << u256(0); // @todo setLValueToStorageItem(_indexAccess); break; case ArrayType::Location::Memory: @@ -1141,7 +1157,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio if (m_context.isLocalVariable(&_declaration)) setLValue(_expression, _declaration); else if (m_context.isStateVariable(&_declaration)) - setLValue(_expression, _declaration); + setLValue(_expression, _declaration); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 9cab757ea..e6caad747 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -140,7 +140,6 @@ void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments con m_currentLValue = move(lvalue); else lvalue->retrieveValue(_expression.getLocation(), true); - } } diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index 68d6797eb..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); + auto const& location = m_context.getStorageLocationOfVariable(_declaration); + m_context << location.first << u256(location.second); } StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): @@ -86,62 +87,78 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): if (m_dataType.isValueType()) { solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); - solAssert(m_dataType.getStorageSize() <= numeric_limits::max(), - "The storage size of " + m_dataType.toString() + " should fit in an unsigned"); - m_size = unsigned(m_dataType.getStorageSize()); + solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size."); } - else - m_size = 0; // unused } void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { + // stack: storage_key storage_offset if (!m_dataType.isValueType()) return; // no distinction between value and reference for non-value types if (!_remove) - m_context << eth::Instruction::DUP1; - if (m_size == 1) - m_context << eth::Instruction::SLOAD; + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType.getStorageBytes() == 32) + m_context << eth::Instruction::POP << eth::Instruction::SLOAD; else - for (unsigned i = 0; i < m_size; ++i) - { - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_size) - m_context << u256(1) << eth::Instruction::ADD; - else - m_context << eth::Instruction::POP; - } + { + 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 { - // stack layout: value value ... value target_ref + // stack: value storage_key storage_offset if (m_dataType.isValueType()) { - if (!_move) // copy values + solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType.getStorageBytes() == 32) { - if (m_size + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_size; ++i) - m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; + // offset should be zero + m_context << eth::Instruction::POP; + if (!_move) + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + m_context << eth::Instruction::SSTORE; } - if (m_size > 1) // store high index value first - m_context << u256(m_size - 1) << eth::Instruction::ADD; - for (unsigned i = 0; i < m_size; ++i) + else { - if (i + 1 >= m_size) - m_context << eth::Instruction::SSTORE; - else - // stack here: value value ... value value (target_ref+offset) - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 - << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // 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 { - solAssert(_sourceType.getCategory() == m_dataType.getCategory(), + solAssert( + _sourceType.getCategory() == m_dataType.getCategory(), "Wrong type conversation for assignment."); if (m_dataType.getCategory() == Type::Category::Array) { @@ -149,11 +166,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc dynamic_cast(m_dataType), dynamic_cast(_sourceType)); if (_move) - m_context << eth::Instruction::POP; + CompilerUtils(m_context).popStackElement(_sourceType); } else if (m_dataType.getCategory() == Type::Category::Struct) { - // stack layout: source_ref target_ref + // 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()) @@ -162,27 +180,37 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_member_ref + 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 target_ref member_offset source_value... - solAssert(2 + memberType->getSizeOnStack() <= 16, "Stack too deep."); - m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) - << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_value... target_member_ref + // stack: source_ref source_off target_ref target_off member_offset source_value... + 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 << eth::Instruction::POP; + m_context + << eth::Instruction::POP << eth::Instruction::POP + << eth::Instruction::POP << eth::Instruction::POP; else - m_context << eth::Instruction::SWAP1; - m_context << eth::Instruction::POP; + m_context + << eth::Instruction::SWAP2 << eth::Instruction::POP + << eth::Instruction::SWAP2 << eth::Instruction::POP; } else - BOOST_THROW_EXCEPTION(InternalCompilerError() - << errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment.")); + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_sourceLocation(_location) + << errinfo_comment("Invalid non-value type for assignment.")); } } @@ -191,12 +219,13 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const if (m_dataType.getCategory() == Type::Category::Array) { if (!_removeReference) - m_context << eth::Instruction::DUP1; + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); ArrayUtils(m_context).clearArray(dynamic_cast(m_dataType)); } else if (m_dataType.getCategory() == Type::Category::Struct) { - // stack layout: ref + // 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()) { @@ -204,32 +233,42 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP2 << eth::Instruction::ADD; + pair const& offsets = structType.getStorageOffsetsOfMember(member.first); + m_context + << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD + << u256(offsets.second); StorageItem(m_context, *memberType).setToZero(); } if (_removeReference) - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } else { solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); - if (m_size == 0 && _removeReference) - m_context << eth::Instruction::POP; - else if (m_size == 1) + // @todo actually use offset + if (!_removeReference) + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType.getStorageBytes() == 32) + { + // offset should be zero m_context - << u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2) - << eth::Instruction::SSTORE; + << eth::Instruction::POP << u256(0) + << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } else { - if (!_removeReference) - m_context << eth::Instruction::DUP1; - for (unsigned i = 0; i < m_size; ++i) - if (i + 1 >= m_size) - m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - else - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::ADD; + 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; } } } @@ -300,6 +339,8 @@ StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); + // storage byte offset must be zero + m_context << eth::Instruction::POP; } void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const diff --git a/libsolidity/LValue.h b/libsolidity/LValue.h index c57c80e37..ad6225162 100644 --- a/libsolidity/LValue.h +++ b/libsolidity/LValue.h @@ -98,7 +98,9 @@ private: }; /** - * Reference to some item in storage. The (starting) position of the item is stored on the stack. + * Reference to some item in storage. On the stack this is , + * where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied + * by 2**i before storing it. */ class StorageItem: public LValue { @@ -107,6 +109,7 @@ public: StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); /// Constructs the LValue and assumes that the storage reference is already on the stack. StorageItem(CompilerContext& _compilerContext, Type const& _type); + virtual unsigned sizeOnStack() const { return 2; } virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, @@ -117,11 +120,6 @@ public: SourceLocation const& _location = SourceLocation(), bool _removeReference = true ) const override; - -private: - /// Number of stack elements occupied by the value (not the reference). - /// Only used for value types. - unsigned m_size; }; /** diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 70cbec5d9..04f86b922 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -35,6 +35,81 @@ namespace dev namespace solidity { +void StorageOffsets::computeOffsets(TypePointers const& _types) +{ + bigint slotOffset = 0; + unsigned byteOffset = 0; + map> offsets; + for (size_t i = 0; i < _types.size(); ++i) + { + TypePointer const& type = _types[i]; + 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[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 (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; +} + +MemberList& MemberList::operator=(MemberList&& _other) +{ + m_memberTypes = std::move(_other.m_memberTypes); + m_storageOffsets = std::move(_other.m_storageOffsets); + return *this; +} + +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_storageOffsets->getStorageSize(); +} + TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) { char const* tokenCstr = Token::toString(_typeToken); @@ -149,7 +224,7 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) return TypePointer(); } -const MemberList Type::EmptyMemberList = MemberList(); +const MemberList Type::EmptyMemberList; IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) @@ -235,10 +310,11 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe return commonType; } -const MemberList IntegerType::AddressMemberList = - MemberList({{"balance", make_shared(256)}, - {"call", make_shared(strings(), strings(), FunctionType::Location::Bare, true)}, - {"send", make_shared(strings{"uint"}, strings{}, FunctionType::Location::Send)}}); +const MemberList IntegerType::AddressMemberList({ + {"balance", make_shared(256)}, + {"call", make_shared(strings(), strings(), FunctionType::Location::Bare, true)}, + {"send", make_shared(strings{"uint"}, strings{}, FunctionType::Location::Send)} +}); IntegerConstantType::IntegerConstantType(Literal const& _literal) { @@ -644,6 +720,9 @@ unsigned ArrayType::getSizeOnStack() const if (m_location == Location::CallData) // offset [length] (stack top) return 1 + (isDynamicallySized() ? 1 : 0); + else if (m_location == Location::Storage) + // storage_key storage_offset + return 2; else // offset return 1; @@ -672,7 +751,7 @@ shared_ptr ArrayType::copyForLocation(ArrayType::Location _location) return copy; } -const MemberList ArrayType::s_arrayTypeMemberList = MemberList({{"length", make_shared(256)}}); +const MemberList ArrayType::s_arrayTypeMemberList({{"length", make_shared(256)}}); bool ContractType::operator==(Type const& _other) const { @@ -748,12 +827,7 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { - bigint size = 0; - for (pair const& member: getMembers()) - size += member.second->getStorageSize(); - if (size >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Struct too large for storage.")); - return max(1, u256(size)); + return max(1, getMembers().getStorageSize()); } bool StructType::canLiveOutsideStorage() const @@ -782,17 +856,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 @@ -808,6 +876,15 @@ bool EnumType::operator==(Type const& _other) const return other.m_enum == m_enum; } +unsigned EnumType::getStorageBytes() const +{ + size_t elements = m_enum.getMembers().size(); + if (elements <= 1) + return 1; + else + return dev::bytesRequired(elements - 1); +} + string EnumType::toString() const { return string("enum ") + m_enum.getName(); @@ -952,6 +1029,13 @@ string FunctionType::toString() const return name + ")"; } +u256 FunctionType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + unsigned FunctionType::getSizeOnStack() const { Location location = m_location; @@ -1074,6 +1158,13 @@ string MappingType::toString() const return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")"; } +u256 VoidType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable void type requested.")); +} + bool TypeType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -1082,6 +1173,13 @@ bool TypeType::operator==(Type const& _other) const return *getActualType() == *other.getActualType(); } +u256 TypeType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable type type requested.")); +} + MemberList const& TypeType::getMembers() const { // We need to lazy-initialize it because of recursive references. @@ -1119,6 +1217,13 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier) swap(params, m_parameterTypes); } +u256 ModifierType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable type type requested.")); +} + bool ModifierType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -1149,23 +1254,29 @@ MagicType::MagicType(MagicType::Kind _kind): switch (m_kind) { case Kind::Block: - m_members = MemberList({{"coinbase", make_shared(0, IntegerType::Modifier::Address)}, - {"timestamp", make_shared(256)}, - {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, - {"difficulty", make_shared(256)}, - {"number", make_shared(256)}, - {"gaslimit", make_shared(256)}}); + m_members = move(MemberList({ + {"coinbase", make_shared(0, IntegerType::Modifier::Address)}, + {"timestamp", make_shared(256)}, + {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, + {"difficulty", make_shared(256)}, + {"number", make_shared(256)}, + {"gaslimit", make_shared(256)} + })); break; case Kind::Message: - m_members = MemberList({{"sender", make_shared(0, IntegerType::Modifier::Address)}, - {"gas", make_shared(256)}, - {"value", make_shared(256)}, - {"data", make_shared(ArrayType::Location::CallData)}, - {"sig", make_shared(4)}}); + m_members = move(MemberList({ + {"sender", make_shared(0, IntegerType::Modifier::Address)}, + {"gas", make_shared(256)}, + {"value", make_shared(256)}, + {"data", make_shared(ArrayType::Location::CallData)}, + {"sig", make_shared(4)} + })); break; case Kind::Transaction: - m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::Address)}, - {"gasprice", make_shared(256)}}); + m_members = move(MemberList({ + {"origin", make_shared(0, IntegerType::Modifier::Address)}, + {"gasprice", make_shared(256)} + })); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 853bd6889..e7601fde8 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. */ @@ -53,6 +73,7 @@ public: MemberList() {} explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} + MemberList& operator=(MemberList&& _other); TypePointer getMemberType(std::string const& _name) const { for (auto const& it: m_memberTypes) @@ -60,12 +81,18 @@ public: return it.second; return TypePointer(); } + /// @returns the offset of the given member in storage slots and bytes inside a slot or + /// a nullptr if the member is not part of storage. + std::pair const* getMemberStorageOffset(std::string const& _name) const; + /// @returns the number of storage slots occupied by the members. + u256 const& getStorageSize() const; MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } MemberMap::const_iterator end() const { return m_memberTypes.end(); } private: MemberMap m_memberTypes; + mutable std::unique_ptr m_storageOffsets; }; /** @@ -97,6 +124,8 @@ public: /// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); + /// Calculates the + virtual Category getCategory() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -126,9 +155,15 @@ public: unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); } /// @returns true if the type is dynamically encoded in calldata virtual bool isDynamicallySized() const { return false; } - /// @returns number of bytes required to hold this value in storage. + /// @returns the number of storage slots required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 getStorageSize() const { return 1; } + /// Multiple small types can be packed into a single storage slot. If such a packing is possible + /// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if + /// it does not fit. + /// In order to avoid computation at runtime of whether such moving is necessary, structs and + /// array data (not each element) always start a new slot. + virtual unsigned getStorageBytes() const { return 32; } /// Returns true if the type can be stored in storage. virtual bool canBeStored() const { return true; } /// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping. @@ -179,6 +214,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } + virtual unsigned getStorageBytes() const override { return m_bits / 8; } virtual bool isValueType() const override { return true; } virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } @@ -251,6 +287,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } + virtual unsigned getStorageBytes() const override { return m_bytes; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); } @@ -275,6 +312,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; } + virtual unsigned getStorageBytes() const override { return 1; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } @@ -284,6 +322,9 @@ public: /** * The type of an array. The flavours are byte array (bytes), statically- ([]) * and dynamically-sized array ([]). + * In storage, all arrays are packed tightly (as long as more than one elementary type fits in + * one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and + * thus start on their own slot. */ class ArrayType: public Type { @@ -345,6 +386,7 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; + virtual unsigned getStorageBytes() const override { return 20; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override; @@ -384,12 +426,12 @@ public: virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override; - virtual unsigned getSizeOnStack() const override { return 1; /*@todo*/ } + virtual unsigned getSizeOnStack() const override { return 2; } virtual std::string toString() const override; 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; @@ -408,6 +450,7 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getSizeOnStack() const override { return 1; } + virtual unsigned getStorageBytes() const override; virtual std::string toString() const override; virtual bool isValueType() const override { return true; } @@ -477,7 +520,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override; virtual MemberList const& getMembers() const override; @@ -527,6 +570,7 @@ private: /** * The type of a mapping, there is one distinct type per key/value type pair. + * Mappings always occupy their own storage slot, but do not actually use it. */ class MappingType: public Type { @@ -537,6 +581,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; + virtual unsigned getSizeOnStack() const override { return 2; } virtual bool canLiveOutsideStorage() const override { return false; } TypePointer const& getKeyType() const { return m_keyType; } @@ -560,7 +605,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString() const override { return "void"; } virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } }; @@ -580,7 +625,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } @@ -606,7 +651,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual bool operator==(Type const& _other) const override; diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 8ccf9b3fe..a07b78872 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -534,6 +534,27 @@ BOOST_AUTO_TEST_CASE(empty_string_on_stack) BOOST_CHECK(callContractFunction("run(bytes0,uint8)", string(), byte(0x02)) == encodeArgs(0x2, string(""), string("abc\0"))); } +BOOST_AUTO_TEST_CASE(inc_dec_operators) +{ + char const* sourceCode = R"( + contract test { + uint8 x; + uint v; + function f() returns (uint r) { + uint a = 6; + r = a; + r += (a++) * 0x10; + r += (++a) * 0x100; + v = 3; + r += (v++) * 0x1000; + r += (++v) * 0x10000; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(0x53866)); +} + BOOST_AUTO_TEST_CASE(state_smoke_test) { char const* sourceCode = "contract test {\n" @@ -970,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)); @@ -2502,7 +2525,7 @@ BOOST_AUTO_TEST_CASE(struct_copy) contract c { struct Nested { uint x; uint y; } struct Struct { uint a; mapping(uint => Struct) b; Nested nested; uint c; } - mapping(uint => Struct) public data; + mapping(uint => Struct) data; function set(uint k) returns (bool) { data[k].a = 1; data[k].nested.x = 3; @@ -3248,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 41ec1f90c..85b77d210 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(unused_expressions) data; } })"; - compileBothVersions(33, sourceCode); + compileBothVersions(36, sourceCode); compareVersions("f()"); } diff --git a/test/SolidityTypes.cpp b/test/SolidityTypes.cpp new file mode 100644 index 000000000..8defd1d89 --- /dev/null +++ b/test/SolidityTypes.cpp @@ -0,0 +1,82 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Unit tests for the type system of Solidity. + */ + +#include +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(SolidityTypes) + +BOOST_AUTO_TEST_CASE(storage_layout_simple) +{ + MemberList members(MemberList::MemberMap({ + {string("first"), Type::fromElementaryTypeName("uint128")}, + {string("second"), Type::fromElementaryTypeName("uint120")}, + {string("wraps"), Type::fromElementaryTypeName("uint16")} + })); + BOOST_REQUIRE_EQUAL(u256(2), members.getStorageSize()); + BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("wraps") != nullptr); + BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(0), unsigned(16))); + BOOST_CHECK(*members.getMemberStorageOffset("wraps") == make_pair(u256(1), unsigned(0))); +} + +BOOST_AUTO_TEST_CASE(storage_layout_mapping) +{ + MemberList members(MemberList::MemberMap({ + {string("first"), Type::fromElementaryTypeName("uint128")}, + {string("second"), make_shared( + Type::fromElementaryTypeName("uint8"), + Type::fromElementaryTypeName("uint8") + )}, + {string("third"), Type::fromElementaryTypeName("uint16")}, + {string("final"), make_shared( + Type::fromElementaryTypeName("uint8"), + Type::fromElementaryTypeName("uint8") + )}, + })); + BOOST_REQUIRE_EQUAL(u256(4), members.getStorageSize()); + BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("third") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("final") != nullptr); + BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(1), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("third") == make_pair(u256(2), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("final") == make_pair(u256(3), unsigned(0))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +}