I have an ABIEncoderV2 struct that holds 3 arrays as such
struct Update {
address[] target;
uint256[] value;
bytes[] data;
}
We hash this struct but these types can't be packed by the ABI, but I still need to get the hash of said struct in JS.
bytes32 updateHash = keccak256(abi.encode(_update));
since solidityKeccak256(types, values) tightly packs the data I can't use that , is there an alternative that doesn't pack the data?
i.e. utils.solidityKeccak256(["address[], uint256[], bytes[]"], [update.target, update.value, update.data]) would not work due to reasons mentioned. I think it would even throw an error IIUC.
You need to use the normal encoder. You are correct, the solidity* functions are only for tightly packed.
To hash normally, use utils.defaultAbiCoder.encode([ "tuple(address[] target, uint[] value, bytes[] data)" ], [ objectHere ]) and you can then just use the normal utils.keccak256 on that. This should match the abi.encode in Solidity. :)
One quick note about your API, if you allow calls that use ecrecover to establish the from, you likely want to include a nonce as well to prevent replaying. You may already have other protection from this, but just in case; for delegated transactions that is often overlooked. :)
Thanks for the quick reply !
One quick note about your API, if you allow calls that use ecrecover to establish the from
We currently do address based authentication for these calls , not signer based authentication. The sender will be a multisig in production. Our replay protection involves the updateHash mapped to a block in the future , if an external call for Update takes place this value gets removed and we require the value to not be null to actually make the external call.
@kyriediculous I'm trying to do the same thing, upgrading a contract from using encodePacked to using encode. The documentation isn't very clear on how to accomplish the same thing as abi.encode with ether.js, so i landed here. I've used your example with the "tuple(..." string, and it almost does it right, but seems to be adding an extra "0000000000000000000000000000000000000000000000000000000000000020" to the front of the encoded value.
Here is my Solidity code:
bytes memory abiEncoded = abi.encode(
_peggyId,
methodName,
_valsetNonce,
_validators,
_powers
);
and my JS
let abiEncoded = ethers.utils.defaultAbiCoder.encode(
[
"tuple(bytes32 peggyId, bytes32 methodName, uint256 valsetNonce, address[] validators, uint256[] powers)"
],
[[peggyId, methodName, valsetNonce, validators, powers]]
)
In your case, that isn鈥檛 a tuple, I don鈥檛 think? Try using it as a non-tuple:
let abiEncoded = ethers.utils.defaultAbiCoder.encode(
[
"bytes32", "bytes32", "uint256", "address[]", "uint256[]"
],
[peggyId, methodName, valsetNonce, validators, powers]
)
Does that work?
Ah, that works.
Most helpful comment
You need to use the normal encoder. You are correct, the
solidity*functions are only for tightly packed.To hash normally, use
utils.defaultAbiCoder.encode([ "tuple(address[] target, uint[] value, bytes[] data)" ], [ objectHere ])and you can then just use the normalutils.keccak256on that. This should match the abi.encode in Solidity. :)One quick note about your API, if you allow calls that use ecrecover to establish the from, you likely want to include a nonce as well to prevent replaying. You may already have other protection from this, but just in case; for delegated transactions that is often overlooked. :)