feat(Wallet) detect uniswapV3 swap events

Part of #10251
This commit is contained in:
Dario Gabriel Lipicar 2023-06-02 07:11:03 -03:00 committed by dlipicar
parent 2fc79fb9b5
commit 449314a4dc
4 changed files with 145 additions and 5 deletions

View File

@ -442,8 +442,8 @@ func (c *transfersCommand) checkAndProcessPendingMultiTx(subTx *Transfer) (Multi
func (c *transfersCommand) checkAndProcessSwapMultiTx(ctx context.Context, subTx *Transfer) (MultiTransactionIDType, error) {
switch subTx.Type {
// If the Tx contains any uniswapV2Swap subTx, generate a Swap multiTx
case uniswapV2Swap:
// If the Tx contains any uniswapV2Swap/uniswapV3Swap subTx, generate a Swap multiTx
case uniswapV2Swap, uniswapV3Swap:
multiTransaction, err := buildUniswapSwapMultitransaction(ctx, c.chainClient, c.tokenManager, subTx)
if err != nil {
return NoMultiTransactionID, err

View File

@ -306,7 +306,7 @@ func (d *ETHDownloader) subTransactionsFromTransactionHash(parent context.Contex
if from == address || to == address {
mustAppend = true
}
case uniswapV2SwapEventType:
case uniswapV2SwapEventType, uniswapV3SwapEventType:
mustAppend = true
}

View File

@ -21,12 +21,14 @@ const (
erc20Transfer Type = "erc20"
erc721Transfer Type = "erc721"
uniswapV2Swap Type = "uniswapV2Swap"
uniswapV3Swap Type = "uniswapV3Swap"
unknownTransaction Type = "unknown"
// Event types
erc20TransferEventType EventType = "erc20Event"
erc721TransferEventType EventType = "erc721Event"
uniswapV2SwapEventType EventType = "uniswapV2SwapEvent"
uniswapV3SwapEventType EventType = "uniswapV3SwapEvent"
unknownEventType EventType = "unknownEvent"
erc20_721TransferEventSignature = "Transfer(address,address,uint256)"
@ -35,6 +37,7 @@ const (
erc721TransferEventIndexedParameters = 4 // signature, from, to, tokenId
uniswapV2SwapEventSignature = "Swap(address,uint256,uint256,uint256,uint256,address)" // also used by SushiSwap
uniswapV3SwapEventSignature = "Swap(address,address,int256,int256,uint160,uint128,int24)"
)
var (
@ -46,6 +49,7 @@ var (
func GetEventType(log *types.Log) EventType {
erc20_721TransferEventSignatureHash := getEventSignatureHash(erc20_721TransferEventSignature)
uniswapV2SwapEventSignatureHash := getEventSignatureHash(uniswapV2SwapEventSignature)
uniswapV3SwapEventSignatureHash := getEventSignatureHash(uniswapV3SwapEventSignature)
if len(log.Topics) > 0 {
switch log.Topics[0] {
@ -58,6 +62,8 @@ func GetEventType(log *types.Log) EventType {
}
case uniswapV2SwapEventSignatureHash:
return uniswapV2SwapEventType
case uniswapV3SwapEventSignatureHash:
return uniswapV3SwapEventType
}
}
@ -72,6 +78,8 @@ func EventTypeToSubtransactionType(eventType EventType) Type {
return erc721Transfer
case uniswapV2SwapEventType:
return uniswapV2Swap
case uniswapV3SwapEventType:
return uniswapV3Swap
}
return unknownTransaction
@ -177,3 +185,47 @@ func parseUniswapV2Log(ethlog *types.Log) (pairAddress common.Address, from comm
return
}
func readInt256(b []byte) *big.Int {
// big.SetBytes can't tell if a number is negative or positive in itself.
// On EVM, if the returned number > max int256, it is negative.
// A number is > max int256 if the bit at position 255 is set.
ret := new(big.Int).SetBytes(b)
if ret.Bit(255) == 1 {
ret.Add(MaxUint256, new(big.Int).Neg(ret))
ret.Add(ret, common.Big1)
ret.Neg(ret)
}
return ret
}
func parseUniswapV3Log(ethlog *types.Log) (poolAddress common.Address, sender common.Address, recipient common.Address, amount0 *big.Int, amount1 *big.Int, err error) {
amount0 = new(big.Int)
amount1 = new(big.Int)
if len(ethlog.Topics) < 3 {
err = fmt.Errorf("not enough topics for uniswapV3 swap %s, %v", "topics", ethlog.Topics)
return
}
poolAddress = ethlog.Address
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
}
if len(ethlog.Topics[2]) != 32 {
err = fmt.Errorf("third topic is not padded to 32 byte address %s, %v", "topic", ethlog.Topics[2])
return
}
copy(sender[:], ethlog.Topics[1][12:])
copy(recipient[:], ethlog.Topics[2][12:])
if len(ethlog.Data) != 32*5 {
err = fmt.Errorf("data is not padded to 5 * 32 bytes big int %s, %v", "data", ethlog.Data)
return
}
amount0 = readInt256(ethlog.Data[0:32])
amount1 = readInt256(ethlog.Data[32:64])
return
}

View File

@ -38,6 +38,29 @@ func fetchUniswapV2PairInfo(ctx context.Context, client *chain.ClientWithFallbac
return &token0Address, &token1Address, nil
}
func fetchUniswapV3PoolInfo(ctx context.Context, client *chain.ClientWithFallback, poolAddress common.Address) (*common.Address, *common.Address, error) {
caller, err := uniswapv3.NewUniswapv3Caller(poolAddress, client)
if err != nil {
return nil, nil, err
}
token0Address, err := caller.Token0(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return nil, nil, err
}
token1Address, err := caller.Token1(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return nil, nil, err
}
return &token0Address, &token1Address, nil
}
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) {
// Either amount0 or amount1 should be 0
if amount1.Sign() == 0 && amount0.Sign() != 0 {
@ -97,16 +120,81 @@ func fetchUniswapV2Info(ctx context.Context, client *chain.ClientWithFallback, t
return
}
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) {
token0 := tokenManager.FindTokenByAddress(chainID, contractAddress0)
if token0 == nil {
err = fmt.Errorf("couldn't find symbol for token0 %v", contractAddress0)
return
}
token1 := tokenManager.FindTokenByAddress(chainID, contractAddress1)
if token1 == nil {
err = fmt.Errorf("couldn't find symbol for token1 %v", contractAddress1)
return
}
// amount0 and amount1 are the balance deltas of the pool
// The positive amount is how much the sender spent
// The negative amount is how much the recipent got
if amount0.Sign() > 0 && amount1.Sign() < 0 {
fromToken = token0
fromAmount = amount0
toToken = token1
toAmount = new(big.Int).Neg(amount1)
} else if amount0.Sign() < 0 && amount1.Sign() > 0 {
fromToken = token1
fromAmount = amount1
toToken = token0
toAmount = new(big.Int).Neg(amount0)
} else {
err = fmt.Errorf("couldn't identify tokens %v %v %v %v", contractAddress0, amount0, contractAddress1, amount1)
return
}
return
}
func fetchUniswapV3Info(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
poolAddress, _, _, amount0, amount1, err := parseUniswapV3Log(log)
if err != nil {
return
}
token0ContractAddress, token1ContractAddress, err := fetchUniswapV3PoolInfo(ctx, client, poolAddress)
if err != nil {
return
}
fromToken, fromAmountInt, toToken, toAmountInt, err := identifyUniswapV3Assets(tokenManager, client.ChainID, amount0, *token0ContractAddress, amount1, *token1ContractAddress)
if err != nil {
// "Soft" error, allow to continue with unknown asset
err = nil
fromAsset = ""
fromAmount = (*hexutil.Big)(big.NewInt(0))
toAsset = ""
toAmount = (*hexutil.Big)(big.NewInt(0))
} else {
fromAsset = fromToken.Symbol
fromAmount = (*hexutil.Big)(fromAmountInt)
toAsset = toToken.Symbol
toAmount = (*hexutil.Big)(toAmountInt)
}
return
}
func fetchUniswapInfo(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, log *types.Log, logType EventType) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) {
switch logType {
case uniswapV2SwapEventType:
return fetchUniswapV2Info(ctx, client, tokenManager, log)
case uniswapV3SwapEventType:
return fetchUniswapV3Info(ctx, client, tokenManager, log)
}
err = fmt.Errorf("wrong log type %s", logType)
return
}
// Build a Swap multitransaction from a list containing one or several uniswapV2 subTxs
// Build a Swap multitransaction from a list containing one or several uniswapV2/uniswapV3 subTxs
// We only care about the first and last swap to identify the input/output token and amounts
func buildUniswapSwapMultitransaction(ctx context.Context, client *chain.ClientWithFallback, tokenManager *token.Manager, transfer *Transfer) (*MultiTransaction, error) {
multiTransaction := MultiTransaction{
@ -121,7 +209,7 @@ func buildUniswapSwapMultitransaction(ctx context.Context, client *chain.ClientW
for _, ethlog := range transfer.Receipt.Logs {
logType := GetEventType(ethlog)
switch logType {
case uniswapV2SwapEventType:
case uniswapV2SwapEventType, uniswapV3SwapEventType:
if firstSwapLog == nil {
firstSwapLog = ethlog
firstSwapLogType = logType