Implementing struct copying from calldata to storage

This commit is contained in:
Djordje Mijovic 2020-08-17 14:47:54 +02:00
parent 762e3f3cee
commit 23f6369a46
4 changed files with 50 additions and 46 deletions

View File

@ -2,7 +2,7 @@
Language Features:
* Allow function definitions outside of contracts, behaving much like internal library functions.
* Code generator: Implementing copying structs from calldata to storage.
Compiler Features:
* SMTChecker: Add underflow and overflow as verification conditions in the CHC engine.

View File

@ -357,38 +357,47 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
"Struct assignment with conversion."
);
solAssert(!structType.containsNestedMapping(), "");
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
for (auto const& member: structType.members(nullptr))
if (sourceType.location() == DataLocation::CallData)
{
// assign each member that can live outside of storage
TypePointer const& memberType = member.type;
solAssert(memberType->nameable(), "");
TypePointer sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)
solAssert(sourceType.sizeOnStack() == 1, "");
solAssert(structType.sizeOnStack() == 1, "");
m_context << Instruction::DUP2 << Instruction::DUP2;
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(structType, &sourceType, 0), 2, 0);
}
else
{
for (auto const& member: structType.members(nullptr))
{
// stack layout: source_ref target_ref
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
// assign each member that can live outside of storage
TypePointer const& memberType = member.type;
solAssert(memberType->nameable(), "");
TypePointer sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)
{
// stack layout: source_ref target_ref
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref source_member_ref source_member_off
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack: source_ref target_ref source_value...
}
else
{
solAssert(sourceType.location() == DataLocation::Memory, "");
// stack layout: source_ref target_ref
m_context << sourceType.memoryOffsetOfMember(member.name);
m_context << Instruction::DUP3 << Instruction::ADD;
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack layout: source_ref target_ref source_value...
}
unsigned stackSize = sourceMemberType->sizeOnStack();
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref source_member_ref source_member_off
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack: source_ref target_ref source_value...
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
}
else
{
solAssert(sourceType.location() == DataLocation::Memory, "");
// stack layout: source_ref target_ref
m_context << sourceType.memoryOffsetOfMember(member.name);
m_context << Instruction::DUP3 << Instruction::ADD;
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack layout: source_ref target_ref source_value...
}
unsigned stackSize = sourceMemberType->sizeOnStack();
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
}
// stack layout: source_ref target_ref
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");

View File

@ -1553,11 +1553,6 @@ string YulUtilFunctions::updateStorageValueFunction(
Whiskers templ(R"(
function <functionName>(slot, value) {
<?fromCalldata>
let valueMem := <convertToMemory>(value)
<!fromCalldata>
let valueMem := value
</fromCalldata>
<#member>
{
<updateMemberCall>
@ -1566,12 +1561,6 @@ string YulUtilFunctions::updateStorageValueFunction(
}
)");
templ("functionName", functionName);
templ("fromCalldata", fromStructType.location() == DataLocation::CallData);
if (fromStructType.location() == DataLocation::CallData)
templ("convertToMemory", conversionFunction(
fromStructType,
*TypeProvider::structType(toStructType.structDefinition(), DataLocation::Memory)
));
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
MemberList::MemberMap fromStructMembers = fromStructType.nativeMembers(nullptr);
@ -1581,9 +1570,10 @@ string YulUtilFunctions::updateStorageValueFunction(
{
solAssert(toStructMembers[i].type->memoryHeadSize() == 32, "");
bool isStruct = toStructMembers[i].type->category() == Type::Category::Struct;
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(toStructMembers[i].name);
memberParams[i]["updateMemberCall"] = Whiskers(R"(
let memberValue := <loadFromMemory>(add(valueMem, <memberMemoryOffset>))
let memberValue := <loadFromMemoryOrCalldata>(add(value, <memberOffset>))
<updateMember>(add(slot, <memberStorageSlotDiff>), <?hasOffset><memberStorageOffset>,</hasOffset> memberValue)
)")
("hasOffset", !isStruct)
@ -1595,8 +1585,12 @@ string YulUtilFunctions::updateStorageValueFunction(
)
("memberStorageSlotDiff", slotDiff.str())
("memberStorageOffset", to_string(offset))
("memberMemoryOffset", fromStructType.memoryOffsetOfMember(fromStructMembers[i].name).str())
("loadFromMemory", readFromMemory(*fromStructMembers[i].type))
("memberOffset",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(fromStructMembers[i].name)) :
fromStructType.memoryOffsetOfMember(fromStructMembers[i].name).str()
)
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*fromStructMembers[i].type, fromCalldata))
.render();
}
templ("member", memberParams);

View File

@ -3,10 +3,11 @@ pragma experimental ABIEncoderV2;
contract C {
struct S {
uint256 a;
uint256 b;
uint64 b;
bytes2 c;
}
uint[153] r;
S s;
function f(uint32 a, S calldata c, uint256 b) external returns (uint256, uint256, byte) {
@ -16,6 +17,6 @@ contract C {
}
// ====
// compileViaYul: true
// compileViaYul: also
// ----
// f(uint32, (uint256, uint256, bytes2), uint256): 1, 42, 23, "ab", 1 -> 42, 23, "b"
// f(uint32, (uint256, uint64, bytes2), uint256): 1, 42, 23, "ab", 1 -> 42, 23, "b"