-
Notifications
You must be signed in to change notification settings - Fork 55
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
fix(erc7683): EIP712 encoding #506
Conversation
I agree with the second point which i've implemented in #519 but on the first point I think we are already compliant.
An example encoding is Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name). So I think in our case, the Permit2 |
So In your example, You can also check the implementation details in It can be verified with the code (ethers v6): import assert from "assert";
import { TypedDataEncoder } from "ethers";
const types = {
PermitWitnessTransferFrom: [
{ name: "permitted", type: "TokenPermissions" },
{ name: "spender", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "witness", type: "CrossChainOrder" },
],
CrossChainOrder: [
{ name: "settlerContract", type: "address" },
{ name: "swapper", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "originChainId", type: "uint32" },
{ name: "initiateDeadline", type: "uint32" },
{ name: "fillDeadline", type: "uint32" },
{ name: "orderData", type: "AcrossOrderData" },
],
AcrossOrderData: [
{ name: "inputToken", type: "address" },
{ name: "inputAmount", type: "uint256" },
{ name: "outputToken", type: "address" },
{ name: "outputAmount", type: "uint256" },
{ name: "bridgeToken", type: "address" },
{ name: "bridgeTokenAmountOutMinimum", type: "uint256" },
{ name: "destinationChainId", type: "uint32" },
{ name: "recipient", type: "address" },
{ name: "exclusivityDeadline", type: "uint32" },
{ name: "message", type: "bytes" },
],
TokenPermissions: [
{ name: "token", type: "address" },
{ name: "amount", type: "uint256" },
],
};
const encoder = new TypedDataEncoder(types);
const result = encoder.encodeType("PermitWitnessTransferFrom");
const expectedResult =
"PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,CrossChainOrder witness)" +
"AcrossOrderData(address inputToken,uint256 inputAmount,address outputToken,uint256 outputAmount,address bridgeToken,uint256 bridgeTokenAmountOutMinimum,uint32 destinationChainId,address recipient,uint32 exclusivityDeadline,bytes message)" +
"CrossChainOrder(address settlerContract,address swapper,uint256 nonce,uint32 originChainId,uint32 initiateDeadline,uint32 fillDeadline,AcrossOrderData orderData)" +
"TokenPermissions(address token,uint256 amount)";
assert.equal(expectedResult, result); The result is:
Also in the function They don't export the import assert from "assert";
// from viem/src/utils/signature/hashTypedData.ts
type MessageTypeProperty = {
name: string;
type: string;
};
function encodeType({
primaryType,
types,
}: {
primaryType: string;
types: Record<string, MessageTypeProperty[]>;
}) {
let result = "";
const unsortedDeps = findTypeDependencies({ primaryType, types });
unsortedDeps.delete(primaryType);
const deps = [primaryType, ...Array.from(unsortedDeps).sort()];
for (const type of deps) {
result += `${type}(${types[type]
.map(({ name, type: t }) => `${t} ${name}`)
.join(",")})`;
}
return result;
}
function findTypeDependencies(
{
primaryType: primaryType_,
types,
}: {
primaryType: string;
types: Record<string, MessageTypeProperty[]>;
},
results: Set<string> = new Set()
): Set<string> {
const match = primaryType_.match(/^\w*/u);
const primaryType = match?.[0]!;
if (results.has(primaryType) || types[primaryType] === undefined) {
return results;
}
results.add(primaryType);
for (const field of types[primaryType]) {
findTypeDependencies({ primaryType: field.type, types }, results);
}
return results;
}
const types = {
PermitWitnessTransferFrom: [
{ name: "permitted", type: "TokenPermissions" },
{ name: "spender", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "witness", type: "CrossChainOrder" },
],
CrossChainOrder: [
{ name: "settlerContract", type: "address" },
{ name: "swapper", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "originChainId", type: "uint32" },
{ name: "initiateDeadline", type: "uint32" },
{ name: "fillDeadline", type: "uint32" },
{ name: "orderData", type: "AcrossOrderData" },
],
AcrossOrderData: [
{ name: "inputToken", type: "address" },
{ name: "inputAmount", type: "uint256" },
{ name: "outputToken", type: "address" },
{ name: "outputAmount", type: "uint256" },
{ name: "bridgeToken", type: "address" },
{ name: "bridgeTokenAmountOutMinimum", type: "uint256" },
{ name: "destinationChainId", type: "uint32" },
{ name: "recipient", type: "address" },
{ name: "exclusivityDeadline", type: "uint32" },
{ name: "message", type: "bytes" },
],
TokenPermissions: [
{ name: "token", type: "address" },
{ name: "amount", type: "uint256" },
],
};
const result = encodeType({
primaryType: "PermitWitnessTransferFrom",
types,
});
const expectedResult =
"PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,CrossChainOrder witness)" +
"AcrossOrderData(address inputToken,uint256 inputAmount,address outputToken,uint256 outputAmount,address bridgeToken,uint256 bridgeTokenAmountOutMinimum,uint32 destinationChainId,address recipient,uint32 exclusivityDeadline,bytes message)" +
"CrossChainOrder(address settlerContract,address swapper,uint256 nonce,uint32 originChainId,uint32 initiateDeadline,uint32 fillDeadline,AcrossOrderData orderData)" +
"TokenPermissions(address token,uint256 amount)";
assert.equal(expectedResult, result); The result is also consistent with the result of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@paco0x need to merge in a recent change to get ci running.
We've discussed this internally and we agree with your interpretation! Thanks for correcting this -- huge help!
LGTM!
@paco0x I don't think I have permissions to merge master into your branch. Could you pull the most recent changes from master and run |
36fdb0c
to
5bd9526
Compare
5bd9526
to
e2d406f
Compare
Thx! I just merged master and updated it a little bit to reduce some duplicated code. |
This PR fixes the inconsistencies with EIP712 in the
ERC7683Across
contract.When encoding the
PermitWitnessTransferFrom
struct from permit2, other referenced struct types should be sorted by name. This point is also mentioned in Uniswap's document: https://docs.uniswap.org/contracts/permit2/reference/signature-transferSo the full normalized type definition should be like(struct fields are omitted for simplicity):
This patch adds a new constant
PERMIT2_CROSS_CHAIN_ORDER_TYPE
for this normalized type.This patch changes
abi.encodePacked
toabi.encode
when encodingCrossChainOrder
andAcrossOrderData
since every member should be in 32 bytes.orderData.message
should be hashed before encoding.The typed-data hash after fixing has already been cross-verified with viem and ethers. I can add tests if it's required.