feat(forge): implement vm.signTypedData cheatcode
Motivation
Solution
closes #10281
PR Checklist
- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
@Haxry pls don't introduce ethers dep bit use alloy. Also pls add a test for the new cheatcodes and make sure priv key is redacted in traces, you can add here https://github.com/foundry-rs/foundry/blob/709f266ff6a26264ac4fc8c06cc40964861c7ebb/crates/evm/traces/src/decoder/mod.rs#L460
Thanks
@grandizzy plz review ! are the changes ok ? I'll add the test too !
@grandizzy plz review ! are the changes ok ? I'll add the test too !
yep, lgtm! I run cargo cheats and pushed commit to fix CI, would be great to have a test and then can be merged, maybe @aviggiano have a solidity test to use here?
Hey Thanks for implementing this feature. I'll test it on my project today
Hey Thanks for implementing this feature. I'll test it on my project today
Awesome, thanks. Mind that the PR wasn't merged yet so you'll need to build locally and test, was wondering if you could provide a sample for input / output to include as a test.
@aviggiano plz provide a sample test case to include in tests
@Haxry I hope this example can help:
https://github.com/aviggiano/foundry-vm-signtypeddata/blob/db023ea33bfa4979a59a5fa00bfd85204a56e987/script/SignTypedData.s.sol#L23-L46
This is the current setup to sign with EIP712:
forge script script/SignTypedData.s.sol --rpc-url base -vvvvv --ffi
You should have a Ledger setup and unlocked. It will prompt you to "blind sign" using cast:
├─ [0] VM::ffi(["cast", "wallet", "sign", "--ledger", "--mnemonic-derivation-path", "m/44'/60'/0'/0/0", "--data", "{\"domain\":{\"chainId\":\"8453\",\"verifyingContract\":\"0xF3a292Dda3F524EA20b5faF2EE0A1c4abA665e4F\"},\"message\":{\"to\":\"0x4200000000000000000000000000000000000006\",\"value\":\"0\",\"data\":\"0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000000\",\"operation\":0,\"baseGas\":\"0\",\"gasPrice\":\"0\",\"gasToken\":\"0x0000000000000000000000000000000000000000\",\"refundReceiver\":\"0x0000000000000000000000000000000000000000\",\"nonce\":1,\"safeTxGas\":\"0\"},\"primaryType\":\"SafeTx\",\"types\":{\"SafeTx\":[{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"data\",\"type\":\"bytes\"},{\"name\":\"operation\",\"type\":\"uint8\"},{\"name\":\"safeTxGas\",\"type\":\"uint256\"},{\"name\":\"baseGas\",\"type\":\"uint256\"},{\"name\":\"gasPrice\",\"type\":\"uint256\"},{\"name\":\"gasToken\",\"type\":\"address\"},{\"name\":\"refundReceiver\",\"type\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint256\"}]}}"])
The goal is to replace this FFI call to cast with vm.signTypedData
@Haxry I hope this example can help:
https://github.com/aviggiano/foundry-vm-signtypeddata/blob/db023ea33bfa4979a59a5fa00bfd85204a56e987/script/SignTypedData.s.sol#L23-L46
Confirm this works with the current cheatcode which requires a private key, one difference though is that the cheatcode returns v, r, s, of sig but I guess that's OK @aviggiano ?
I think we need also a cheatcode that would enable signing with the ledger and not by receiving priv key, @Haxry @aviggiano wdyt? thanks
@grandizzy returning v, r, s is OK,
but requiring the private key is not sufficient for my use case, since the biggest motivation for this feature is being able to sign transactions with ledger.
So yeah, it would be important to have a cheatcode that does not require the private key.
@Haxry any updates here? to bring the PR to the finish line you simply have to:
- add a cheatcode variant which doesn't require the pk, and which outputs the digest
- add uni tests
it's been a month since the last update, so if you are too busy i can take over
@Haxry i finally took over cause we want to integrate this feature asap, as 2 other PRs that implement eip712-related cheatcodes will be merged soon. Regardless of that, thanks for your contribution!
fyi, i've named the cheatcode that produces the digest vm.eip712HashTypedData so that it follows the same naming convention as the other two:
- https://github.com/foundry-rs/foundry/pull/10570
- https://github.com/foundry-rs/foundry/pull/10626
the vm.signTypedData hasn't been renamed, as it already follows the naming standard of the "crypto" cheatcodes
@aviggiano please lmk if this is what u needed
@0xrusowsky thanks
I still don't see how I can sign without a private key, though
My goal is to use EIP-712 with ledger. How would I accomplish this with the current cheatcode?
@0xrusowsky thanks
I still don't see how I can sign without a private key, though
My goal is to use EIP-712 with ledger. How would I accomplish this with the current cheatcode?
my bad, i should have read more carefully the previous convos that u had... now i see that u want to get rid of the FFI dependency
@aviggiano if the cheatcode were to simply perfom the ffi call to cast under the hood, and adopt this API:
/// Signs human-readable typed data `jsonData` using a hardware wallet.
///
/// This cheatcode performs a foreign function call to `cast` to abstract away complexity of
/// manually building the calldata. Because of that, it requires the FFI cheatcode to be enabled.
///
/// # Params:
///
/// * jsonData: the typed data must follow the EIP-712 standard.
/// * walletType: the hardware wallet type, either "ledger" or "trezor".
/// * walletArgs: the wallet derivation path or the mnemonic index.
function signTypedData(string calldata jsonData, string calldata walletType, string calldata walletArgs)
external pure returns (uint8 v, bytes32 r,bytes32 s);
would it solve your issues? note that u'd still have to enable the FFI flag
I think this solves my problem, thanks
@aviggiano unfortunately, the ledger support will have to wait a little bit more.
after discussing it with @zerosnacks and @grandizzy, we've agreed that my proposal is not optimal due to the inherent risks of executing FFI calls on the terminal (even if it's a call to cast in this case).
Instead, we will think how to come around the limitation that forces cheatcodes to be synchronous. However, this requires some design and exploration work.
Ah damn so the async part is the source of complexity here. I was also building something on top of @aviggiano 's lib and the ffi limitation screws with my usecase a bit more. I wanted to have the option of both signing and broadcasting from the ledger in the same script and that's currently not possible with this solution, because if forge connects to the ledger, the ffi call to cast won't run.
Any intution on how likely it would be to make async cheatcodes a reality?
going to close this one in favor of fix of https://github.com/foundry-rs/foundry/issues/10986