foundry icon indicating copy to clipboard operation
foundry copied to clipboard

feat(forge): implement vm.signTypedData cheatcode

Open Haxry opened this issue 8 months ago • 17 comments

Motivation

Solution

closes #10281

PR Checklist

  • [ ] Added Tests
  • [ ] Added Documentation
  • [ ] Breaking changes

Haxry avatar Apr 17 '25 14:04 Haxry

@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 avatar Apr 17 '25 14:04 grandizzy

@grandizzy plz review ! are the changes ok ? I'll add the test too !

Haxry avatar Apr 19 '25 14:04 Haxry

@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?

grandizzy avatar Apr 22 '25 05:04 grandizzy

Hey Thanks for implementing this feature. I'll test it on my project today

aviggiano avatar Apr 22 '25 11:04 aviggiano

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.

grandizzy avatar Apr 22 '25 13:04 grandizzy

@aviggiano plz provide a sample test case to include in tests

Haxry avatar Apr 22 '25 16:04 Haxry

@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

aviggiano avatar Apr 22 '25 18:04 aviggiano

@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 avatar Apr 23 '25 11:04 grandizzy

@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.

aviggiano avatar Apr 23 '25 11:04 aviggiano

@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

0xrusowsky avatar May 26 '25 13:05 0xrusowsky

@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 avatar May 27 '25 18:05 0xrusowsky

@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?

aviggiano avatar May 27 '25 19:05 aviggiano

@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

0xrusowsky avatar May 27 '25 20:05 0xrusowsky

@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

0xrusowsky avatar May 27 '25 23:05 0xrusowsky

I think this solves my problem, thanks

aviggiano avatar May 28 '25 09:05 aviggiano

@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.

0xrusowsky avatar May 28 '25 17:05 0xrusowsky

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?

bowd avatar Jun 02 '25 19:06 bowd

going to close this one in favor of fix of https://github.com/foundry-rs/foundry/issues/10986

grandizzy avatar Oct 02 '25 12:10 grandizzy