From 28d34ce98f21e54ac2f5c4ba8130adff6bd296c8 Mon Sep 17 00:00:00 2001 From: Pavel Shibaev Date: Sat, 7 Sep 2024 00:11:29 +0200 Subject: [PATCH] (feat) Add OFAC list check --- client/chain/chain.go | 59 ++++++++++++++++++- client/chain/chain_test.go | 68 ++++++++++++++++++++++ examples/chain/8_OfflineSigning/example.go | 4 +- 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/client/chain/chain.go b/client/chain/chain.go index 3f9f5a17..e6f3e3eb 100644 --- a/client/chain/chain.go +++ b/client/chain/chain.go @@ -6,7 +6,9 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" "math" + "net/http" "strconv" "strings" "sync" @@ -61,6 +63,7 @@ const ( defaultBroadcastTimeout = 40 * time.Second defaultTimeoutHeight = 20 defaultTimeoutHeightSyncInterval = 10 * time.Second + defaultOfacListURL = "https://raw.githubusercontent.com/InjectiveLabs/injective-lists/master/wallets/ofac.json" SpotOrderbook = "injective.exchange.v1beta1.EventOrderbookUpdate.spot_orderbooks" DerivativeOrderbook = "injective.exchange.v1beta1.EventOrderbookUpdate.derivative_orderbooks" ) @@ -321,6 +324,8 @@ type chainClient struct { sessionEnabled bool + ofacTxList []string + authQueryClient authtypes.QueryClient authzQueryClient authztypes.QueryClient bankQueryClient banktypes.QueryClient @@ -342,6 +347,29 @@ type chainClient struct { canSign bool } +func (cc *chainClient) loadOfacList() error { + response, err := http.Get(defaultOfacListURL) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("request to the OFAC upstream failed with code: %s", response.Status) + } + + body, err := io.ReadAll(response.Body) + if err != nil { + return err + } + + var ofacList []string + if err := json.Unmarshal(body, &ofacList); err != nil { + return err + } + return nil +} + func NewChainClient( ctx client.Context, network common.Network, @@ -453,6 +481,12 @@ func NewChainClient( go cc.syncTimeoutHeight() } + err = cc.loadOfacList() + if err != nil { + err = errors.Wrap(err, "failed to load the OFAC list") + return nil, err + } + return cc, nil } @@ -774,6 +808,29 @@ func (c *chainClient) BuildSignedTx(clientCtx client.Context, accNum, accSeq, in } func (c *chainClient) buildSignedTx(clientCtx client.Context, txf tx.Factory, msgs ...sdk.Msg) ([]byte, error) { + isAddrInTheList := func(list []string, addr string) bool { + for _, item := range list { + if item == addr { + return true + } + } + return false + } + k, err := txf.Keybase().Key(clientCtx.FromName) + if err != nil { + err = errors.Wrap(err, "error parsing signer account address") + return nil, err + } + signerAddressPubKey, err := k.GetPubKey() + if err != nil { + err = errors.Wrap(err, "error getting signer public key") + return nil, err + } + if isAddrInTheList(c.ofacTxList, sdk.AccAddress(signerAddressPubKey.Address()).String()) { + err = errors.Errorf("Address is in the OFAC list") + return nil, err + } + ctx := context.Background() if clientCtx.Simulate { simTxBytes, err := txf.BuildSimTx(msgs...) @@ -796,7 +853,7 @@ func (c *chainClient) buildSignedTx(clientCtx client.Context, txf tx.Factory, ms c.gasWanted = adjustedGas } - txf, err := PrepareFactory(clientCtx, txf) + txf, err = PrepareFactory(clientCtx, txf) if err != nil { return nil, errors.Wrap(err, "failed to prepareFactory") } diff --git a/client/chain/chain_test.go b/client/chain/chain_test.go index 0016f37f..2b8c10d6 100644 --- a/client/chain/chain_test.go +++ b/client/chain/chain_test.go @@ -1,6 +1,12 @@ package chain import ( + "context" + exchangetypes "github.com/InjectiveLabs/sdk-go/chain/exchange/types" + exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange" + spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" "os" "testing" @@ -51,6 +57,68 @@ func createClient(senderAddress cosmtypes.AccAddress, cosmosKeyring keyring.Keyr return chainClient, err } +func TestOfacList(t *testing.T) { + network := common.LoadNetwork("testnet", "lb") + tmClient, err := rpchttp.New(network.TmEndpoint, "/websocket") + assert.NoError(t, err) + + senderAddress, cosmosKeyring, err := accountForTests() + assert.NoError(t, err) + + clientCtx, err := NewClientContext( + network.ChainId, + senderAddress.String(), + cosmosKeyring, + ) + assert.NoError(t, err) + + clientCtx = clientCtx.WithNodeURI(network.TmEndpoint).WithClient(tmClient) + + exchangeClient, err := exchangeclient.NewExchangeClient(network) + assert.NoError(t, err) + ctx := context.Background() + req := spotExchangePB.MarketsRequest{ + MarketStatus: "active", + } + res, err := exchangeClient.GetSpotMarkets(ctx, &req) + assert.NoError(t, err) + + marketsAssistant, err := NewMarketsAssistantInitializedFromChain(ctx, exchangeClient) + assert.NoError(t, err) + + cc, err := createClient(senderAddress, cosmosKeyring, network) + assert.NoError(t, err) + + defaultSubaccountID := cc.DefaultSubaccount(senderAddress) + marketId := res.Markets[0].MarketId + amount := decimal.NewFromFloat(2) + price := decimal.NewFromFloat(1.02) + + order := cc.CreateSpotOrder( + defaultSubaccountID, + &SpotOrderData{ + OrderType: exchangetypes.OrderType_BUY, //BUY SELL BUY_PO SELL_PO + Quantity: amount, + Price: price, + FeeRecipient: senderAddress.String(), + MarketId: marketId, + }, + marketsAssistant, + ) + + msg := new(exchangetypes.MsgCreateSpotLimitOrder) + msg.Sender = senderAddress.String() + msg.Order = exchangetypes.SpotOrder(*order) + + accNum, accSeq := cc.GetAccNonce() + + cc.(*chainClient).ofacTxList = []string{ + senderAddress.String(), + } + _, err = cc.BuildSignedTx(clientCtx, accNum, accSeq, 20000, msg) + assert.Error(t, err) +} + func TestDefaultSubaccount(t *testing.T) { network := common.LoadNetwork("devnet", "lb") senderAddress, cosmosKeyring, err := accountForTests() diff --git a/examples/chain/8_OfflineSigning/example.go b/examples/chain/8_OfflineSigning/example.go index 00cacf59..1002b13e 100644 --- a/examples/chain/8_OfflineSigning/example.go +++ b/examples/chain/8_OfflineSigning/example.go @@ -32,7 +32,7 @@ func LoadTxFromFile(fileName string) ([]byte, error) { } func main() { - network := common.LoadNetwork("devnet", "") + network := common.LoadNetwork("devnet", "lb") tmClient, err := rpchttp.New(network.TmEndpoint, "/websocket") if err != nil { panic(err) @@ -86,7 +86,7 @@ func main() { } defaultSubaccountID := chainClient.DefaultSubaccount(senderAddress) - marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0" + marketId := "0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b" amount := decimal.NewFromFloat(2) price := decimal.NewFromFloat(1.02)