diff --git a/ArrayUtils.cpp b/ArrayUtils.cpp index 0dea5345c..f0d7d6a81 100644 --- a/ArrayUtils.cpp +++ b/ArrayUtils.cpp @@ -37,140 +37,120 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack layout: [source_ref] target_ref (top) // need to leave target_ref on the stack at the end solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); + solAssert( + _sourceType.getLocation() == ArrayType::Location::CallData || + _sourceType.getLocation() == ArrayType::Location::Storage, + "Given array location not implemented." + ); IntegerType uint256(256); Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType()); Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType()); - switch (_sourceType.getLocation()) + // 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 + // store target_ref + for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); + if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) + retrieveLength(_sourceType); // otherwise, length is already there + // stack: target_ref source_ref source_length + m_context << eth::Instruction::DUP3; + // stack: target_ref source_ref source_length target_ref + retrieveLength(_targetType); + // stack: target_ref source_ref source_length target_ref target_length + if (_targetType.isDynamicallySized()) + // store new target length + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + if (sourceBaseType->getCategory() == Type::Category::Mapping) { - case ArrayType::Location::CallData: - { - solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here."); - solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here."); - // This also assumes that after "length" we only have zeros, i.e. it cannot be used to - // slice a byte array from calldata. - - // stack: source_offset source_len target_ref - // fetch old length and convert to words - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - convertLengthToSize(_targetType); - // stack here: source_offset source_len target_ref target_length_words - // actual array data is stored at SHA3(storage_offset) - m_context << eth::Instruction::DUP2; - CompilerUtils(m_context).computeHashStatic(); - // compute target_data_end - m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD - << eth::Instruction::SWAP1; - // stack here: source_offset source_len target_ref target_data_end target_data_ref - // store length (in bytes) - m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5 - << eth::Instruction::SSTORE; - // jump to end if length is zero - m_context << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); - // add length to source offset - m_context << eth::Instruction::DUP5 << eth::Instruction::DUP5 << eth::Instruction::ADD; - // stack now: source_offset source_len target_ref target_data_end target_data_ref source_end - // store start offset - m_context << eth::Instruction::DUP6; - // stack now: source_offset source_len target_ref target_data_end target_data_ref source_end calldata_offset - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart - // copy from calldata and store - << eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD - << eth::Instruction::DUP4 << eth::Instruction::SSTORE - // increment target_data_ref by 1 - << eth::Instruction::SWAP2 << u256(1) << eth::Instruction::ADD - // increment calldata_offset by 32 - << eth::Instruction::SWAP2 << u256(32) << eth::Instruction::ADD - // check for loop condition - << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::GT; - m_context.appendConditionalJumpTo(copyLoopStart); - m_context << eth::Instruction::POP << eth::Instruction::POP; - m_context << copyLoopEnd; - - // now clear leftover bytes of the old value - // stack now: source_offset source_len target_ref target_data_end target_data_ref - clearStorageLoop(IntegerType(256)); - // stack now: source_offset source_len target_ref target_data_end - - m_context << eth::Instruction::POP << eth::Instruction::SWAP2 + solAssert(targetBaseType->getCategory() == Type::Category::Mapping, ""); + solAssert(_sourceType.getLocation() == ArrayType::Location::Storage, ""); + // nothing to copy + m_context + << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; - break; + return; } - case ArrayType::Location::Storage: + // compute hashes (data positions) + m_context << eth::Instruction::SWAP1; + if (_targetType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref source_ref source_length target_length target_data_pos + m_context << eth::Instruction::SWAP1; + convertLengthToSize(_targetType); + m_context << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: target_ref source_ref source_length target_data_pos target_data_end + m_context << eth::Instruction::SWAP3; + // stack: target_ref target_data_end source_length target_data_pos source_ref + // skip copying if source length is zero + m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + + if (_sourceType.getLocation() == ArrayType::Location::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; + convertLengthToSize(_sourceType); + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart; + // check for loop condition + m_context + << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::Instruction::GT << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(copyLoopEnd); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + // copy + if (sourceBaseType->getCategory() == Type::Category::Array) { - // this copies source to target and also clears target if it was larger - - solAssert(sourceBaseType->getStorageSize() == targetBaseType->getStorageSize(), - "Copying with different storage sizes not yet implemented."); - // stack: source_ref target_ref - // store target_ref - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - // stack: target_ref source_ref target_ref - // fetch lengthes - retrieveLength(_targetType); - m_context << eth::Instruction::SWAP2; - // stack: target_ref target_len target_ref source_ref - retrieveLength(_sourceType); - // stack: target_ref target_len target_ref source_ref source_len - if (_targetType.isDynamicallySized()) - // store new target length - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; - // compute hashes (data positions) - m_context << eth::Instruction::SWAP2; - if (_targetType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - m_context << eth::Instruction::SWAP1; - if (_sourceType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref target_len source_len target_data_pos source_data_pos - m_context << eth::Instruction::DUP4; - convertLengthToSize(_sourceType); - m_context << eth::Instruction::DUP4; - convertLengthToSize(_sourceType); - // stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size - // @todo we might be able to go without a third counter - m_context << u256(0); - // stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size counter - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart; - // check for loop condition - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::GT << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); - // copy - m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD; - StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); - m_context << eth::dupInstruction(5 + sourceBaseType->getSizeOnStack()) - << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()) << eth::Instruction::ADD; - StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); - // increment - m_context << targetBaseType->getStorageSize() << eth::Instruction::ADD; - m_context.appendJumpTo(copyLoopStart); - m_context << copyLoopEnd; - - // zero-out leftovers in target - // stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size counter - // add counter to target_data_pos - m_context << eth::Instruction::DUP5 << eth::Instruction::ADD - << eth::Instruction::SWAP5 << eth::Instruction::POP; - // stack: target_ref target_len target_data_pos_updated target_data_pos source_data_pos target_size source_size - // add size to target_data_pos to get target_data_end - m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SWAP4 - << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - clearStorageLoop(*targetBaseType); + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; + copyArrayToStorage( + dynamic_cast(*targetBaseType), + dynamic_cast(*sourceBaseType) + ); m_context << eth::Instruction::POP; - break; } - default: - solAssert(false, "Given byte array location not implemented."); + else + { + m_context << eth::Instruction::DUP3; + if (_sourceType.getLocation() == ArrayType::Location::Storage) + 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()); + m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()); + StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); } + // increment source + m_context + << eth::Instruction::SWAP2 + << (_sourceType.getLocation() == ArrayType::Location::Storage ? + sourceBaseType->getStorageSize() : + sourceBaseType->getCalldataEncodedSize()) + << eth::Instruction::ADD + << eth::Instruction::SWAP2; + // increment target + m_context + << eth::Instruction::SWAP1 + << targetBaseType->getStorageSize() + << eth::Instruction::ADD + << eth::Instruction::SWAP1; + m_context.appendJumpTo(copyLoopStart); + m_context << copyLoopEnd; + + // zero-out leftovers in target + // stack: target_ref target_data_end source_data_pos target_data_pos 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; } void ArrayUtils::clearArray(ArrayType const& _type) const @@ -178,7 +158,7 @@ void ArrayUtils::clearArray(ArrayType const& _type) const solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); if (_type.isDynamicallySized()) clearDynamicArray(_type); - else if (_type.getLength() == 0) + else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) m_context << eth::Instruction::POP; else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value { @@ -272,6 +252,11 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const void ArrayUtils::clearStorageLoop(Type const& _type) const { + if (_type.getCategory() == Type::Category::Mapping) + { + m_context << eth::Instruction::POP; + return; + } // stack: end_pos pos eth::AssemblyItem loopStart = m_context.newTag(); m_context << loopStart; @@ -290,13 +275,25 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const m_context << eth::Instruction::POP; } -void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType) const +void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const { - if (_arrayType.isByteArray()) - m_context << u256(31) << eth::Instruction::ADD - << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - else if (_arrayType.getBaseType()->getStorageSize() > 1) - m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; + if (_arrayType.getLocation() == ArrayType::Location::Storage) + { + if (_arrayType.isByteArray()) + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + else if (_arrayType.getBaseType()->getStorageSize() > 1) + m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; + } + else + { + if (!_arrayType.isByteArray()) + m_context << _arrayType.getBaseType()->getCalldataEncodedSize() << eth::Instruction::MUL; + else if (_pad) + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::DUP1 + << eth::Instruction::SWAP2 << eth::Instruction::DIV << eth::Instruction::MUL; + } } void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const diff --git a/ArrayUtils.h b/ArrayUtils.h index 73e88340e..31cca8173 100644 --- a/ArrayUtils.h +++ b/ArrayUtils.h @@ -60,10 +60,11 @@ public: /// Stack pre: end_ref start_ref /// Stack post: end_ref void clearStorageLoop(Type const& _type) const; - /// Converts length to size (multiplies by size on stack), rounds up for byte arrays. + /// Converts length to size (number of storage slots or calldata/memory bytes). + /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. /// Stack pre: length /// Stack post: size - void convertLengthToSize(ArrayType const& _arrayType) const; + 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 diff --git a/Compiler.cpp b/Compiler.cpp index c880d49d1..241cf0402 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -150,7 +150,7 @@ void Compiler::appendConstructor(FunctionDefinition const& _constructor) // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program unsigned argumentSize = 0; for (ASTPointer const& var: _constructor.getParameters()) - argumentSize += CompilerUtils::getPaddedSize(var->getType()->getCalldataEncodedSize()); + argumentSize += var->getType()->getCalldataEncodedSize(); if (argumentSize > 0) { @@ -208,8 +208,7 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool bigint parameterHeadEnd = offset; for (TypePointer const& type: _typeParameters) - parameterHeadEnd += type->isDynamicallySized() ? 32 : - CompilerUtils::getPaddedSize(type->getCalldataEncodedSize()); + parameterHeadEnd += type->isDynamicallySized() ? 32 : type->getCalldataEncodedSize(); solAssert(parameterHeadEnd <= numeric_limits::max(), "Arguments too large."); unsigned stackHeightOfPreviousDynamicArgument = 0; @@ -228,8 +227,8 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool // Retrieve data start offset by adding length to start offset of previous dynamic type unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument; m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth); - ArrayUtils(m_context).convertLengthToSize(*previousDynamicType); - m_context << u256(32) << eth::Instruction::MUL << eth::Instruction::ADD; + ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true); + m_context << eth::Instruction::ADD; } else m_context << u256(parameterHeadEnd); @@ -240,7 +239,7 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool else { m_context << u256(offset); - offset += CompilerUtils::getPaddedSize(type->getCalldataEncodedSize()); + offset += type->getCalldataEncodedSize(); } break; default: diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp index 7f8f72ca7..8a26b5d17 100644 --- a/CompilerUtils.cpp +++ b/CompilerUtils.cpp @@ -92,7 +92,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } else { - solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory byte arrays not yet implemented."); + solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented."); m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes // jump to end if length is zero @@ -177,8 +177,7 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { - unsigned _encodedSize = _type.getCalldataEncodedSize(); - unsigned numBytes = _padToWordBoundaries ? getPaddedSize(_encodedSize) : _encodedSize; + unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); bool leftAligned = _type.getCategory() == Type::Category::String; if (numBytes == 0) m_context << eth::Instruction::POP << u256(0); @@ -202,8 +201,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { - unsigned _encodedSize = _type.getCalldataEncodedSize(); - unsigned numBytes = _padToWordBoundaries ? getPaddedSize(_encodedSize) : _encodedSize; + unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); bool leftAligned = _type.getCategory() == Type::Category::String; if (numBytes == 0) m_context << eth::Instruction::POP; diff --git a/CompilerUtils.h b/CompilerUtils.h index 24ebbc81f..043de41dd 100644 --- a/CompilerUtils.h +++ b/CompilerUtils.h @@ -71,9 +71,6 @@ public: /// Stack pre: memory_offset value... /// Stack post: (memory_offset+length) void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); - /// @returns _size rounded up to the next multiple of 32 (the number of bytes occupied in the - /// padded calldata) - static unsigned getPaddedSize(unsigned _size) { return ((_size + 31) / 32) * 32; } /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index adaaff23e..6b7978872 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -74,7 +74,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // move offset to memory CompilerUtils(m_context).storeInMemory(length); - unsigned argLen = CompilerUtils::getPaddedSize(paramType->getCalldataEncodedSize()); + unsigned argLen = paramType->getCalldataEncodedSize(); length -= argLen; m_context << u256(argLen + 32) << u256(length) << eth::Instruction::SHA3; @@ -782,8 +782,9 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) else { u256 elementSize = - location == ArrayType::Location::Storage ? arrayType.getBaseType()->getStorageSize() : - CompilerUtils::getPaddedSize(arrayType.getBaseType()->getCalldataEncodedSize()); + location == ArrayType::Location::Storage ? + arrayType.getBaseType()->getStorageSize() : + arrayType.getBaseType()->getCalldataEncodedSize(); solAssert(elementSize != 0, "Invalid element size."); if (elementSize > 1) m_context << elementSize << eth::Instruction::MUL; @@ -801,8 +802,8 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) switch (location) { case ArrayType::Location::CallData: - // no lvalue - CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false); + if (arrayType.getBaseType()->isValueType()) + CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false); break; case ArrayType::Location::Storage: setLValueToStorageItem(_indexAccess); @@ -1021,7 +1022,7 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio //@todo only return the first return value for now Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : _functionType.getReturnParameterTypes().front().get(); - unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0; + unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; m_context << u256(retSize) << u256(0); if (bare) @@ -1051,8 +1052,8 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio if (_functionType.gasSet()) m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); else - // send all gas except for the 21 needed to execute "SUB" and "CALL" - m_context << u256(_functionType.valueSet() ? 6741 : 41) << eth::Instruction::GAS << eth::Instruction::SUB; + // send all gas except for the 41 / 6741 needed to execute "SUB" and "CALL" + m_context << u256(41 + (_functionType.valueSet() ? 6700 : 0)) << eth::Instruction::GAS << eth::Instruction::SUB; m_context << eth::Instruction::CALL; auto tag = m_context.appendConditionalJump(); m_context << eth::Instruction::STOP << tag; // STOP if CALL leaves 0. @@ -1083,7 +1084,7 @@ void ExpressionCompiler::appendArgumentsCopyToMemory( appendTypeConversion(*_arguments[i]->getType(), *expectedType, true); bool pad = _padToWordBoundaries; // Do not pad if the first argument has exactly four bytes - if (i == 0 && pad && _padExceptionIfFourBytes && expectedType->getCalldataEncodedSize() == 4) + if (i == 0 && pad && _padExceptionIfFourBytes && expectedType->getCalldataEncodedSize(false) == 4) pad = false; appendTypeMoveToMemory(*expectedType, pad); } diff --git a/LValue.cpp b/LValue.cpp index 7a81ff927..a036be80b 100644 --- a/LValue.cpp +++ b/LValue.cpp @@ -212,6 +212,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const } 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) diff --git a/Types.cpp b/Types.cpp index 0b8259bfe..454d79d9b 100644 --- a/Types.cpp +++ b/Types.cpp @@ -589,11 +589,12 @@ bool ArrayType::operator==(Type const& _other) const return isDynamicallySized() || getLength() == other.getLength(); } -unsigned ArrayType::getCalldataEncodedSize() const +unsigned ArrayType::getCalldataEncodedSize(bool _padded) const { if (isDynamicallySized()) return 0; - bigint size = bigint(getLength()) * (isByteArray() ? 1 : getBaseType()->getCalldataEncodedSize()); + bigint size = bigint(getLength()) * (isByteArray() ? 1 : getBaseType()->getCalldataEncodedSize(_padded)); + size = ((size + 31) / 32) * 32; solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); return unsigned(size); } diff --git a/Types.h b/Types.h index afecf3c8e..6cef8d64a 100644 --- a/Types.h +++ b/Types.h @@ -120,8 +120,10 @@ public: /// @returns number of bytes used by this type when encoded for CALL, or 0 if the encoding /// is not a simple big-endian encoding or the type cannot be stored in calldata. - /// Note that irrespective of this size, each calldata element is padded to a multiple of 32 bytes. - virtual unsigned getCalldataEncodedSize() const { return 0; } + /// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes. + virtual unsigned getCalldataEncodedSize(bool _padded) const { (void)_padded; return 0; } + /// Convenience version of @see getCalldataEncodedSize(bool) + 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. @@ -176,7 +178,7 @@ public: virtual bool operator==(Type const& _other) const override; - virtual unsigned getCalldataEncodedSize() const override { return m_bits / 8; } + virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } virtual bool isValueType() const override { return true; } virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } @@ -247,7 +249,7 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool operator==(Type const& _other) const override; - virtual unsigned getCalldataEncodedSize() const override { return m_bytes; } + virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "string" + dev::toString(m_bytes); } @@ -271,7 +273,7 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; - virtual unsigned getCalldataEncodedSize() const { return 1; } + virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } @@ -302,7 +304,7 @@ public: virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(const Type& _other) const override; - virtual unsigned getCalldataEncodedSize() const override; + virtual unsigned getCalldataEncodedSize(bool _padded) const override; virtual bool isDynamicallySized() const { return m_hasDynamicLength; } virtual u256 getStorageSize() const override; virtual unsigned getSizeOnStack() const override;