@@ -4,17 +4,21 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "math/big"
78
89 "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
910 opnode_bindings "github.com/ethereum-optimism/optimism/op-node/bindings"
1011 bindingspreview "github.com/ethereum-optimism/optimism/op-node/bindings/preview"
12+ "github.com/ethereum-optimism/optimism/op-node/rollup"
1113 "github.com/ethereum-optimism/optimism/op-node/withdrawals"
1214 op_service "github.com/ethereum-optimism/optimism/op-service"
1315 "github.com/ethereum-optimism/optimism/op-service/apis"
1416 oplog "github.com/ethereum-optimism/optimism/op-service/log"
1517 "github.com/ethereum-optimism/optimism/op-service/txintent/bindings"
1618 "github.com/ethereum-optimism/optimism/op-service/txintent/contractio"
1719 "github.com/ethereum-optimism/optimism/op-service/txmgr"
20+ "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
21+ "github.com/ethereum/go-ethereum"
1822 "github.com/ethereum/go-ethereum/accounts/abi/bind"
1923 "github.com/ethereum/go-ethereum/common"
2024 "github.com/ethereum/go-ethereum/ethclient"
5761 Usage : "Path to the rollup config of the target chain. Only required for proving using super roots." ,
5862 EnvVars : op_service .PrefixEnvVar (EnvVarPrefix , "ROLLUP_CONFIG" ),
5963 }
64+ DisputeGameFlag = & cli.StringFlag {
65+ Name : "dispute-game" ,
66+ Usage : "Address of SuperFaultDisputeGame. When provided, reads super root proof from on-chain extraData instead of supervisor-rpc. Requires --rollup.config but not --supervisor or --depset." ,
67+ EnvVars : op_service .PrefixEnvVar (EnvVarPrefix , "DISPUTE_GAME" ),
68+ }
6069)
6170
6271func ProveWithdrawal (ctx * cli.Context ) error {
@@ -130,10 +139,39 @@ func ProveWithdrawal(ctx *cli.Context) error {
130139 return err
131140 }
132141 } else {
133- logger .Info ("Proving withdrawal using super root proof" )
134- txData , err = txDataForSuperRootProof (ctx , l1EthClient , proofClient , l2Client , txHash , factory , portal )
135- if err != nil {
136- return err
142+ // Check if --dispute-game flag is provided for the new flow
143+ if disputeGameStr := ctx .String (DisputeGameFlag .Name ); disputeGameStr != "" {
144+ logger .Info ("Proving withdrawal using super root from dispute game extraData" )
145+ disputeGameAddr := common .HexToAddress (disputeGameStr )
146+
147+ // Load rollup config (still required for timestamp→block number conversion)
148+ rollupCfg , err := loadRollupConfig (ctx , RollupConfigFlag .Name )
149+ if err != nil {
150+ return fmt .Errorf ("failed to load rollup config: %w" , err )
151+ }
152+
153+ txData , err = txDataForSuperRootProofFromGame (
154+ ctx .Context ,
155+ l1Client ,
156+ l1EthClient ,
157+ proofClient ,
158+ l2Client ,
159+ txHash ,
160+ disputeGameAddr ,
161+ portalAddr ,
162+ portal ,
163+ rollupCfg ,
164+ )
165+ if err != nil {
166+ return err
167+ }
168+ } else {
169+ // Existing supervisor-based flow
170+ logger .Info ("Proving withdrawal using super root from supervisor" )
171+ txData , err = txDataForSuperRootProof (ctx , l1EthClient , proofClient , l2Client , txHash , factory , portal )
172+ if err != nil {
173+ return err
174+ }
137175 }
138176 }
139177
@@ -242,6 +280,158 @@ func txDataForSuperRootProof(ctx *cli.Context, l1EthClient apis.EthClient, proof
242280 return txData , nil
243281}
244282
283+ // txDataForSuperRootProofFromGame builds withdrawal proof tx data by reading the super root proof
284+ // directly from a SuperFaultDisputeGame's extraData, without requiring supervisor-rpc or depset.
285+ func txDataForSuperRootProofFromGame (
286+ ctx context.Context ,
287+ l1Client * ethclient.Client ,
288+ l1EthClient apis.EthClient ,
289+ proofClient * gethclient.Client ,
290+ l2Client * ethclient.Client ,
291+ txHash common.Hash ,
292+ disputeGameAddr common.Address ,
293+ portalAddr common.Address ,
294+ portal * bindingspreview.OptimismPortal2 ,
295+ rollupCfg * rollup.Config ,
296+ ) ([]byte , error ) {
297+ // Load ABI and prepare contract calls
298+ gameABI := snapshots .LoadSuperFaultDisputeGameABI ()
299+
300+ // Call extraData() to get encoded super root proof
301+ extraDataCallData , err := gameABI .Pack ("extraData" )
302+ if err != nil {
303+ return nil , fmt .Errorf ("failed to pack extraData call: %w" , err )
304+ }
305+ extraDataResult , err := l1Client .CallContract (ctx , ethereum.CallMsg {
306+ To : & disputeGameAddr ,
307+ Data : extraDataCallData ,
308+ }, nil )
309+ if err != nil {
310+ return nil , fmt .Errorf ("failed to call extraData: %w" , err )
311+ }
312+
313+ // Unpack extraData result (returns bytes)
314+ unpackedExtra , err := gameABI .Unpack ("extraData" , extraDataResult )
315+ if err != nil {
316+ return nil , fmt .Errorf ("failed to unpack extraData: %w" , err )
317+ }
318+ if len (unpackedExtra ) == 0 {
319+ return nil , errors .New ("extraData returned empty result" )
320+ }
321+ extraDataBytes , ok := unpackedExtra [0 ].([]byte )
322+ if ! ok {
323+ return nil , errors .New ("extraData result is not []byte" )
324+ }
325+
326+ // Decode super root proof from extraData
327+ superRootProof , err := withdrawals .DecodeSuperRootProof (extraDataBytes )
328+ if err != nil {
329+ return nil , fmt .Errorf ("failed to decode super root proof: %w" , err )
330+ }
331+
332+ // Get target L2 chain ID from portal
333+ targetChainID , err := l2ChainIDForPortal (ctx , l1EthClient , portal )
334+ if err != nil {
335+ return nil , fmt .Errorf ("failed to get target chain ID from portal: %w" , err )
336+ }
337+
338+ // Find output root index for target chain in the super root proof
339+ var outputRootIndex * big.Int
340+ for i , outputRoot := range superRootProof .OutputRoots {
341+ if outputRoot .ChainID .Uint64 () == targetChainID {
342+ outputRootIndex = big .NewInt (int64 (i ))
343+ break
344+ }
345+ }
346+ if outputRootIndex == nil {
347+ return nil , fmt .Errorf ("target chain ID %d not found in super root proof" , targetChainID )
348+ }
349+
350+ // Get L2 sequence number (timestamp) from the dispute game
351+ seqNumCallData , err := gameABI .Pack ("l2SequenceNumber" )
352+ if err != nil {
353+ return nil , fmt .Errorf ("failed to pack l2SequenceNumber call: %w" , err )
354+ }
355+ seqNumResult , err := l1Client .CallContract (ctx , ethereum.CallMsg {
356+ To : & disputeGameAddr ,
357+ Data : seqNumCallData ,
358+ }, nil )
359+ if err != nil {
360+ return nil , fmt .Errorf ("failed to call l2SequenceNumber: %w" , err )
361+ }
362+ unpackedSeq , err := gameABI .Unpack ("l2SequenceNumber" , seqNumResult )
363+ if err != nil {
364+ return nil , fmt .Errorf ("failed to unpack l2SequenceNumber: %w" , err )
365+ }
366+ if len (unpackedSeq ) == 0 {
367+ return nil , errors .New ("l2SequenceNumber returned empty result" )
368+ }
369+ l2SequenceNumber , ok := unpackedSeq [0 ].(* big.Int )
370+ if ! ok {
371+ return nil , errors .New ("l2SequenceNumber result is not *big.Int" )
372+ }
373+
374+ // Convert sequence number (timestamp) to L2 block number using rollup config
375+ l2BlockNumber , err := rollupCfg .TargetBlockNumber (l2SequenceNumber .Uint64 ())
376+ if err != nil {
377+ return nil , fmt .Errorf ("failed to get L2 block number from sequence number: %w" , err )
378+ }
379+
380+ // Fetch the L2 header at that block
381+ l2Header , err := l2Client .HeaderByNumber (ctx , new (big.Int ).SetUint64 (l2BlockNumber ))
382+ if err != nil {
383+ return nil , fmt .Errorf ("failed to get L2 header: %w" , err )
384+ }
385+
386+ // Get withdrawal receipt and parse MessagePassed event
387+ receipt , err := l2Client .TransactionReceipt (ctx , txHash )
388+ if err != nil {
389+ return nil , fmt .Errorf ("failed to get withdrawal receipt: %w" , err )
390+ }
391+ ev , err := withdrawals .ParseMessagePassed (receipt )
392+ if err != nil {
393+ return nil , fmt .Errorf ("failed to parse withdrawal event: %w" , err )
394+ }
395+
396+ // Build the withdrawal storage proof
397+ withdrawalProof , storageRoot , err := withdrawals .GetWithdrawalProof (ctx , proofClient , ev , l2Header )
398+ if err != nil {
399+ return nil , fmt .Errorf ("failed to get withdrawal proof: %w" , err )
400+ }
401+
402+ // Pack the proveWithdrawalTransaction call data
403+ txData , err := w3 .MustNewFunc ("proveWithdrawalTransaction(" +
404+ "(uint256 Nonce, address Sender, address Target, uint256 Value, uint256 GasLimit, bytes Data)," +
405+ "address DisputeGameProxy," +
406+ "uint256 OutputRootIndex," +
407+ "(bytes1 Version, uint64 Timestamp, (uint256 ChainID, bytes32 Root)[] OutputRoots)," +
408+ "(bytes32 Version, bytes32 StateRoot, bytes32 MessagePasserStorageRoot, bytes32 LatestBlockhash)," +
409+ "bytes[])" , "" ).EncodeArgs (
410+ bindingspreview.TypesWithdrawalTransaction {
411+ Nonce : ev .Nonce ,
412+ Sender : ev .Sender ,
413+ Target : ev .Target ,
414+ Value : ev .Value ,
415+ GasLimit : ev .GasLimit ,
416+ Data : ev .Data ,
417+ },
418+ disputeGameAddr ,
419+ outputRootIndex ,
420+ superRootProof ,
421+ opnode_bindings.TypesOutputRootProof {
422+ Version : [32 ]byte {},
423+ StateRoot : l2Header .Root ,
424+ MessagePasserStorageRoot : storageRoot ,
425+ LatestBlockhash : l2Header .Hash (),
426+ },
427+ withdrawalProof ,
428+ )
429+ if err != nil {
430+ return nil , fmt .Errorf ("failed to pack prove withdrawal transaction: %w" , err )
431+ }
432+ return txData , nil
433+ }
434+
245435func l2ChainIDForPortal (ctx context.Context , l1EthClient apis.EthClient , portal * bindingspreview.OptimismPortal2 ) (uint64 , error ) {
246436 systemConfigAddr , err := portal .SystemConfig (& bind.CallOpts {Context : ctx })
247437 if err != nil {
@@ -265,6 +455,7 @@ func proveFlags() []cli.Flag {
265455 SupervisorFlag ,
266456 DepSetFlag ,
267457 RollupConfigFlag ,
458+ DisputeGameFlag ,
268459 }
269460 cliFlags = append (cliFlags , txmgr .CLIFlagsWithDefaults (EnvVarPrefix , txmgr .DefaultChallengerFlagValues )... )
270461 cliFlags = append (cliFlags , oplog .CLIFlags (EnvVarPrefix )... )
0 commit comments