Browse Source

Fetch and store packed values.

cl-refactor
chriseth 10 years ago
parent
commit
925acfad80
  1. 11
      libsolidity/Compiler.cpp
  2. 16
      libsolidity/CompilerContext.cpp
  3. 9
      libsolidity/CompilerContext.h
  4. 1
      libsolidity/CompilerUtils.cpp
  5. 24
      libsolidity/ExpressionCompiler.cpp
  6. 119
      libsolidity/LValue.cpp
  7. 99
      libsolidity/Types.cpp
  8. 25
      libsolidity/Types.h
  9. 150
      test/SolidityEndToEndTest.cpp
  10. 2
      test/SolidityOptimizer.cpp

11
libsolidity/Compiler.cpp

@ -274,10 +274,19 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
void Compiler::registerStateVariables(ContractDefinition const& _contract) void Compiler::registerStateVariables(ContractDefinition const& _contract)
{ {
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts()))
for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables()) for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables())
if (!variable->isConstant()) 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) void Compiler::initializeStateVariables(ContractDefinition const& _contract)

16
libsolidity/CompilerContext.cpp

@ -37,15 +37,13 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio
m_magicGlobals.insert(&_declaration); 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; m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
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);
} }
void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::startFunction(Declaration const& _function)
@ -170,7 +168,7 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const
return m_asm.deposit() - _offset - 1; return m_asm.deposit() - _offset - 1;
} }
u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const pair<u256, unsigned> CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const
{ {
auto it = m_stateVariables.find(&_declaration); auto it = m_stateVariables.find(&_declaration);
solAssert(it != m_stateVariables.end(), "Variable not found in storage."); solAssert(it != m_stateVariables.end(), "Variable not found in storage.");

9
libsolidity/CompilerContext.h

@ -24,6 +24,7 @@
#include <ostream> #include <ostream>
#include <stack> #include <stack>
#include <utility>
#include <libevmcore/Instruction.h> #include <libevmcore/Instruction.h>
#include <libevmcore/Assembly.h> #include <libevmcore/Assembly.h>
#include <libsolidity/ASTForward.h> #include <libsolidity/ASTForward.h>
@ -42,7 +43,7 @@ class CompilerContext
{ {
public: public:
void addMagicGlobal(MagicVariableDeclaration const& _declaration); 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 addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(VariableDeclaration const& _declaration); void removeVariable(VariableDeclaration const& _declaration);
void addAndInitializeVariable(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 /// 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. /// with baseToCurrentStackOffset to point to the same stack element.
unsigned currentToBaseStackOffset(unsigned _offset) const; unsigned currentToBaseStackOffset(unsigned _offset) const;
u256 getStorageLocationOfVariable(Declaration const& _declaration) const; std::pair<u256, unsigned> getStorageLocationOfVariable(Declaration const& _declaration) const;
/// Appends a JUMPI instruction to a new tag and @returns the tag /// Appends a JUMPI instruction to a new tag and @returns the tag
eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); }
@ -144,10 +145,8 @@ private:
std::set<Declaration const*> m_magicGlobals; std::set<Declaration const*> m_magicGlobals;
/// Other already compiled contracts to be used in contract creation calls. /// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, bytes const*> m_compiledContracts; std::map<ContractDefinition const*, bytes const*> m_compiledContracts;
/// Size of the state variables, offset of next variable to be added.
u256 m_stateVariablesSize = 0;
/// Storage offsets of state variables /// Storage offsets of state variables
std::map<Declaration const*, u256> m_stateVariables; std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Offsets of local variables on the stack (relative to stack base). /// Offsets of local variables on the stack (relative to stack base).
std::map<Declaration const*, unsigned> m_localVariables; std::map<Declaration const*, unsigned> m_localVariables;
/// Labels pointing to the entry points of functions. /// Labels pointing to the entry points of functions.

1
libsolidity/CompilerUtils.cpp

@ -199,7 +199,6 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
return numBytes; return numBytes;
} }
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
{ {
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);

24
libsolidity/ExpressionCompiler.cpp

@ -67,9 +67,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); length += CompilerUtils(m_context).storeInMemory(length, *paramType, true);
// retrieve the position of the variable // retrieve the position of the variable
m_context << m_context.getStorageLocationOfVariable(_varDecl); auto const& location = m_context.getStorageLocationOfVariable(_varDecl);
TypePointer returnType = _varDecl.getType(); m_context << location.first;
TypePointer returnType = _varDecl.getType();
for (TypePointer const& paramType: paramTypes) for (TypePointer const& paramType: paramTypes)
{ {
// move offset to memory // move offset to memory
@ -81,7 +82,6 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
returnType = dynamic_cast<MappingType const&>(*returnType).getValueType(); returnType = dynamic_cast<MappingType const&>(*returnType).getValueType();
} }
m_context << u256(0); // @todo
unsigned retSizeOnStack = 0; unsigned retSizeOnStack = 0;
solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); solAssert(accessorType.getReturnParameterTypes().size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
@ -93,21 +93,20 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{ {
if (types[i]->getCategory() == Type::Category::Mapping) if (types[i]->getCategory() == Type::Category::Mapping)
continue; continue;
m_context pair<u256, unsigned> const& offsets = structType->getStorageOffsetsOfMember(names[i]);
<< eth::Instruction::DUP2 << structType->getStorageOffsetOfMember(names[i]) m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second);
<< eth::Instruction::ADD;
m_context << u256(0); //@todo
StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true); StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true);
solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented.");
m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
retSizeOnStack += types[i]->getSizeOnStack(); retSizeOnStack += types[i]->getSizeOnStack();
} }
m_context << eth::Instruction::POP << eth::Instruction::POP; m_context << eth::Instruction::POP;
} }
else else
{ {
// simple value // simple value
solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); solAssert(accessorType.getReturnParameterTypes().size() == 1, "");
m_context << u256(location.second);
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
retSizeOnStack = returnType->getSizeOnStack(); retSizeOnStack = returnType->getSizeOnStack();
} }
@ -666,10 +665,9 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
case Type::Category::Struct: case Type::Category::Struct:
{ {
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType()); StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
m_context << eth::Instruction::POP; //@todo m_context << eth::Instruction::POP; // structs always align to new slot
m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; pair<u256, unsigned> const& offsets = type.getStorageOffsetsOfMember(member);
//@todo m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second);
m_context << u256(0);
setLValueToStorageItem(_memberAccess); setLValueToStorageItem(_memberAccess);
break; break;
} }

