abi.encodePacked is a non standard encoding that should preferably not be used due to its ambiguity with dynamic types, if you are not dealing with dynamic types and inputs which size is already multiple of 32 then they can both give the same output.
I suppose you took that piece of code from Ethernaut level 16. Preservation. Where setTimeSignature
is defined as such :
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
This is not a dynamic type, it's a constant fixed size array of bytes8, however the standard encoding specification says :
Note that for any X, len(enc(X)) is a multiple of 32
Leading to 28 bytes of padding after the 4 bytes which is not what the abi expects. You can check for yourself that those produce different outputs :
function encodePacked(uint256 time) public view returns (bytes memory) {
return abi.encodePacked(setTimeSignature, time);
}
// 0x3beb26c40000000000000000000000000000000000000000000000000000000000000000 (4 + 32 bytes)
function encode(uint256 time) public view returns (bytes memory) {
return abi.encode(setTimeSignature, time);
}
// 0x3beb26c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 (32 + 32 bytes)
The first 4 bytes of calldata should be the function selector immediately followed by the encoded parameters (with standard encoding). This is why they are using abi.encodePacked
to encode the selector in place, taking only exactly 4 bytes. The encoding of a uint is the same accross abi.encode
and abi.encodePacked
.
All those are equivalent because uint256 requires no padding:
function encodePacked(uint256 time) public view returns (bytes memory) {
return abi.encodePacked(setTimeSignature, time);
}
function encodeHybrid(uint256 time) public view returns (bytes memory) {
return abi.encodePacked(abi.encodePacked(setTimeSignature), abi.encode(time));
}
function encodeHybrid2(uint256 time) public view returns (bytes memory) {
return abi.encodePacked(abi.encodePacked(setTimeSignature), abi.encodePacked(time));
}
Does this work only because timezone1Library was compiled to
understand abi.encodePacked? Is this default in newer versions of the
Solidity compiler?
It works only because it is correct in producing what the abi expects as calldata parameters : 4 bytes of function selector followed by the abi.encoded parameters. It does so by relying on a special case though.. This syntax is not always correct (dynamic types or padding required according to the standard encoding will lead to a wrong output).
You can also use a more readable syntax such as :
function encodeWithSignature(uint256 time) public view returns (bytes memory) {
return abi.encodeWithSignature("setTime(uint256)", time);
}
which does exactly the same as the functions above or your initial example but in a more readable way imo, plus it always produces a correctly encoded output for the parameters.