github.com/status-im/status-go@v1.1.0/services/wallet/transfer/swap_identifier.go (about)

     1  package transfer
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  
     8  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
     9  	"github.com/ethereum/go-ethereum/common"
    10  	"github.com/ethereum/go-ethereum/common/hexutil"
    11  	"github.com/ethereum/go-ethereum/core/types"
    12  
    13  	uniswapv2 "github.com/status-im/status-go/contracts/uniswapV2"
    14  	uniswapv3 "github.com/status-im/status-go/contracts/uniswapV3"
    15  	"github.com/status-im/status-go/rpc/chain"
    16  	w_common "github.com/status-im/status-go/services/wallet/common"
    17  	"github.com/status-im/status-go/services/wallet/token"
    18  )
    19  
    20  const ETHSymbol string = "ETH"
    21  const WETHSymbol string = "WETH"
    22  
    23  func fetchUniswapV2PairInfo(ctx context.Context, client chain.ClientInterface, pairAddress common.Address) (*common.Address, *common.Address, error) {
    24  	caller, err := uniswapv2.NewUniswapv2Caller(pairAddress, client)
    25  	if err != nil {
    26  		return nil, nil, err
    27  	}
    28  
    29  	token0Address, err := caller.Token0(&bind.CallOpts{
    30  		Context: ctx,
    31  	})
    32  	if err != nil {
    33  		return nil, nil, err
    34  	}
    35  
    36  	token1Address, err := caller.Token1(&bind.CallOpts{
    37  		Context: ctx,
    38  	})
    39  	if err != nil {
    40  		return nil, nil, err
    41  	}
    42  
    43  	return &token0Address, &token1Address, nil
    44  }
    45  
    46  func fetchUniswapV3PoolInfo(ctx context.Context, client chain.ClientInterface, poolAddress common.Address) (*common.Address, *common.Address, error) {
    47  	caller, err := uniswapv3.NewUniswapv3Caller(poolAddress, client)
    48  	if err != nil {
    49  		return nil, nil, err
    50  	}
    51  
    52  	token0Address, err := caller.Token0(&bind.CallOpts{
    53  		Context: ctx,
    54  	})
    55  	if err != nil {
    56  		return nil, nil, err
    57  	}
    58  
    59  	token1Address, err := caller.Token1(&bind.CallOpts{
    60  		Context: ctx,
    61  	})
    62  	if err != nil {
    63  		return nil, nil, err
    64  	}
    65  
    66  	return &token0Address, &token1Address, nil
    67  }
    68  
    69  func identifyUniswapV2Asset(tokenManager *token.Manager, chainID uint64, amount0 *big.Int, contractAddress0 common.Address, amount1 *big.Int, contractAddress1 common.Address) (token *token.Token, amount *big.Int, err error) {
    70  	// Either amount0 or amount1 should be 0
    71  	if amount1.Sign() == 0 && amount0.Sign() != 0 {
    72  		token = tokenManager.FindTokenByAddress(chainID, contractAddress0)
    73  		if token == nil {
    74  			err = fmt.Errorf("couldn't find symbol for token0 %v", contractAddress0)
    75  			return
    76  		}
    77  		amount = amount0
    78  	} else if amount0.Sign() == 0 && amount1.Sign() != 0 {
    79  		token = tokenManager.FindTokenByAddress(chainID, contractAddress1)
    80  		if token == nil {
    81  			err = fmt.Errorf("couldn't find symbol for token1 %v", contractAddress1)
    82  			return
    83  		}
    84  		amount = amount1
    85  	} else {
    86  		err = fmt.Errorf("couldn't identify token %v %v %v %v", contractAddress0, amount0, contractAddress1, amount1)
    87  		return
    88  	}
    89  
    90  	return
    91  }
    92  
    93  func fetchUniswapV2Info(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
    94  	pairAddress, _, _, amount0In, amount1In, amount0Out, amount1Out, err := w_common.ParseUniswapV2Log(log)
    95  	if err != nil {
    96  		return
    97  	}
    98  
    99  	token0ContractAddress, token1ContractAddress, err := fetchUniswapV2PairInfo(ctx, client, pairAddress)
   100  	if err != nil {
   101  		return
   102  	}
   103  
   104  	fromToken, fromAmountInt, err := identifyUniswapV2Asset(tokenManager, client.NetworkID(), amount0In, *token0ContractAddress, amount1In, *token1ContractAddress)
   105  	if err != nil {
   106  		// "Soft" error, allow to continue with unknown asset
   107  		fromAsset = ""
   108  		fromAmount = (*hexutil.Big)(big.NewInt(0))
   109  	} else {
   110  		fromAsset = fromToken.Symbol
   111  		fromAmount = (*hexutil.Big)(fromAmountInt)
   112  	}
   113  
   114  	toToken, toAmountInt, err := identifyUniswapV2Asset(tokenManager, client.NetworkID(), amount0Out, *token0ContractAddress, amount1Out, *token1ContractAddress)
   115  	if err != nil {
   116  		// "Soft" error, allow to continue with unknown asset
   117  		toAsset = ""
   118  		toAmount = (*hexutil.Big)(big.NewInt(0))
   119  	} else {
   120  		toAsset = toToken.Symbol
   121  		toAmount = (*hexutil.Big)(toAmountInt)
   122  	}
   123  
   124  	err = nil
   125  	return
   126  }
   127  
   128  func identifyUniswapV3Assets(tokenManager *token.Manager, chainID uint64, amount0 *big.Int, contractAddress0 common.Address, amount1 *big.Int, contractAddress1 common.Address) (fromToken *token.Token, fromAmount *big.Int, toToken *token.Token, toAmount *big.Int, err error) {
   129  	token0 := tokenManager.FindTokenByAddress(chainID, contractAddress0)
   130  	if token0 == nil {
   131  		err = fmt.Errorf("couldn't find symbol for token0 %v", contractAddress0)
   132  		return
   133  	}
   134  
   135  	token1 := tokenManager.FindTokenByAddress(chainID, contractAddress1)
   136  	if token1 == nil {
   137  		err = fmt.Errorf("couldn't find symbol for token1 %v", contractAddress1)
   138  		return
   139  	}
   140  
   141  	// amount0 and amount1 are the balance deltas of the pool
   142  	// The positive amount is how much the sender spent
   143  	// The negative amount is how much the recipent got
   144  	if amount0.Sign() > 0 && amount1.Sign() < 0 {
   145  		fromToken = token0
   146  		fromAmount = amount0
   147  		toToken = token1
   148  		toAmount = new(big.Int).Neg(amount1)
   149  	} else if amount0.Sign() < 0 && amount1.Sign() > 0 {
   150  		fromToken = token1
   151  		fromAmount = amount1
   152  		toToken = token0
   153  		toAmount = new(big.Int).Neg(amount0)
   154  	} else {
   155  		err = fmt.Errorf("couldn't identify tokens %v %v %v %v", contractAddress0, amount0, contractAddress1, amount1)
   156  		return
   157  	}
   158  
   159  	return
   160  }
   161  
   162  func fetchUniswapV3Info(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
   163  	poolAddress, _, _, amount0, amount1, err := w_common.ParseUniswapV3Log(log)
   164  	if err != nil {
   165  		return
   166  	}
   167  
   168  	token0ContractAddress, token1ContractAddress, err := fetchUniswapV3PoolInfo(ctx, client, poolAddress)
   169  	if err != nil {
   170  		return
   171  	}
   172  
   173  	fromToken, fromAmountInt, toToken, toAmountInt, err := identifyUniswapV3Assets(tokenManager, client.NetworkID(), amount0, *token0ContractAddress, amount1, *token1ContractAddress)
   174  	if err != nil {
   175  		// "Soft" error, allow to continue with unknown asset
   176  		err = nil
   177  		fromAsset = ""
   178  		fromAmount = (*hexutil.Big)(big.NewInt(0))
   179  		toAsset = ""
   180  		toAmount = (*hexutil.Big)(big.NewInt(0))
   181  	} else {
   182  		fromAsset = fromToken.Symbol
   183  		fromAmount = (*hexutil.Big)(fromAmountInt)
   184  		toAsset = toToken.Symbol
   185  		toAmount = (*hexutil.Big)(toAmountInt)
   186  	}
   187  
   188  	return
   189  }
   190  
   191  func fetchUniswapInfo(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log, logType w_common.EventType) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
   192  	switch logType {
   193  	case w_common.UniswapV2SwapEventType:
   194  		return fetchUniswapV2Info(ctx, client, tokenManager, log)
   195  	case w_common.UniswapV3SwapEventType:
   196  		return fetchUniswapV3Info(ctx, client, tokenManager, log)
   197  	}
   198  	err = fmt.Errorf("wrong log type %s", logType)
   199  	return
   200  }
   201  
   202  // Build a Swap multitransaction from a list containing one or several uniswapV2/uniswapV3 subTxs
   203  // We only care about the first and last swap to identify the input/output token and amounts
   204  func buildUniswapSwapMultitransaction(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, transfer *Transfer) (*MultiTransaction, error) {
   205  	multiTransaction := MultiTransaction{
   206  		Type:          MultiTransactionSwap,
   207  		FromNetworkID: transfer.NetworkID,
   208  		FromTxHash:    transfer.Receipt.TxHash,
   209  		FromAddress:   transfer.Address,
   210  		ToNetworkID:   transfer.NetworkID,
   211  		ToTxHash:      transfer.Receipt.TxHash,
   212  		ToAddress:     transfer.Address,
   213  		Timestamp:     transfer.Timestamp,
   214  	}
   215  
   216  	var firstSwapLog, lastSwapLog *types.Log
   217  	var firstSwapLogType, lastSwapLogType w_common.EventType
   218  	hasWETHDepositLog := false
   219  	hasWETHWithdrawalLog := false
   220  
   221  	for _, ethlog := range transfer.Receipt.Logs {
   222  		logType := w_common.GetEventType(ethlog)
   223  		switch logType {
   224  		case w_common.WETHDepositEventType:
   225  			hasWETHDepositLog = true
   226  		case w_common.WETHWithdrawalEventType:
   227  			hasWETHWithdrawalLog = true
   228  		case w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType:
   229  			if firstSwapLog == nil {
   230  				firstSwapLog = ethlog
   231  				firstSwapLogType = logType
   232  			}
   233  			lastSwapLog = ethlog
   234  			lastSwapLogType = logType
   235  		}
   236  	}
   237  
   238  	var err error
   239  
   240  	multiTransaction.FromAsset, multiTransaction.FromAmount, multiTransaction.ToAsset, multiTransaction.ToAmount, err = fetchUniswapInfo(ctx, client, tokenManager, firstSwapLog, firstSwapLogType)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	if firstSwapLog != lastSwapLog {
   246  		_, _, multiTransaction.ToAsset, multiTransaction.ToAmount, err = fetchUniswapInfo(ctx, client, tokenManager, lastSwapLog, lastSwapLogType)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  	}
   251  
   252  	// WETH and ETH have same decimals value, no need to change From/To Amount
   253  	if multiTransaction.FromAsset == WETHSymbol && hasWETHDepositLog {
   254  		multiTransaction.FromAsset = ETHSymbol
   255  	}
   256  
   257  	if multiTransaction.ToAsset == WETHSymbol && hasWETHWithdrawalLog {
   258  		multiTransaction.ToAsset = ETHSymbol
   259  	}
   260  
   261  	return &multiTransaction, nil
   262  }