Skip to content

Commit

Permalink
Merge pull request #2426 from fluidity-money/develop-integrate-trader…
Browse files Browse the repository at this point in the history
…-joe

integrate trader joe
  • Loading branch information
af-afk authored Nov 23, 2023
2 parents 996fd65 + 6cece55 commit ad2185b
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 32 deletions.
1 change: 1 addition & 0 deletions common/ethereum/applications/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ func GetApplicationFee(transfer worker.EthereumApplicationTransfer, client *ethc
transfer,
client,
fluidTokenContract,
tokenDecimals,
)
emission.TraderJoe += util.MaybeRatToFloat(feeData.Fee)

Expand Down
6 changes: 6 additions & 0 deletions common/ethereum/applications/trader-joe/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@ func init() {
if traderJoeSwapAbi, err = ethAbi.JSON(reader); err != nil {
panic(err)
}

reader = strings.NewReader(traderJoeLBPairAbiString)

if traderJoeLBPairAbi, err = ethAbi.JSON(reader); err != nil {
panic(err)
}
}
208 changes: 193 additions & 15 deletions common/ethereum/applications/trader-joe/trader-joe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
// source code is governed by a GPL-style license that can be found in the
// LICENSE.md file.

// FIXME: we didn't have the time to test this, and it just returns 0. in
// the future we'll unpack their fee structure, and track the volume.

package trader_joe

import (
"fmt"
"math"
"math/big"

"github.com/fluidity-money/fluidity-app/common/ethereum"
"github.com/fluidity-money/fluidity-app/lib/log"
"github.com/fluidity-money/fluidity-app/lib/types/applications"
"github.com/fluidity-money/fluidity-app/lib/types/worker"

Expand All @@ -19,7 +19,40 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
)

const traderJoeSwapLogTopic = "0xad7d6f97abf51ce18e17a38f4d70e975be9c0708474987bb3e26ad21bd93ca70"
const (
// Trader Joe fees are always e18
FeeDecimals = 18
traderJoeSwapLogTopic = "0xad7d6f97abf51ce18e17a38f4d70e975be9c0708474987bb3e26ad21bd93ca70"
)

const traderJoeLBPairAbiString = `[
{
"inputs": [],
"name": "getTokenX",
"outputs": [
{
"internalType": "contract IERC20",
"name": "tokenX",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "getTokenY",
"outputs": [
{
"internalType": "contract IERC20",
"name": "tokenY",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
}
]`

