mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #5996 from ethereum/calldataArrays
Allow calldata arrays with dynamically encoded base type.
This commit is contained in:
commit
0f7b1b31a1
@ -1,6 +1,7 @@
|
|||||||
### 0.6.0 (unreleased)
|
### 0.6.0 (unreleased)
|
||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
|
* Allow calldata arrays with dynamically encoded base types with ABIEncoderV2.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
|
@ -372,16 +372,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);
|
TypePointer baseType = type(*var);
|
||||||
if (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
|
||||||
{
|
|
||||||
baseType = arrayType->baseType();
|
|
||||||
if (
|
|
||||||
!m_scope->isInterface() &&
|
|
||||||
baseType->dataStoredIn(DataLocation::CallData) &&
|
|
||||||
baseType->isDynamicallyEncoded()
|
|
||||||
)
|
|
||||||
m_errorReporter.typeError(var->location(), "Calldata arrays with dynamically encoded base types are not yet supported.");
|
|
||||||
}
|
|
||||||
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
||||||
baseType = arrayType->baseType();
|
baseType = arrayType->baseType();
|
||||||
|
|
||||||
|
@ -1354,13 +1354,10 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
|||||||
|
|
||||||
string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
// This does not work with arrays of complex types - the array access
|
|
||||||
// is not yet implemented in Solidity.
|
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||||
if (!_type.isDynamicallySized())
|
if (!_type.isDynamicallySized())
|
||||||
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
|
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
|
||||||
if (_type.baseType()->isDynamicallyEncoded())
|
solAssert(_type.baseType()->calldataEncodedSize() > 0, "");
|
||||||
solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity.");
|
|
||||||
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
|
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
|
||||||
|
|
||||||
string functionName =
|
string functionName =
|
||||||
@ -1376,7 +1373,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
|||||||
length := calldataload(offset)
|
length := calldataload(offset)
|
||||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||||
arrayPos := add(offset, 0x20)
|
arrayPos := add(offset, 0x20)
|
||||||
if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
|
if gt(add(arrayPos, mul(length, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
else
|
else
|
||||||
@ -1391,7 +1388,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
|||||||
w("functionName", functionName);
|
w("functionName", functionName);
|
||||||
w("readableTypeName", _type.toString(true));
|
w("readableTypeName", _type.toString(true));
|
||||||
w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
|
w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
|
||||||
w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length()));
|
if (!_type.isDynamicallySized())
|
||||||
|
w("length", toCompactHexWithPrefix(_type.length()));
|
||||||
return w.render();
|
return w.render();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1030,7 +1030,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const
|
void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const
|
||||||
{
|
{
|
||||||
/// Stack: reference [length] index
|
/// Stack: reference [length] index
|
||||||
DataLocation location = _arrayType.location();
|
DataLocation location = _arrayType.location();
|
||||||
@ -1050,28 +1050,41 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
|
|||||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||||
|
|
||||||
// stack: <base_ref> <index>
|
// stack: <base_ref> <index>
|
||||||
m_context << Instruction::SWAP1;
|
|
||||||
// stack: <index> <base_ref>
|
|
||||||
switch (location)
|
switch (location)
|
||||||
{
|
{
|
||||||
case DataLocation::Memory:
|
case DataLocation::Memory:
|
||||||
case DataLocation::CallData:
|
case DataLocation::CallData:
|
||||||
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
|
|
||||||
m_context << u256(32) << Instruction::ADD;
|
|
||||||
|
|
||||||
if (!_arrayType.isByteArray())
|
if (!_arrayType.isByteArray())
|
||||||
{
|
{
|
||||||
m_context << Instruction::SWAP1;
|
|
||||||
if (location == DataLocation::CallData)
|
if (location == DataLocation::CallData)
|
||||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
{
|
||||||
|
if (_arrayType.baseType()->isDynamicallyEncoded())
|
||||||
|
m_context << u256(0x20);
|
||||||
|
else
|
||||||
|
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
m_context << u256(_arrayType.memoryHeadSize());
|
m_context << u256(_arrayType.memoryHeadSize());
|
||||||
m_context << Instruction::MUL;
|
m_context << Instruction::MUL;
|
||||||
}
|
}
|
||||||
|
// stack: <base_ref> <index * size>
|
||||||
|
|
||||||
|
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
|
||||||
|
m_context << u256(32) << Instruction::ADD;
|
||||||
|
|
||||||
|
if (_keepReference)
|
||||||
|
m_context << Instruction::DUP2;
|
||||||
|
|
||||||
m_context << Instruction::ADD;
|
m_context << Instruction::ADD;
|
||||||
break;
|
break;
|
||||||
case DataLocation::Storage:
|
case DataLocation::Storage:
|
||||||
{
|
{
|
||||||
|
if (_keepReference)
|
||||||
|
m_context << Instruction::DUP2;
|
||||||
|
else
|
||||||
|
m_context << Instruction::SWAP1;
|
||||||
|
// stack: [<base_ref>] <index> <base_ref>
|
||||||
|
|
||||||
eth::AssemblyItem endTag = m_context.newTag();
|
eth::AssemblyItem endTag = m_context.newTag();
|
||||||
if (_arrayType.isByteArray())
|
if (_arrayType.isByteArray())
|
||||||
{
|
{
|
||||||
|
@ -97,11 +97,13 @@ public:
|
|||||||
/// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef.
|
/// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef.
|
||||||
/// If @a _arrayType is a byte array, takes tight coding into account.
|
/// If @a _arrayType is a byte array, takes tight coding into account.
|
||||||
void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const;
|
void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const;
|
||||||
/// Performs bounds checking and returns a reference on the stack.
|
/// Checks whether the index is out of range and returns the absolute offset of the element reference[index]
|
||||||
|
/// (i.e. reference + index * size_of_base_type).
|
||||||
|
/// If @a _keepReference is true, the base reference to the beginning of the array is kept on the stack.
|
||||||
/// Stack pre: reference [length] index
|
/// Stack pre: reference [length] index
|
||||||
/// Stack post (storage): storage_slot byte_offset
|
/// Stack post (storage): [reference] storage_slot byte_offset
|
||||||
/// Stack post: memory/calldata_offset
|
/// Stack post: [reference] memory/calldata_offset
|
||||||
void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const;
|
void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Adds the given number of bytes to a storage byte offset counter and also increments
|
/// Adds the given number of bytes to a storage byte offset counter and also increments
|
||||||
|
@ -1551,10 +1551,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
|||||||
_indexAccess.indexExpression()->accept(*this);
|
_indexAccess.indexExpression()->accept(*this);
|
||||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true);
|
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true);
|
||||||
// stack layout: <base_ref> [<length>] <index>
|
// stack layout: <base_ref> [<length>] <index>
|
||||||
ArrayUtils(m_context).accessIndex(arrayType);
|
|
||||||
switch (arrayType.location())
|
switch (arrayType.location())
|
||||||
{
|
{
|
||||||
case DataLocation::Storage:
|
case DataLocation::Storage:
|
||||||
|
ArrayUtils(m_context).accessIndex(arrayType);
|
||||||
if (arrayType.isByteArray())
|
if (arrayType.isByteArray())
|
||||||
{
|
{
|
||||||
solAssert(!arrayType.isString(), "Index access to string is not allowed.");
|
solAssert(!arrayType.isString(), "Index access to string is not allowed.");
|
||||||
@ -1564,18 +1564,75 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
|||||||
setLValueToStorageItem(_indexAccess);
|
setLValueToStorageItem(_indexAccess);
|
||||||
break;
|
break;
|
||||||
case DataLocation::Memory:
|
case DataLocation::Memory:
|
||||||
|
ArrayUtils(m_context).accessIndex(arrayType);
|
||||||
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray());
|
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray());
|
||||||
break;
|
break;
|
||||||
case DataLocation::CallData:
|
case DataLocation::CallData:
|
||||||
//@todo if we implement this, the value in calldata has to be added to the base offset
|
if (arrayType.baseType()->isDynamicallyEncoded())
|
||||||
solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented.");
|
{
|
||||||
if (arrayType.baseType()->isValueType())
|
// stack layout: <base_ref> <length> <index>
|
||||||
CompilerUtils(m_context).loadFromMemoryDynamic(
|
ArrayUtils(m_context).accessIndex(arrayType, true, true);
|
||||||
*arrayType.baseType(),
|
// stack layout: <base_ref> <ptr_to_tail>
|
||||||
true,
|
|
||||||
!arrayType.isByteArray(),
|
unsigned int baseEncodedSize = arrayType.baseType()->calldataEncodedSize();
|
||||||
false
|
solAssert(baseEncodedSize > 1, "");
|
||||||
);
|
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
ArrayUtils(m_context).accessIndex(arrayType, true);
|
||||||
|
if (arrayType.baseType()->isValueType())
|
||||||
|
CompilerUtils(m_context).loadFromMemoryDynamic(
|
||||||
|
*arrayType.baseType(),
|
||||||
|
true,
|
||||||
|
!arrayType.isByteArray(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
else
|
||||||
|
solAssert(
|
||||||
|
arrayType.baseType()->category() == Type::Category::Struct ||
|
||||||
|
arrayType.baseType()->category() == Type::Category::Array,
|
||||||
|
"Invalid statically sized non-value base type on array access."
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,31 @@ public:
|
|||||||
return m_blockNumber;
|
return m_blockNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Range>
|
||||||
|
bytes encodeArray(bool _dynamicallySized, bool _dynamicallyEncoded, Range const& _elements)
|
||||||
|
{
|
||||||
|
bytes result;
|
||||||
|
if (_dynamicallySized)
|
||||||
|
result += encode(u256(_elements.size()));
|
||||||
|
if (_dynamicallyEncoded)
|
||||||
|
{
|
||||||
|
u256 offset = u256(_elements.size()) * 32;
|
||||||
|
std::vector<bytes> subEncodings;
|
||||||
|
for (auto const& element: _elements)
|
||||||
|
{
|
||||||
|
result += encode(offset);
|
||||||
|
subEncodings.emplace_back(encode(element));
|
||||||
|
offset += subEncodings.back().size();
|
||||||
|
}
|
||||||
|
for (auto const& subEncoding: subEncodings)
|
||||||
|
result += subEncoding;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
for (auto const& element: _elements)
|
||||||
|
result += encode(element);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class CppFunction, class... Args>
|
template <class CppFunction, class... Args>
|
||||||
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments)
|
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments)
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
#include <libdevcore/Keccak256.h>
|
#include <libdevcore/Keccak256.h>
|
||||||
|
|
||||||
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -8309,6 +8310,348 @@ BOOST_AUTO_TEST_CASE(calldata_struct_function_type)
|
|||||||
ABI_CHECK(callContractFunctionNoEncoding("f((function))", fn_C_h), encodeArgs(23));
|
ABI_CHECK(callContractFunctionNoEncoding("f((function))", fn_C_h), encodeArgs(23));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_array_dynamic_bytes)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f1(bytes[1] calldata a) external returns (uint256, uint256, uint256, uint256) {
|
||||||
|
return (a[0].length, uint8(a[0][0]), uint8(a[0][1]), uint8(a[0][2]));
|
||||||
|
}
|
||||||
|
function f2(bytes[1] calldata a, bytes[1] calldata b) external returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
|
||||||
|
return (a[0].length, uint8(a[0][0]), uint8(a[0][1]), uint8(a[0][2]), b[0].length, uint8(b[0][0]), uint8(b[0][1]));
|
||||||
|
}
|
||||||
|
function g1(bytes[2] calldata a) external returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
|
||||||
|
return (a[0].length, uint8(a[0][0]), uint8(a[0][1]), uint8(a[0][2]), a[1].length, uint8(a[1][0]), uint8(a[1][1]), uint8(a[1][2]));
|
||||||
|
}
|
||||||
|
function g2(bytes[] calldata a) external returns (uint256[8] memory) {
|
||||||
|
return [a.length, a[0].length, uint8(a[0][0]), uint8(a[0][1]), a[1].length, uint8(a[1][0]), uint8(a[1][1]), uint8(a[1][2])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
bytes bytes010203 = bytes{1,2,3}+bytes(29,0);
|
||||||
|
bytes bytes040506 = bytes{4,5,6}+bytes(29,0);
|
||||||
|
bytes bytes0102 = bytes{1,2}+bytes(30,0);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f1(bytes[1])", 0x20, 0x20, 3, bytes010203),
|
||||||
|
encodeArgs(3, 1, 2, 3)
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f2(bytes[1],bytes[1])", 0x40, 0xA0, 0x20, 3, bytes010203, 0x20, 2, bytes0102),
|
||||||
|
encodeArgs(3, 1, 2, 3, 2, 1, 2)
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("g1(bytes[2])", 0x20, 0x40, 0x80, 3, bytes010203, 3, bytes040506),
|
||||||
|
encodeArgs(3, 1, 2, 3, 3, 4, 5, 6)
|
||||||
|
);
|
||||||
|
// same offset for both arrays
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("g1(bytes[2])", 0x20, 0x40, 0x40, 3, bytes010203),
|
||||||
|
encodeArgs(3, 1, 2, 3, 3, 1, 2, 3)
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("g2(bytes[])", 0x20, 2, 0x40, 0x80, 2, bytes0102, 3, bytes040506),
|
||||||
|
encodeArgs(2, 2, 1, 2, 3, 4, 5, 6)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_dynamic_array_to_memory)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(uint256[][] calldata a) external returns (uint, uint256[] memory) {
|
||||||
|
uint256[] memory m = a[0];
|
||||||
|
return (a.length, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(uint256[][])", 0x20, 1, 0x20, 2, 23, 42),
|
||||||
|
encodeArgs(1, 0x40, 2, 23, 42)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_bytes_array_to_memory)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(bytes[] calldata a) external returns (uint, uint, bytes memory) {
|
||||||
|
bytes memory m = a[0];
|
||||||
|
return (a.length, m.length, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[])", 0x20, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)),
|
||||||
|
encodeArgs(1, 2, 0x60, 2, bytes{'a','b'} + bytes(30, 0))
|
||||||
|
);
|
||||||
|
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, bytes(32, 'x')),
|
||||||
|
encodeArgs(1, 32, 0x60, 32, bytes(32, 'x'))
|
||||||
|
);
|
||||||
|
bytes x_zero_a = bytes{'x'} + bytes(30, 0) + bytes{'a'};
|
||||||
|
bytes a_zero_x = bytes{'a'} + bytes(30, 0) + bytes{'x'};
|
||||||
|
bytes a_m_x = bytes{'a'} + bytes(30, 'm') + bytes{'x'};
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, x_zero_a),
|
||||||
|
encodeArgs(1, 32, 0x60, 32, x_zero_a)
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, a_zero_x),
|
||||||
|
encodeArgs(1, 32, 0x60, 32, a_zero_x)
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[])", 0x20, 1, 0x20, 32, a_m_x),
|
||||||
|
encodeArgs(1, 32, 0x60, 32, a_m_x)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_bytes_array_bounds)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(bytes[] calldata a, uint256 i) external returns (uint) {
|
||||||
|
return uint8(a[0][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[],uint256)", 0x40, 0, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)),
|
||||||
|
encodeArgs('a')
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[],uint256)", 0x40, 1, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)),
|
||||||
|
encodeArgs('b')
|
||||||
|
);
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(bytes[],uint256)", 0x40, 2, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)),
|
||||||
|
encodeArgs()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_string_array)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(string[] calldata a) external returns (uint, uint, uint, string memory) {
|
||||||
|
string memory s1 = a[0];
|
||||||
|
bytes memory m1 = bytes(s1);
|
||||||
|
return (a.length, m1.length, uint8(m1[0]), s1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
ABI_CHECK(
|
||||||
|
callContractFunction("f(string[])", 0x20, 1, 0x20, 2, bytes{'a', 'b'} + bytes(30, 0)),
|
||||||
|
encodeArgs(1, 2, 'a', 0x80, 2, bytes{'a', 'b'} + bytes(30, 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_array_two_dimensional)
|
||||||
|
{
|
||||||
|
vector<vector<u256>> data {
|
||||||
|
{ 0x0A01, 0x0A02, 0x0A03 },
|
||||||
|
{ 0x0B01, 0x0B02, 0x0B03, 0x0B04 }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (bool outerDynamicallySized: { true, false })
|
||||||
|
{
|
||||||
|
string arrayType = outerDynamicallySized ? "uint256[][]" : "uint256[][2]";
|
||||||
|
string sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function test()" + arrayType + R"( calldata a) external returns (uint256) {
|
||||||
|
return a.length;
|
||||||
|
}
|
||||||
|
function test()" + arrayType + R"( calldata a, uint256 i) external returns (uint256) {
|
||||||
|
return a[i].length;
|
||||||
|
}
|
||||||
|
function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) {
|
||||||
|
return a[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
bytes encoding = encodeArray(
|
||||||
|
outerDynamicallySized,
|
||||||
|
true,
|
||||||
|
data | boost::adaptors::transformed([&](vector<u256> const& _values) {
|
||||||
|
return encodeArray(true, false, _values);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ")", 0x20, encoding), encodeArgs(data.size()));
|
||||||
|
for (size_t i = 0; i < data.size(); i++)
|
||||||
|
{
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size()));
|
||||||
|
for (size_t j = 0; j < data[i].size(); j++)
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j]));
|
||||||
|
// out of bounds access
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs());
|
||||||
|
}
|
||||||
|
// out of bounds access
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, data.size(), encoding), encodeArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_array_dynamic_three_dimensional)
|
||||||
|
{
|
||||||
|
vector<vector<vector<u256>>> data {
|
||||||
|
{
|
||||||
|
{ 0x010A01, 0x010A02, 0x010A03 },
|
||||||
|
{ 0x010B01, 0x010B02, 0x010B03 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ 0x020A01, 0x020A02, 0x020A03 },
|
||||||
|
{ 0x020B01, 0x020B02, 0x020B03 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (bool outerDynamicallySized: { true, false })
|
||||||
|
for (bool middleDynamicallySized: { true, false })
|
||||||
|
for (bool innerDynamicallySized: { true, false })
|
||||||
|
{
|
||||||
|
// only test dynamically encoded arrays
|
||||||
|
if (!outerDynamicallySized && !middleDynamicallySized && !innerDynamicallySized)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string arrayType = "uint256";
|
||||||
|
arrayType += innerDynamicallySized ? "[]" : "[3]";
|
||||||
|
arrayType += middleDynamicallySized ? "[]" : "[2]";
|
||||||
|
arrayType += outerDynamicallySized ? "[]" : "[2]";
|
||||||
|
|
||||||
|
string sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function test()" + arrayType + R"( calldata a) external returns (uint256) {
|
||||||
|
return a.length;
|
||||||
|
}
|
||||||
|
function test()" + arrayType + R"( calldata a, uint256 i) external returns (uint256) {
|
||||||
|
return a[i].length;
|
||||||
|
}
|
||||||
|
function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) {
|
||||||
|
return a[i][j].length;
|
||||||
|
}
|
||||||
|
function test()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) {
|
||||||
|
return a[i][j][k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
bytes encoding = encodeArray(
|
||||||
|
outerDynamicallySized,
|
||||||
|
middleDynamicallySized || innerDynamicallySized,
|
||||||
|
data | boost::adaptors::transformed([&](auto const& _middleData) {
|
||||||
|
return encodeArray(
|
||||||
|
middleDynamicallySized,
|
||||||
|
innerDynamicallySized,
|
||||||
|
_middleData | boost::adaptors::transformed([&](auto const& _values) {
|
||||||
|
return encodeArray(innerDynamicallySized, false, _values);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ")", 0x20, encoding), encodeArgs(data.size()));
|
||||||
|
for (size_t i = 0; i < data.size(); i++)
|
||||||
|
{
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size()));
|
||||||
|
for (size_t j = 0; j < data[i].size(); j++)
|
||||||
|
{
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j].size()));
|
||||||
|
for (size_t k = 0; k < data[i][j].size(); k++)
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k]));
|
||||||
|
// out of bounds access
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, data[i][j].size(), encoding), encodeArgs());
|
||||||
|
}
|
||||||
|
// out of bounds access
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs());
|
||||||
|
}
|
||||||
|
// out of bounds access
|
||||||
|
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, data.size(), encoding), encodeArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_array_dynamic_invalid)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(uint256[][] calldata a) external returns (uint) {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
function g(uint256[][] calldata a) external returns (uint) {
|
||||||
|
a[0];
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
// valid access stub
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 0), encodeArgs(42));
|
||||||
|
// invalid on argument decoding
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 1), encodeArgs());
|
||||||
|
// invalid on outer access
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 1, 0x20), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("g(uint256[][])", 0x20, 1, 0x20), encodeArgs());
|
||||||
|
// invalid on inner access
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][])", 0x20, 1, 0x20, 2, 0x42), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("g(uint256[][])", 0x20, 1, 0x20, 2, 0x42), encodeArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calldata_array_dynamic_invalid_static_middle)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
contract C {
|
||||||
|
function f(uint256[][1][] calldata a) external returns (uint) {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
function g(uint256[][1][] calldata a) external returns (uint) {
|
||||||
|
a[0];
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
function h(uint256[][1][] calldata a) external returns (uint) {
|
||||||
|
a[0][0];
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
|
||||||
|
// valid access stub
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 0), encodeArgs(42));
|
||||||
|
// invalid on argument decoding
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1), encodeArgs());
|
||||||
|
// invalid on outer access
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1, 0x20), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("g(uint256[][1][])", 0x20, 1, 0x20), encodeArgs());
|
||||||
|
// invalid on inner access
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1, 0x20, 0x20), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("g(uint256[][1][])", 0x20, 1, 0x20, 0x20), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("h(uint256[][1][])", 0x20, 1, 0x20, 0x20), encodeArgs());
|
||||||
|
ABI_CHECK(callContractFunction("f(uint256[][1][])", 0x20, 1, 0x20, 0x20, 1), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("g(uint256[][1][])", 0x20, 1, 0x20, 0x20, 1), encodeArgs(42));
|
||||||
|
ABI_CHECK(callContractFunction("h(uint256[][1][])", 0x20, 1, 0x20, 0x20, 1), encodeArgs());
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(literal_strings)
|
BOOST_AUTO_TEST_CASE(literal_strings)
|
||||||
{
|
{
|
||||||
char const* sourceCode = R"(
|
char const* sourceCode = R"(
|
||||||
|
@ -4,4 +4,3 @@ contract Test {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
|
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
|
||||||
// TypeError: (65-82): Calldata arrays with dynamically encoded base types are not yet supported.
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
contract Test {
|
||||||
|
function f(uint[][] calldata) external { }
|
||||||
|
function g(uint[][1] calldata) external { }
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (31-48): This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature.
|
||||||
|
// TypeError: (78-96): This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature.
|
@ -6,4 +6,3 @@ contract Test {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
|
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
|
||||||
// TypeError: (131-145): Calldata arrays with dynamically encoded base types are not yet supported.
|
|
||||||
|
Loading…
Reference in New Issue
Block a user