Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable arb filter for affiliate swap messages (backport #6890) #6894

Merged
merged 3 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Features
* [#6890](https://github.com/osmosis-labs/osmosis/pull/6890) Enable arb filter for affiliate swap contract
### Misc Improvements

* [#6788](https://github.com/osmosis-labs/osmosis/pull/6788) Improve error message when CL LP fails due to slippage bound hit.
Expand Down
69 changes: 69 additions & 0 deletions x/txfees/keeper/txfee_filters/arb_tx.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
package txfee_filters

import (
"encoding/json"

authztypes "github.com/cosmos/cosmos-sdk/x/authz"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"

gammtypes "github.com/osmosis-labs/osmosis/v20/x/gamm/types"
poolmanagertypes "github.com/osmosis-labs/osmosis/v20/x/poolmanager/types"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// See this for reference: https://github.com/osmosis-labs/affiliate-swap
type Swap struct {
Routes []poolmanagertypes.SwapAmountInRoute `json:"routes"`
TokenOutMinAmount sdk.Coin `json:"token_out_min_amount"`
FeePercentage sdk.Dec `json:"fee_percentage"`
FeeCollector string `json:"fee_collector"`
TokenIn string `json:"token_in,omitempty"`
}

type AffiliateSwapMsg struct {
Swap `json:"swap"`
}

// TokenDenomsOnPath implements types.SwapMsgRoute.
func (m AffiliateSwapMsg) TokenDenomsOnPath() []string {
denoms := make([]string, 0, len(m.Routes)+1)
denoms = append(denoms, m.TokenInDenom())
for i := 0; i < len(m.Routes); i++ {
denoms = append(denoms, m.Routes[i].TokenOutDenom)
}
return denoms
}

// TokenInDenom implements types.SwapMsgRoute.
func (m AffiliateSwapMsg) TokenInDenom() string {
return m.TokenIn
}

// TokenOutDenom implements types.SwapMsgRoute.
func (m AffiliateSwapMsg) TokenOutDenom() string {
lastPoolInRoute := m.Routes[len(m.Routes)-1]
return lastPoolInRoute.TokenOutDenom
}

var _ poolmanagertypes.SwapMsgRoute = AffiliateSwapMsg{}

// We check if a tx is an arbitrage for the mempool right now by seeing:
// 1) does start token of a msg = final token of msg (definitionally correct)
// 2) does it have multiple swap messages, with different tx ins. If so, we assume its an arb.
Expand Down Expand Up @@ -49,6 +89,35 @@ func isArbTxLooseAuthz(msg sdk.Msg, swapInDenom string, lpTypesSeen map[gammtype
return swapInDenom, false
}

// Detects the affiliate swap message from the CosmWasm contract
// See an example here:
// // https://celatone.osmosis.zone/osmosis-1/txs/315EB6284778EBB5BAC0F94CC740F5D7E35DDA5BBE4EC9EC79F012548589C6E5
if msgExecuteContract, ok := msg.(*wasmtypes.MsgExecuteContract); ok {
// Grab token in from the funds sent to the contract
tokensIn := msgExecuteContract.GetFunds()
if len(tokensIn) != 1 {
return swapInDenom, false
}
tokenIn := tokensIn[0]

// Get the contract message and attempt to unmarshal it into the affiliate swap message
contractMessage := msgExecuteContract.GetMsg()
var affiliateSwapMsg AffiliateSwapMsg
if err := json.Unmarshal(contractMessage, &affiliateSwapMsg); err != nil {
// If we can't unmarshal it, it's not an affiliate swap message
return swapInDenom, false
}

// Otherwise, we have an affiliate swap message, so we check if it's an arb
affiliateSwapMsg.TokenIn = tokenIn.Denom
swapInDenom, isArb := isArbTxLooseSwapMsg(affiliateSwapMsg, swapInDenom)
if isArb {
return swapInDenom, true
}

return swapInDenom, false
}

// (4) Check that the tx doesn't have both JoinPool & ExitPool msgs
lpMsg, isLpMsg := msg.(gammtypes.LiquidityChangeMsg)
if isLpMsg {
Expand Down
66 changes: 66 additions & 0 deletions x/txfees/keeper/txfee_filters/arb_tx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package txfee_filters_test

import (
"encoding/json"
"testing"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"

"github.com/osmosis-labs/osmosis/v20/app/apptesting"
"github.com/osmosis-labs/osmosis/v20/x/gamm/types"
poolmanagertypes "github.com/osmosis-labs/osmosis/v20/x/poolmanager/types"
"github.com/osmosis-labs/osmosis/v20/x/txfees/keeper/txfee_filters"
)

type KeeperTestSuite struct {
apptesting.KeeperTestHelper
}

func TestTxFeeFilters(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

// Tests that the arb filter is enabled on the affiliate swap msg.
func (suite *KeeperTestSuite) TestIsArbTxLooseAuthz_AffiliateSwapMsg() {
affiliateSwapMsg := &txfee_filters.AffiliateSwapMsg{
Swap: txfee_filters.Swap{
FeeCollector: "osmo1dldrxz5p8uezxz3qstpv92de7wgfp7hvr72dcm",
FeePercentage: sdk.ZeroDec(),
Routes: []poolmanagertypes.SwapAmountInRoute{
{
PoolId: 1221,
TokenOutDenom: "uosmo",
},
{
PoolId: 3,
TokenOutDenom: "ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4",
},
{
PoolId: 4,
TokenOutDenom: "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB",
},
{
PoolId: 1251,
TokenOutDenom: "ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4",
},
},
TokenOutMinAmount: sdk.NewCoin("ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4", sdk.NewInt(217084399)),
},
}

affiliateSwapMsgBz, err := json.Marshal(affiliateSwapMsg)
suite.Require().NoError(err)

// https://celatone.osmosis.zone/osmosis-1/txs/315EB6284778EBB5BAC0F94CC740F5D7E35DDA5BBE4EC9EC79F012548589C6E5
executeMsg := &wasmtypes.MsgExecuteContract{
Contract: "osmo1etpha3a65tds0hmn3wfjeag6wgxgrkuwg2zh94cf5hapz7mz04dq6c25s5",
Sender: "osmo1dldrxz5p8uezxz3qstpv92de7wgfp7hvr72dcm",
Funds: sdk.NewCoins(sdk.NewCoin("ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4", sdk.NewInt(217084399))),
Msg: affiliateSwapMsgBz,
}

_, isArb := txfee_filters.IsArbTxLooseAuthz(executeMsg, executeMsg.Funds[0].Denom, map[types.LiquidityChangeType]bool{})
suite.Require().True(isArb)
}
11 changes: 11 additions & 0 deletions x/txfees/keeper/txfee_filters/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package txfee_filters

import (
sdk "github.com/cosmos/cosmos-sdk/types"

gammtypes "github.com/osmosis-labs/osmosis/v20/x/gamm/types"
)

func IsArbTxLooseAuthz(msg sdk.Msg, swapInDenom string, lpTypesSeen map[gammtypes.LiquidityChangeType]bool) (string, bool) {
return isArbTxLooseAuthz(msg, swapInDenom, lpTypesSeen)
}