mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6209 from ethereum/dynamicCalldataStructs
Allow dynamically encoded calldata structs with ABIEncoderV2.
This commit is contained in:
commit
6683ec90ca
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
* Allow calldata arrays with dynamically encoded base types with ABIEncoderV2.
|
* Allow calldata arrays with dynamically encoded base types with ABIEncoderV2.
|
||||||
|
* Allow dynamically encoded calldata structs with ABIEncoderV2.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
|
@ -371,18 +371,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
|||||||
};
|
};
|
||||||
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
|
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
|
||||||
{
|
{
|
||||||
TypePointer baseType = type(*var);
|
|
||||||
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
|
||||||
baseType = arrayType->baseType();
|
|
||||||
|
|
||||||
if (
|
|
||||||
!m_scope->isInterface() &&
|
|
||||||
baseType->dataStoredIn(DataLocation::CallData)
|
|
||||||
)
|
|
||||||
if (auto const* structType = dynamic_cast<StructType const*>(baseType.get()))
|
|
||||||
if (structType->isDynamicallyEncoded())
|
|
||||||
m_errorReporter.typeError(var->location(), "Dynamically encoded calldata structs are not yet supported.");
|
|
||||||
|
|
||||||
checkArgumentAndReturnParameter(*var);
|
checkArgumentAndReturnParameter(*var);
|
||||||
var->accept(*this);
|
var->accept(*this);
|
||||||
}
|
}
|
||||||
|
@ -1212,7 +1212,6 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
|
|||||||
if (structType->dataStoredIn(DataLocation::CallData))
|
if (structType->dataStoredIn(DataLocation::CallData))
|
||||||
{
|
{
|
||||||
solAssert(!_fromMemory, "");
|
solAssert(!_fromMemory, "");
|
||||||
solUnimplementedAssert(!structType->isDynamicallyEncoded(), "Dynamically encoded calldata structs are not yet implemented.");
|
|
||||||
return abiDecodingFunctionCalldataStruct(*structType);
|
return abiDecodingFunctionCalldataStruct(*structType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -97,6 +97,53 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
|
|||||||
m_context << Instruction::REVERT;
|
m_context << Instruction::REVERT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||||
|
{
|
||||||
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||||
|
|
||||||
|
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||||
|
solAssert(baseEncodedSize > 1, "");
|
||||||
|
|
||||||
|
// returns the absolute offset of the tail in "base_ref"
|
||||||
|
m_context.appendInlineAssembly(Whiskers(R"({
|
||||||
|
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
||||||
|
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
||||||
|
base_ref := add(base_ref, rel_offset_of_tail)
|
||||||
|
})")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||||
|
// stack layout: <absolute_offset_of_tail> <garbage>
|
||||||
|
|
||||||
|
if (!_type.isDynamicallySized())
|
||||||
|
{
|
||||||
|
m_context << Instruction::POP;
|
||||||
|
// stack layout: <absolute_offset_of_tail>
|
||||||
|
solAssert(
|
||||||
|
_type.category() == Type::Category::Struct ||
|
||||||
|
_type.category() == Type::Category::Array,
|
||||||
|
"Invalid dynamically encoded base type on tail access."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||||
|
solAssert(!!arrayType, "Invalid dynamically sized type.");
|
||||||
|
unsigned int calldataStride = arrayType->calldataStride();
|
||||||
|
solAssert(calldataStride > 0, "");
|
||||||
|
|
||||||
|
// returns the absolute offset of the tail in "base_ref"
|
||||||
|
// and the length of the tail in "length"
|
||||||
|
m_context.appendInlineAssembly(
|
||||||
|
Whiskers(R"({
|
||||||
|
length := calldataload(base_ref)
|
||||||
|
base_ref := add(base_ref, 0x20)
|
||||||
|
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||||
|
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||||
|
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
|
||||||
|
{"base_ref", "length"}
|
||||||
|
);
|
||||||
|
// stack layout: <absolute_offset_of_tail> <length>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsigned CompilerUtils::loadFromMemory(
|
unsigned CompilerUtils::loadFromMemory(
|
||||||
unsigned _offset,
|
unsigned _offset,
|
||||||
Type const& _type,
|
Type const& _type,
|
||||||
|
@ -65,6 +65,13 @@ public:
|
|||||||
/// Stack post:
|
/// Stack post:
|
||||||
void revertWithStringData(Type const& _argumentType);
|
void revertWithStringData(Type const& _argumentType);
|
||||||
|
|
||||||
|
/// Computes the absolute calldata offset of a tail given a base reference and the (absolute)
|
||||||
|
/// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also
|
||||||
|
/// returns the array length on the stack.
|
||||||
|
/// Stack pre: base_ref tail_ptr
|
||||||
|
/// Stack post: tail_ref [length]
|
||||||
|
void accessCalldataTail(Type const& _type);
|
||||||
|
|
||||||
/// Loads data from memory to the stack.
|
/// Loads data from memory to the stack.
|
||||||
/// @param _offset offset in memory (or calldata)
|
/// @param _offset offset in memory (or calldata)
|
||||||
/// @param _type data type to load
|
/// @param _type data type to load
|
||||||
|
@ -1424,20 +1424,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
|||||||
}
|
}
|
||||||
case DataLocation::CallData:
|
case DataLocation::CallData:
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(!type.isDynamicallyEncoded(), "");
|
if (_memberAccess.annotation().type->isDynamicallyEncoded())
|
||||||
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
|
||||||
// For non-value types the calldata offset is returned directly.
|
|
||||||
if (_memberAccess.annotation().type->isValueType())
|
|
||||||
{
|
{
|
||||||
solAssert(_memberAccess.annotation().type->calldataEncodedSize(false) > 0, "");
|
m_context << Instruction::DUP1;
|
||||||
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false);
|
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||||
|
CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(
|
{
|
||||||
_memberAccess.annotation().type->category() == Type::Category::Array ||
|
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||||
_memberAccess.annotation().type->category() == Type::Category::Struct,
|
// For non-value types the calldata offset is returned directly.
|
||||||
""
|
if (_memberAccess.annotation().type->isValueType())
|
||||||
);
|
{
|
||||||
|
solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, "");
|
||||||
|
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(
|
||||||
|
_memberAccess.annotation().type->category() == Type::Category::Array ||
|
||||||
|
_memberAccess.annotation().type->category() == Type::Category::Struct,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -1574,47 +1582,8 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
|||||||
ArrayUtils(m_context).accessIndex(arrayType, true, true);
|
ArrayUtils(m_context).accessIndex(arrayType, true, true);
|
||||||
// stack layout: <base_ref> <ptr_to_tail>
|
// stack layout: <base_ref> <ptr_to_tail>
|
||||||
|
|
||||||
unsigned int baseEncodedSize = arrayType.baseType()->calldataEncodedSize();
|
CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType());
|
||||||
solAssert(baseEncodedSize > 1, "");
|
// stack layout: <tail_ref> [length]
|
||||||
|
|
||||||
// returns the absolute offset of the accessed element in "base_ref"
|
|
||||||
m_context.appendInlineAssembly(Whiskers(R"({
|
|
||||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
|
||||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
|
||||||
base_ref := add(base_ref, rel_offset_of_tail)
|
|
||||||
})")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"});
|
|
||||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
|
||||||
|
|
||||||
if (!arrayType.baseType()->isDynamicallySized())
|
|
||||||
{
|
|
||||||
m_context << Instruction::POP;
|
|
||||||
// stack layout: <absolute_offset_of_element>
|
|
||||||
solAssert(
|
|
||||||
arrayType.baseType()->category() == Type::Category::Struct ||
|
|
||||||
arrayType.baseType()->category() == Type::Category::Array,
|
|
||||||
"Invalid dynamically encoded base type on array access."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto const* baseArrayType = dynamic_cast<ArrayType const*>(arrayType.baseType().get());
|
|
||||||
solAssert(!!baseArrayType, "Invalid dynamically sized type.");
|
|
||||||
unsigned int calldataStride = baseArrayType->calldataStride();
|
|
||||||
solAssert(calldataStride > 0, "");
|
|
||||||
|
|
||||||
// returns the absolute offset of the accessed element in "base_ref"
|
|
||||||
// and the length of the accessed element in "ptr_to_length"
|
|
||||||
m_context.appendInlineAssembly(
|
|
||||||
Whiskers(R"({
|
|
||||||
length := calldataload(base_ref)
|
|
||||||
base_ref := add(base_ref, 0x20)
|
|
||||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
|
||||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
|
||||||
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
|
|
||||||
{"base_ref", "length"}
|
|
||||||
);
|
|
||||||
// stack layout: <absolute_offset_of_element> <length_of_element>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
struct S2 { uint256 b; }
|
||||||
|
struct S { uint256 a; S2[] children; }
|
||||||
|
function f(S calldata s) external pure returns (uint256, uint256, uint256, uint256) {
|
||||||
|
return (s.children.length, s.a, s.children[0].b, s.children[1].b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// f((uint256,(uint256)[])): 32, 17, 64, 2, 23, 42 -> 2, 17, 23, 42
|
@ -0,0 +1,10 @@
|
|||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
struct S { uint256[] a; }
|
||||||
|
function f(S calldata s) external pure returns (uint256 a, uint256 b, uint256 c) {
|
||||||
|
return (s.a.length, s.a[0], s.a[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// f((uint256[])): 32, 32, 2, 42, 23 -> 2, 42, 23
|
@ -0,0 +1,7 @@
|
|||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract Test {
|
||||||
|
struct S { int[] a; }
|
||||||
|
function f(S calldata) external { }
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
|
Loading…
Reference in New Issue
Block a user