119
libsolidity/LValue.cpp

@ -77,7 +77,8 @@ void StackVariable::setToZero(SourceLocation const& _location, bool) const
StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration):
StorageItem(_compilerContext, *_declaration.getType()) StorageItem(_compilerContext, *_declaration.getType())
{ {
m_context << m_context.getStorageLocationOfVariable(_declaration) << u256(0); auto const& location = m_context.getStorageLocationOfVariable(_declaration);
m_context << location.first << u256(location.second);
} }
StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
@ -86,7 +87,6 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
if (m_dataType.isValueType()) if (m_dataType.isValueType())
{ {
solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), "");
//@todo the meaning of getStorageSize() probably changes
solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size."); solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size.");
} }
} }
@ -98,12 +98,18 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
return; // no distinction between value and reference for non-value types return; // no distinction between value and reference for non-value types
if (!_remove) if (!_remove)
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
m_context if (m_dataType.getStorageBytes() == 32)
<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 m_context << eth::Instruction::POP << eth::Instruction::SLOAD;
<< u256(2) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; else
//@todo higher order bits might be dirty. Is this bad? {
//@todo this does not work for types that are left-aligned on the stack. m_context
// make those types right-aligned? << 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 void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const
@ -111,12 +117,43 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
// stack: value storage_key storage_offset // stack: value storage_key storage_offset
if (m_dataType.isValueType()) if (m_dataType.isValueType())
{ {
//@todo OR the value into the storage like it is done for ByteArrayElement solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size.");
m_context solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size.");
<< u256(2) << eth::Instruction::EXP << eth::Instruction::DUP3 << eth::Instruction::MUL if (m_dataType.getStorageBytes() == 32)
<< eth::Instruction::SWAP1 << eth::Instruction::SSTORE; {
if (_move) // offset should be zero
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
if (!_move)
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
m_context << eth::Instruction::SSTORE;
}
else
{
// OR the value into the other values in the storage slot
m_context << u256(0x100) << eth::Instruction::EXP;
// stack: value storage_ref multiplier
// fetch old value
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
// stack: value storege_ref multiplier old_full_value
// clear bytes in old value
m_context
<< eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1)
<< eth::Instruction::MUL;
m_context << eth::Instruction::NOT << eth::Instruction::AND;
// stack: value storage_ref multiplier cleared_value
m_context
<< eth::Instruction::SWAP1 << eth::Instruction::DUP4;
// stack: value storage_ref cleared_value multiplier value
if (m_dataType.getCategory() == Type::Category::FixedBytes)
m_context
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(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 else
{ {
@ -134,28 +171,31 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
else if (m_dataType.getCategory() == Type::Category::Struct) else if (m_dataType.getCategory() == Type::Category::Struct)
{ {
// stack layout: source_ref source_offset target_ref target_offset // 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<StructType const&>(m_dataType); auto const& structType = dynamic_cast<StructType const&>(m_dataType);
solAssert(structType == _sourceType, "Struct assignment with conversion."); solAssert(structType == _sourceType, "Struct assignment with conversion.");
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
//@todo actually use offsets
// assign each member that is not a mapping // assign each member that is not a mapping
TypePointer const& memberType = member.second; TypePointer const& memberType = member.second;
if (memberType->getCategory() == Type::Category::Mapping) if (memberType->getCategory() == Type::Category::Mapping)
continue; continue;
m_context << structType.getStorageOffsetOfMember(member.first) pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first);
<< eth::Instruction::DUP5 << eth::Instruction::DUP2 << eth::Instruction::ADD; m_context
m_context << u256(0); // zero offset << offsets.first << u256(offsets.second)
// stack: source_ref source_off target_ref target_off member_offset source_member_ref source_member_off << 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); StorageItem(m_context, *memberType).retrieveValue(_location, true);
// stack: source_ref source_off target_ref target_off member_offset source_value... // stack: source_ref source_off target_ref target_off member_offset source_value...
solAssert(2 + memberType->getSizeOnStack() <= 16, "Stack too deep."); solAssert(4 + memberType->getSizeOnStack() <= 16, "Stack too deep.");
m_context << eth::dupInstruction(3 + memberType->getSizeOnStack()) m_context
<< eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; << eth::dupInstruction(4 + memberType->getSizeOnStack())
// stack: source_ref source_off target_ref target_off member_offset source_value... target_member_ref << eth::dupInstruction(3 + memberType->getSizeOnStack()) << eth::Instruction::ADD
m_context << u256(0); // zero offset << 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); StorageItem(m_context, *memberType).storeValue(*memberType, _location, true);
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP << eth::Instruction::POP;
} }
if (_move) if (_move)
m_context m_context
@ -185,6 +225,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
else if (m_dataType.getCategory() == Type::Category::Struct) else if (m_dataType.getCategory() == Type::Category::Struct)
{ {
// stack layout: storage_key storage_offset // stack layout: storage_key storage_offset
// @todo this can be improved for packed types
auto const& structType = dynamic_cast<StructType const&>(m_dataType); auto const& structType = dynamic_cast<StructType const&>(m_dataType);
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
@ -192,11 +233,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
TypePointer const& memberType = member.second; TypePointer const& memberType = member.second;
if (memberType->getCategory() == Type::Category::Mapping) if (memberType->getCategory() == Type::Category::Mapping)
continue; continue;
// @todo actually use offset pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first);
m_context m_context
<< structType.getStorageOffsetOfMember(member.first) << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::DUP3 << eth::Instruction::ADD; << u256(offsets.second);
m_context << u256(0);
StorageItem(m_context, *memberType).setToZero(); StorageItem(m_context, *memberType).setToZero();
} }
if (_removeReference) if (_removeReference)
@ -208,7 +248,28 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
// @todo actually use offset // @todo actually use offset
if (!_removeReference) if (!_removeReference)
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
m_context << eth::Instruction::POP << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; if (m_dataType.getStorageBytes() == 32)
{
// offset should be zero
m_context
<< eth::Instruction::POP << u256(0)
<< eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
}
else
{
m_context << u256(0x100) << eth::Instruction::EXP;
// stack: storage_ref multiplier
// fetch old value
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
// stack: storege_ref multiplier old_full_value
// clear bytes in old value
m_context
<< eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1)
<< eth::Instruction::MUL;
m_context << eth::Instruction::NOT << eth::Instruction::AND;
// stack: storage_ref cleared_value
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
}
} }
} }

