diff --git a/client/chain/chain.go b/client/chain/chain.go index 3f9f5a17..7ea14439 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 @@ -342,6 +344,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, @@ -440,15 +465,25 @@ 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() } @@ -774,6 +809,21 @@ 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) { + 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 c.ofacChecker.IsBlacklisted(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 +846,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") } @@ -1153,6 +1203,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 +1237,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 { diff --git a/client/chain/chain_test.go b/client/chain/chain_test.go index 0016f37f..ea162c0f 100644 --- a/client/chain/chain_test.go +++ b/client/chain/chain_test.go @@ -1,9 +1,14 @@ package chain import ( + "encoding/json" + "github.com/stretchr/testify/require" + "io" "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/InjectiveLabs/sdk-go/client" "github.com/InjectiveLabs/sdk-go/client/common" rpchttp "github.com/cometbft/cometbft/rpc/client/http" @@ -51,6 +56,47 @@ 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) + + testList := []string{ + senderAddress.String(), + } + jsonData, err := json.Marshal(testList) + assert.NoError(t, err) + err = os.Remove(getOfacListPath()) + assert.NoError(t, err) + file, err := os.Create(getOfacListPath()) + _, err = io.WriteString(file, string(jsonData)) + assert.NoError(t, err) + err = file.Close() + assert.NoError(t, err) + clientCtx, err := NewClientContext( + network.ChainId, + senderAddress.String(), + cosmosKeyring, + ) + assert.NoError(t, err) + + clientCtx = clientCtx.WithNodeURI(network.TmEndpoint).WithClient(tmClient) + + _, err = NewChainClient( + clientCtx, + network, + common.OptionGasPrices(client.DefaultGasPriceWithDenom), + ) + require.Error(t, err) + err = os.Remove(getOfacListPath()) + assert.NoError(t, err) + err = DownloadOfacList() + assert.NoError(t, err) +} + func TestDefaultSubaccount(t *testing.T) { network := common.LoadNetwork("devnet", "lb") senderAddress, cosmosKeyring, err := accountForTests() @@ -103,5 +149,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..1b7e4914 --- /dev/null +++ b/client/chain/ofac.go @@ -0,0 +1,90 @@ +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" +) + +type OfacChecker struct { + ofacListPath string + ofacList []string +} + +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(currentDirectory, defaultofacListFilename) +} + +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 + } + return nil +} + +func (oc *OfacChecker) loadOfacList() error { + file, err := os.ReadFile(oc.ofacListPath) + if err != nil { + return err + } + + err = json.Unmarshal(file, &oc.ofacList) + if err != nil { + return err + } + return nil +} + +func (oc *OfacChecker) IsBlacklisted(address string) bool { + for _, item := range oc.ofacList { + if item == address { + return true + } + } + return false +} diff --git a/examples/chain/8_OfflineSigning/example.go b/examples/chain/8_OfflineSigning/example.go index 00cacf59..9c372272 100644 --- a/examples/chain/8_OfflineSigning/example.go +++ b/examples/chain/8_OfflineSigning/example.go @@ -86,7 +86,7 @@ func main() { } defaultSubaccountID := chainClient.DefaultSubaccount(senderAddress) - marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0" + marketId := "0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b" amount := decimal.NewFromFloat(2) price := decimal.NewFromFloat(1.02) 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) + } +} diff --git a/ofac.json b/ofac.json new file mode 100644 index 00000000..e4aea502 --- /dev/null +++ b/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" +] \ No newline at end of file