diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 10256900d..92cc2733f 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -1122,6 +1122,6 @@ VerifiedBlockRef BlockChain::verifyBlock(bytes const& _block, function(type)); m_isLValue = (*m_memberName == "length" && - arrayType.location() != ReferenceType::Location::CallData && arrayType.isDynamicallySized()); + arrayType.location() != DataLocation::CallData && arrayType.isDynamicallySized()); } else m_isLValue = false; @@ -942,7 +942,7 @@ void IndexAccess::checkTypeRequirements(TypePointers const*) m_type = make_shared(1); else m_type = type.getBaseType(); - m_isLValue = type.location() != ReferenceType::Location::CallData; + m_isLValue = type.location() != DataLocation::CallData; break; } case Type::Category::Mapping: @@ -959,7 +959,7 @@ void IndexAccess::checkTypeRequirements(TypePointers const*) { TypeType const& type = dynamic_cast(*m_base->getType()); if (!m_index) - m_type = make_shared(make_shared(ReferenceType::Location::Memory, type.getActualType())); + m_type = make_shared(make_shared(DataLocation::Memory, type.getActualType())); else { m_index->checkTypeRequirements(nullptr); @@ -967,7 +967,9 @@ void IndexAccess::checkTypeRequirements(TypePointers const*) if (!length) BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected.")); m_type = make_shared(make_shared( - ReferenceType::Location::Memory, type.getActualType(), length->literalValue(nullptr))); + DataLocation::Memory, type.getActualType(), + length->literalValue(nullptr) + )); } break; } diff --git a/libsolidity/ArrayUtils.cpp b/libsolidity/ArrayUtils.cpp index b770b815c..43ffff0b6 100644 --- a/libsolidity/ArrayUtils.cpp +++ b/libsolidity/ArrayUtils.cpp @@ -38,10 +38,10 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // 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.location() == ReferenceType::Location::Storage, ""); + solAssert(_targetType.location() == DataLocation::Storage, ""); solAssert( - _sourceType.location() == ReferenceType::Location::CallData || - _sourceType.location() == ReferenceType::Location::Storage, + _sourceType.location() == DataLocation::CallData || + _sourceType.location() == DataLocation::Storage, "Given array location not implemented." ); @@ -51,7 +51,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // TODO unroll loop for small sizes - bool sourceIsStorage = _sourceType.location() == ReferenceType::Location::Storage; + bool sourceIsStorage = _sourceType.location() == DataLocation::Storage; bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16; bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16; @@ -69,7 +69,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << eth::Instruction::POP; // stack: target_ref source_ref [source_length] // retrieve source length - if (_sourceType.location() != ReferenceType::Location::CallData || !_sourceType.isDynamicallySized()) + if (_sourceType.location() != DataLocation::CallData || !_sourceType.isDynamicallySized()) retrieveLength(_sourceType); // otherwise, length is already there // stack: target_ref source_ref source_length m_context << eth::Instruction::DUP3; @@ -82,7 +82,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons if (sourceBaseType->getCategory() == Type::Category::Mapping) { solAssert(targetBaseType->getCategory() == Type::Category::Mapping, ""); - solAssert(_sourceType.location() == ReferenceType::Location::Storage, ""); + solAssert(_sourceType.location() == DataLocation::Storage, ""); // nothing to copy m_context << eth::Instruction::POP << eth::Instruction::POP @@ -106,7 +106,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); - if (_sourceType.location() == ReferenceType::Location::Storage && _sourceType.isDynamicallySized()) + if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) CompilerUtils(m_context).computeHashStatic(); // stack: target_ref target_data_end source_length target_data_pos source_data_pos m_context << eth::Instruction::SWAP2; @@ -155,7 +155,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // checking is easier. // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] m_context << eth::dupInstruction(3 + byteOffsetSize); - if (_sourceType.location() == ReferenceType::Location::Storage) + if (_sourceType.location() == DataLocation::Storage) { if (haveByteOffsetSource) m_context << eth::Instruction::DUP2; @@ -231,7 +231,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons void ArrayUtils::clearArray(ArrayType const& _type) const { unsigned stackHeightStart = m_context.getStackHeight(); - solAssert(_type.location() == ReferenceType::Location::Storage, ""); + solAssert(_type.location() == DataLocation::Storage, ""); if (_type.getBaseType()->getStorageBytes() < 32) { solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); @@ -286,7 +286,7 @@ void ArrayUtils::clearArray(ArrayType const& _type) const void ArrayUtils::clearDynamicArray(ArrayType const& _type) const { - solAssert(_type.location() == ReferenceType::Location::Storage, ""); + solAssert(_type.location() == DataLocation::Storage, ""); solAssert(_type.isDynamicallySized(), ""); unsigned stackHeightStart = m_context.getStackHeight(); @@ -314,7 +314,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const { - solAssert(_type.location() == ReferenceType::Location::Storage, ""); + solAssert(_type.location() == DataLocation::Storage, ""); solAssert(_type.isDynamicallySized(), ""); if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32) solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); @@ -399,7 +399,7 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const { - if (_arrayType.location() == ReferenceType::Location::Storage) + if (_arrayType.location() == DataLocation::Storage) { if (_arrayType.getBaseType()->getStorageSize() <= 1) { @@ -437,13 +437,13 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const m_context << eth::Instruction::DUP1; switch (_arrayType.location()) { - case ReferenceType::Location::CallData: + case DataLocation::CallData: // length is stored on the stack break; - case ReferenceType::Location::Memory: + case DataLocation::Memory: m_context << eth::Instruction::MLOAD; break; - case ReferenceType::Location::Storage: + case DataLocation::Storage: m_context << eth::Instruction::SLOAD; break; } @@ -452,16 +452,16 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const void ArrayUtils::accessIndex(ArrayType const& _arrayType) const { - ReferenceType::Location location = _arrayType.location(); + DataLocation location = _arrayType.location(); eth::Instruction load = - location == ReferenceType::Location::Storage ? eth::Instruction::SLOAD : - location == ReferenceType::Location::Memory ? eth::Instruction::MLOAD : + location == DataLocation::Storage ? eth::Instruction::SLOAD : + location == DataLocation::Memory ? eth::Instruction::MLOAD : eth::Instruction::CALLDATALOAD; // retrieve length if (!_arrayType.isDynamicallySized()) m_context << _arrayType.getLength(); - else if (location == ReferenceType::Location::CallData) + else if (location == DataLocation::CallData) // length is stored on the stack m_context << eth::Instruction::SWAP1; else @@ -476,20 +476,20 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const m_context << eth::Instruction::SWAP1; if (_arrayType.isDynamicallySized()) { - if (location == ReferenceType::Location::Storage) + if (location == DataLocation::Storage) CompilerUtils(m_context).computeHashStatic(); - else if (location == ReferenceType::Location::Memory) + else if (location == DataLocation::Memory) m_context << u256(32) << eth::Instruction::ADD; } // stack: switch (location) { - case ReferenceType::Location::CallData: + case DataLocation::CallData: if (!_arrayType.isByteArray()) - m_context - << eth::Instruction::SWAP1 - << _arrayType.getBaseType()->getCalldataEncodedSize() - << eth::Instruction::MUL; + { + m_context << eth::Instruction::SWAP1; + m_context << _arrayType.getBaseType()->getCalldataEncodedSize() << eth::Instruction::MUL; + } m_context << eth::Instruction::ADD; if (_arrayType.getBaseType()->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic( @@ -499,7 +499,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const false ); break; - case ReferenceType::Location::Storage: + case DataLocation::Storage: m_context << eth::Instruction::SWAP1; if (_arrayType.getBaseType()->getStorageBytes() <= 16) { @@ -527,7 +527,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const m_context << eth::Instruction::ADD << u256(0); } break; - case ReferenceType::Location::Memory: + case DataLocation::Memory: solAssert(false, "Memory lvalues not yet implemented."); } } diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 82e98dfff..0b88ed8ae 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -245,21 +245,35 @@ void Compiler::appendCalldataUnpacker( { // We do not check the calldata size, everything is zero-paddedd + //@todo this does not yet support nested arrays + if (_startOffset == u256(-1)) _startOffset = u256(CompilerUtils::dataStartOffset); m_context << _startOffset; for (TypePointer const& type: _typeParameters) { + // stack: v1 v2 ... v(k-1) mem_offset switch (type->getCategory()) { case Type::Category::Array: { auto const& arrayType = dynamic_cast(*type); - if (arrayType.location() == ReferenceType::Location::CallData) + solAssert(arrayType.location() != DataLocation::Storage, ""); + solAssert(!arrayType.getBaseType()->isDynamicallySized(), "Nested arrays not yet implemented."); + if (_fromMemory) + { + solAssert(arrayType.location() == DataLocation::Memory, ""); + // compute data pointer + //@todo once we support nested arrays, this offset needs to be dynamic. + m_context << eth::Instruction::DUP1 << _startOffset << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1 << u256(0x20) << eth::Instruction::ADD; + } + else { - solAssert(!_fromMemory, ""); - if (type->isDynamicallySized()) + // first load from calldata and potentially convert to memory if arrayType is memory + TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); + if (calldataType->isDynamicallySized()) { // put on stack: data_pointer length CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); @@ -276,17 +290,17 @@ void Compiler::appendCalldataUnpacker( { // leave the pointer on the stack m_context << eth::Instruction::DUP1; - m_context << u256(type->getCalldataEncodedSize()) << eth::Instruction::ADD; + m_context << u256(calldataType->getCalldataEncodedSize()) << eth::Instruction::ADD; + } + if (arrayType.location() == DataLocation::Memory) + { + // copy to memory + // move calldata type up again + CompilerUtils(m_context).moveIntoStack(calldataType->getSizeOnStack()); + CompilerUtils(m_context).convertType(*calldataType, arrayType); + // fetch next pointer again + CompilerUtils(m_context).moveToStackTop(arrayType.getSizeOnStack()); } - } - else - { - solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); - // compute data pointer - m_context << eth::Instruction::DUP1 << _startOffset << eth::Instruction::ADD; - if (!_fromMemory) - solAssert(false, "Not yet implemented."); - m_context << eth::Instruction::SWAP1 << u256(0x20) << eth::Instruction::ADD; } break; } diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 349877a29..4d57dc926 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -107,16 +107,18 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound auto const& type = dynamic_cast(_type); solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); - if (type.location() == ReferenceType::Location::CallData) + if (type.location() == DataLocation::CallData) { + if (!type.isDynamicallySized()) + m_context << type.getLength(); // stack: target source_offset source_len m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; - // stack: target source_offset source_len source_len source_offset target + // stack: target source_offset source_len source_len source_offset target m_context << eth::Instruction::CALLDATACOPY; m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; } - else if (type.location() == ReferenceType::Location::Memory) + else if (type.location() == DataLocation::Memory) { // memcpy using the built-in contract ArrayUtils(m_context).retrieveLength(type); @@ -183,7 +185,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } else { - solAssert(type.location() == ReferenceType::Location::Storage, ""); + solAssert(type.location() == DataLocation::Storage, ""); m_context << eth::Instruction::POP; // remove offset, arrays always start new slot m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes @@ -274,10 +276,16 @@ void CompilerUtils::encodeToMemory( else { copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->getSizeOnStack()); - if (targetType->isValueType()) - convertType(*_givenTypes[i], *targetType, true); solAssert(!!targetType, "Externalable type expected."); - storeInMemoryDynamic(*targetType, _padToWordBoundaries); + TypePointer type = targetType; + if ( + _givenTypes[i]->dataStoredIn(DataLocation::Storage) || + _givenTypes[i]->dataStoredIn(DataLocation::CallData) + ) + type = _givenTypes[i]; // delay conversion + else + convertType(*_givenTypes[i], *targetType, true); + storeInMemoryDynamic(*type, _padToWordBoundaries); } stackPos += _givenTypes[i]->getSizeOnStack(); } @@ -304,13 +312,13 @@ void CompilerUtils::encodeToMemory( // stack: ... // copy length to memory m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); - if (arrayType.location() == ReferenceType::Location::CallData) + if (arrayType.location() == DataLocation::CallData) m_context << eth::Instruction::DUP2; // length is on stack - else if (arrayType.location() == ReferenceType::Location::Storage) + else if (arrayType.location() == DataLocation::Storage) m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; else { - solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); + solAssert(arrayType.location() == DataLocation::Memory, ""); m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; } // stack: ... @@ -432,18 +440,18 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp ArrayType const& targetType = dynamic_cast(_targetType); switch (targetType.location()) { - case ReferenceType::Location::Storage: + case DataLocation::Storage: // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. solAssert( targetType.isPointer() && - typeOnStack.location() == ReferenceType::Location::Storage, + typeOnStack.location() == DataLocation::Storage, "Invalid conversion to storage type." ); break; - case ReferenceType::Location::Memory: + case DataLocation::Memory: { // Copy the array to a free position in memory, unless it is already in memory. - if (typeOnStack.location() != ReferenceType::Location::Memory) + if (typeOnStack.location() != DataLocation::Memory) { // stack: (variably sized) unsigned stackSize = typeOnStack.getSizeOnStack(); @@ -452,7 +460,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp // stack: (variably sized) if (targetType.isDynamicallySized()) { - bool fromStorage = (typeOnStack.location() == ReferenceType::Location::Storage); + bool fromStorage = (typeOnStack.location() == DataLocation::Storage); // store length if (fromStorage) { @@ -483,11 +491,25 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp // Stack storeFreeMemoryPointer(); } - else if (typeOnStack.location() == ReferenceType::Location::CallData) + else if (typeOnStack.location() == DataLocation::CallData) { - // Stack: - //@todo - solAssert(false, "Not yet implemented."); + // Stack: [] + // length is present if dynamically sized + fetchFreeMemoryPointer(); + moveIntoStack(typeOnStack.getSizeOnStack()); + // stack: memptr calldataoffset [] + if (typeOnStack.isDynamicallySized()) + { + solAssert(targetType.isDynamicallySized(), ""); + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP2; + storeInMemoryDynamic(IntegerType(256)); + moveIntoStack(typeOnStack.getSizeOnStack()); + } + else + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + // stack: mem_ptr mem_data_ptr calldataoffset [] + storeInMemoryDynamic(typeOnStack); + storeFreeMemoryPointer(); } // nothing to do for memory to memory break; @@ -504,8 +526,8 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp auto& targetType = dynamic_cast(_targetType); auto& stackType = dynamic_cast(_typeOnStack); solAssert( - targetType.location() == ReferenceType::Location::Storage && - stackType.location() == ReferenceType::Location::Storage, + targetType.location() == DataLocation::Storage && + stackType.location() == DataLocation::Storage, "Non-storage structs not yet implemented." ); solAssert( diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index 32dc93a2c..a880f9ee4 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -99,8 +99,9 @@ public: bool _copyDynamicDataInPlace = false ); - /// Appends code for an implicit or explicit type conversion. For now this comprises only erasing - /// higher-order bits (@see appendHighBitCleanup) when widening integer. + /// Appends code for an implicit or explicit type conversion. This includes erasing higher + /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory + /// if a reference type is converted from calldata or storage to memory. /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be /// necessary. void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 12274c7ab..bfb945d8f 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -109,34 +109,40 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& } unsigned retSizeOnStack = 0; solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); + auto const& returnTypes = accessorType.getReturnParameterTypes(); if (StructType const* structType = dynamic_cast(returnType.get())) { // remove offset m_context << eth::Instruction::POP; auto const& names = accessorType.getReturnParameterNames(); - auto const& types = accessorType.getReturnParameterTypes(); // struct for (size_t i = 0; i < names.size(); ++i) { - if (types[i]->getCategory() == Type::Category::Mapping || types[i]->getCategory() == Type::Category::Array) + if (returnTypes[i]->getCategory() == Type::Category::Mapping) continue; + if (auto arrayType = dynamic_cast(returnTypes[i].get())) + if (!arrayType->isByteArray()) + 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 is not yet implemented."); - m_context << eth::Instruction::SWAP1; - retSizeOnStack += types[i]->getSizeOnStack(); + TypePointer memberType = structType->getMemberType(names[i]); + StorageItem(m_context, *memberType).retrieveValue(SourceLocation(), true); + utils().convertType(*memberType, *returnTypes[i]); + utils().moveToStackTop(returnTypes[i]->getSizeOnStack()); + retSizeOnStack += returnTypes[i]->getSizeOnStack(); } // remove slot m_context << eth::Instruction::POP; } else { - // simple value - solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + // simple value or array + solAssert(returnTypes.size() == 1, ""); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); - retSizeOnStack = returnType->getSizeOnStack(); + utils().convertType(*returnType, *returnTypes.front()); + retSizeOnStack = returnTypes.front()->getSizeOnStack(); } + solAssert(retSizeOnStack == utils().getSizeOnStack(returnTypes), ""); solAssert(retSizeOnStack <= 15, "Stack is too deep."); m_context << eth::dupInstruction(retSizeOnStack + 1); m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); @@ -146,10 +152,13 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) { CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.getRightHandSide().accept(*this); - if (_assignment.getType()->isValueType()) - utils().convertType(*_assignment.getRightHandSide().getType(), *_assignment.getType()); - // We need this conversion mostly in the case of compound assignments. For non-value types - // the conversion is done in LValue::storeValue. + TypePointer type = _assignment.getRightHandSide().getType(); + if (!_assignment.getType()->dataStoredIn(DataLocation::Storage)) + { + utils().convertType(*type, *_assignment.getType()); + type = _assignment.getType(); + } + _assignment.getLeftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); @@ -175,7 +184,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; } } - m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); + m_currentLValue->storeValue(*type, _assignment.getLocation()); m_currentLValue.reset(); return false; } @@ -709,10 +718,10 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) else switch (type.location()) { - case ReferenceType::Location::CallData: + case DataLocation::CallData: m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; break; - case ReferenceType::Location::Storage: + case DataLocation::Storage: setLValue(_memberAccess, type); break; default: @@ -755,13 +764,13 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); // remove storage byte offset - if (arrayType.location() == ReferenceType::Location::Storage) + if (arrayType.location() == DataLocation::Storage) m_context << eth::Instruction::POP; _indexAccess.getIndexExpression()->accept(*this); // stack layout: [] ArrayUtils(m_context).accessIndex(arrayType); - if (arrayType.location() == ReferenceType::Location::Storage) + if (arrayType.location() == DataLocation::Storage) { if (arrayType.isByteArray()) { @@ -1119,14 +1128,10 @@ void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { + solAssert(_expectedType.isValueType(), "Not implemented for non-value types."); _expression.accept(*this); - if (_expectedType.isValueType()) - { - utils().convertType(*_expression.getType(), _expectedType, true); - utils().storeInMemoryDynamic(_expectedType); - } - else - utils().storeInMemoryDynamic(*_expression.getType()->mobileType()); + utils().convertType(*_expression.getType(), _expectedType, true); + utils().storeInMemoryDynamic(_expectedType); } void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression) diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index e60797967..87f9da7ee 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -439,7 +439,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) "Location has to be calldata for external functions " "(remove the \"memory\" or \"storage\" keyword)." )); - type = ref->copyForLocation(ReferenceType::Location::CallData, true); + type = ref->copyForLocation(DataLocation::CallData, true); } else if (_variable.isCallableParameter() && _variable.getScope()->isPublic()) { @@ -449,7 +449,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) "Location has to be memory for publicly visible functions " "(remove the \"storage\" keyword)." )); - type = ref->copyForLocation(ReferenceType::Location::Memory, true); + type = ref->copyForLocation(DataLocation::Memory, true); } else { @@ -458,8 +458,8 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) bool isPointer = !_variable.isStateVariable(); type = ref->copyForLocation( loc == Location::Memory ? - ReferenceType::Location::Memory : - ReferenceType::Location::Storage, + DataLocation::Memory : + DataLocation::Storage, isPointer ); } diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index bedd2e7b0..b90b88846 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -144,12 +144,13 @@ TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) else if (_typeToken == Token::Bool) return make_shared(); else if (_typeToken == Token::Bytes) - return make_shared(ReferenceType::Location::Storage); + return make_shared(DataLocation::Storage); else if (_typeToken == Token::String) - return make_shared(ReferenceType::Location::Storage, true); + return make_shared(DataLocation::Storage, true); else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + - std::string(Token::toString(_typeToken)) + " to type.")); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment( + "Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type." + )); } TypePointer Type::fromElementaryTypeName(string const& _name) @@ -180,7 +181,7 @@ TypePointer Type::fromMapping(ElementaryTypeName& _keyType, TypeName& _valueType if (!valueType) BOOST_THROW_EXCEPTION(_valueType.createTypeError("Invalid type name.")); // Convert value type to storage reference. - valueType = ReferenceType::copyForLocationIfReference(ReferenceType::Location::Storage, valueType); + valueType = ReferenceType::copyForLocationIfReference(DataLocation::Storage, valueType); return make_shared(keyType, valueType); } @@ -198,10 +199,10 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length auto const* length = dynamic_cast(_length->getType().get()); if (!length) BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length.")); - return make_shared(ReferenceType::Location::Storage, baseType, length->literalValue(nullptr)); + return make_shared(DataLocation::Storage, baseType, length->literalValue(nullptr)); } else - return make_shared(ReferenceType::Location::Storage, baseType); + return make_shared(DataLocation::Storage, baseType); } TypePointer Type::forLiteral(Literal const& _literal) @@ -670,7 +671,7 @@ TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const return _operator == Token::Delete ? make_shared() : TypePointer(); } -TypePointer ReferenceType::copyForLocationIfReference(Location _location, TypePointer const& _type) +TypePointer ReferenceType::copyForLocationIfReference(DataLocation _location, TypePointer const& _type) { if (auto type = dynamic_cast(_type.get())) return type->copyForLocation(_location, false); @@ -686,11 +687,11 @@ string ReferenceType::stringForReferencePart() const { switch (m_location) { - case Location::Storage: + case DataLocation::Storage: return string("storage ") + (m_isPointer ? "pointer" : "ref"); - case Location::CallData: + case DataLocation::CallData: return "calldata"; - case Location::Memory: + case DataLocation::Memory: return "memory"; } solAssert(false, ""); @@ -705,11 +706,11 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) return false; // memory/calldata to storage can be converted, but only to a direct storage reference - if (convertTo.location() == Location::Storage && location() != Location::Storage && convertTo.isPointer()) + if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) return false; - if (convertTo.location() == Location::CallData && location() != convertTo.location()) + if (convertTo.location() == DataLocation::CallData && location() != convertTo.location()) return false; - if (convertTo.location() == Location::Storage && !convertTo.isPointer()) + if (convertTo.location() == DataLocation::Storage && !convertTo.isPointer()) { // Less restrictive conversion, since we need to copy anyway. if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType())) @@ -788,10 +789,10 @@ u256 ArrayType::getStorageSize() const unsigned ArrayType::getSizeOnStack() const { - if (m_location == Location::CallData) + if (m_location == DataLocation::CallData) // offset [length] (stack top) return 1 + (isDynamicallySized() ? 1 : 0); - else if (m_location == Location::Storage) + else if (m_location == DataLocation::Storage) // storage_key storage_offset return 2; else @@ -828,12 +829,12 @@ TypePointer ArrayType::externalType() const return TypePointer(); if (isDynamicallySized()) - return std::make_shared(Location::CallData, m_baseType->externalType()); + return std::make_shared(DataLocation::CallData, m_baseType->externalType()); else - return std::make_shared(Location::CallData, m_baseType->externalType(), m_length); + return std::make_shared(DataLocation::CallData, m_baseType->externalType(), m_length); } -TypePointer ArrayType::copyForLocation(ReferenceType::Location _location, bool _isPointer) const +TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const { auto copy = make_shared(_location); copy->m_isPointer = _isPointer; @@ -949,9 +950,9 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const return false; auto& convertTo = dynamic_cast(_convertTo); // memory/calldata to storage can be converted, but only to a direct storage reference - if (convertTo.location() == Location::Storage && location() != Location::Storage && convertTo.isPointer()) + if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) return false; - if (convertTo.location() == Location::CallData && location() != convertTo.location()) + if (convertTo.location() == DataLocation::CallData && location() != convertTo.location()) return false; return this->m_struct == convertTo.m_struct; } @@ -1009,7 +1010,7 @@ MemberList const& StructType::getMembers() const return *m_members; } -TypePointer StructType::copyForLocation(ReferenceType::Location _location, bool _isPointer) const +TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const { auto copy = make_shared(m_struct); copy->m_location = _location; @@ -1115,6 +1116,9 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } else if (auto arrayType = dynamic_cast(returnType.get())) { + if (arrayType->isByteArray()) + // Return byte arrays as as whole. + break; returnType = arrayType->getBaseType(); paramNames.push_back(""); paramTypes.push_back(make_shared(256)); @@ -1128,15 +1132,21 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): if (auto structType = dynamic_cast(returnType.get())) { for (auto const& member: structType->getMembers()) - if (member.type->getCategory() != Category::Mapping && member.type->getCategory() != Category::Array) + if (member.type->getCategory() != Category::Mapping) { - retParamNames.push_back(member.name); + if (auto arrayType = dynamic_cast(member.type.get())) + if (!arrayType->isByteArray()) + continue; retParams.push_back(member.type); + retParamNames.push_back(member.name); } } else { - retParams.push_back(returnType); + retParams.push_back(ReferenceType::copyForLocationIfReference( + DataLocation::Memory, + returnType + )); retParamNames.push_back(""); } @@ -1549,7 +1559,7 @@ MagicType::MagicType(MagicType::Kind _kind): {"sender", make_shared(0, IntegerType::Modifier::Address)}, {"gas", make_shared(256)}, {"value", make_shared(256)}, - {"data", make_shared(ReferenceType::Location::CallData)}, + {"data", make_shared(DataLocation::CallData)}, {"sig", make_shared(4)} }); break; diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 0f86ac95f..cca5dde71 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -44,6 +44,8 @@ using FunctionTypePointer = std::shared_ptr; using TypePointers = std::vector; +enum class DataLocation { Storage, CallData, Memory }; + /** * Helper class to compute storage offsets of members of structs and contracts. */ @@ -202,6 +204,9 @@ public: /// This returns the corresponding integer type for IntegerConstantTypes and the pointer type /// for storage reference types. virtual TypePointer mobileType() const { return shared_from_this(); } + /// @returns true if this is a non-value type and the data of this type is stored at the + /// given location. + virtual bool dataStoredIn(DataLocation) const { return false; } /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& getMembers() const { return EmptyMemberList; } @@ -365,15 +370,15 @@ public: class ReferenceType: public Type { public: - enum class Location { Storage, CallData, Memory }; - explicit ReferenceType(Location _location): m_location(_location) {} - Location location() const { return m_location; } + explicit ReferenceType(DataLocation _location): m_location(_location) {} + DataLocation location() const { return m_location; } /// @returns a copy of this type with location (recursively) changed to @a _location, /// whereas isPointer is only shallowly changed - the deep copy is always a bound reference. - virtual TypePointer copyForLocation(Location _location, bool _isPointer) const = 0; + virtual TypePointer copyForLocation(DataLocation _location, bool _isPointer) const = 0; virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } + virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } /// Storage references can be pointers or bound references. In general, local variables are of /// pointer type, state variables are bound references. Assignments to pointers or deleting @@ -389,14 +394,14 @@ public: /// @returns a copy of @a _type having the same location as this (and is not a pointer type) /// if _type is a reference type and an unmodified copy of _type otherwise. /// This function is mostly useful to modify inner types appropriately. - static TypePointer copyForLocationIfReference(Location _location, TypePointer const& _type); + static TypePointer copyForLocationIfReference(DataLocation _location, TypePointer const& _type); protected: TypePointer copyForLocationIfReference(TypePointer const& _type) const; /// @returns a human-readable description of the reference part of the type. std::string stringForReferencePart() const; - Location m_location = Location::Storage; + DataLocation m_location = DataLocation::Storage; bool m_isPointer = true; }; @@ -413,20 +418,20 @@ public: virtual Category getCategory() const override { return Category::Array; } /// Constructor for a byte array ("bytes") and string. - explicit ArrayType(Location _location, bool _isString = false): + explicit ArrayType(DataLocation _location, bool _isString = false): ReferenceType(_location), m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), m_baseType(std::make_shared(1)) { } /// Constructor for a dynamically sized array type ("type[]") - ArrayType(Location _location, TypePointer const& _baseType): + ArrayType(DataLocation _location, TypePointer const& _baseType): ReferenceType(_location), m_baseType(copyForLocationIfReference(_baseType)) { } /// Constructor for a fixed-size array type ("type[20]") - ArrayType(Location _location, TypePointer const& _baseType, u256 const& _length): + ArrayType(DataLocation _location, TypePointer const& _baseType, u256 const& _length): ReferenceType(_location), m_baseType(copyForLocationIfReference(_baseType)), m_hasDynamicLength(false), @@ -454,7 +459,7 @@ public: TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;} u256 const& getLength() const { return m_length; } - TypePointer copyForLocation(Location _location, bool _isPointer) const override; + TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; private: /// String is interpreted as a subtype of Bytes. @@ -533,7 +538,7 @@ public: virtual Category getCategory() const override { return Category::Struct; } explicit StructType(StructDefinition const& _struct): //@todo only storage until we have non-storage structs - ReferenceType(Location::Storage), m_struct(_struct) {} + ReferenceType(DataLocation::Storage), m_struct(_struct) {} virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; @@ -544,7 +549,7 @@ public: virtual MemberList const& getMembers() const override; - TypePointer copyForLocation(Location _location, bool _isPointer) const override; + TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; std::pair const& getStorageOffsetsOfMember(std::string const& _name) const; @@ -636,8 +641,11 @@ public: FunctionTypePointer externalFunctionType() const; virtual TypePointer externalType() const override { return externalFunctionType(); } + /// Creates the type of a function. explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); + /// Creates the accessor function type of a state variable. explicit FunctionType(VariableDeclaration const& _varDecl); + /// Creates the function type of an event. explicit FunctionType(EventDefinition const& _event); FunctionType( strings const& _parameterTypes, diff --git a/libweb3jsonrpc/JsonHelper.h b/libweb3jsonrpc/JsonHelper.h index d445ad0dc..dd868f8de 100644 --- a/libweb3jsonrpc/JsonHelper.h +++ b/libweb3jsonrpc/JsonHelper.h @@ -43,7 +43,7 @@ namespace eth { class Transaction; -class BlockDetails; +struct BlockDetails; class Interface; using Transactions = std::vector; using UncleHashes = h256s; diff --git a/mix/MixClient.h b/mix/MixClient.h index afb9f5430..1172bffbd 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -59,6 +59,7 @@ public: dev::eth::ExecutionResult call(Address const& _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, eth::BlockNumber _blockNumber = eth::PendingBlock, eth::FudgeFactor _ff = eth::FudgeFactor::Strict) override; dev::eth::ExecutionResult create(Address const& _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * eth::szabo, eth::BlockNumber _blockNumber = eth::PendingBlock, eth::FudgeFactor _ff = eth::FudgeFactor::Strict) override; + using ClientBase::submitTransaction; void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, bool _gasAuto); Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice, bool _gasAuto); dev::eth::ExecutionResult call(Address const& _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, eth::BlockNumber _blockNumber, bool _gasAuto, eth::FudgeFactor _ff = eth::FudgeFactor::Strict); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 66a8f8820..95f253d56 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4243,9 +4243,9 @@ BOOST_AUTO_TEST_CASE(return_string) function get1() returns (string r) { return s; } -// function get2() returns (string r) { -// r = s; -// } + function get2() returns (string r) { + r = s; + } } )"; compileAndRun(sourceCode, 0, "Main"); @@ -4253,8 +4253,86 @@ BOOST_AUTO_TEST_CASE(return_string) bytes args = encodeArgs(u256(0x20), u256(s.length()), s); BOOST_REQUIRE(callContractFunction("set(string)", asString(args)) == encodeArgs()); BOOST_CHECK(callContractFunction("get1()") == args); -// BOOST_CHECK(callContractFunction("get2()") == args); -// BOOST_CHECK(callContractFunction("s()") == args); + BOOST_CHECK(callContractFunction("get2()") == args); + BOOST_CHECK(callContractFunction("s()") == args); +} + +BOOST_AUTO_TEST_CASE(return_multiple_strings_of_various_sizes) +{ + char const* sourceCode = R"( + contract Main { + string public s1; + string public s2; + function set(string _s1, uint x, string _s2) external returns (uint) { + s1 = _s1; + s2 = _s2; + return x; + } + function get() returns (string r1, string r2) { + r1 = s1; + r2 = s2; + } + } + )"; + compileAndRun(sourceCode, 0, "Main"); + string s1( + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + ); + string s2( + "ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZ" + "ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZ" + "ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZ" + "ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZ" + "ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZ" + ); + vector lengthes{0, 30, 32, 63, 64, 65, 210, 300}; + for (auto l1: lengthes) + for (auto l2: lengthes) + { + bytes dyn1 = encodeArgs(u256(l1), s1.substr(0, l1)); + bytes dyn2 = encodeArgs(u256(l2), s2.substr(0, l2)); + bytes args = encodeArgs(u256(0x60), u256(l1), u256(0x60 + dyn1.size())) + dyn1 + dyn2; + BOOST_REQUIRE( + callContractFunction("set(string,uint256,string)", asString(args)) == + encodeArgs(u256(l1)) + ); + bytes result = encodeArgs(u256(0x40), u256(0x40 + dyn1.size())) + dyn1 + dyn2; + BOOST_CHECK(callContractFunction("get()") == result); + BOOST_CHECK(callContractFunction("s1()") == encodeArgs(0x20) + dyn1); + BOOST_CHECK(callContractFunction("s2()") == encodeArgs(0x20) + dyn2); + } +} + +BOOST_AUTO_TEST_CASE(accessor_involving_strings) +{ + char const* sourceCode = R"( + contract Main { + struct stringData { string a; uint b; string c; } + mapping(uint => stringData[]) public data; + function set(uint x, uint y, string a, uint b, string c) external returns (bool) { + data[x].length = y + 1; + data[x][y].a = a; + data[x][y].b = b; + data[x][y].c = c; + return true; + } + } + )"; + compileAndRun(sourceCode, 0, "Main"); + string s1("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); + string s2("ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVXYZ"); + bytes s1Data = encodeArgs(u256(s1.length()), s1); + bytes s2Data = encodeArgs(u256(s2.length()), s2); + u256 b = 765; + u256 x = 7; + u256 y = 123; + bytes args = encodeArgs(x, y, u256(0xa0), b, u256(0xa0 + s1Data.size()), s1Data, s2Data); + bytes result = encodeArgs(u256(0x60), b, u256(0x60 + s1Data.size()), s1Data, s2Data); + BOOST_REQUIRE(callContractFunction("set(uint256,uint256,string,uint256,string)", asString(args)) == encodeArgs(true)); + BOOST_REQUIRE(callContractFunction("data(uint256,uint256)", x, y) == result); } BOOST_AUTO_TEST_CASE(storage_array_ref) diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 718798a5a..7892de673 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -77,13 +77,13 @@ BOOST_AUTO_TEST_CASE(storage_layout_mapping) BOOST_AUTO_TEST_CASE(storage_layout_arrays) { - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(1), 32).getStorageSize() == 1); - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(1), 33).getStorageSize() == 2); - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(2), 31).getStorageSize() == 2); - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(7), 8).getStorageSize() == 2); - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(7), 9).getStorageSize() == 3); - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(31), 9).getStorageSize() == 9); - BOOST_CHECK(ArrayType(ReferenceType::Location::Storage, make_shared(32), 9).getStorageSize() == 9); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(1), 32).getStorageSize() == 1); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(1), 33).getStorageSize() == 2); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(2), 31).getStorageSize() == 2); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(7), 8).getStorageSize() == 2); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(7), 9).getStorageSize() == 3); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(31), 9).getStorageSize() == 9); + BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared(32), 9).getStorageSize() == 9); } BOOST_AUTO_TEST_SUITE_END()