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

Add abi.encode and abi.encodePacked #2980

Merged
merged 5 commits into from
Apr 12, 2018
Merged

Add abi.encode and abi.encodePacked #2980

merged 5 commits into from
Apr 12, 2018

Conversation

axic
Copy link
Member

@axic axic commented Sep 27, 2017

Fixes #1707. Continuation of #2851 (managed to overwrite that branch with something else)

Add the global abi with the following four functions:

  • abi.encode(...) -> bytes
  • abi.encodePacked(...) -> bytes
  • abi.encodeWithSelector(bytes4, ...) -> bytes
  • abi.encodeWithSignature(string, ...) -> bytes

abi.encode ABI-encodes the provided arguments according to their types. abi.encodePacked performs packed encoding. abi.encodeWithSelector(a, x, ...) acts like abi.encode(x, ...) but prepends the four bytes in a. abi.encodeWithSignature(a, x, ...) is identical to abi.encode(bytes4(keccak256(a)), x, ...).

TODO:

  • test that the selector functions properly type check the first parameter
  • test encoding interleaved with other memory operations
  • test that encoding functions are non-pure if they arguments are non-pure

@axic
Copy link
Member Author

axic commented Sep 27, 2017

Needs tests with more types.

@axic axic force-pushed the abi-api branch 3 times, most recently from c8c8553 to 6ad5c08 Compare September 27, 2017 12:34
}
function g(uint8 a) {
bytes request = abi.encode(
bytes4(keccak256(abi.encodePacked("f(uint8,string)"))),
Copy link
Member Author

Choose a reason for hiding this comment

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

This made me realise we could offer abi.encodeSelector(string) as a shorthand for this. Though having it a library seems better, too bad abi cannot be extended by libraries (yet).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we do want that, but perhaps encodeWithSelector

Copy link
Member Author

Choose a reason for hiding this comment

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

There are two options. I was going with:

abi.encode(
  abi.encodeSelector("f(uint8,string)"),
  a,
  "He"
);

Otherwise we can have three functions with varargs:

  • abi.encode
  • abi.encodeWithSelector (should enforce the first parameter being a string type ...)
  • abi.encodePacked

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately it does need the third version, otherwise it would be packed.

This means for encodeWithSelector we need to enforce the first parameter to be string (to be hashed) or bytes4 (already hashed).

}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("g(uin8)"), encodeArgs(u256(42)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing a t in uint8

Copy link
Contributor

@chriseth chriseth Sep 27, 2017

Choose a reason for hiding this comment

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

Also the encodeArgs call has to be moved to an argument to callContractFunction, or, even shorter:
ABI_CHECK(callContractFunction("g(uint8)", 42), bytes());

@axic axic force-pushed the abi-api branch 4 times, most recently from efa36c1 to c86fcc9 Compare September 27, 2017 12:52
@axic
Copy link
Member Author

axic commented Sep 27, 2017

Need to add docs.

@axic
Copy link
Member Author

axic commented Nov 29, 2017

The current API is as follows:

  • abi.encode() -> bytes
  • abi.encodePacked() -> bytes
  • abi.encodeWithSelector(, ) -> bytes

The reason for encodeWithSelector is to have an (memory-)optimised version of manually concatenating encodePacked("f(uint8,string)") .. encode(uint8(1), string("hello world")).

@axic
Copy link
Member Author

axic commented Dec 12, 2017

@chriseth do you agree with API proposed above? If so I should finish this PR.

@chriseth
Copy link
Contributor

Yes, fine with the API. For encodeWithSelector, we should also allow users to pass the 4-bytes-selector.

@axic
Copy link
Member Author

axic commented Dec 12, 2017

The (so far) final API we have agreed on:

  • abi.encode(...) -> bytes
  • abi.encodePacked(...) -> bytes
  • abi.encodeWithSelector(bytes4, ...) -> bytes
  • abi.encodeWithSelector(string, ...) -> bytes

Plus raise a error if encodeWithSelector's first argument is a literal which can be implicitly converted to both bytes4 and string (it is inconclusive).

@protinam
Copy link

protinam commented Mar 2, 2018

I would find this extremely useful; at the moment the combination of Solidity's stack size limitation and the lack of manual packed encoding is leading to a lot of inefficiencies in the Wyvern Exchange's contracts, - e.g. https://github.com/ProjectWyvern/wyvern-ethereum/blob/b31d60ae58ff251c236f9f95b499b24f26a4a78c/contracts/exchange/ExchangeCore.sol#L223

Anything I can help with to move this along?

@chriseth
Copy link
Contributor

chriseth commented Mar 2, 2018

@axic what is still to be done here?

@chriseth
Copy link
Contributor

Let's get this done!

@chriseth
Copy link
Contributor

Rebased.

@VoR0220
Copy link
Member

VoR0220 commented Mar 17, 2018

Correct me if I'm wrong, but would this be serializing the bytes into the proper format and have an implicit "require" if the data cannot be formatted into the desired function signature?

@chriseth
Copy link
Contributor

@VoR0220 the format is specified by the type of the arguments. There are some types that cannot be serialized (mappings are an example) in which case you would get a compile-time error.

@chriseth
Copy link
Contributor

@axic

Plus raise a error if encodeWithSelector's first argument is a literal which can be implicitly converted to both bytes4 and string (it is inconclusive).

Do we have a test case for this?

This is not relevant anymore, because we split the single overloaded function into two different functions.

@chriseth
Copy link
Contributor

Fixed everything.

return keccak256(uint8(1));
}
function g() pure public returns (bytes) {
return abi.encode(1);
Copy link
Member Author

Choose a reason for hiding this comment

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

This isn't tight packing? Should instead have the same 5 methods as tight_packing_literals.sol

Copy link
Contributor

Choose a reason for hiding this comment

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

The idea was that these are the "good" cases that are variations of tight_pcaking_literals.sol. Perhaps the naming of the file is bad.

@axic
Copy link
Member Author

axic commented Apr 12, 2018

I am sorry its still missing a test for struct encoding with abi.*.

I'd just do keccak256(abi.encode(s)), keccak256(abi.encodeWithSelector(0x12345678, s)), keccak256(abi.encodeWithSignature("f((uint,uint))",s)) and a failure case for abi.encodePacked(s) where s is a struct.

@axic
Copy link
Member Author

axic commented Apr 12, 2018

This is not relevant anymore, because we split the single overloaded function into two different functions.

Ah right, that's clear then 😉

@chriseth
Copy link
Contributor

@axic are you talking about syntax or semantics tests for encoding structs?

@axic
Copy link
Member Author

axic commented Apr 12, 2018

encodePacked(s) is a syntax, the rest is semantics

@chriseth
Copy link
Contributor

Ok! I did not add semantics tests because they should be the same as the regular encoder tests, but I will add some.

@chriseth
Copy link
Contributor

syntax tests for encodePacked is in specialFunctions/abi_encode_structs.sol and specialFunctions/abi_encode_structs_abiv2.sol.

@axic
Copy link
Member Author

axic commented Apr 12, 2018

syntax tests for encodePacked is in specialFunctions/abi_encode_structs.sol and specialFunctions/abi_encode_structs_abiv2.sol.

Sorry missed that, that should be fine.

Only semantics missing then.

@chriseth
Copy link
Contributor

Hm, and abi.encode for structs also has a semantics test. I will add structs to the encodeWithSelector and encodeWithSignature tests.

@axic
Copy link
Member Author

axic commented Apr 12, 2018

It has a couple, but couldn't find any for structs.

expectation =
encodeArgs(0x20, 4 + 0x120) +
bytes{0x7c, 0x79, 0x30, 0x02} +
encodeArgs(u256(-1), 0x60, u256(3), 0x1234567, 0x60, 0x1234, 38, "Lorem ipsum dolor sit ethereum........") +
Copy link
Member Author

Choose a reason for hiding this comment

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

I think 0x1234567 is missing the last nibble 8.

Copy link
Contributor

Choose a reason for hiding this comment

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

that is s.a, whose value is 0x1234567.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah right, in this test case x is a stray value.

Copy link
Contributor

Choose a reason for hiding this comment

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

oh yeah, do you want me to change it?

Copy link
Member Author

Choose a reason for hiding this comment

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

If you want. It would be cleaner but I'm not adamant. Actually, lets keep it, once we have detection for side-effect free statements it will be detected :)

expectation =
encodeArgs(0x20, 4 + 0x120) +
bytes{0x12, 0x34, 0x56, 0x78} +
encodeArgs(u256(-1), 0x60, u256(3), 0x1234567, 0x60, 0x1234, 38, "Lorem ipsum dolor sit ethereum........") +
Copy link
Member Author

Choose a reason for hiding this comment

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

Same here.

@axic
Copy link
Member Author

axic commented Apr 12, 2018

@chriseth it looks good to me. I cannot approve since I am the one opening the PR. I trust the ABI expectations are correct, but couldn't review all of them manually.

@chriseth chriseth merged commit 44416d1 into develop Apr 12, 2018
@ProphetDaniel
Copy link

ProphetDaniel commented Apr 12, 2018

I'm working with ERC827 standard (ethereum/EIPs#827) and there is an interesting functionality I need to implement that depends on call method with a parameter.

transfer( _to, _value, bytes4(keccak256("setCounter()")).toBytes());
// The following doesn't work out.
// transfer( _to, _value, bytes4(keccak256("setCounter(uint16)"),_color).toBytes());

where toBytes() comes from the following StringsAndBytes.sol library:

library StringsAndBytes{
    /* bytes32 (fixed-size array) to bytes (dynamically-sized array) */
    function toBytes(bytes4 _bytes4) internal pure returns (bytes){
        // string memory str = string(_bytes32);
        // TypeError: Explicit type conversion not allowed from "bytes32" to "string storage pointer"
        bytes memory bytesArray = new bytes(4);
        for (uint256 i; i < 4; i++) {
            bytesArray[i] = _bytes4[i];
        }
        return bytesArray;
    }//
}

And the token contract is:

pragma solidity ^0.4.21;
import "ERC827Token.sol";
import "StringsAndBytes.sol";

contract SpecialToken is ERC827Token{
    using StringsAndBytes for bytes4;
    // Name of the token
    string constant public name = "SpecialToken";
    // Token abbreviation
    string constant public symbol = "SpT";
    // Decimal places
    uint8 constant public decimals = 18;

    function transferThis(address _to, uint256 _value, uint16 _color) {
        transfer( _to, _value, bytes4(keccak256("setCounter()")).toBytes()); // works
        _to.call(bytes4(keccak256("setCounter(uint16)")), 4); // works
        // transfer( _to, _value, bytes4(keccak256("setCounter(uint16)"),_color).toBytes()); // does not work
    }
}
contract ColoredCoin{
    event coinColor (uint16 color);

    function setCounter(){
        emit coinColor(3);
    }
    function setCounter(uint16 _color){
        emit coinColor(_color);
    }
}

Should I just await abi.encodeWithSignature or is there an alternative implementation that I can already start using?

@axic axic deleted the abi-api branch April 13, 2018 01:10
@axic
Copy link
Member Author

axic commented Apr 13, 2018

I think this PR isn't the proper location to discuss what alternatives are for a given programming task in Solidity. This feature should be part of the next release, which is out hopefully very soon (days?). However one must note, that it can easily take weeks for the frameworks (such as truffle) to pick a new version up.

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.

6 participants