feat(wallet): detect ETH L2 to L1/L2 bridge transactions

This commit is contained in:
Dario Gabriel Lipicar 2023-07-03 14:32:44 -03:00 committed by dlipicar
parent e6711c8cc8
commit 66a21aa7e4
5 changed files with 262 additions and 88 deletions

View File

@ -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))
}

View File

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)

View File

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

View File

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

View File

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