parent
2fc79fb9b5
commit
449314a4dc
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue