2017-08-07 14:43:26 +00:00
|
|
|
/*
|
|
|
|
This file is part of solidity.
|
|
|
|
|
|
|
|
solidity is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
solidity is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* @author Christian <chris@ethereum.org>
|
|
|
|
* @date 2017
|
2018-06-12 18:28:52 +00:00
|
|
|
* Routines that generate Yul code related to ABI encoding, decoding and type conversions.
|
2017-08-07 14:43:26 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <libsolidity/codegen/ABIFunctions.h>
|
|
|
|
|
2017-09-01 11:59:21 +00:00
|
|
|
#include <libsolidity/codegen/CompilerUtils.h>
|
2017-08-07 14:43:26 +00:00
|
|
|
#include <libdevcore/Whiskers.h>
|
|
|
|
|
2017-09-01 11:59:21 +00:00
|
|
|
#include <boost/algorithm/string/join.hpp>
|
|
|
|
#include <boost/range/adaptor/reversed.hpp>
|
2017-08-07 14:43:26 +00:00
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace dev;
|
|
|
|
using namespace dev::solidity;
|
|
|
|
|
|
|
|
string ABIFunctions::tupleEncoder(
|
|
|
|
TypePointers const& _givenTypes,
|
|
|
|
TypePointers const& _targetTypes,
|
|
|
|
bool _encodeAsLibraryTypes
|
|
|
|
)
|
|
|
|
{
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions options;
|
|
|
|
options.encodeAsLibraryTypes = _encodeAsLibraryTypes;
|
|
|
|
options.encodeFunctionFromStack = true;
|
|
|
|
options.padded = true;
|
|
|
|
options.dynamicInplace = false;
|
|
|
|
|
2017-08-25 16:58:12 +00:00
|
|
|
string functionName = string("abi_encode_tuple_");
|
|
|
|
for (auto const& t: _givenTypes)
|
|
|
|
functionName += t->identifier() + "_";
|
|
|
|
functionName += "_to_";
|
|
|
|
for (auto const& t: _targetTypes)
|
|
|
|
functionName += t->identifier() + "_";
|
2019-01-21 15:43:32 +00:00
|
|
|
functionName += options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
|
2018-01-17 11:05:43 +00:00
|
|
|
return createExternallyUsedFunction(functionName, [&]() {
|
2017-08-25 16:58:12 +00:00
|
|
|
// Note that the values are in reverse due to the difference in calling semantics.
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(headStart <valueParams>) -> tail {
|
|
|
|
tail := add(headStart, <headSize>)
|
|
|
|
<encodeElements>
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
size_t const headSize_ = headSize(_targetTypes);
|
|
|
|
templ("headSize", to_string(headSize_));
|
|
|
|
string valueParams;
|
|
|
|
string encodeElements;
|
|
|
|
size_t headPos = 0;
|
|
|
|
size_t stackPos = 0;
|
|
|
|
for (size_t i = 0; i < _givenTypes.size(); ++i)
|
2017-08-07 14:43:26 +00:00
|
|
|
{
|
2017-08-25 16:58:12 +00:00
|
|
|
solAssert(_givenTypes[i], "");
|
|
|
|
solAssert(_targetTypes[i], "");
|
|
|
|
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
|
|
|
|
string valueNames = "";
|
|
|
|
for (size_t j = 0; j < sizeOnStack; j++)
|
|
|
|
{
|
|
|
|
valueNames += "value" + to_string(stackPos) + ", ";
|
|
|
|
valueParams = ", value" + to_string(stackPos) + valueParams;
|
|
|
|
stackPos++;
|
|
|
|
}
|
|
|
|
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
|
|
|
|
Whiskers elementTempl(
|
|
|
|
dynamic ?
|
|
|
|
string(R"(
|
|
|
|
mstore(add(headStart, <pos>), sub(tail, headStart))
|
|
|
|
tail := <abiEncode>(<values> tail)
|
|
|
|
)") :
|
|
|
|
string(R"(
|
|
|
|
<abiEncode>(<values> add(headStart, <pos>))
|
|
|
|
)")
|
|
|
|
);
|
|
|
|
elementTempl("values", valueNames);
|
|
|
|
elementTempl("pos", to_string(headPos));
|
2019-01-21 15:43:32 +00:00
|
|
|
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
2017-08-25 16:58:12 +00:00
|
|
|
encodeElements += elementTempl.render();
|
|
|
|
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
2017-08-25 16:58:12 +00:00
|
|
|
solAssert(headPos == headSize_, "");
|
|
|
|
templ("valueParams", valueParams);
|
|
|
|
templ("encodeElements", encodeElements);
|
2017-08-07 14:43:26 +00:00
|
|
|
|
2017-08-25 16:58:12 +00:00
|
|
|
return templ.render();
|
|
|
|
});
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
|
2019-01-23 10:32:25 +00:00
|
|
|
string ABIFunctions::tupleEncoderPacked(
|
|
|
|
TypePointers const& _givenTypes,
|
|
|
|
TypePointers const& _targetTypes
|
|
|
|
)
|
|
|
|
{
|
|
|
|
EncodingOptions options;
|
|
|
|
options.encodeAsLibraryTypes = false;
|
|
|
|
options.encodeFunctionFromStack = true;
|
|
|
|
options.padded = false;
|
|
|
|
options.dynamicInplace = true;
|
|
|
|
|
|
|
|
string functionName = string("abi_encode_tuple_packed_");
|
|
|
|
for (auto const& t: _givenTypes)
|
|
|
|
functionName += t->identifier() + "_";
|
|
|
|
functionName += "_to_";
|
|
|
|
for (auto const& t: _targetTypes)
|
|
|
|
functionName += t->identifier() + "_";
|
|
|
|
functionName += options.toFunctionNameSuffix();
|
|
|
|
|
|
|
|
return createExternallyUsedFunction(functionName, [&]() {
|
|
|
|
solAssert(!_givenTypes.empty(), "");
|
|
|
|
|
|
|
|
// Note that the values are in reverse due to the difference in calling semantics.
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(pos <valueParams>) -> end {
|
|
|
|
<encodeElements>
|
|
|
|
end := pos
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
string valueParams;
|
|
|
|
string encodeElements;
|
|
|
|
size_t stackPos = 0;
|
|
|
|
for (size_t i = 0; i < _givenTypes.size(); ++i)
|
|
|
|
{
|
|
|
|
solAssert(_givenTypes[i], "");
|
|
|
|
solAssert(_targetTypes[i], "");
|
|
|
|
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
|
|
|
|
string valueNames = "";
|
|
|
|
for (size_t j = 0; j < sizeOnStack; j++)
|
|
|
|
{
|
|
|
|
valueNames += "value" + to_string(stackPos) + ", ";
|
|
|
|
valueParams = ", value" + to_string(stackPos) + valueParams;
|
|
|
|
stackPos++;
|
|
|
|
}
|
|
|
|
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
|
|
|
|
Whiskers elementTempl(
|
|
|
|
dynamic ?
|
|
|
|
string(R"(
|
|
|
|
pos := <abiEncode>(<values> pos)
|
|
|
|
)") :
|
|
|
|
string(R"(
|
|
|
|
<abiEncode>(<values> pos)
|
|
|
|
pos := add(pos, <calldataEncodedSize>)
|
|
|
|
)")
|
|
|
|
);
|
|
|
|
elementTempl("values", valueNames);
|
|
|
|
if (!dynamic)
|
|
|
|
elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
|
|
|
|
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
|
|
|
encodeElements += elementTempl.render();
|
|
|
|
}
|
|
|
|
templ("valueParams", valueParams);
|
|
|
|
templ("encodeElements", encodeElements);
|
|
|
|
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
2017-09-01 11:59:21 +00:00
|
|
|
string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|
|
|
{
|
|
|
|
string functionName = string("abi_decode_tuple_");
|
|
|
|
for (auto const& t: _types)
|
|
|
|
functionName += t->identifier();
|
|
|
|
if (_fromMemory)
|
|
|
|
functionName += "_fromMemory";
|
|
|
|
|
2018-01-17 11:05:43 +00:00
|
|
|
return createExternallyUsedFunction(functionName, [&]() {
|
2017-09-01 11:59:21 +00:00
|
|
|
TypePointers decodingTypes;
|
|
|
|
for (auto const& t: _types)
|
|
|
|
decodingTypes.emplace_back(t->decodingType());
|
|
|
|
|
|
|
|
Whiskers templ(R"(
|
2019-03-04 22:26:46 +00:00
|
|
|
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
|
2017-12-15 08:47:06 +00:00
|
|
|
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
<decodeElements>
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
templ("minimumSize", to_string(headSize(decodingTypes)));
|
|
|
|
|
|
|
|
string decodeElements;
|
|
|
|
vector<string> valueReturnParams;
|
|
|
|
size_t headPos = 0;
|
|
|
|
size_t stackPos = 0;
|
|
|
|
for (size_t i = 0; i < _types.size(); ++i)
|
|
|
|
{
|
|
|
|
solAssert(_types[i], "");
|
|
|
|
solAssert(decodingTypes[i], "");
|
|
|
|
size_t sizeOnStack = _types[i]->sizeOnStack();
|
|
|
|
solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
|
|
|
|
solAssert(sizeOnStack > 0, "");
|
|
|
|
vector<string> valueNamesLocal;
|
|
|
|
for (size_t j = 0; j < sizeOnStack; j++)
|
|
|
|
{
|
2018-12-10 18:02:39 +00:00
|
|
|
valueNamesLocal.emplace_back("value" + to_string(stackPos));
|
|
|
|
valueReturnParams.emplace_back("value" + to_string(stackPos));
|
2017-09-01 11:59:21 +00:00
|
|
|
stackPos++;
|
|
|
|
}
|
|
|
|
bool dynamic = decodingTypes[i]->isDynamicallyEncoded();
|
2017-10-13 15:29:09 +00:00
|
|
|
Whiskers elementTempl(
|
|
|
|
dynamic ?
|
|
|
|
R"(
|
2017-09-01 11:59:21 +00:00
|
|
|
{
|
2017-10-13 15:29:09 +00:00
|
|
|
let offset := <load>(add(headStart, <pos>))
|
2017-12-15 08:47:06 +00:00
|
|
|
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
<values> := <abiDecode>(add(headStart, offset), dataEnd)
|
|
|
|
}
|
2017-10-13 15:29:09 +00:00
|
|
|
)" :
|
|
|
|
R"(
|
|
|
|
{
|
|
|
|
let offset := <pos>
|
|
|
|
<values> := <abiDecode>(add(headStart, offset), dataEnd)
|
|
|
|
}
|
|
|
|
)"
|
|
|
|
);
|
2017-09-01 11:59:21 +00:00
|
|
|
elementTempl("load", _fromMemory ? "mload" : "calldataload");
|
|
|
|
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
|
|
|
|
elementTempl("pos", to_string(headPos));
|
|
|
|
elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
|
|
|
|
decodeElements += elementTempl.render();
|
|
|
|
headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
|
|
|
|
}
|
|
|
|
templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
|
2019-03-04 22:26:46 +00:00
|
|
|
templ("arrow", valueReturnParams.empty() ? "" : "->");
|
2017-09-01 11:59:21 +00:00
|
|
|
templ("decodeElements", decodeElements);
|
|
|
|
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-01-17 11:05:43 +00:00
|
|
|
pair<string, set<string>> ABIFunctions::requestedFunctions()
|
2017-08-07 14:43:26 +00:00
|
|
|
{
|
2019-03-05 16:20:28 +00:00
|
|
|
std::set<string> empty;
|
|
|
|
swap(empty, m_externallyUsedFunctions);
|
|
|
|
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
|
2019-01-21 15:43:32 +00:00
|
|
|
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
|
|
|
{
|
|
|
|
string suffix;
|
|
|
|
if (!padded)
|
|
|
|
suffix += "_nonPadded";
|
|
|
|
if (dynamicInplace)
|
|
|
|
suffix += "_inplace";
|
|
|
|
if (encodeFunctionFromStack)
|
|
|
|
suffix += "_fromStack";
|
|
|
|
if (encodeAsLibraryTypes)
|
|
|
|
suffix += "_library";
|
|
|
|
return suffix;
|
|
|
|
}
|
|
|
|
|
2019-03-05 16:20:28 +00:00
|
|
|
|
2017-08-07 14:43:26 +00:00
|
|
|
string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
|
|
|
{
|
|
|
|
string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(value) -> cleaned {
|
|
|
|
<body>
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
switch (_type.category())
|
|
|
|
{
|
2018-09-03 15:45:58 +00:00
|
|
|
case Type::Category::Address:
|
2019-01-17 23:41:13 +00:00
|
|
|
templ("body", "cleaned := " + cleanupFunction(IntegerType(160), _revertOnFailure) + "(value)");
|
2018-09-03 15:45:58 +00:00
|
|
|
break;
|
2017-08-07 14:43:26 +00:00
|
|
|
case Type::Category::Integer:
|
|
|
|
{
|
|
|
|
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
|
|
|
if (type.numBits() == 256)
|
|
|
|
templ("body", "cleaned := value");
|
|
|
|
else if (type.isSigned())
|
|
|
|
templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)");
|
|
|
|
else
|
|
|
|
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::RationalNumber:
|
|
|
|
templ("body", "cleaned := value");
|
|
|
|
break;
|
|
|
|
case Type::Category::Bool:
|
|
|
|
templ("body", "cleaned := iszero(iszero(value))");
|
|
|
|
break;
|
|
|
|
case Type::Category::FixedPoint:
|
|
|
|
solUnimplemented("Fixed point types not implemented.");
|
|
|
|
break;
|
|
|
|
case Type::Category::Array:
|
|
|
|
case Type::Category::Struct:
|
2019-02-18 17:48:35 +00:00
|
|
|
case Type::Category::Mapping:
|
2017-09-01 11:59:21 +00:00
|
|
|
solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
|
|
|
|
templ("body", "cleaned := value");
|
2017-08-07 14:43:26 +00:00
|
|
|
break;
|
|
|
|
case Type::Category::FixedBytes:
|
|
|
|
{
|
|
|
|
FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type);
|
|
|
|
if (type.numBytes() == 32)
|
|
|
|
templ("body", "cleaned := value");
|
|
|
|
else if (type.numBytes() == 0)
|
2018-08-02 14:22:33 +00:00
|
|
|
// This is disallowed in the type system.
|
|
|
|
solAssert(false, "");
|
2017-08-07 14:43:26 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
size_t numBits = type.numBytes() * 8;
|
|
|
|
u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits);
|
|
|
|
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::Contract:
|
2018-09-05 15:59:55 +00:00
|
|
|
{
|
|
|
|
AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
|
|
|
|
StateMutability::Payable :
|
|
|
|
StateMutability::NonPayable
|
|
|
|
);
|
2019-01-17 23:41:13 +00:00
|
|
|
templ("body", "cleaned := " + cleanupFunction(addressType, _revertOnFailure) + "(value)");
|
2017-08-07 14:43:26 +00:00
|
|
|
break;
|
2018-09-05 15:59:55 +00:00
|
|
|
}
|
2017-08-07 14:43:26 +00:00
|
|
|
case Type::Category::Enum:
|
|
|
|
{
|
|
|
|
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
|
|
|
|
solAssert(members > 0, "empty enum should have caused a parser error.");
|
2017-11-21 12:40:21 +00:00
|
|
|
Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value");
|
2017-08-07 14:43:26 +00:00
|
|
|
w("members", to_string(members));
|
|
|
|
if (_revertOnFailure)
|
|
|
|
w("failure", "revert(0, 0)");
|
|
|
|
else
|
|
|
|
w("failure", "invalid()");
|
|
|
|
templ("body", w.render());
|
|
|
|
break;
|
|
|
|
}
|
2018-02-21 10:07:30 +00:00
|
|
|
case Type::Category::InaccessibleDynamic:
|
|
|
|
templ("body", "cleaned := 0");
|
|
|
|
break;
|
2017-08-07 14:43:26 +00:00
|
|
|
default:
|
|
|
|
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|
|
|
{
|
|
|
|
string functionName =
|
|
|
|
"convert_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier();
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(value) -> converted {
|
|
|
|
<body>
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
string body;
|
|
|
|
auto toCategory = _to.category();
|
|
|
|
auto fromCategory = _from.category();
|
|
|
|
switch (fromCategory)
|
|
|
|
{
|
2018-09-03 15:45:58 +00:00
|
|
|
case Type::Category::Address:
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <convert>(value)")
|
|
|
|
("convert", conversionFunction(IntegerType(160), _to))
|
|
|
|
.render();
|
|
|
|
break;
|
2017-08-07 14:43:26 +00:00
|
|
|
case Type::Category::Integer:
|
|
|
|
case Type::Category::RationalNumber:
|
|
|
|
case Type::Category::Contract:
|
|
|
|
{
|
|
|
|
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from))
|
|
|
|
solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType.");
|
|
|
|
if (toCategory == Type::Category::FixedBytes)
|
|
|
|
{
|
|
|
|
solAssert(
|
|
|
|
fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber,
|
|
|
|
"Invalid conversion to FixedBytesType requested."
|
|
|
|
);
|
|
|
|
FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to);
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <shiftLeft>(<clean>(value))")
|
2019-03-05 16:20:28 +00:00
|
|
|
("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
2017-08-07 14:43:26 +00:00
|
|
|
("clean", cleanupFunction(_from))
|
|
|
|
.render();
|
|
|
|
}
|
|
|
|
else if (toCategory == Type::Category::Enum)
|
|
|
|
{
|
|
|
|
solAssert(_from.mobileType(), "");
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <cleanEnum>(<cleanInt>(value))")
|
2019-01-17 23:41:13 +00:00
|
|
|
("cleanEnum", cleanupFunction(_to))
|
2017-08-07 14:43:26 +00:00
|
|
|
// "mobileType()" returns integer type for rational
|
|
|
|
("cleanInt", cleanupFunction(*_from.mobileType()))
|
|
|
|
.render();
|
|
|
|
}
|
|
|
|
else if (toCategory == Type::Category::FixedPoint)
|
|
|
|
solUnimplemented("Not yet implemented - FixedPointType.");
|
2018-09-03 15:45:58 +00:00
|
|
|
else if (toCategory == Type::Category::Address)
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <convert>(value)")
|
|
|
|
("convert", conversionFunction(_from, IntegerType(160)))
|
|
|
|
.render();
|
2017-08-07 14:43:26 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
solAssert(
|
|
|
|
toCategory == Type::Category::Integer ||
|
|
|
|
toCategory == Type::Category::Contract,
|
|
|
|
"");
|
2018-09-03 15:45:58 +00:00
|
|
|
IntegerType const addressType(160);
|
2017-08-07 14:43:26 +00:00
|
|
|
IntegerType const& to =
|
|
|
|
toCategory == Type::Category::Integer ?
|
|
|
|
dynamic_cast<IntegerType const&>(_to) :
|
|
|
|
addressType;
|
|
|
|
|
|
|
|
// Clean according to the "to" type, except if this is
|
|
|
|
// a widening conversion.
|
|
|
|
IntegerType const* cleanupType = &to;
|
|
|
|
if (fromCategory != Type::Category::RationalNumber)
|
|
|
|
{
|
|
|
|
IntegerType const& from =
|
|
|
|
fromCategory == Type::Category::Integer ?
|
|
|
|
dynamic_cast<IntegerType const&>(_from) :
|
|
|
|
addressType;
|
|
|
|
if (to.numBits() > from.numBits())
|
|
|
|
cleanupType = &from;
|
|
|
|
}
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <cleanInt>(value)")
|
|
|
|
("cleanInt", cleanupFunction(*cleanupType))
|
|
|
|
.render();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::Bool:
|
|
|
|
{
|
|
|
|
solAssert(_from == _to, "Invalid conversion for bool.");
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <clean>(value)")
|
|
|
|
("clean", cleanupFunction(_from))
|
|
|
|
.render();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::FixedPoint:
|
|
|
|
solUnimplemented("Fixed point types not implemented.");
|
|
|
|
break;
|
|
|
|
case Type::Category::Array:
|
|
|
|
solUnimplementedAssert(false, "Array conversion not implemented.");
|
|
|
|
break;
|
|
|
|
case Type::Category::Struct:
|
|
|
|
solUnimplementedAssert(false, "Struct conversion not implemented.");
|
|
|
|
break;
|
|
|
|
case Type::Category::FixedBytes:
|
|
|
|
{
|
|
|
|
FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);
|
|
|
|
if (toCategory == Type::Category::Integer)
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <convert>(<shift>(value))")
|
2019-03-05 16:20:28 +00:00
|
|
|
("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8))
|
2017-08-07 14:43:26 +00:00
|
|
|
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
|
|
|
|
.render();
|
2018-09-03 15:45:58 +00:00
|
|
|
else if (toCategory == Type::Category::Address)
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <convert>(value)")
|
|
|
|
("convert", conversionFunction(_from, IntegerType(160)))
|
|
|
|
.render();
|
2017-08-07 14:43:26 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// clear for conversion to longer bytes
|
|
|
|
solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <clean>(value)")
|
|
|
|
("clean", cleanupFunction(from))
|
|
|
|
.render();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::Function:
|
|
|
|
{
|
|
|
|
solAssert(false, "Conversion should not be called for function types.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::Enum:
|
|
|
|
{
|
|
|
|
solAssert(toCategory == Type::Category::Integer || _from == _to, "");
|
|
|
|
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from);
|
|
|
|
body =
|
|
|
|
Whiskers("converted := <clean>(value)")
|
|
|
|
("clean", cleanupFunction(enumType))
|
|
|
|
.render();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::Tuple:
|
|
|
|
{
|
|
|
|
solUnimplementedAssert(false, "Tuple conversion not implemented.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
solAssert(false, "");
|
|
|
|
}
|
|
|
|
|
2018-09-03 15:45:58 +00:00
|
|
|
solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("body", body);
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
|
|
|
|
{
|
|
|
|
string functionName = "cleanup_combined_external_function_id";
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(addr_and_selector) -> cleaned {
|
|
|
|
cleaned := <clean>(addr_and_selector)
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
("clean", cleanupFunction(FixedBytesType(24)))
|
|
|
|
.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiEncodingFunction(
|
|
|
|
Type const& _from,
|
|
|
|
Type const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
2019-01-21 15:43:32 +00:00
|
|
|
TypePointer toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false);
|
2018-07-18 13:46:23 +00:00
|
|
|
solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented.");
|
2017-08-07 14:43:26 +00:00
|
|
|
Type const& to = *toInterface;
|
|
|
|
|
|
|
|
if (_from.category() == Type::Category::StringLiteral)
|
2019-01-21 15:43:32 +00:00
|
|
|
return abiEncodingFunctionStringLiteral(_from, to, _options);
|
2017-08-07 14:43:26 +00:00
|
|
|
else if (auto toArray = dynamic_cast<ArrayType const*>(&to))
|
|
|
|
{
|
|
|
|
solAssert(_from.category() == Type::Category::Array, "");
|
|
|
|
solAssert(to.dataStoredIn(DataLocation::Memory), "");
|
|
|
|
ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from);
|
|
|
|
if (fromArray.location() == DataLocation::CallData)
|
2019-01-21 15:43:32 +00:00
|
|
|
return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options);
|
2017-08-07 14:43:26 +00:00
|
|
|
else if (!fromArray.isByteArray() && (
|
|
|
|
fromArray.location() == DataLocation::Memory ||
|
|
|
|
fromArray.baseType()->storageBytes() > 16
|
|
|
|
))
|
2019-01-21 15:43:32 +00:00
|
|
|
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
|
2017-08-07 14:43:26 +00:00
|
|
|
else if (fromArray.location() == DataLocation::Memory)
|
2019-01-21 15:43:32 +00:00
|
|
|
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
|
2017-08-07 14:43:26 +00:00
|
|
|
else if (fromArray.location() == DataLocation::Storage)
|
2019-01-21 15:43:32 +00:00
|
|
|
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
|
2017-08-07 14:43:26 +00:00
|
|
|
else
|
|
|
|
solAssert(false, "");
|
|
|
|
}
|
2017-08-24 13:20:49 +00:00
|
|
|
else if (auto const* toStruct = dynamic_cast<StructType const*>(&to))
|
2017-08-07 14:43:26 +00:00
|
|
|
{
|
2017-08-24 13:20:49 +00:00
|
|
|
StructType const* fromStruct = dynamic_cast<StructType const*>(&_from);
|
|
|
|
solAssert(fromStruct, "");
|
2019-01-21 15:43:32 +00:00
|
|
|
return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options);
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
else if (_from.category() == Type::Category::Function)
|
|
|
|
return abiEncodingFunctionFunctionType(
|
|
|
|
dynamic_cast<FunctionType const&>(_from),
|
|
|
|
to,
|
2019-01-21 15:43:32 +00:00
|
|
|
_options
|
2017-08-07 14:43:26 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
solAssert(_from.sizeOnStack() == 1, "");
|
|
|
|
solAssert(to.isValueType(), "");
|
|
|
|
solAssert(to.calldataEncodedSize() == 32, "");
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
solAssert(!to.isDynamicallyEncoded(), "");
|
|
|
|
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(value, pos) {
|
|
|
|
mstore(pos, <cleanupConvert>)
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
|
2019-02-07 11:02:15 +00:00
|
|
|
if (_from.dataStoredIn(DataLocation::Storage))
|
2017-08-07 14:43:26 +00:00
|
|
|
{
|
|
|
|
// special case: convert storage reference type to value type - this is only
|
|
|
|
// possible for library calls where we just forward the storage reference
|
2019-01-21 15:43:32 +00:00
|
|
|
solAssert(_options.encodeAsLibraryTypes, "");
|
2019-01-22 11:24:54 +00:00
|
|
|
solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
|
2018-11-29 08:34:17 +00:00
|
|
|
solAssert(to == IntegerType::uint256(), "");
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("cleanupConvert", "value");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-01-22 11:24:54 +00:00
|
|
|
string cleanupConvert;
|
2017-08-07 14:43:26 +00:00
|
|
|
if (_from == to)
|
2019-01-22 11:24:54 +00:00
|
|
|
cleanupConvert = cleanupFunction(_from) + "(value)";
|
2017-08-07 14:43:26 +00:00
|
|
|
else
|
2019-01-22 11:24:54 +00:00
|
|
|
cleanupConvert = conversionFunction(_from, to) + "(value)";
|
|
|
|
if (!_options.padded)
|
2019-03-05 16:20:28 +00:00
|
|
|
cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")";
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("cleanupConvert", cleanupConvert);
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-01-22 11:24:54 +00:00
|
|
|
string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
|
|
|
|
Type const& _givenType,
|
|
|
|
Type const& _targetType,
|
|
|
|
ABIFunctions::EncodingOptions const& _options
|
|
|
|
)
|
|
|
|
{
|
|
|
|
string functionName =
|
|
|
|
"abi_encodeUpdatedPos_" +
|
|
|
|
_givenType.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_targetType.identifier() +
|
|
|
|
_options.toFunctionNameSuffix();
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
string encoder = abiEncodingFunction(_givenType, _targetType, _options);
|
|
|
|
if (_targetType.isDynamicallyEncoded())
|
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(value, pos) -> updatedPos {
|
|
|
|
updatedPos := <encode>(value, pos)
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
("encode", encoder)
|
|
|
|
.render();
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
|
|
|
|
solAssert(encodedSize != 0, "Invalid encoded size.");
|
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(value, pos) -> updatedPos {
|
|
|
|
<encode>(value, pos)
|
|
|
|
updatedPos := add(pos, <encodedSize>)
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
("encode", encoder)
|
|
|
|
("encodedSize", toCompactHexWithPrefix(encodedSize))
|
|
|
|
.render();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-07 14:43:26 +00:00
|
|
|
string ABIFunctions::abiEncodingFunctionCalldataArray(
|
|
|
|
Type const& _from,
|
|
|
|
Type const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
solAssert(_to.isDynamicallySized(), "");
|
|
|
|
solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type.");
|
|
|
|
solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type.");
|
|
|
|
auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from);
|
|
|
|
auto const& toArrayType = dynamic_cast<ArrayType const&>(_to);
|
|
|
|
|
|
|
|
solAssert(fromArrayType.location() == DataLocation::CallData, "");
|
|
|
|
|
|
|
|
solAssert(
|
|
|
|
*fromArrayType.copyForLocation(DataLocation::Memory, true) ==
|
|
|
|
*toArrayType.copyForLocation(DataLocation::Memory, true),
|
|
|
|
""
|
|
|
|
);
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
return createFunction(functionName, [&]() {
|
2017-11-15 17:18:00 +00:00
|
|
|
solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently.");
|
2017-08-07 14:43:26 +00:00
|
|
|
// TODO if this is not a byte array, we might just copy byte-by-byte anyway,
|
|
|
|
// because the encoding is position-independent, but we have to check that.
|
|
|
|
Whiskers templ(R"(
|
2017-09-27 13:56:58 +00:00
|
|
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
2017-08-07 14:43:26 +00:00
|
|
|
function <functionName>(start, length, pos) -> end {
|
2019-02-06 11:26:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
2017-08-07 14:43:26 +00:00
|
|
|
<copyFun>(start, pos, length)
|
2019-01-22 11:24:54 +00:00
|
|
|
end := add(pos, <lengthPadded>)
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
)");
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("functionName", functionName);
|
2017-09-27 13:56:58 +00:00
|
|
|
templ("readableTypeNameFrom", _from.toString(true));
|
|
|
|
templ("readableTypeNameTo", _to.toString(true));
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("copyFun", m_utils.copyToMemoryFunction(true));
|
|
|
|
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
2017-08-07 14:43:26 +00:00
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|
|
|
ArrayType const& _from,
|
|
|
|
ArrayType const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
|
|
|
|
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
|
|
|
|
solAssert(_from.length() == _to.length(), "");
|
|
|
|
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), "");
|
|
|
|
solAssert(!_from.isByteArray(), "");
|
|
|
|
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
bool dynamic = _to.isDynamicallyEncoded();
|
|
|
|
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
|
|
|
|
bool inMemory = _from.dataStoredIn(DataLocation::Memory);
|
2019-01-22 11:24:54 +00:00
|
|
|
bool const usesTail = dynamicBase && !_options.dynamicInplace;
|
2017-08-07 14:43:26 +00:00
|
|
|
Whiskers templ(
|
2019-01-22 11:24:54 +00:00
|
|
|
usesTail ?
|
2017-08-07 14:43:26 +00:00
|
|
|
R"(
|
2017-09-27 13:56:58 +00:00
|
|
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
2017-08-07 14:43:26 +00:00
|
|
|
function <functionName>(value, pos) <return> {
|
|
|
|
let length := <lengthFun>(value)
|
2019-02-06 11:26:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
2017-08-07 14:43:26 +00:00
|
|
|
let headStart := pos
|
|
|
|
let tail := add(pos, mul(length, 0x20))
|
|
|
|
let srcPtr := <dataAreaFun>(value)
|
|
|
|
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
|
|
|
{
|
|
|
|
mstore(pos, sub(tail, headStart))
|
2017-08-24 13:20:49 +00:00
|
|
|
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail)
|
2017-08-07 14:43:26 +00:00
|
|
|
srcPtr := <nextArrayElement>(srcPtr)
|
2017-09-27 13:56:17 +00:00
|
|
|
pos := add(pos, 0x20)
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
pos := tail
|
|
|
|
<assignEnd>
|
|
|
|
}
|
|
|
|
)" :
|
|
|
|
R"(
|
2017-09-27 13:56:58 +00:00
|
|
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
2017-08-07 14:43:26 +00:00
|
|
|
function <functionName>(value, pos) <return> {
|
|
|
|
let length := <lengthFun>(value)
|
2019-02-06 11:26:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
2017-08-07 14:43:26 +00:00
|
|
|
let srcPtr := <dataAreaFun>(value)
|
|
|
|
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
|
|
|
{
|
2019-01-22 11:24:54 +00:00
|
|
|
pos := <encodeToMemoryFun>(<arrayElementAccess>, pos)
|
2017-08-07 14:43:26 +00:00
|
|
|
srcPtr := <nextArrayElement>(srcPtr)
|
|
|
|
}
|
|
|
|
<assignEnd>
|
|
|
|
}
|
|
|
|
)"
|
|
|
|
);
|
|
|
|
templ("functionName", functionName);
|
2017-09-27 13:56:58 +00:00
|
|
|
templ("readableTypeNameFrom", _from.toString(true));
|
|
|
|
templ("readableTypeNameTo", _to.toString(true));
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("return", dynamic ? " -> end " : "");
|
|
|
|
templ("assignEnd", dynamic ? "end := pos" : "");
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
|
2019-01-21 15:43:32 +00:00
|
|
|
|
|
|
|
EncodingOptions subOptions(_options);
|
|
|
|
subOptions.encodeFunctionFromStack = false;
|
2019-01-22 11:24:54 +00:00
|
|
|
subOptions.padded = true;
|
|
|
|
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
2017-08-24 13:20:49 +00:00
|
|
|
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
|
2017-08-07 14:43:26 +00:00
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiEncodingFunctionMemoryByteArray(
|
|
|
|
ArrayType const& _from,
|
|
|
|
ArrayType const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
|
|
|
|
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
|
|
|
|
solAssert(_from.length() == _to.length(), "");
|
|
|
|
solAssert(_from.dataStoredIn(DataLocation::Memory), "");
|
|
|
|
solAssert(_from.isByteArray(), "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
solAssert(_to.isByteArray(), "");
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(value, pos) -> end {
|
|
|
|
let length := <lengthFun>(value)
|
2019-01-22 11:24:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
|
|
|
<copyFun>(add(value, 0x20), pos, length)
|
|
|
|
end := add(pos, <lengthPadded>)
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("copyFun", m_utils.copyToMemoryFunction(false));
|
|
|
|
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
2017-08-07 14:43:26 +00:00
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
|
|
|
ArrayType const& _from,
|
|
|
|
ArrayType const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
|
|
|
|
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
|
|
|
|
solAssert(_from.length() == _to.length(), "");
|
|
|
|
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
if (_from.isByteArray())
|
|
|
|
{
|
|
|
|
solAssert(_to.isByteArray(), "");
|
|
|
|
Whiskers templ(R"(
|
2017-09-27 13:56:58 +00:00
|
|
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
2017-08-07 14:43:26 +00:00
|
|
|
function <functionName>(value, pos) -> ret {
|
|
|
|
let slotValue := sload(value)
|
|
|
|
switch and(slotValue, 1)
|
|
|
|
case 0 {
|
|
|
|
// short byte array
|
|
|
|
let length := and(div(slotValue, 2), 0x7f)
|
2019-01-22 11:24:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
|
|
|
mstore(pos, and(slotValue, not(0xff)))
|
|
|
|
ret := add(pos, <lengthPaddedShort>)
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
case 1 {
|
|
|
|
// long byte array
|
|
|
|
let length := div(slotValue, 2)
|
2019-01-22 11:24:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
2017-08-07 14:43:26 +00:00
|
|
|
let dataPos := <arrayDataSlot>(value)
|
|
|
|
let i := 0
|
|
|
|
for { } lt(i, length) { i := add(i, 0x20) } {
|
|
|
|
mstore(add(pos, i), sload(dataPos))
|
|
|
|
dataPos := add(dataPos, 1)
|
|
|
|
}
|
2019-01-22 11:24:54 +00:00
|
|
|
ret := add(pos, <lengthPaddedLong>)
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
2017-09-27 13:56:58 +00:00
|
|
|
templ("readableTypeNameFrom", _from.toString(true));
|
|
|
|
templ("readableTypeNameTo", _to.toString(true));
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
|
|
|
templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
|
|
|
|
templ("lengthPaddedLong", _options.padded ? "i" : "length");
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from));
|
2017-08-07 14:43:26 +00:00
|
|
|
return templ.render();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Multiple items per slot
|
|
|
|
solAssert(_from.baseType()->storageBytes() <= 16, "");
|
|
|
|
solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
|
|
|
|
solAssert(_from.baseType()->isValueType(), "");
|
|
|
|
bool dynamic = _to.isDynamicallyEncoded();
|
|
|
|
size_t storageBytes = _from.baseType()->storageBytes();
|
|
|
|
size_t itemsPerSlot = 32 / storageBytes;
|
|
|
|
// This always writes full slot contents to memory, which might be
|
2019-01-22 11:24:54 +00:00
|
|
|
// more than desired, i.e. it always writes beyond the end of memory.
|
2017-08-07 14:43:26 +00:00
|
|
|
Whiskers templ(
|
|
|
|
R"(
|
2017-09-27 13:56:58 +00:00
|
|
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
2017-08-07 14:43:26 +00:00
|
|
|
function <functionName>(value, pos) <return> {
|
|
|
|
let length := <lengthFun>(value)
|
2019-02-06 11:26:54 +00:00
|
|
|
pos := <storeLength>(pos, length)
|
2017-08-07 14:43:26 +00:00
|
|
|
let originalPos := pos
|
|
|
|
let srcPtr := <dataArea>(value)
|
|
|
|
for { let i := 0 } lt(i, length) { i := add(i, <itemsPerSlot>) }
|
|
|
|
{
|
|
|
|
let data := sload(srcPtr)
|
|
|
|
<#items>
|
|
|
|
<encodeToMemoryFun>(<shiftRightFun>(data), pos)
|
|
|
|
pos := add(pos, <elementEncodedSize>)
|
|
|
|
</items>
|
|
|
|
srcPtr := add(srcPtr, 1)
|
|
|
|
}
|
|
|
|
pos := add(originalPos, mul(length, <elementEncodedSize>))
|
|
|
|
<assignEnd>
|
|
|
|
}
|
|
|
|
)"
|
|
|
|
);
|
|
|
|
templ("functionName", functionName);
|
2017-09-27 13:56:58 +00:00
|
|
|
templ("readableTypeNameFrom", _from.toString(true));
|
|
|
|
templ("readableTypeNameTo", _to.toString(true));
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("return", dynamic ? " -> end " : "");
|
|
|
|
templ("assignEnd", dynamic ? "end := pos" : "");
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("dataArea", m_utils.arrayDataAreaFunction(_from));
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("itemsPerSlot", to_string(itemsPerSlot));
|
2019-01-22 11:24:54 +00:00
|
|
|
// We use padded size because array elements are always padded.
|
2017-08-07 14:43:26 +00:00
|
|
|
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
|
|
|
templ("elementEncodedSize", elementEncodedSize);
|
2019-01-21 15:43:32 +00:00
|
|
|
|
|
|
|
EncodingOptions subOptions(_options);
|
|
|
|
subOptions.encodeFunctionFromStack = false;
|
2019-01-22 11:24:54 +00:00
|
|
|
subOptions.padded = true;
|
2017-08-07 14:43:26 +00:00
|
|
|
string encodeToMemoryFun = abiEncodingFunction(
|
|
|
|
*_from.baseType(),
|
|
|
|
*_to.baseType(),
|
2019-01-21 15:43:32 +00:00
|
|
|
subOptions
|
2017-08-07 14:43:26 +00:00
|
|
|
);
|
|
|
|
templ("encodeToMemoryFun", encodeToMemoryFun);
|
|
|
|
std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
|
|
|
|
for (size_t i = 0; i < itemsPerSlot; ++i)
|
2019-03-05 16:20:28 +00:00
|
|
|
items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8);
|
2017-08-07 14:43:26 +00:00
|
|
|
templ("items", items);
|
|
|
|
return templ.render();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-24 13:20:49 +00:00
|
|
|
string ABIFunctions::abiEncodingFunctionStruct(
|
|
|
|
StructType const& _from,
|
|
|
|
StructType const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-24 13:20:49 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-24 13:20:49 +00:00
|
|
|
|
2017-11-15 17:18:00 +00:00
|
|
|
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
|
2017-08-24 13:20:49 +00:00
|
|
|
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
bool fromStorage = _from.location() == DataLocation::Storage;
|
|
|
|
bool dynamic = _to.isDynamicallyEncoded();
|
|
|
|
Whiskers templ(R"(
|
2017-09-27 13:56:58 +00:00
|
|
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
2017-08-24 13:20:49 +00:00
|
|
|
function <functionName>(value, pos) <return> {
|
|
|
|
let tail := add(pos, <headSize>)
|
|
|
|
<init>
|
|
|
|
<#members>
|
|
|
|
{
|
|
|
|
// <memberName>
|
2019-01-22 12:46:33 +00:00
|
|
|
<preprocess>
|
|
|
|
let memberValue := <retrieveValue>
|
2017-08-24 13:20:49 +00:00
|
|
|
<encode>
|
|
|
|
}
|
|
|
|
</members>
|
|
|
|
<assignEnd>
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
2017-09-27 13:56:58 +00:00
|
|
|
templ("readableTypeNameFrom", _from.toString(true));
|
|
|
|
templ("readableTypeNameTo", _to.toString(true));
|
2017-08-24 13:20:49 +00:00
|
|
|
templ("return", dynamic ? " -> end " : "");
|
2019-01-22 11:24:54 +00:00
|
|
|
if (dynamic && _options.dynamicInplace)
|
|
|
|
templ("assignEnd", "end := pos");
|
|
|
|
else if (dynamic && !_options.dynamicInplace)
|
|
|
|
templ("assignEnd", "end := tail");
|
|
|
|
else
|
|
|
|
templ("assignEnd", "");
|
2017-08-24 13:20:49 +00:00
|
|
|
// to avoid multiple loads from the same slot for subsequent members
|
|
|
|
templ("init", fromStorage ? "let slotValue := 0" : "");
|
|
|
|
u256 previousSlotOffset(-1);
|
|
|
|
u256 encodingOffset = 0;
|
|
|
|
vector<map<string, string>> members;
|
|
|
|
for (auto const& member: _to.members(nullptr))
|
|
|
|
{
|
|
|
|
solAssert(member.type, "");
|
|
|
|
if (!member.type->canLiveOutsideStorage())
|
|
|
|
continue;
|
2019-01-21 15:43:32 +00:00
|
|
|
TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false);
|
2018-07-18 13:46:23 +00:00
|
|
|
solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented.");
|
2017-08-24 13:20:49 +00:00
|
|
|
auto memberTypeFrom = _from.memberType(member.name);
|
|
|
|
solAssert(memberTypeFrom, "");
|
|
|
|
bool dynamicMember = memberTypeTo->isDynamicallyEncoded();
|
|
|
|
if (dynamicMember)
|
|
|
|
solAssert(dynamic, "");
|
2019-01-22 12:46:33 +00:00
|
|
|
|
|
|
|
members.push_back({});
|
|
|
|
members.back()["preprocess"] = "";
|
|
|
|
|
2017-08-24 13:20:49 +00:00
|
|
|
if (fromStorage)
|
|
|
|
{
|
|
|
|
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
|
|
|
|
u256 storageSlotOffset;
|
|
|
|
size_t intraSlotOffset;
|
|
|
|
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
|
|
|
|
if (memberTypeFrom->isValueType())
|
|
|
|
{
|
|
|
|
if (storageSlotOffset != previousSlotOffset)
|
|
|
|
{
|
2019-01-22 12:46:33 +00:00
|
|
|
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
2017-08-24 13:20:49 +00:00
|
|
|
previousSlotOffset = storageSlotOffset;
|
|
|
|
}
|
2019-03-05 16:20:28 +00:00
|
|
|
members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
2017-08-24 13:20:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
|
|
|
|
solAssert(intraSlotOffset == 0, "");
|
2019-01-22 12:46:33 +00:00
|
|
|
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")";
|
2017-08-24 13:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
|
2019-01-22 12:46:33 +00:00
|
|
|
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
|
2017-08-24 13:20:49 +00:00
|
|
|
}
|
2019-01-22 12:46:33 +00:00
|
|
|
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions subOptions(_options);
|
|
|
|
subOptions.encodeFunctionFromStack = false;
|
2019-01-22 11:24:54 +00:00
|
|
|
// Like with arrays, struct members are always padded.
|
|
|
|
subOptions.padded = true;
|
|
|
|
|
|
|
|
string encode;
|
|
|
|
if (_options.dynamicInplace)
|
|
|
|
encode = Whiskers{"pos := <encode>(memberValue, pos)"}
|
|
|
|
("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions))
|
2019-02-13 15:56:46 +00:00
|
|
|
.render();
|
2019-01-22 11:24:54 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
Whiskers encodeTempl(
|
|
|
|
dynamicMember ?
|
|
|
|
string(R"(
|
|
|
|
mstore(add(pos, <encodingOffset>), sub(tail, pos))
|
|
|
|
tail := <abiEncode>(memberValue, tail)
|
|
|
|
)") :
|
|
|
|
"<abiEncode>(memberValue, add(pos, <encodingOffset>))"
|
|
|
|
);
|
|
|
|
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
|
|
|
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
|
|
|
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
|
|
|
|
encode = encodeTempl.render();
|
|
|
|
}
|
|
|
|
members.back()["encode"] = encode;
|
2017-08-24 13:20:49 +00:00
|
|
|
|
|
|
|
members.back()["memberName"] = member.name;
|
|
|
|
}
|
|
|
|
templ("members", members);
|
2019-01-22 11:24:54 +00:00
|
|
|
if (_options.dynamicInplace)
|
|
|
|
solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size.");
|
2017-08-24 13:20:49 +00:00
|
|
|
templ("headSize", toCompactHexWithPrefix(encodingOffset));
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-07 14:43:26 +00:00
|
|
|
string ABIFunctions::abiEncodingFunctionStringLiteral(
|
|
|
|
Type const& _from,
|
|
|
|
Type const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
solAssert(_from.category() == Type::Category::StringLiteral, "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
auto const& strType = dynamic_cast<StringLiteralType const&>(_from);
|
|
|
|
string const& value = strType.value();
|
|
|
|
solAssert(_from.sizeOnStack() == 0, "");
|
|
|
|
|
|
|
|
if (_to.isDynamicallySized())
|
|
|
|
{
|
2019-01-22 11:24:54 +00:00
|
|
|
solAssert(_to.category() == Type::Category::Array, "");
|
2017-08-07 14:43:26 +00:00
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(pos) -> end {
|
2019-01-22 11:24:54 +00:00
|
|
|
pos := <storeLength>(pos, <length>)
|
2017-08-07 14:43:26 +00:00
|
|
|
<#word>
|
|
|
|
mstore(add(pos, <offset>), <wordValue>)
|
|
|
|
</word>
|
|
|
|
end := add(pos, <overallSize>)
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
|
2018-06-12 18:28:52 +00:00
|
|
|
// TODO this can make use of CODECOPY for large strings once we have that in Yul
|
2017-08-07 14:43:26 +00:00
|
|
|
size_t words = (value.size() + 31) / 32;
|
|
|
|
templ("length", to_string(value.size()));
|
2019-01-22 11:24:54 +00:00
|
|
|
templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast<ArrayType const&>(_to), _options));
|
|
|
|
if (_options.padded)
|
|
|
|
templ("overallSize", to_string(words * 32));
|
|
|
|
else
|
|
|
|
templ("overallSize", to_string(value.size()));
|
|
|
|
|
2017-08-07 14:43:26 +00:00
|
|
|
vector<map<string, string>> wordParams(words);
|
|
|
|
for (size_t i = 0; i < words; ++i)
|
|
|
|
{
|
2019-01-22 11:24:54 +00:00
|
|
|
wordParams[i]["offset"] = to_string(i * 32);
|
2017-08-07 14:43:26 +00:00
|
|
|
wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex();
|
|
|
|
}
|
|
|
|
templ("word", wordParams);
|
|
|
|
return templ.render();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
solAssert(_to.category() == Type::Category::FixedBytes, "");
|
|
|
|
solAssert(value.size() <= 32, "");
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(pos) {
|
|
|
|
mstore(pos, <wordValue>)
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex());
|
|
|
|
return templ.render();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiEncodingFunctionFunctionType(
|
|
|
|
FunctionType const& _from,
|
|
|
|
Type const& _to,
|
2019-01-21 15:43:32 +00:00
|
|
|
EncodingOptions const& _options
|
2017-08-07 14:43:26 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
solAssert(_from.kind() == FunctionType::Kind::External, "");
|
|
|
|
solAssert(_from == _to, "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_encode_" +
|
|
|
|
_from.identifier() +
|
|
|
|
"_to_" +
|
|
|
|
_to.identifier() +
|
2019-01-21 15:43:32 +00:00
|
|
|
_options.toFunctionNameSuffix();
|
2017-08-07 14:43:26 +00:00
|
|
|
|
2019-01-21 15:43:32 +00:00
|
|
|
if (_options.encodeFunctionFromStack)
|
2017-08-07 14:43:26 +00:00
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
return Whiskers(R"(
|
2017-10-04 14:56:54 +00:00
|
|
|
function <functionName>(addr, function_id, pos) {
|
|
|
|
mstore(pos, <combineExtFun>(addr, function_id))
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
2019-03-05 16:20:28 +00:00
|
|
|
("combineExtFun", m_utils.combineExternalFunctionIdFunction())
|
2017-08-07 14:43:26 +00:00
|
|
|
.render();
|
|
|
|
});
|
|
|
|
else
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
return Whiskers(R"(
|
2017-10-04 14:56:54 +00:00
|
|
|
function <functionName>(addr_and_function_id, pos) {
|
|
|
|
mstore(pos, <cleanExtFun>(addr_and_function_id))
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
2017-10-04 14:56:54 +00:00
|
|
|
("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
|
2017-08-07 14:43:26 +00:00
|
|
|
.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-09-01 11:59:21 +00:00
|
|
|
string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack)
|
|
|
|
{
|
2017-10-13 15:29:09 +00:00
|
|
|
// The decoding function has to perform bounds checks unless it decodes a value type.
|
|
|
|
// Conversely, bounds checks have to be performed before the decoding function
|
|
|
|
// of a value type is called.
|
|
|
|
|
2017-09-01 11:59:21 +00:00
|
|
|
TypePointer decodingType = _type.decodingType();
|
|
|
|
solAssert(decodingType, "");
|
|
|
|
|
|
|
|
if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType.get()))
|
|
|
|
{
|
|
|
|
if (arrayType->dataStoredIn(DataLocation::CallData))
|
|
|
|
{
|
|
|
|
solAssert(!_fromMemory, "");
|
|
|
|
return abiDecodingFunctionCalldataArray(*arrayType);
|
|
|
|
}
|
|
|
|
else if (arrayType->isByteArray())
|
|
|
|
return abiDecodingFunctionByteArray(*arrayType, _fromMemory);
|
|
|
|
else
|
|
|
|
return abiDecodingFunctionArray(*arrayType, _fromMemory);
|
|
|
|
}
|
|
|
|
else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get()))
|
2019-02-05 19:29:57 +00:00
|
|
|
{
|
|
|
|
if (structType->dataStoredIn(DataLocation::CallData))
|
|
|
|
{
|
|
|
|
solAssert(!_fromMemory, "");
|
|
|
|
return abiDecodingFunctionCalldataStruct(*structType);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return abiDecodingFunctionStruct(*structType, _fromMemory);
|
|
|
|
}
|
2017-09-01 11:59:21 +00:00
|
|
|
else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get()))
|
|
|
|
return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
|
2017-10-13 15:29:09 +00:00
|
|
|
else
|
|
|
|
return abiDecodingFunctionValueType(_type, _fromMemory);
|
|
|
|
}
|
2017-09-01 11:59:21 +00:00
|
|
|
|
2019-02-13 15:22:42 +00:00
|
|
|
string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromMemory)
|
2017-10-13 15:29:09 +00:00
|
|
|
{
|
|
|
|
TypePointer decodingType = _type.decodingType();
|
|
|
|
solAssert(decodingType, "");
|
2017-09-01 11:59:21 +00:00
|
|
|
solAssert(decodingType->sizeOnStack() == 1, "");
|
|
|
|
solAssert(decodingType->isValueType(), "");
|
|
|
|
solAssert(decodingType->calldataEncodedSize() == 32, "");
|
|
|
|
solAssert(!decodingType->isDynamicallyEncoded(), "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier() +
|
|
|
|
(_fromMemory ? "_fromMemory" : "");
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
Whiskers templ(R"(
|
|
|
|
function <functionName>(offset, end) -> value {
|
|
|
|
value := <cleanup>(<load>(offset))
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
templ("load", _fromMemory ? "mload" : "calldataload");
|
|
|
|
// Cleanup itself should use the type and not decodingType, because e.g.
|
|
|
|
// the decoding type of an enum is a plain int.
|
|
|
|
templ("cleanup", cleanupFunction(_type, true));
|
|
|
|
return templ.render();
|
|
|
|
});
|
2017-10-13 15:29:09 +00:00
|
|
|
|
2017-09-01 11:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory)
|
|
|
|
{
|
|
|
|
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
|
|
|
solAssert(!_type.isByteArray(), "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier() +
|
|
|
|
(_fromMemory ? "_fromMemory" : "");
|
|
|
|
|
|
|
|
solAssert(!_type.dataStoredIn(DataLocation::Storage), "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
string load = _fromMemory ? "mload" : "calldataload";
|
|
|
|
bool dynamicBase = _type.baseType()->isDynamicallyEncoded();
|
|
|
|
Whiskers templ(
|
|
|
|
R"(
|
|
|
|
// <readableTypeName>
|
|
|
|
function <functionName>(offset, end) -> array {
|
2017-12-15 08:47:06 +00:00
|
|
|
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
let length := <retrieveLength>
|
|
|
|
array := <allocate>(<allocationSize>(length))
|
|
|
|
let dst := array
|
|
|
|
<storeLength> // might update offset and dst
|
|
|
|
let src := offset
|
|
|
|
<staticBoundsCheck>
|
|
|
|
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
|
|
|
{
|
|
|
|
let elementPos := <retrieveElementPos>
|
|
|
|
mstore(dst, <decodingFun>(elementPos, end))
|
|
|
|
dst := add(dst, 0x20)
|
|
|
|
src := add(src, <baseEncodedSize>)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)"
|
|
|
|
);
|
|
|
|
templ("functionName", functionName);
|
|
|
|
templ("readableTypeName", _type.toString(true));
|
|
|
|
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("allocate", m_utils.allocationFunction());
|
|
|
|
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
2017-09-01 11:59:21 +00:00
|
|
|
if (_type.isDynamicallySized())
|
|
|
|
templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
|
|
|
|
else
|
|
|
|
templ("storeLength", "");
|
|
|
|
if (dynamicBase)
|
|
|
|
{
|
|
|
|
templ("staticBoundsCheck", "");
|
|
|
|
templ("retrieveElementPos", "add(offset, " + load + "(src))");
|
|
|
|
templ("baseEncodedSize", "0x20");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize());
|
2017-12-15 08:47:06 +00:00
|
|
|
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }");
|
2017-09-01 11:59:21 +00:00
|
|
|
templ("retrieveElementPos", "src");
|
|
|
|
templ("baseEncodedSize", baseEncodedSize);
|
|
|
|
}
|
|
|
|
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
|
|
|
{
|
|
|
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
|
|
|
if (!_type.isDynamicallySized())
|
|
|
|
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
|
2019-02-13 08:22:45 +00:00
|
|
|
solAssert(_type.baseType()->calldataEncodedSize() > 0, "");
|
2017-09-01 11:59:21 +00:00
|
|
|
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier();
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
string templ;
|
|
|
|
if (_type.isDynamicallySized())
|
|
|
|
templ = R"(
|
|
|
|
// <readableTypeName>
|
|
|
|
function <functionName>(offset, end) -> arrayPos, length {
|
2017-12-15 08:47:06 +00:00
|
|
|
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
length := calldataload(offset)
|
2017-12-15 08:47:06 +00:00
|
|
|
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
arrayPos := add(offset, 0x20)
|
2019-02-13 08:22:45 +00:00
|
|
|
if gt(add(arrayPos, mul(length, <baseEncodedSize>)), end) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
}
|
|
|
|
)";
|
|
|
|
else
|
|
|
|
templ = R"(
|
|
|
|
// <readableTypeName>
|
|
|
|
function <functionName>(offset, end) -> arrayPos {
|
|
|
|
arrayPos := offset
|
2017-12-15 08:47:06 +00:00
|
|
|
if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
}
|
|
|
|
)";
|
|
|
|
Whiskers w{templ};
|
|
|
|
w("functionName", functionName);
|
|
|
|
w("readableTypeName", _type.toString(true));
|
|
|
|
w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
|
2019-02-13 08:22:45 +00:00
|
|
|
if (!_type.isDynamicallySized())
|
|
|
|
w("length", toCompactHexWithPrefix(_type.length()));
|
2017-09-01 11:59:21 +00:00
|
|
|
return w.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory)
|
|
|
|
{
|
|
|
|
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
|
|
|
solAssert(_type.isByteArray(), "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier() +
|
|
|
|
(_fromMemory ? "_fromMemory" : "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
Whiskers templ(
|
|
|
|
R"(
|
|
|
|
function <functionName>(offset, end) -> array {
|
2017-12-15 08:47:06 +00:00
|
|
|
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
let length := <load>(offset)
|
|
|
|
array := <allocate>(<allocationSize>(length))
|
|
|
|
mstore(array, length)
|
|
|
|
let src := add(offset, 0x20)
|
|
|
|
let dst := add(array, 0x20)
|
2017-12-15 08:47:06 +00:00
|
|
|
if gt(add(src, length), end) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
<copyToMemFun>(src, dst, length)
|
|
|
|
}
|
|
|
|
)"
|
|
|
|
);
|
|
|
|
templ("functionName", functionName);
|
|
|
|
templ("load", _fromMemory ? "mload" : "calldataload");
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("allocate", m_utils.allocationFunction());
|
|
|
|
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
|
|
|
templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory));
|
2017-09-01 11:59:21 +00:00
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-02-05 19:29:57 +00:00
|
|
|
string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
|
|
|
{
|
|
|
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
|
|
|
solAssert(_type.calldataEncodedSize(true) != 0, "");
|
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier();
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
Whiskers w{R"(
|
|
|
|
// <readableTypeName>
|
|
|
|
function <functionName>(offset, end) -> value {
|
|
|
|
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
|
|
|
|
value := offset
|
|
|
|
}
|
|
|
|
)"};
|
|
|
|
w("functionName", functionName);
|
|
|
|
w("readableTypeName", _type.toString(true));
|
|
|
|
w("minimumSize", to_string(_type.calldataEncodedSize(true)));
|
|
|
|
return w.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-09-01 11:59:21 +00:00
|
|
|
string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory)
|
|
|
|
{
|
2019-02-05 19:29:57 +00:00
|
|
|
solAssert(!_type.dataStoredIn(DataLocation::CallData), "");
|
2017-09-01 11:59:21 +00:00
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier() +
|
|
|
|
(_fromMemory ? "_fromMemory" : "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
Whiskers templ(R"(
|
|
|
|
// <readableTypeName>
|
|
|
|
function <functionName>(headStart, end) -> value {
|
2017-12-15 08:47:06 +00:00
|
|
|
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
|
2017-09-01 11:59:21 +00:00
|
|
|
value := <allocate>(<memorySize>)
|
|
|
|
<#members>
|
|
|
|
{
|
|
|
|
// <memberName>
|
|
|
|
<decode>
|
|
|
|
}
|
|
|
|
</members>
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
templ("functionName", functionName);
|
|
|
|
templ("readableTypeName", _type.toString(true));
|
2019-03-05 16:20:28 +00:00
|
|
|
templ("allocate", m_utils.allocationFunction());
|
2017-09-01 11:59:21 +00:00
|
|
|
solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
|
|
|
|
templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
|
|
|
|
size_t headPos = 0;
|
|
|
|
vector<map<string, string>> members;
|
|
|
|
for (auto const& member: _type.members(nullptr))
|
|
|
|
{
|
|
|
|
solAssert(member.type, "");
|
|
|
|
solAssert(member.type->canLiveOutsideStorage(), "");
|
|
|
|
auto decodingType = member.type->decodingType();
|
|
|
|
solAssert(decodingType, "");
|
|
|
|
bool dynamic = decodingType->isDynamicallyEncoded();
|
2017-10-13 15:29:09 +00:00
|
|
|
Whiskers memberTempl(
|
|
|
|
dynamic ?
|
|
|
|
R"(
|
|
|
|
let offset := <load>(add(headStart, <pos>))
|
2017-12-15 08:47:06 +00:00
|
|
|
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
|
2017-10-13 15:29:09 +00:00
|
|
|
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
|
|
|
|
)" :
|
|
|
|
R"(
|
|
|
|
let offset := <pos>
|
|
|
|
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
|
|
|
|
)"
|
|
|
|
);
|
2017-09-01 11:59:21 +00:00
|
|
|
memberTempl("load", _fromMemory ? "mload" : "calldataload");
|
|
|
|
memberTempl("pos", to_string(headPos));
|
|
|
|
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
|
|
|
|
memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false));
|
|
|
|
|
|
|
|
members.push_back({});
|
|
|
|
members.back()["decode"] = memberTempl.render();
|
|
|
|
members.back()["memberName"] = member.name;
|
|
|
|
headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize();
|
|
|
|
}
|
|
|
|
templ("members", members);
|
|
|
|
templ("minimumSize", toCompactHexWithPrefix(headPos));
|
|
|
|
return templ.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack)
|
|
|
|
{
|
|
|
|
solAssert(_type.kind() == FunctionType::Kind::External, "");
|
|
|
|
|
|
|
|
string functionName =
|
|
|
|
"abi_decode_" +
|
|
|
|
_type.identifier() +
|
|
|
|
(_fromMemory ? "_fromMemory" : "") +
|
|
|
|
(_forUseOnStack ? "_onStack" : "");
|
|
|
|
|
|
|
|
return createFunction(functionName, [&]() {
|
|
|
|
if (_forUseOnStack)
|
|
|
|
{
|
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(offset, end) -> addr, function_selector {
|
|
|
|
addr, function_selector := <splitExtFun>(<load>(offset))
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
("load", _fromMemory ? "mload" : "calldataload")
|
2019-03-05 16:20:28 +00:00
|
|
|
("splitExtFun", m_utils.splitExternalFunctionIdFunction())
|
2017-09-01 11:59:21 +00:00
|
|
|
.render();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(offset, end) -> fun {
|
|
|
|
fun := <cleanExtFun>(<load>(offset))
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
("load", _fromMemory ? "mload" : "calldataload")
|
|
|
|
("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
|
|
|
|
.render();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-01-22 11:24:54 +00:00
|
|
|
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
|
2019-02-06 11:26:54 +00:00
|
|
|
{
|
2019-01-22 11:24:54 +00:00
|
|
|
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
|
2019-02-06 11:26:54 +00:00
|
|
|
return createFunction(functionName, [&]() {
|
2019-01-22 11:24:54 +00:00
|
|
|
if (_type.isDynamicallySized() && !_options.dynamicInplace)
|
2019-02-06 11:26:54 +00:00
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(pos, length) -> updated_pos {
|
|
|
|
mstore(pos, length)
|
|
|
|
updated_pos := add(pos, 0x20)
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
.render();
|
|
|
|
else
|
|
|
|
return Whiskers(R"(
|
|
|
|
function <functionName>(pos, length) -> updated_pos {
|
|
|
|
updated_pos := pos
|
|
|
|
}
|
|
|
|
)")
|
|
|
|
("functionName", functionName)
|
|
|
|
.render();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-07 14:43:26 +00:00
|
|
|
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
|
|
|
{
|
2019-03-05 16:20:28 +00:00
|
|
|
return m_functionCollector->createFunction(_name, _creator);
|
2017-08-07 14:43:26 +00:00
|
|
|
}
|
|
|
|
|
2018-01-17 11:05:43 +00:00
|
|
|
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
|
|
|
|
{
|
|
|
|
string name = createFunction(_name, _creator);
|
|
|
|
m_externallyUsedFunctions.insert(name);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2017-08-07 14:43:26 +00:00
|
|
|
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
|
|
|
{
|
|
|
|
size_t headSize = 0;
|
|
|
|
for (auto const& t: _targetTypes)
|
|
|
|
{
|
|
|
|
if (t->isDynamicallyEncoded())
|
|
|
|
headSize += 0x20;
|
|
|
|
else
|
|
|
|
headSize += t->calldataEncodedSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
return headSize;
|
|
|
|
}
|
2019-01-21 15:43:32 +00:00
|
|
|
|