Skip to content
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

Merged
merged 4 commits into from
Nov 29, 2017
Merged

ABI decoder #2863

merged 4 commits into from
Nov 29, 2017

Conversation

chriseth
Copy link
Contributor

@chriseth chriseth commented Sep 1, 2017

  • always check that offset + min size <= size, if possible do it efficiently (i.e. group statically sized types) - reconfirm this, currently we only check offset < end and not offset + size < end
  • implement struct decoding
  • manual coverage testing
  • clarify if we need to call "interfaceType()" for the decoder types.

@@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ABIDecoderV2?

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tupleDecoder is actually missing

Copy link
Contributor Author

@chriseth chriseth Sep 1, 2017

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 ;)

@chriseth
Copy link
Contributor Author

Latest version should work for all value types - but does not yet have any tests.

@axic
Copy link
Member

axic commented Sep 19, 2017

@chriseth can you rebase on top of #2924 ?

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;``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing parentheses.

@@ -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;
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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");
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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";
Copy link
Member

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.

Copy link
Contributor Author

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.

@chriseth chriseth force-pushed the structDecoder branch 3 times, most recently from e11e681 to 4992c5f Compare September 25, 2017 17:38
@chriseth
Copy link
Contributor Author

Rebased, fixed some stuff, did manual coverage tests and ran against full EndToEnd tests.

The only thing left here is the decoder for structs.

@chriseth chriseth changed the title [WIP] ABI decoder ABI decoder Oct 4, 2017
@chriseth
Copy link
Contributor Author

chriseth commented Oct 4, 2017

@axic this is ready for review.

@axic
Copy link
Member

axic commented Oct 5, 2017

Needs rebasing since the other ABI PRs were merged.

/// Can allocate memory.
/// Stack pre: <source_offset>
/// Stack post: <value0> <value1> ... <valuen>
void abiDecode(TypePointers const& _parameterTypes, bool _fromMemory = false);
Copy link
Member

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);
Copy link
Member

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)
Copy link
Member

@axic axic Oct 5, 2017

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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);
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Member

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.

@@ -146,6 +160,29 @@ class ABIFunctions
bool _fromStack
);

/// @returns the name of the ABI decodinf function for the given type
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray comment


BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework)

BOOST_AUTO_TEST_CASE(BOTH_ENCODERS_macro)
Copy link
Member

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.

@chriseth
Copy link
Contributor Author

Closes #3049

@chriseth
Copy link
Contributor Author

@axic this is ready to be merged from my side.

@axic
Copy link
Member

axic commented Nov 22, 2017

Can some of the commits be squashed? Many just say "decoder", "decoder fixes", "decoder tests" :)

@axic
Copy link
Member

axic commented Nov 22, 2017

Also need to be rebased and changelog updated.

@chriseth
Copy link
Contributor Author

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));
Copy link
Member

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?

Copy link
Contributor Author

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.

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());
Copy link
Member

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?

Copy link
Contributor Author

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.

@chriseth
Copy link
Contributor Author

Added short input tests for all types the old decoder can handle.

memPtr := mload(<freeMemoryPointer>)
let newFreePtr := add(memPtr, size)
// protect against overflow
switch or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) case 1 { revert(0, 0) }
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check all switches later, I think we should merge this pr without that change.

Copy link
Member

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.

@chriseth
Copy link
Contributor Author

Can this be merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants