solidity icon indicating copy to clipboard operation
solidity copied to clipboard

Remove abi.encodePacked

Open axic opened this issue 4 years ago • 16 comments
trafficstars

abi.encodePacked was introduced in 0.4.22 as a backwards compatibility measure for the irregular "packed" encoding.

Since then we have introduced bytes.concat (#10903) and learned that most projects (rightly) do not rely on the packed encoding, but only concatenation. I suggest we drop abi.encodePacked in the next breaking release.

(We could consider introducing a message with #11508 if we want to test the waters 😅 , but would rather refrain from this.)

axic avatar Jun 29 '21 12:06 axic

Which types can be used with encodePacked that cannot be used with bytes.concat?

chriseth avatar Jun 30 '21 12:06 chriseth

Which types can be used with encodePacked that cannot be used with bytes.concat?

Arrays. https://docs.soliditylang.org/en/v0.8.6/abi-spec.html.

What is the effect of this proposal on event index encoding?

charles-cooper avatar Aug 01 '21 15:08 charles-cooper

Event index encoding will not change, since we only plan to remove abi.encodePacked from the language.

Is packed encoding of arrays used in the real world?

chriseth avatar Aug 02 '21 08:08 chriseth

Is packed encoding of arrays used in the real world?

One place where it can be used is for EIP-712 hashes - https://eips.ethereum.org/EIPS/eip-712#definition-of-encodedata.

For example if there is a struct that contains an array:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract MultiTransferDemo {
    struct MultiTransfer {
        uint256 id;
        Transfer[] transfers;
    }

    struct Transfer {
        uint256 amount;
        address recepient;
    }

    function hash(MultiTransfer memory mt) public pure returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        "MultiTransfer(uint256 id,Transfer[] transfers)Transfer(uint256 amount,address recepient)"
                    ),
                    mt.id,
                    hashTransfers(mt.transfers)
                )
            );
    }

    function hashTransfers(Transfer[] memory transfers)
        public
        pure
        returns (bytes32)
    {
        bytes32[] memory hashedTransfers = new bytes32[](transfers.length);
        for (uint256 i = 0; i < transfers.length; i++) {
            hashedTransfers[i] = hashTransfer(transfers[i]);
        }
        return keccak256(abi.encodePacked(hashedTransfers));
    }

    function hashTransfer(Transfer memory transfer)
        public
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    keccak256("Transfer(uint256 amount,address recepient)"),
                    transfer.amount,
                    transfer.recepient
                )
            );
    }
}

It can also be implemented via bytes.concat, but I'm not sure which version has better performance:

    function hashTransfers2(Transfer[] memory transfers)
        public
        pure
        returns (bytes32)
    {
        bytes memory encodedTransfers = new bytes(0);
        for (uint256 i = 0; i < transfers.length; i++) {
            encodedTransfers = bytes.concat(
                encodedTransfers,
                hashTransfer(transfers[i])
            );
        }
        return keccak256(encodedTransfers);
    }

e-nikolov avatar Mar 09 '22 03:03 e-nikolov

Which types can be used with encodePacked that cannot be used with bytes.concat?

As a real world example, this use of encodePacked():

https://github.com/Anish-Agnihotri/merkle-airdrop-starter/blob/83d6c65d00a10b40e0abdd84a5afa692553a9e72/contracts/src/MerkleClaimERC20.sol#L66

Would need to use casting to do the same thing:

keccak256(bytes.concat(bytes20(a),bytes32(value)));

Would it be considered more 'idiomatic' to use bytes.concat and cast from address and uintX?

maurelian avatar Mar 15 '22 14:03 maurelian

I think that address is actually something that should be allowed as input in bytes.concat(). It does have an unambiguous binary representation and an explicit cast to bytes20 should not be needed.

cameel avatar Apr 26 '22 11:04 cameel

Curious on the current thinking around this. Is this still being considered?

fredlacs avatar Jun 16 '22 12:06 fredlacs

Current plan is to indeed remove abi.encodePacked as soon as https://github.com/ethereum/solidity/issues/11593 is done and it's possible to reimplement this generically in user code :-).

ekpyron avatar Sep 14 '22 15:09 ekpyron

#11593 is this issue :)

cameel avatar Sep 14 '22 20:09 cameel

#11593 is this issue :)

Ah, sorry, I meant https://github.com/ethereum/solidity/issues/13321 :-).

ekpyron avatar Sep 15 '22 08:09 ekpyron

I think that address is actually something that should be allowed as input in bytes.concat(). It does have an unambiguous binary representation and an explicit cast to bytes20 should not be needed.

@cameel I think the real solution is #11284 and not adding non-bytes types to bytes.concat.

axic avatar Sep 27 '22 21:09 axic

I think it's a solution for the general case but I also wouldn't mind easing on the overly verbose conversions in select cases where they don't add anything. Even with some nicer conversion from #11284 this would be more verbose than it needs to be for clarity.

cameel avatar Sep 27 '22 22:09 cameel

What's your latest advice on how to convert an address to bytes32?

I'm still relying on abi.encodePacked since it's clearer than bytes.concat(bytes20(addr)).

PaulRBerg avatar Jun 18 '23 12:06 PaulRBerg

I think abi.encodePacked is very convenient for concatenation. Especially when concatenating multiple types that are not all bytesN or bytes.

For instance, to concatenate like: abi.encodePacked(address, bytesN, uint256, …)

CJ42 avatar Oct 12 '23 22:10 CJ42

The most common problem i have met is that how can i do this using other languages? such as js/golang?

exqlnet avatar Feb 23 '24 07:02 exqlnet