From fedf1a8af2d1a8d3a18a4a9b21bb3a56ec2cc5f2 Mon Sep 17 00:00:00 2001 From: Pavel Shibaev Date: Sat, 7 Sep 2024 00:11:29 +0200 Subject: [PATCH 1/2] (feat) Add OFAC list check --- client/chain/chain.go | 27 +++++- client/chain/chain_test.go | 71 +++++++++++++- client/chain/ofac.go | 97 +++++++++++++++++++ client/metadata/ofac.json | 48 +++++++++ .../chain/ofac/1_DownloadOfacList/example.go | 12 +++ 5 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 client/chain/ofac.go create mode 100644 client/metadata/ofac.json create mode 100644 examples/chain/ofac/1_DownloadOfacList/example.go diff --git a/client/chain/chain.go b/client/chain/chain.go index 3f9f5a17..a78826c5 100644 --- a/client/chain/chain.go +++ b/client/chain/chain.go @@ -321,6 +321,8 @@ type chainClient struct { sessionEnabled bool + ofacChecker *OfacChecker + authQueryClient authtypes.QueryClient authzQueryClient authztypes.QueryClient bankQueryClient banktypes.QueryClient @@ -440,15 +442,23 @@ func NewChainClient( subaccountToNonce: make(map[ethcommon.Hash]uint32), } + _ = NewTxFactory(ctx).WithSequence(0).WithAccountNumber(0).WithGas(0) + + cc.ofacChecker, err = NewOfacChecker() + if err != nil { + return nil, errors.Wrap(err, "Error creating OFAC checker") + } if cc.canSign { var err error - - cc.accNum, cc.accSeq, err = cc.txFactory.AccountRetriever().GetAccountNumberSequence(ctx, ctx.GetFromAddress()) + account, err := cc.txFactory.AccountRetriever().GetAccount(ctx, ctx.GetFromAddress()) if err != nil { - err = errors.Wrap(err, "failed to get initial account num and seq") + err = errors.Wrapf(err, "failed to get account") return nil, err } - + if cc.ofacChecker.IsBlacklisted(account.GetAddress().String()) { + return nil, errors.Errorf("Address %s is in the OFAC list", account.GetAddress()) + } + cc.accNum, cc.accSeq = account.GetAccountNumber(), account.GetSequence() go cc.runBatchBroadcast() go cc.syncTimeoutHeight() } @@ -1153,6 +1163,9 @@ func (c *chainClient) GetAuthzGrants(ctx context.Context, req authztypes.QueryGr } func (c *chainClient) BuildGenericAuthz(granter, grantee, msgtype string, expireIn time.Time) *authztypes.MsgGrant { + if c.ofacChecker.IsBlacklisted(granter) { + panic("Address is in the OFAC list") // panics should generally be avoided, but otherwise function signature should be changed + } authz := authztypes.NewGenericAuthorization(msgtype) authzAny := codectypes.UnsafePackAny(authz) return &authztypes.MsgGrant{ @@ -1184,6 +1197,9 @@ var ( ) func (c *chainClient) BuildExchangeAuthz(granter, grantee string, authzType ExchangeAuthz, subaccountId string, markets []string, expireIn time.Time) *authztypes.MsgGrant { + if c.ofacChecker.IsBlacklisted(granter) { + panic("Address is in the OFAC list") // panics should generally be avoided, but otherwise function signature should be changed + } var typedAuthzAny codectypes.Any var typedAuthzBytes []byte switch authzType { @@ -1279,6 +1295,9 @@ func (c *chainClient) BuildExchangeBatchUpdateOrdersAuthz( derivativeMarkets []string, expireIn time.Time, ) *authztypes.MsgGrant { + if c.ofacChecker.IsBlacklisted(granter) { + panic("Address is in the OFAC list") // panics should generally be avoided, but otherwise function signature should be changed + } typedAuthz := &exchangetypes.BatchUpdateOrdersAuthz{ SubaccountId: subaccountId, SpotMarkets: spotMarkets, diff --git a/client/chain/chain_test.go b/client/chain/chain_test.go index 0016f37f..57466e42 100644 --- a/client/chain/chain_test.go +++ b/client/chain/chain_test.go @@ -1,9 +1,13 @@ package chain import ( + "encoding/json" + "io" "os" "testing" + "github.com/stretchr/testify/suite" + "github.com/InjectiveLabs/sdk-go/client" "github.com/InjectiveLabs/sdk-go/client/common" rpchttp "github.com/cometbft/cometbft/rpc/client/http" @@ -51,6 +55,72 @@ func createClient(senderAddress cosmtypes.AccAddress, cosmosKeyring keyring.Keyr return chainClient, err } +type OfacTestSuite struct { + suite.Suite + network common.Network + tmClient *rpchttp.HTTP + senderAddress cosmtypes.AccAddress + cosmosKeyring keyring.Keyring +} + +func (suite *OfacTestSuite) SetupTest() { + var err error + suite.network = common.LoadNetwork("testnet", "lb") + suite.tmClient, err = rpchttp.New(suite.network.TmEndpoint, "/websocket") + suite.NoError(err) + + suite.senderAddress, suite.cosmosKeyring, err = accountForTests() + suite.NoError(err) + + // Prepare OFAC list file + testList := []string{ + suite.senderAddress.String(), + } + jsonData, err := json.Marshal(testList) + suite.NoError(err) + + ofacListFilename = "ofac_test.json" + file, err := os.Create(getOfacListPath()) + suite.NoError(err) + + _, err = io.WriteString(file, string(jsonData)) + suite.NoError(err) + + err = file.Close() + suite.NoError(err) +} + +func (suite *OfacTestSuite) TearDownTest() { + err := os.Remove(getOfacListPath()) + suite.NoError(err) + ofacListFilename = defaultofacListFilename +} + +func (suite *OfacTestSuite) TestOfacList() { + clientCtx, err := NewClientContext( + suite.network.ChainId, + suite.senderAddress.String(), + suite.cosmosKeyring, + ) + suite.NoError(err) + + clientCtx = clientCtx.WithNodeURI(suite.network.TmEndpoint).WithClient(suite.tmClient) + testChecker, err := NewOfacChecker() + suite.NoError(err) + suite.Equal(1, len(testChecker.ofacList)) + + _, err = NewChainClient( + clientCtx, + suite.network, + common.OptionGasPrices(client.DefaultGasPriceWithDenom), + ) + suite.Error(err) +} + +func TestOfacTestSuite(t *testing.T) { + suite.Run(t, new(OfacTestSuite)) +} + func TestDefaultSubaccount(t *testing.T) { network := common.LoadNetwork("devnet", "lb") senderAddress, cosmosKeyring, err := accountForTests() @@ -103,5 +173,4 @@ func TestGetSubaccountWithIndex(t *testing.T) { if subaccountThirty != expectedSubaccountThirtyIdHash { t.Error("The subaccount with index 30 was calculated incorrectly") } - } diff --git a/client/chain/ofac.go b/client/chain/ofac.go new file mode 100644 index 00000000..0a8b3eee --- /dev/null +++ b/client/chain/ofac.go @@ -0,0 +1,97 @@ +package chain + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" +) + +const ( + defaultOfacListURL = "https://raw.githubusercontent.com/InjectiveLabs/injective-lists/master/wallets/ofac.json" + defaultofacListFilename = "ofac.json" +) + +var ( + ofacListFilename = defaultofacListFilename +) + +type OfacChecker struct { + ofacListPath string + ofacList map[string]bool +} + +func NewOfacChecker() (*OfacChecker, error) { + checker := &OfacChecker{ + ofacListPath: getOfacListPath(), + } + if _, err := os.Stat(checker.ofacListPath); os.IsNotExist(err) { + if err := DownloadOfacList(); err != nil { + return nil, err + } + } + if err := checker.loadOfacList(); err != nil { + return nil, err + } + return checker, nil +} + +func getOfacListPath() string { + currentDirectory, _ := os.Getwd() + for !strings.HasSuffix(currentDirectory, "sdk-go") { + currentDirectory = filepath.Dir(currentDirectory) + } + return filepath.Join(filepath.Join(filepath.Join(currentDirectory, "client"), "metadata"), ofacListFilename) +} + +func DownloadOfacList() error { + resp, err := http.Get(defaultOfacListURL) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to download OFAC list, status code: %d", resp.StatusCode) + } + + outFile, err := os.Create(getOfacListPath()) + if err != nil { + return err + } + defer outFile.Close() + + _, err = io.Copy(outFile, resp.Body) + if err != nil { + return err + } + _, err = outFile.WriteString("\n") + if err != nil { + return err + } + return nil +} + +func (oc *OfacChecker) loadOfacList() error { + file, err := os.ReadFile(oc.ofacListPath) + if err != nil { + return err + } + var list []string + err = json.Unmarshal(file, &list) + if err != nil { + return err + } + oc.ofacList = make(map[string]bool) + for _, item := range list { + oc.ofacList[item] = true + } + return nil +} + +func (oc *OfacChecker) IsBlacklisted(address string) bool { + return oc.ofacList[address] +} diff --git a/client/metadata/ofac.json b/client/metadata/ofac.json new file mode 100644 index 00000000..59b4160e --- /dev/null +++ b/client/metadata/ofac.json @@ -0,0 +1,48 @@ +[ + "0x179f48c78f57a3a78f0608cc9197b8972921d1d2", + "0x1967d8af5bd86a497fb3dd7899a020e47560daaf", + "0x19aa5fe80d33a56d56c78e82ea5e50e5d80b4dff", + "0x19aa5fe80d33a56d56c78e82ea5e50e5d80b4dff", + "0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a", + "0x2f389ce8bd8ff92de3402ffce4691d17fc4f6535", + "0x2f389ce8bd8ff92de3402ffce4691d17fc4f6535", + "0x2f50508a8a3d323b91336fa3ea6ae50e55f32185", + "0x308ed4b7b49797e1a98d3818bff6fe5385410370", + "0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d", + "0x3efa30704d2b8bbac821307230376556cf8cc39e", + "0x48549a34ae37b12f6a30566245176994e17c6b4a", + "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c", + "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c", + "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c", + "0x530a64c0ce595026a4a556b703644228179e2d57", + "0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0", + "0x5a7a51bfb49f190e5a6060a5bc6052ac14a3b59f", + "0x5f48c2a71b2cc96e3f0ccae4e39318ff0dc375b2", + "0x6be0ae71e6c41f2f9d0d1a3b8d0f75e6f6a0b46e", + "0x6f1ca141a28907f78ebaa64fb83a9088b02a8352", + "0x746aebc06d2ae31b71ac51429a19d54e797878e9", + "0x77777feddddffc19ff86db637967013e6c6a116c", + "0x797d7ae72ebddcdea2a346c1834e04d1f8df102b", + "0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c", + "0x901bb9583b24d97e995513c6778dc6888ab6870e", + "0x961c5be54a2ffc17cf4cb021d863c42dacd47fc1", + "0x97b1043abd9e6fc31681635166d430a458d14f9c", + "0x9c2bc757b66f24d60f016b6237f8cdd414a879fa", + "0x9f4cda013e354b8fc285bf4b9a60460cee7f7ea9", + "0xa7e5d5a720f06526557c513402f2e6b5fa20b008", + "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40", + "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40", + "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40", + "0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a", + "0xca0840578f57fe71599d29375e16783424023357", + "0xd0975b32cea532eadddfc9c60481976e39db3472", + "0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b", + "0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b", + "0xe1d865c3d669dcc8c57c8d023140cb204e672ee4", + "0xe7aa314c77f4233c18c6cc84384a9247c0cf367b", + "0xed6e0a7e4ac94d976eebfb82ccf777a3c6bad921", + "0xf3701f445b6bdafedbca97d1e477357839e4120d", + "0xfac583c0cf07ea434052c49115a4682172ab6b4f", + "0xfec8a60023265364d066a1212fde3930f6ae8da7", + "0xffbac21a641dcfe4552920138d90f3638b3c9fba" +] diff --git a/examples/chain/ofac/1_DownloadOfacList/example.go b/examples/chain/ofac/1_DownloadOfacList/example.go new file mode 100644 index 00000000..4b6f7992 --- /dev/null +++ b/examples/chain/ofac/1_DownloadOfacList/example.go @@ -0,0 +1,12 @@ +package main + +import ( + chainclient "github.com/InjectiveLabs/sdk-go/client/chain" +) + +func main() { + err := chainclient.DownloadOfacList() + if err != nil { + panic(err) + } +} From 0d5726e9b91c354ac514df4ef396cf7bf1ca5f76 Mon Sep 17 00:00:00 2001 From: Pavel Shibaev Date: Wed, 11 Sep 2024 20:54:41 +0200 Subject: [PATCH 2/2] Remove unused code in chain.go --- client/chain/chain.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/chain/chain.go b/client/chain/chain.go index a78826c5..de5f06dd 100644 --- a/client/chain/chain.go +++ b/client/chain/chain.go @@ -442,8 +442,6 @@ func NewChainClient( subaccountToNonce: make(map[ethcommon.Hash]uint32), } - _ = NewTxFactory(ctx).WithSequence(0).WithAccountNumber(0).WithGas(0) - cc.ofacChecker, err = NewOfacChecker() if err != nil { return nil, errors.Wrap(err, "Error creating OFAC checker") @@ -465,7 +463,6 @@ func NewChainClient( return cc, nil } - func (c *chainClient) syncNonce() { num, seq, err := c.txFactory.AccountRetriever().GetAccountNumberSequence(c.ctx, c.ctx.GetFromAddress()) if err != nil {