const traderJoeSwapAbiString = `[
{
Expand All @@ -43,6 +76,12 @@ const traderJoeSwapAbiString = `[
"name": "id",
"type": "uint24"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "amountsIn",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes32",
Expand Down Expand Up @@ -74,10 +113,14 @@ const traderJoeSwapAbiString = `[
]
`

var traderJoeSwapAbi ethAbi.ABI
var (
traderJoeSwapAbi ethAbi.ABI
traderJoeLBPairAbi ethAbi.ABI
)

// GetTraderJoeFees always returns 0 fees (for now!)
func GetTraderJoeFees(transfer worker.EthereumApplicationTransfer, client *ethclient.Client, fluidTokenContract ethCommon.Address) (feeData applications.ApplicationFeeData, err error) {
// GetTraderJoeFees returns the volume and fees from a Trader Joe Swap event.
// The exact amounts of each are encoded as part of the log.
func GetTraderJoeFees(transfer worker.EthereumApplicationTransfer, client *ethclient.Client, fluidTokenContract ethCommon.Address, tokenDecimals int) (feeData applications.ApplicationFeeData, err error) {
if len(transfer.Log.Topics) < 1 {
return feeData, fmt.Errorf("not enough log topics passed!")
}
Expand All @@ -97,20 +140,155 @@ func GetTraderJoeFees(transfer worker.EthereumApplicationTransfer, client *ethcl
)
}

// there are 7 slots in this event, and 2 of them are indexed, so... 5
if len(unpacked) != 5 {
// there are 9 slots in this event, and 3 of them are indexed, so... 6
if len(unpacked) != 6 {
return feeData, fmt.Errorf(
"unpacked the wrong number of values! Expected %v, got %v",
9,
6,
len(unpacked),
)
}

// needing to make this 0 here explicitly because if it's
// new(big.Rat) it will be 0/1 causing tests to fail
// convert the pair contract's address to the go ethereum address type
contractAddr := ethereum.ConvertInternalAddress(transfer.Log.Address)

// figure out which token is which in the pair contract
tokenXaddr_, err := ethereum.StaticCall(client, contractAddr, traderJoeLBPairAbi, "getTokenX")

if err != nil {
return feeData, fmt.Errorf(
"Failed to get tokenX address! %v",
err,
)
}

tokenXaddr, err := ethereum.CoerceBoundContractResultsToAddress(tokenXaddr_)

if err != nil {
return feeData, fmt.Errorf(
"Failed to coerce tokenX address! %v",
err,
)
}

tokenYaddr_, err := ethereum.StaticCall(client, contractAddr, traderJoeLBPairAbi, "getTokenY")

if err != nil {
return feeData, fmt.Errorf(
"Failed to get tokenY address! %v",
err,
)
}

tokenYaddr, err := ethereum.CoerceBoundContractResultsToAddress(tokenYaddr_)

if err != nil {
return feeData, fmt.Errorf(
"Failed to coerce tokenY address! %v",
err,
)
}

var (
zero = big.NewInt(0)
swapContainsFluid = fluidTokenContract == tokenXaddr || fluidTokenContract == tokenYaddr
)

fluidTransferAmount := new(big.Rat)

// amountsIn is [32]{[0..15,16..31]} where the left side is token X and the right side is token Y
amountsIn_ := unpacked[1]
amountsIn, err := ethereum.CoerceBoundContractResultsToBytes32([]interface{}{amountsIn_})

if err != nil {
return feeData, fmt.Errorf(
"Failed to coerce amountsIn to bytes32! %v",
err,
)
}

// amountsOut is [32]{[0..15,16..31]} where the left side is token X and the right side is token Y
amountsOut_ := unpacked[2]
amountsOut, err := ethereum.CoerceBoundContractResultsToBytes32([]interface{}{amountsOut_})

if err != nil {
return feeData, fmt.Errorf(
"Failed to coerce amountsOut to bytes32! %v",
err,
)
}

switch true {
case !swapContainsFluid:
log.App(func(k *log.Log) {
k.Format(
"Received a Trader Joe swap in transaction %#v not involving the fluid token - skipping!",
transfer.TransactionHash.String(),
)
})

return feeData, nil

// rather than determining which direction the swap was, look for the non-zero part of amountIn/amountOut
case tokenXaddr == fluidTokenContract:
// token X is the fluid token
// amountIn for token X is the rightmost 16 bits
amountInX := new(big.Int).SetBytes(amountsIn[16:])

if amountInX.Cmp(zero) != 0 {
// X was the in token, so set it as the transfer amount
fluidTransferAmount.SetInt(amountInX)
break
}

// X was not the in token, so get the fluid volume from amountsOut
amountOutX := new(big.Int).SetBytes(amountsOut[16:])
fluidTransferAmount.SetInt(amountOutX)

case tokenYaddr == fluidTokenContract:
// token Y is the fluid token
// amountIn for token Y is the leftmost 16 bits
amountInY := new(big.Int).SetBytes(amountsIn[:16])

if amountInY.Cmp(zero) != 0 {
// Y was the in token, so set it as the transfer amount
fluidTransferAmount.SetInt(amountInY)
break
}

// Y was not the in token, so get the fluid volume from amountsOut
amountOutY := new(big.Int).SetBytes(amountsOut[:16])
fluidTransferAmount.SetInt(amountOutY)
}

// we only consider the user-paid totalFees, not protocolFees
totalFees_ := unpacked[4]
totalFees, err := ethereum.CoerceBoundContractResultsToBytes32([]interface{}{totalFees_})

if err != nil {
return feeData, fmt.Errorf(
"Failed to coerce totalFees to bytes32! %v",
err,
)
}

// the fee is stored in the first 16 bytes
fee := new(big.Int).SetBytes(totalFees[:16])

// the fee has its own decimals
feeDecimalsAdjusted := math.Pow10(FeeDecimals)
feeDecimalsRat := new(big.Rat).SetFloat64(feeDecimalsAdjusted)

feeRat := new(big.Rat).SetInt(fee)
feeRat.Quo(feeRat, feeDecimalsRat)

decimalsAdjusted := math.Pow10(tokenDecimals)
decimalsRat := new(big.Rat).SetFloat64(decimalsAdjusted)

fluidTransferAmount.Quo(fluidTransferAmount, decimalsRat)

feeData.Fee = new(big.Rat).SetInt64(0)
feeData.Volume = new(big.Rat)
feeData.Fee = feeRat
feeData.Volume = fluidTransferAmount

return
return feeData, nil
}
19 changes: 19 additions & 0 deletions common/ethereum/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,25 @@ func CoerceBoundContractResultsToUint8(results []interface{}) (uint8, error) {
return result, nil
}

func CoerceBoundContractResultsToBytes32(results []interface{}) ([32]byte, error) {
var result [32]byte

if resultsLen := len(results); resultsLen != 1 {
return result, fmt.Errorf(
"returned results did not have length of 1! was %v",
resultsLen,
)
}

result, ok := results[0].([32]byte)

if !ok {
return result, fmt.Errorf("results did not contain bytes32 ([32]byte)!")
}

return result, nil
}

func BigPow(left *big.Rat, count int) *big.Rat {

if count == 0 {
Expand Down
40 changes: 23 additions & 17 deletions tests/integrations/ethereum/trader-joe.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,45 @@

package main

// WARNING: this is not a representative test of reality. this is a faux
// test for making sure our catch-all works. as such, the fees are 0

const integrationTestTraderJoe = `[
{
"transfer": {
"transaction": "0x22e978cddf7f2f31c7572fb87d4634287d23424cc365f12eb2520297cfbe56d3",
"transaction": "0x9cfe557706abf6c80320e6f6312a10a96d067e8a1f0f9df2cf3452566c9d2358",
"log": {
"data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAKy8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6AmAAAAAAAAAAAGuvix2TuVs4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATiQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo",
"address": "0x7a08eaa93c05abd6b86bb09b0f565d6fc499ee35",
"data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAKzIAAAAAAAAAAB378HzmLGfsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIZV0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJxAAAAAAAAAAAAATRX/djOXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPasyxPYSsAAAAAAAAAAAAAAAAAAAAA",
"address": "0xb6a2a7456e651cc7ceb99735a086bff540e94d5d",
"topics": [
"0xad7d6f97abf51ce18e17a38f4d70e975be9c0708474987bb3e26ad21bd93ca70"
"0xad7d6f97abf51ce18e17a38f4d70e975be9c0708474987bb3e26ad21bd93ca70",
"0x000000000000000000000000b4315e873dbcf96ffd0acd8ea43f689d8c20fb30",
"0x000000000000000000000000aec109dcd8521d4e12a7ec04532cbf9ecaffcc52"
],
"log_index": "3"
"log_index": "12"
},
"application": 22
},
"transaction": {
"to": "0xb6a2a7456e651cc7ceb99735a086bff540e94d5d",
"from": "0x7a08eaa93c05abd6b86bb09b0f565d6fc499ee35",
"hash": "0x22e978cddf7f2f31c7572fb87d4634287d23424cc365f12eb2520297cfbe56d3"
"from": "0xaec109dcd8521d4e12a7ec04532cbf9ecaffcc52",
"hash": "0x9cfe557706abf6c80320e6f6312a10a96d067e8a1f0f9df2cf3452566c9d2358"
},
"expected_sender": "0x7a08eaa93c05abd6b86bb09b0f565d6fc499ee35",
"expected_recipient": "0x7a08eaa93c05abd6b86bb09b0f565d6fc499ee35",
"expected_fees": "0/1",
"expected_volume": "",
"expected_sender": "0xaec109dcd8521d4e12a7ec04532cbf9ecaffcc52",
"expected_recipient": "0xb6a2a7456e651cc7ceb99735a086bff540e94d5d",
"expected_fees": "678055004708027/125000000000000000",
"expected_volume": "550237/250000",
"expected_emission": {
"trader_joe": 0
"trader_joe": 0.005424440037664216
},
"rpc_methods": {},
"call_methods": {},
"call_methods": {
"getTokenX()": {
"": "0x0000000000000000000000004cfa50b7ce747e2d61724fcac57f24b748ff2b2a"
},
"getTokenY()": {
"": "0x000000000000000000000000912ce59144191c1204e64559fe8253a0e49e6548"
}
},
"token_decimals": 6,
"contract_address": "0xb6a2a7456e651cc7ceb99735a086bff540e94d5d"
"contract_address": "0x4cfa50b7ce747e2d61724fcac57f24b748ff2b2a"
}
]
`

0 comments on commit ad2185b

Please sign in to comment.