feat(wallet): detect ETH L2 to L1/L2 bridge transactions
This commit is contained in:
parent
e6711c8cc8
commit
66a21aa7e4
|
@ -38,6 +38,8 @@ const (
|
|||
UniswapV3SwapEventType EventType = "uniswapV3SwapEvent"
|
||||
HopBridgeTransferSentToL2EventType EventType = "hopBridgeTransferSentToL2Event"
|
||||
HopBridgeTransferFromL1CompletedEventType EventType = "hopBridgeTransferFromL1CompletedEvent"
|
||||
HopBridgeWithdrawalBondedEventType EventType = "hopBridgeWithdrawalBondedEvent"
|
||||
HopBridgeTransferSentEventType EventType = "hopBridgeTransferSentEvent"
|
||||
UnknownEventType EventType = "unknownEvent"
|
||||
|
||||
// Deposit (index_topic_1 address dst, uint256 wad)
|
||||
|
@ -61,6 +63,10 @@ const (
|
|||
hopBridgeTransferSentToL2EventSignature = "TransferSentToL2(uint256,address,uint256,uint256,uint256,address,uint256)"
|
||||
// TransferFromL1Completed (index_topic_1 address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline, index_topic_2 address relayer, uint256 relayerFee)
|
||||
HopBridgeTransferFromL1CompletedEventSignature = "TransferFromL1Completed(address,uint256,uint256,uint256,address,uint256)"
|
||||
// WithdrawalBonded (index_topic_1 bytes32 transferID, uint256 amount)
|
||||
hopBridgeWithdrawalBondedEventSignature = "WithdrawalBonded(bytes32,uint256)"
|
||||
// TransferSent (index_topic_1 bytes32 transferID, index_topic_2 uint256 chainId, index_topic_3 address recipient, uint256 amount, bytes32 transferNonce, uint256 bonderFee, uint256 index, uint256 amountOutMin, uint256 deadline)
|
||||
hopBridgeTransferSentEventSignature = "TransferSent(bytes32,uint256,address,uint256,bytes32,uint256,uint256,uint256,uint256)"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -77,6 +83,8 @@ func GetEventType(log *types.Log) EventType {
|
|||
uniswapV3SwapEventSignatureHash := GetEventSignatureHash(uniswapV3SwapEventSignature)
|
||||
hopBridgeTransferSentToL2EventSignatureHash := GetEventSignatureHash(hopBridgeTransferSentToL2EventSignature)
|
||||
hopBridgeTransferFromL1CompletedEventSignatureHash := GetEventSignatureHash(HopBridgeTransferFromL1CompletedEventSignature)
|
||||
hopBridgeWithdrawalBondedEventSignatureHash := GetEventSignatureHash(hopBridgeWithdrawalBondedEventSignature)
|
||||
hopBridgeTransferSentEventSignatureHash := GetEventSignatureHash(hopBridgeTransferSentEventSignature)
|
||||
|
||||
if len(log.Topics) > 0 {
|
||||
switch log.Topics[0] {
|
||||
|
@ -99,6 +107,10 @@ func GetEventType(log *types.Log) EventType {
|
|||
return HopBridgeTransferSentToL2EventType
|
||||
case hopBridgeTransferFromL1CompletedEventSignatureHash:
|
||||
return HopBridgeTransferFromL1CompletedEventType
|
||||
case hopBridgeWithdrawalBondedEventSignatureHash:
|
||||
return HopBridgeWithdrawalBondedEventType
|
||||
case hopBridgeTransferSentEventSignatureHash:
|
||||
return HopBridgeTransferSentEventType
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,9 +127,9 @@ func EventTypeToSubtransactionType(eventType EventType) Type {
|
|||
return UniswapV2Swap
|
||||
case UniswapV3SwapEventType:
|
||||
return UniswapV3Swap
|
||||
case HopBridgeTransferSentToL2EventType:
|
||||
case HopBridgeTransferSentToL2EventType, HopBridgeTransferSentEventType:
|
||||
return HopBridgeFrom
|
||||
case HopBridgeTransferFromL1CompletedEventType:
|
||||
case HopBridgeTransferFromL1CompletedEventType, HopBridgeWithdrawalBondedEventType:
|
||||
return HopBridgeTo
|
||||
}
|
||||
|
||||
|
@ -383,6 +395,80 @@ func ParseHopBridgeTransferFromL1CompletedLog(ethlog *types.Log) (recipient comm
|
|||
return
|
||||
}
|
||||
|
||||
func ParseHopWithdrawalBondedLog(ethlog *types.Log) (transferID *big.Int, amount *big.Int, err error) {
|
||||
transferID = new(big.Int)
|
||||
amount = new(big.Int)
|
||||
|
||||
if len(ethlog.Topics) < 2 {
|
||||
err = fmt.Errorf("not enough topics for HopWithdrawalBonded event %s, %v", "topics", ethlog.Topics)
|
||||
return
|
||||
}
|
||||
|
||||
if len(ethlog.Topics[1]) != 32 {
|
||||
err = fmt.Errorf("second topic is not padded to 32 byte address %s, %v", "topic", ethlog.Topics[1])
|
||||
return
|
||||
}
|
||||
transferID.SetBytes(ethlog.Topics[1][:])
|
||||
|
||||
if len(ethlog.Data) != 32*1 {
|
||||
err = fmt.Errorf("data is not padded to 1 * 32 bytes big int %s, %v", "data", ethlog.Data)
|
||||
return
|
||||
}
|
||||
|
||||
amount.SetBytes(ethlog.Data[0:32])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseHopBridgeTransferSentLog(ethlog *types.Log) (transferID *big.Int, chainID uint64, recipient common.Address, amount *big.Int, transferNonce *big.Int, bonderFee *big.Int, index *big.Int, amountOutMin *big.Int, deadline *big.Int, err error) {
|
||||
transferID = new(big.Int)
|
||||
chainIDInt := new(big.Int)
|
||||
amount = new(big.Int)
|
||||
transferNonce = new(big.Int)
|
||||
bonderFee = new(big.Int)
|
||||
index = new(big.Int)
|
||||
amountOutMin = new(big.Int)
|
||||
deadline = new(big.Int)
|
||||
|
||||
if len(ethlog.Topics) < 4 {
|
||||
err = fmt.Errorf("not enough topics for HopBridgeTransferSent event %s, %v", "topics", ethlog.Topics)
|
||||
return
|
||||
}
|
||||
|
||||
if len(ethlog.Topics[1]) != 32 {
|
||||
err = fmt.Errorf("second topic is not padded to 32 byte big int %s, %v", "topic", ethlog.Topics[1])
|
||||
return
|
||||
}
|
||||
transferID.SetBytes(ethlog.Topics[1][:])
|
||||
|
||||
if len(ethlog.Topics[2]) != 32 {
|
||||
err = fmt.Errorf("third topic is not padded to 32 byte big int %s, %v", "topic", ethlog.Topics[2])
|
||||
return
|
||||
}
|
||||
chainIDInt.SetBytes(ethlog.Topics[2][:])
|
||||
chainID = chainIDInt.Uint64()
|
||||
|
||||
if len(ethlog.Topics[3]) != 32 {
|
||||
err = fmt.Errorf("fourth topic is not padded to 32 byte address %s, %v", "topic", ethlog.Topics[3])
|
||||
return
|
||||
}
|
||||
copy(recipient[:], ethlog.Topics[2][12:])
|
||||
|
||||
if len(ethlog.Data) != 32*6 {
|
||||
err = fmt.Errorf("data is not padded to 6 * 32 bytes big int %s, %v", "data", ethlog.Data)
|
||||
return
|
||||
}
|
||||
|
||||
amount.SetBytes(ethlog.Data[0:32])
|
||||
transferNonce.SetBytes(ethlog.Data[32:64])
|
||||
bonderFee.SetBytes(ethlog.Data[64:96])
|
||||
index.SetBytes(ethlog.Data[96:128])
|
||||
amountOutMin.SetBytes(ethlog.Data[128:160])
|
||||
deadline.SetBytes(ethlog.Data[160:192])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetEventSignatureHash(signature string) common.Hash {
|
||||
return crypto.Keccak256Hash([]byte(signature))
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
@ -16,115 +17,198 @@ import (
|
|||
// Current approach is not failsafe (for example, if multiple identical bridge operations are triggered
|
||||
// at the same time)
|
||||
// Recipient + Relayer + Data should match in both Origin and Destination transactions
|
||||
func getHopBridgeCrossTxID(recipient common.Address, relayer common.Address, logData []byte) string {
|
||||
return fmt.Sprintf("%s_%s_%s", recipient.String(), relayer.String(), hex.EncodeToString(logData))
|
||||
func getHopBridgeFromL1CrossTxID(recipient common.Address, relayer common.Address, logData []byte) string {
|
||||
return fmt.Sprintf("FromL1_%s_%s_%s", recipient.String(), relayer.String(), hex.EncodeToString(logData))
|
||||
}
|
||||
|
||||
func getHopBridgeFromL2CrossTxID(transferID *big.Int) string {
|
||||
return fmt.Sprintf("FromL2_0x%s", transferID.Text(16))
|
||||
}
|
||||
|
||||
type originTxParams struct {
|
||||
fromNetworkID uint64
|
||||
fromTxHash common.Hash
|
||||
fromAddress common.Address
|
||||
fromAsset string
|
||||
fromAmount *big.Int
|
||||
toNetworkID uint64
|
||||
toAddress common.Address
|
||||
crossTxID string
|
||||
}
|
||||
|
||||
func upsertHopBridgeOriginTx(ctx context.Context, transactionManager *TransactionManager, params originTxParams) (*MultiTransaction, error) {
|
||||
// Try to find "destination" half of the multiTx
|
||||
multiTx, err := transactionManager.GetBridgeDestinationMultiTransaction(ctx, params.toNetworkID, params.crossTxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if multiTx == nil {
|
||||
multiTx = &MultiTransaction{
|
||||
// Data from "origin" transaction
|
||||
FromNetworkID: params.fromNetworkID,
|
||||
FromTxHash: params.fromTxHash,
|
||||
FromAddress: params.fromAddress,
|
||||
FromAsset: params.fromAsset,
|
||||
FromAmount: (*hexutil.Big)(params.fromAmount),
|
||||
ToNetworkID: params.toNetworkID,
|
||||
ToAddress: params.toAddress,
|
||||
// To be replaced by "destination" transaction, need to be non-null
|
||||
ToAsset: params.fromAsset,
|
||||
ToAmount: (*hexutil.Big)(params.fromAmount),
|
||||
// Common data
|
||||
Type: MultiTransactionBridge,
|
||||
CrossTxID: params.crossTxID,
|
||||
}
|
||||
|
||||
_, err := transactionManager.InsertMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
multiTx.FromNetworkID = params.fromNetworkID
|
||||
multiTx.FromTxHash = params.fromTxHash
|
||||
multiTx.FromAddress = params.fromAddress
|
||||
multiTx.FromAsset = params.fromAsset
|
||||
multiTx.FromAmount = (*hexutil.Big)(params.fromAmount)
|
||||
|
||||
err := transactionManager.UpdateMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return multiTx, nil
|
||||
}
|
||||
|
||||
type destinationTxParams struct {
|
||||
toNetworkID uint64
|
||||
toTxHash common.Hash
|
||||
toAddress common.Address
|
||||
toAsset string
|
||||
toAmount *big.Int
|
||||
crossTxID string
|
||||
}
|
||||
|
||||
func upsertHopBridgeDestinationTx(ctx context.Context, transactionManager *TransactionManager, params destinationTxParams) (*MultiTransaction, error) {
|
||||
// Try to find "origin" half of the multiTx
|
||||
multiTx, err := transactionManager.GetBridgeOriginMultiTransaction(ctx, params.toNetworkID, params.crossTxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if multiTx == nil {
|
||||
multiTx = &MultiTransaction{
|
||||
// To be replaced by "origin" transaction, need to be non-null
|
||||
FromAddress: params.toAddress,
|
||||
FromAsset: params.toAsset,
|
||||
FromAmount: (*hexutil.Big)(params.toAmount),
|
||||
// Data from "destination" transaction
|
||||
ToNetworkID: params.toNetworkID,
|
||||
ToTxHash: params.toTxHash,
|
||||
ToAddress: params.toAddress,
|
||||
ToAsset: params.toAsset,
|
||||
ToAmount: (*hexutil.Big)(params.toAmount),
|
||||
// Common data
|
||||
Type: MultiTransactionBridge,
|
||||
CrossTxID: params.crossTxID,
|
||||
}
|
||||
|
||||
_, err := transactionManager.InsertMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
multiTx.ToTxHash = params.toTxHash
|
||||
multiTx.ToAsset = params.toAsset
|
||||
multiTx.ToAmount = (*hexutil.Big)(params.toAmount)
|
||||
|
||||
err := transactionManager.UpdateMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return multiTx, nil
|
||||
}
|
||||
|
||||
func buildHopBridgeMultitransaction(ctx context.Context, client *chain.ClientWithFallback, transactionManager *TransactionManager, tokenManager *token.Manager, subTx *Transfer) (*MultiTransaction, error) {
|
||||
// Identify if it's from/to transaction
|
||||
switch w_common.GetEventType(subTx.Log) {
|
||||
case w_common.HopBridgeTransferSentToL2EventType:
|
||||
// L1-L2 Origin transaciton
|
||||
fromChainID := subTx.NetworkID
|
||||
fromTxHash := subTx.Receipt.TxHash
|
||||
|
||||
// L1->L2 Origin transaction
|
||||
toChainID, recipient, relayer, fromAmount, err := w_common.ParseHopBridgeTransferSentToL2Log(subTx.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crossTxID := getHopBridgeCrossTxID(recipient, relayer, subTx.Log.Data)
|
||||
|
||||
// Try to find "destination" half of the multiTx
|
||||
multiTx, err := transactionManager.GetBridgeDestinationMultiTransaction(ctx, toChainID, crossTxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
params := originTxParams{
|
||||
fromNetworkID: subTx.NetworkID,
|
||||
fromTxHash: subTx.Receipt.TxHash,
|
||||
fromAddress: subTx.From,
|
||||
fromAsset: "ETH",
|
||||
fromAmount: fromAmount,
|
||||
toNetworkID: toChainID,
|
||||
toAddress: recipient,
|
||||
crossTxID: getHopBridgeFromL1CrossTxID(recipient, relayer, subTx.Log.Data),
|
||||
}
|
||||
|
||||
if multiTx == nil {
|
||||
multiTx = &MultiTransaction{
|
||||
// Data from "origin" transaction
|
||||
FromNetworkID: fromChainID,
|
||||
FromTxHash: fromTxHash,
|
||||
FromAddress: subTx.From,
|
||||
FromAsset: "ETH",
|
||||
FromAmount: (*hexutil.Big)(fromAmount),
|
||||
ToNetworkID: toChainID,
|
||||
ToAddress: recipient,
|
||||
// To be replaced by "destination" transaction, need to be non-null
|
||||
ToAmount: (*hexutil.Big)(fromAmount),
|
||||
// Common data
|
||||
Type: MultiTransactionBridge,
|
||||
CrossTxID: crossTxID,
|
||||
}
|
||||
|
||||
_, err := transactionManager.InsertMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
multiTx.FromNetworkID = fromChainID
|
||||
multiTx.FromTxHash = fromTxHash
|
||||
multiTx.FromAddress = subTx.From
|
||||
multiTx.FromAsset = "ETH"
|
||||
multiTx.FromAmount = (*hexutil.Big)(fromAmount)
|
||||
|
||||
err := transactionManager.UpdateMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return multiTx, nil
|
||||
return upsertHopBridgeOriginTx(ctx, transactionManager, params)
|
||||
|
||||
case w_common.HopBridgeTransferFromL1CompletedEventType:
|
||||
// L1-L2 Destination transaciton
|
||||
toChainID := subTx.NetworkID
|
||||
toTxHash := subTx.Receipt.TxHash
|
||||
|
||||
// L1->L2 Destination transaction
|
||||
recipient, relayer, toAmount, err := w_common.ParseHopBridgeTransferFromL1CompletedLog(subTx.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crossTxID := getHopBridgeCrossTxID(recipient, relayer, subTx.Log.Data)
|
||||
|
||||
// Try to find "origin" half of the multiTx
|
||||
multiTx, err := transactionManager.GetBridgeOriginMultiTransaction(ctx, toChainID, crossTxID)
|
||||
params := destinationTxParams{
|
||||
toNetworkID: subTx.NetworkID,
|
||||
toTxHash: subTx.Receipt.TxHash,
|
||||
toAddress: recipient,
|
||||
toAsset: "ETH",
|
||||
toAmount: toAmount,
|
||||
crossTxID: getHopBridgeFromL1CrossTxID(recipient, relayer, subTx.Log.Data),
|
||||
}
|
||||
|
||||
return upsertHopBridgeDestinationTx(ctx, transactionManager, params)
|
||||
|
||||
case w_common.HopBridgeTransferSentEventType:
|
||||
// L2->L1 / L2->L2 Origin transaction
|
||||
transferID, toChainID, recipient, fromAmount, _, _, _, _, _, err := w_common.ParseHopBridgeTransferSentLog(subTx.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if multiTx == nil {
|
||||
multiTx = &MultiTransaction{
|
||||
// To be replaced by "origin" transaction, need to be non-null
|
||||
FromAddress: recipient,
|
||||
FromAsset: "ETH",
|
||||
FromAmount: (*hexutil.Big)(toAmount),
|
||||
// Data from "destination" transaction
|
||||
ToNetworkID: toChainID,
|
||||
ToTxHash: toTxHash,
|
||||
ToAddress: recipient,
|
||||
ToAsset: "ETH",
|
||||
ToAmount: (*hexutil.Big)(toAmount),
|
||||
// Common data
|
||||
Type: MultiTransactionBridge,
|
||||
CrossTxID: crossTxID,
|
||||
}
|
||||
|
||||
_, err := transactionManager.InsertMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
multiTx.ToTxHash = toTxHash
|
||||
multiTx.ToAsset = "ETH"
|
||||
multiTx.ToAmount = (*hexutil.Big)(toAmount)
|
||||
|
||||
err := transactionManager.UpdateMultiTransaction(multiTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := originTxParams{
|
||||
fromNetworkID: subTx.NetworkID,
|
||||
fromTxHash: subTx.Receipt.TxHash,
|
||||
fromAddress: subTx.From,
|
||||
fromAsset: "ETH",
|
||||
fromAmount: fromAmount,
|
||||
toNetworkID: toChainID,
|
||||
toAddress: recipient,
|
||||
crossTxID: getHopBridgeFromL2CrossTxID(transferID),
|
||||
}
|
||||
return multiTx, nil
|
||||
|
||||
return upsertHopBridgeOriginTx(ctx, transactionManager, params)
|
||||
|
||||
case w_common.HopBridgeWithdrawalBondedEventType:
|
||||
// L2->L1 / L2->L2 Destination transaction
|
||||
transferID, toAmount, err := w_common.ParseHopWithdrawalBondedLog(subTx.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := destinationTxParams{
|
||||
toNetworkID: subTx.NetworkID,
|
||||
toTxHash: subTx.Receipt.TxHash,
|
||||
toAddress: subTx.Address,
|
||||
toAsset: "ETH",
|
||||
toAmount: toAmount,
|
||||
crossTxID: getHopBridgeFromL2CrossTxID(transferID),
|
||||
}
|
||||
|
||||
return upsertHopBridgeDestinationTx(ctx, transactionManager, params)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -483,7 +483,7 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
|
|||
txNonce = new(uint64)
|
||||
*txNonce = t.Transaction.Nonce()
|
||||
txSize = new(uint64)
|
||||
*txSize = uint64(t.Transaction.Size())
|
||||
*txSize = t.Transaction.Size()
|
||||
}
|
||||
|
||||
dbFields := transferDBFields{
|
||||
|
|
|
@ -175,6 +175,7 @@ func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Bloc
|
|||
|
||||
if err != nil {
|
||||
if err == core.ErrTxTypeNotSupported {
|
||||
log.Error("Tx Type not supported", "tx chain id", tx.ChainId(), "type", tx.Type(), "error", err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
|
@ -187,7 +188,7 @@ func (d *ETHDownloader) getTransfersInBlock(ctx context.Context, blk *types.Bloc
|
|||
// We might miss some subTransactions of interest for some transaction types. We need to check if we
|
||||
// find the address in the transaction data.
|
||||
switch tx.Type() {
|
||||
case types.OptimismDepositTxType, types.ArbitrumDepositTxType, types.ArbitrumRetryTxType:
|
||||
case types.DynamicFeeTxType, types.OptimismDepositTxType, types.ArbitrumDepositTxType, types.ArbitrumRetryTxType:
|
||||
mustCheckSubTxs = !areSubTxsCheckedForTxHash[tx.Hash()] && w_common.TxDataContainsAddress(tx.Type(), tx.Data(), address)
|
||||
}
|
||||
}
|
||||
|
@ -343,6 +344,8 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(tx *types.Transaction
|
|||
mustAppend = true
|
||||
case w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType:
|
||||
mustAppend = true
|
||||
case w_common.HopBridgeWithdrawalBondedEventType, w_common.HopBridgeTransferSentEventType:
|
||||
mustAppend = true
|
||||
}
|
||||
|
||||
if mustAppend {
|
||||
|
|
Loading…
Reference in New Issue