99
libsolidity/Types.cpp

@ -35,54 +35,72 @@ namespace dev
namespace solidity namespace solidity
{ {
std::pair<u256, unsigned> const* MemberList::getMemberStorageOffset(string const& _name) const void StorageOffsets::computeOffsets(TypePointers const& _types)
{ {
if (!m_storageOffsets) bigint slotOffset = 0;
unsigned byteOffset = 0;
map<size_t, pair<u256, unsigned>> offsets;
for (size_t i = 0; i < _types.size(); ++i)
{ {
bigint slotOffset = 0; TypePointer const& type = _types[i];
unsigned byteOffset = 0; if (!type->canBeStored())
map<string, pair<u256, unsigned>> offsets; continue;
for (auto const& nameAndType: m_memberTypes) if (byteOffset + type->getStorageBytes() > 32)
{ {
TypePointer const& type = nameAndType.second; // would overflow, go to next slot
if (!type->canBeStored())
continue;
if (byteOffset + type->getStorageBytes() > 32)
{
// would overflow, go to next slot
++slotOffset;
byteOffset = 0;
}
if (slotOffset >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
offsets[nameAndType.first] = make_pair(u256(slotOffset), byteOffset);
solAssert(type->getStorageSize() >= 1, "Invalid storage size.");
if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32)
byteOffset += type->getStorageBytes();
else
{
slotOffset += type->getStorageSize();
byteOffset = 0;
}
}
if (byteOffset > 0)
++slotOffset; ++slotOffset;
byteOffset = 0;
}
if (slotOffset >= bigint(1) << 256) if (slotOffset >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
m_storageSize = u256(slotOffset); offsets[i] = make_pair(u256(slotOffset), byteOffset);
m_storageOffsets.reset(new decltype(offsets)(move(offsets))); solAssert(type->getStorageSize() >= 1, "Invalid storage size.");
if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32)
byteOffset += type->getStorageBytes();
else
{
slotOffset += type->getStorageSize();
byteOffset = 0;
}
} }
if (m_storageOffsets->count(_name)) if (byteOffset > 0)
return &((*m_storageOffsets)[_name]); ++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<u256, unsigned> const* StorageOffsets::getOffset(size_t _index) const
{
if (m_offsets.count(_index))
return &m_offsets.at(_index);
else else
return nullptr; return nullptr;
} }
std::pair<u256, unsigned> 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 u256 const& MemberList::getStorageSize() const
{ {
// trigger lazy computation // trigger lazy computation
getMemberStorageOffset(""); getMemberStorageOffset("");
return m_storageSize; return m_storageOffsets->getStorageSize();
} }
TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
@ -830,18 +848,11 @@ MemberList const& StructType::getMembers() const
return *m_members; return *m_members;
} }
u256 StructType::getStorageOffsetOfMember(string const& _name) const pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const
{ {
auto const* offsets = getMembers().getMemberStorageOffset(_name);
//@todo cache member offset? solAssert(offsets, "Storage offset of non-existing member requested.");
u256 offset; return *offsets;
for (ASTPointer<VariableDeclaration> 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."));
} }
TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const

25
libsolidity/Types.h

@ -43,6 +43,26 @@ using TypePointer = std::shared_ptr<Type const>;
using FunctionTypePointer = std::shared_ptr<FunctionType const>; using FunctionTypePointer = std::shared_ptr<FunctionType const>;
using TypePointers = std::vector<TypePointer>; using TypePointers = std::vector<TypePointer>;
/**
* 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<u256, unsigned> 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<size_t, std::pair<u256, unsigned>> m_offsets;
};
/** /**
* List of members of a type. * List of members of a type.
*/ */
@ -71,8 +91,7 @@ public:
private: private:
MemberMap m_memberTypes; MemberMap m_memberTypes;
mutable u256 m_storageSize = 0; mutable std::unique_ptr<StorageOffsets> m_storageOffsets;
mutable std::unique_ptr<std::map<std::string, std::pair<u256, unsigned>>> m_storageOffsets;
}; };
/** /**
@ -411,7 +430,7 @@ public:
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
u256 getStorageOffsetOfMember(std::string const& _name) const; std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const;
private: private:
StructDefinition const& m_struct; StructDefinition const& m_struct;

150
test/SolidityEndToEndTest.cpp

@ -991,18 +991,20 @@ BOOST_AUTO_TEST_CASE(multiple_elementary_accessors)
BOOST_AUTO_TEST_CASE(complex_accessors) BOOST_AUTO_TEST_CASE(complex_accessors)
{ {
char const* sourceCode = "contract test {\n" char const* sourceCode = R"(
" mapping(uint256 => bytes4) public to_string_map;\n" contract test {
" mapping(uint256 => bool) public to_bool_map;\n" mapping(uint256 => bytes4) public to_string_map;
" mapping(uint256 => uint256) public to_uint_map;\n" mapping(uint256 => bool) public to_bool_map;
" mapping(uint256 => mapping(uint256 => uint256)) public to_multiple_map;\n" mapping(uint256 => uint256) public to_uint_map;
" function test() {\n" mapping(uint256 => mapping(uint256 => uint256)) public to_multiple_map;
" to_string_map[42] = \"24\";\n" function test() {
" to_bool_map[42] = false;\n" to_string_map[42] = "24";
" to_uint_map[42] = 12;\n" to_bool_map[42] = false;
" to_multiple_map[42][23] = 31;\n" to_uint_map[42] = 12;
" }\n" to_multiple_map[42][23] = 31;
"}\n"; }
}
)";
compileAndRun(sourceCode); compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("to_string_map(uint256)", 42) == encodeArgs("24")); BOOST_CHECK(callContractFunction("to_string_map(uint256)", 42) == encodeArgs("24"));
BOOST_CHECK(callContractFunction("to_bool_map(uint256)", 42) == encodeArgs(false)); BOOST_CHECK(callContractFunction("to_bool_map(uint256)", 42) == encodeArgs(false));
@ -3269,6 +3271,130 @@ BOOST_AUTO_TEST_CASE(constant_variables)
})"; })";
compileAndRun(sourceCode); 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() BOOST_AUTO_TEST_SUITE_END()
} }

2
test/SolidityOptimizer.cpp

@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(unused_expressions)
data; data;
} }
})"; })";
compileBothVersions(29, sourceCode); compileBothVersions(36, sourceCode);
compareVersions("f()"); compareVersions("f()");
} }

Loading…
Cancel
Save