-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ABI decoder #2863
ABI decoder #2863
Conversation
@@ -325,6 +325,16 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter | |||
{ | |||
// We do not check the calldata size, everything is zero-padded | |||
|
|||
TODO: What about external functions? | |||
if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ABIDecoderV2
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should have named it differently. Do you want to have a second experimental feature name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a different feature, isn't? Or do you think they would mature the same rate? I mean the question is: would you consider to promote one without the other out of experimental status or would prefer to only promote them together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main feature is being able to call a function on a different contract and pass structs and nested arrays. You always need both the encoder and the decoder.
I also think that the decoder should be easier to write, because you do not have to deal with storage.
auto ret = m_context.pushNewTag(); | ||
m_context << Instruction::SWAP1; | ||
|
||
string decoderName = m_context.abiFunctions().tupleDecoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tupleDecoder
is actually missing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just a quick 'save to github in case anything happens over the weekend'. It practically contains no code ;)
9c759b9
to
053c998
Compare
Latest version should work for all value types - but does not yet have any tests. |
Changelog.md
Outdated
@@ -3,6 +3,8 @@ | |||
Features: | |||
* Optimizer: Add new optimization step to remove unused ``JUMPDEST``s. | |||
* Type Checker: Warn on using literals as tight packing parameters in ``keccak256``, ``sha3``, ``sha256`` and ``ripemd160``. | |||
* Code Generator: New ABI decoder which supports structs and arbitrarily nested | |||
arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing closing parentheses.
db4662d
to
0a4cfc6
Compare
libsolidity/ast/Types.h
Outdated
@@ -748,6 +748,7 @@ class StructType: public ReferenceType | |||
{ | |||
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : shared_from_this(); | |||
} | |||
virtual TypePointer decodingType() const override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
encodingType
above seems to be the exact same? Also if two functions must exists it is probably better placing them next to each other, either header or code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are different for some types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, but removed it from struct because the default is to use the encoding type.
case Type::Category::Struct: | ||
solAssert(false, "Struct cleanup requested."); | ||
solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); | ||
templ("body", "cleaned := value"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the actual cleanup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no cleanup to be performed for a storage pointer. All 2**256 values are valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this part of the change is not restricted to this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the encoder does not call cleanup on structs - the control flow branches off earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or actually, better explanation: The encoder has a "source type" and a "target type", while the decoder only has a single type. For structs, the target type is already an integer and not a struct, and thus the cleanup will be called on the integer.
for (auto const& t: _types) | ||
functionName += t->identifier(); | ||
if (_fromMemory) | ||
functionName += "_fromMemory"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, wondering if we should commit to camelcase or underscores, this one mixes the two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
underscores are used to separate components.
e11e681
to
4992c5f
Compare
Rebased, fixed some stuff, did manual coverage tests and ran against full EndToEnd tests. The only thing left here is the decoder for structs. |
4992c5f
to
5cfde8d
Compare
@axic this is ready for review. |
Needs rebasing since the other ABI PRs were merged. |
libsolidity/codegen/CompilerUtils.h
Outdated
/// Can allocate memory. | ||
/// Stack pre: <source_offset> | ||
/// Stack post: <value0> <value1> ... <valuen> | ||
void abiDecode(TypePointers const& _parameterTypes, bool _fromMemory = false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency I'd go with abiDecodeV2
for the moment.
// Use the new JULIA-based decoding function | ||
auto stackHeightBefore = m_context.stackHeight(); | ||
CompilerUtils utils(m_context); | ||
utils.abiDecode(_typeParameters, _fromMemory); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rest of the code uses CompilerUtils(m_context).
/// into memory. If @a _fromMemory is true, decodes from memory instead of | ||
/// from calldata. | ||
/// Can allocate memory. | ||
/// Inputs: <source_offset> <source_end> (layout reversed on stack) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the inputs if _fromMemory
is true, otherwise no inputs or is it an offset within the calldata?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are either pointers to calldata or to memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, are those both absolute pointers or is source_end
actually source_len
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both are absolute pointers.
/// Outputs: <value0> <value1> ... <valuen> | ||
/// The values represent stack slots. If a type occupies more or less than one | ||
/// stack slot, it takes exactly that number of values. | ||
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it returns the function name which has a signature of <name>(source_offset, source_end) -> <v0>, <v1>, ..., <vn>
. is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I was going to try it in the IR pull request and that API should work well.
0edf658
to
27e7b46
Compare
27e7b46
to
fe1230a
Compare
libsolidity/codegen/ABIFunctions.h
Outdated
@@ -146,6 +160,29 @@ class ABIFunctions | |||
bool _fromStack | |||
); | |||
|
|||
/// @returns the name of the ABI decodinf function for the given type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
decodinf
@@ -318,6 +318,7 @@ void CompilerContext::appendInlineAssembly( | |||
|
|||
ErrorList errors; | |||
ErrorReporter errorReporter(errors); | |||
// cout << _assembly << endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stray comment
@@ -895,6 +904,7 @@ void ContractCompiler::appendMissingFunctions() | |||
} | |||
m_context.appendMissingLowLevelFunctions(); | |||
string abiFunctions = m_context.abiFunctions().requestedFunctions(); | |||
// cout << abiFunctions << endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stray comment
test/libsolidity/ABIDecoderTests.cpp
Outdated
|
||
BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework) | ||
|
||
BOOST_AUTO_TEST_CASE(BOTH_ENCODERS_macro) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mixed case test case name.
6072d29
to
019b816
Compare
Closes #3049 |
@axic this is ready to be merged from my side. |
019b816
to
cc3b651
Compare
Can some of the commits be squashed? Many just say "decoder", "decoder fixes", "decoder tests" :) |
Also need to be rebased and changelog updated. |
cc3b651
to
80bce92
Compare
Rebased and changed changelog. |
@@ -369,7 +357,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) | |||
)"; | |||
BOTH_ENCODERS( | |||
compileAndRun(sourceCode); | |||
callContractFunction("f(uint256)"); | |||
callContractFunction("f(uint256)", u256(0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just remind me: does the newer decoder changes semantics and requires the minimum length of the encoding present as calldata?
Do we have a specific test for short data with the new decoder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the new decoder does not tolerate short data and there are multiple tests.
test/libsolidity/ABIDecoderTests.cpp
Outdated
compileAndRun(sourceCode); | ||
ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1)); | ||
ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0)); | ||
ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), encodeArgs()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests the new encoder to abort on short input. Should this case have the same case for the old encoder allowing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add that if you want.
80bce92
to
b3f2daf
Compare
Added short input tests for all types the old decoder can handle. |
b3f2daf
to
d74599d
Compare
memPtr := mload(<freeMemoryPointer>) | ||
let newFreePtr := add(memPtr, size) | ||
// protect against overflow | ||
switch or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) case 1 { revert(0, 0) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be changed to if
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check all switch
es later, I think we should merge this pr without that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind, the only really safe ones without review are the those checking for case 0
.
Can this be merged? |
d74599d
to
9d8e3ff
Compare
offset < end
and notoffset + size < end