Skip to content

Conversation

@dohaki
Copy link
Contributor

@dohaki dohaki commented Jan 23, 2023

Closes ACX-590

This allows the usage of EIP-712 structured and typed data signatures for updating relayer fees. The implementation of EIP712CrossChain is based on OZ's implementation of the EIP-712 specification but is less strict to allow the verification of signatures cross-chain.

@linear
Copy link

linear bot commented Jan 23, 2023

ACX-590 Add support for EIP712

Instead of a cryptic bytestring, the user would be presented with a human-readable data before signing the message.

https://eips.ethereum.org/EIPS/eip-712

We should only implement this if there a tested library, for example one from OZ we can use. If we had to roll our own implementation then the risk wouldn't be worth it IMO

bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
// NOTE: We use a hardcoded address to allow cross-chain verification
address public immutable _VERIFYING_CONTRACT = 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC;
Copy link
Member

Choose a reason for hiding this comment

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

Would we need to change this in production? Isn't this contract the destination SpokePool's address?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is again the same problem as with the chain id. Because this value is part of the domain separator, we either need to hardcode it across all chains / spoke pool contracts OR make it also a parameter. I decided to hardcode it because:

  1. The spec defines this only with the keyword may, so my interpretation is that this is optional. See https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
  2. If this would be a parameter, then the function signatures of the methods speedUpDeposit and fillRelayWithUpdatedFee would need to change. Because we would need to somehow pass the address of the respective spoke pool (same value for corresponding chains)?

Copy link
Member

Choose a reason for hiding this comment

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

hmmm this is an interesting point @dohaki . So one way around this is to just hardcode the verifying contract so that its essentially ignored. Does that break wallet clients ability to parse this message? I just don't want them to reject it because the verifyingContract isn't actually the contract that is verifying it

Copy link
Member

Choose a reason for hiding this comment

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

As discussed offline, it seems that some of the fields like verifyingContract are optional in the EIP so we don't need to use that one. I still think we should keep chainId = originChainId. If we remove verifyingContract i don't think we expose ourselves to any replay attacks where someone submits the signature to a different spoke pool on the originChain. The SpokePools now allow fills to be paused including the fillRelayWithUpdatedFee so that will revert on deprecated SpokePools. Additionally, even if the fillRelayWithUpdatedFee succeeds, the relayer will note receive a refund if the SpokePool isn't connected to the HubPool anymore.

Copy link
Member

@nicholaspai nicholaspai left a comment

Choose a reason for hiding this comment

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

Left some high level comments !

originChainId,
},
};
const signature = await depositor._signTypedData(typedData.domain, typedData.types, typedData.message);
Copy link
Member

Choose a reason for hiding this comment

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

  1. This is how we'd sign the speed up in the FE after this change goes through? Is there a non underscore function we can use instead?
  2. Do we need any additional unit tests? Like verifying that the typed data has the expected fields and can be parsed by a wallet client?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. This is how we'd sign the speed up in the FE after this change goes through? Is there a non underscore function we can use instead?

Yes, we would use this in the FE to speed up. This is just a wrapper method provided by ethers and will eventually be replaced by signTypedData without an underscore. Alternatively, we could just directly use the JSON RPC like:

const signature = await depositor.provider.send("eth_signTypedData_v4", [fromAddress, encodedMsgParams]

where we need to do some additional encoding. This approach is a bit verbose though.

  1. Do we need any additional unit tests? Like verifying that the typed data has the expected fields and can be parsed by a wallet client?

Not sure if I understand correctly. Verifying whether the typed data has the expected fields is already covered in our tests, no? Basically, the test case is where if the signed message is not equal to the expected message then the verification fails. I am also not sure how to write a unit test on whether or not a wallet client can parse the typed data. Doesn't this depend on the implementation of the wallet? And can't we assume that if a wallet supports the method eth_signTypedData_v4 then it can parse any message supported by the spec?

Copy link
Member

Choose a reason for hiding this comment

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

eventually be replaced by signTypedData without an underscore

Any idea when this is going to happen? Not that it blocks us i'm just curious what's holding this up

Copy link
Member

Choose a reason for hiding this comment

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

Verifying whether the typed data has the expected fields is already covered in our tests, no? Basically, the test case is where if the signed message is not equal to the expected message then the verification fails

Do we need to perform additional checks that the signed message is in the correct EIP712 format?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

eventually be replaced by signTypedData without an underscore

Any idea when this is going to happen? Not that it blocks us i'm just curious what's holding this up

No idea tbh... Couldn't find anything on this in the repo.

Verifying whether the typed data has the expected fields is already covered in our tests, no? Basically, the test case is where if the signed message is not equal to the expected message then the verification fails

Do we need to perform additional checks that the signed message is in the correct EIP712 format?

Is this possible? The signature is in the end just the same bytes string as a message signed with signer.signMessage.

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

LGTM!

Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Copy link
Contributor

@james-a-morris james-a-morris left a comment

Choose a reason for hiding this comment

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

Left some comments.

Comment on lines +709 to +718
keccak256(
abi.encode(
keccak256(
"UpdateRelayerFeeMessage(uint64 newRelayerFeePct,uint32 depositId,uint256 originChainId)"
),
newRelayerFeePct,
depositId,
originChainId
)
),
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to store some of these values in local variables with descriptive names? From a readability standpoint, there are several nested function calls that may not be entirely clear for a newcomer to the repo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, I agree that this is a bit nested. But tbh I don't know what to put in local variables because this hash structure is defined like that in the EIP. I could do

bytes32 hashStruct = keccak256(...);
bytes32 expectedTypedDataV4Hash = _hashTypedDataV4(hashStruct);

but not sure if this makes it really more readable?

Copy link
Member

Choose a reason for hiding this comment

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

Is there a gas difference if you change the syntax? Can run the test like so REPORT_GAS=true hardhat test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nicholaspai Thanks for the suggestion. Yes, with the additional local variable the methods speedUpDeposit and fillRelayWithUpdatedFee cost 10 gas more.

Copy link
Member

Choose a reason for hiding this comment

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

this is an interesting case where smart contract engineering is different from off-chain engineering. Temp variables increase readability but incur a real cost! In this case I'd opt for the reduction in readability to save gas

Copy link
Contributor

@amateima amateima left a comment

Choose a reason for hiding this comment

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

Nice work 👍

@dohaki dohaki requested a review from james-a-morris January 27, 2023 08:42
Copy link
Contributor

@james-a-morris james-a-morris left a comment

Choose a reason for hiding this comment

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

Great work 🔥 Thanks for addressing my comments

@dohaki
Copy link
Contributor Author

dohaki commented Jan 31, 2023

@nicholaspai I e2e tested a deployed version of the contract with the frontend and everything seems fine 👍 When you are happy, then I can merge

@nicholaspai
Copy link
Member

@nicholaspai I e2e tested a deployed version of the contract with the frontend and everything seems fine 👍 When you are happy, then I can merge

#222 (comment)

Just one comment then LGTM

Copy link
Member

@nicholaspai nicholaspai left a comment

Choose a reason for hiding this comment

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

@dohaki
Copy link
Contributor Author

dohaki commented Feb 1, 2023

#222 (comment)

@nicholaspai Thanks! I haven't actually put the nested stuff in a local variable. So this PR has the version with lower gas costs

@dohaki dohaki merged commit 74a239f into master Feb 3, 2023
@dohaki dohaki deleted the eip-712-support branch February 3, 2023 15:49
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