From 045819a6598c7e8342d8c652cb3d8807865d7cb5 Mon Sep 17 00:00:00 2001 From: skudasov Date: Thu, 16 Jan 2025 13:53:20 +0100 Subject: [PATCH] Sui support --- book/src/SUMMARY.md | 1 + .../framework/components/blockchains/sui.md | 78 +++++++++++++++++++ framework/.changeset/v0.4.4.md | 1 + framework/components/blockchain/blockchain.go | 18 ++--- framework/components/blockchain/sui.go | 46 +++++++---- framework/examples/myproject/smoke_sui.toml | 1 + .../examples/myproject/smoke_sui_test.go | 13 +++- framework/examples/myproject_cll/go.mod | 4 + framework/examples/myproject_cll/go.sum | 8 ++ framework/go.mod | 2 +- 10 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 book/src/framework/components/blockchains/sui.md create mode 100644 framework/.changeset/v0.4.4.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 832177f23..f102715b2 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -38,6 +38,7 @@ - [EVM](framework/components/blockchains/evm.md) - [Solana](framework/components/blockchains/solana.md) - [Aptos](framework/components/blockchains/aptos.md) + - [Sui](framework/components/blockchains/sui.md) - [Optimism Stack]() - [Arbitrum Stack]() - [Chainlink](framework/components/chainlink.md) diff --git a/book/src/framework/components/blockchains/sui.md b/book/src/framework/components/blockchains/sui.md new file mode 100644 index 000000000..a8b5f1af6 --- /dev/null +++ b/book/src/framework/components/blockchains/sui.md @@ -0,0 +1,78 @@ +# Sui Blockchain Client + +API is available on [localhost:9000](http://localhost:9000) + +## Configuration + +```toml +[blockchain_a] + type = "sui" + image = "mysten/sui-tools:mainnet" # if omitted default is mysten/sui-tools:devnet + contracts_dir = "$your_dir" +``` + +## Usage + +```golang +package examples + +import ( + "context" + "fmt" + "github.com/block-vision/sui-go-sdk/models" + "github.com/block-vision/sui-go-sdk/signer" + "github.com/block-vision/sui-go-sdk/sui" + "github.com/smartcontractkit/chainlink-testing-framework/framework" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" + "github.com/stretchr/testify/require" + "testing" +) + +type CfgSui struct { + BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"` +} + +func TestSuiSmoke(t *testing.T) { + in, err := framework.Load[CfgSui](t) + require.NoError(t, err) + + bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA) + require.NoError(t, err) + + // network is already funded, here are the keys + _ = bc.NetworkSpecificData.SuiAccount.Mnemonic + _ = bc.NetworkSpecificData.SuiAccount.PublicBase64Key + _ = bc.NetworkSpecificData.SuiAccount.SuiAddress + + // execute any additional commands, to deploy contracts or set up + _, err = framework.ExecContainer(bc.ContainerName, []string{"ls", "-lah"}) + require.NoError(t, err) + + t.Run("test something", func(t *testing.T) { + // use internal URL to connect Chainlink nodes + _ = bc.Nodes[0].DockerInternalHTTPUrl + // use host URL to interact + _ = bc.Nodes[0].HostHTTPUrl + + cli := sui.NewSuiClient(bc.Nodes[0].HostHTTPUrl) + + signerAccount, err := signer.NewSignertWithMnemonic(bc.NetworkSpecificData.SuiAccount.Mnemonic) + require.NoError(t, err) + rsp, err := cli.SuiXGetAllBalance(context.Background(), models.SuiXGetAllBalanceRequest{ + Owner: signerAccount.Address, + }) + require.NoError(t, err) + fmt.Printf("My funds: %v\n", rsp) + }) +} +``` + +## Test Private Keys + +Since Sui doesn't have official local development chain we are using real node and generating mnemonic at start then funding that account through internal faucet, see +``` + // network is already funded, here are the keys + _ = bc.NetworkSpecificData.SuiAccount.Mnemonic + _ = bc.NetworkSpecificData.SuiAccount.PublicBase64Key + _ = bc.NetworkSpecificData.SuiAccount.SuiAddress +``` \ No newline at end of file diff --git a/framework/.changeset/v0.4.4.md b/framework/.changeset/v0.4.4.md new file mode 100644 index 000000000..c4cff6326 --- /dev/null +++ b/framework/.changeset/v0.4.4.md @@ -0,0 +1 @@ +- Add Sui network support \ No newline at end of file diff --git a/framework/components/blockchain/blockchain.go b/framework/components/blockchain/blockchain.go index 79a3731cc..77004b0e9 100644 --- a/framework/components/blockchain/blockchain.go +++ b/framework/components/blockchain/blockchain.go @@ -30,17 +30,17 @@ type Input struct { // Output is a blockchain network output, ChainID and one or more nodes that forms the network type Output struct { - UseCache bool `toml:"use_cache"` - Family string `toml:"family"` - ContainerName string `toml:"container_name"` - GeneratedData *GeneratedData `toml:"generated_data"` - Container testcontainers.Container `toml:"-"` - ChainID string `toml:"chain_id"` - Nodes []*Node `toml:"nodes"` + UseCache bool `toml:"use_cache"` + Family string `toml:"family"` + ContainerName string `toml:"container_name"` + NetworkSpecificData *NetworkSpecificData `toml:"network_specific_data"` + Container testcontainers.Container `toml:"-"` + ChainID string `toml:"chain_id"` + Nodes []*Node `toml:"nodes"` } -type GeneratedData struct { - Mnemonic string `toml:"mnemonic"` +type NetworkSpecificData struct { + SuiAccount *SuiWalletInfo } // Node represents blockchain node output, URLs required for connection locally and inside docker network diff --git a/framework/components/blockchain/sui.go b/framework/components/blockchain/sui.go index 8a56be022..7054b3aae 100644 --- a/framework/components/blockchain/sui.go +++ b/framework/components/blockchain/sui.go @@ -15,7 +15,14 @@ import ( "time" ) -type SuiKeyInfo struct { +const ( + DefaultFaucetPort = "9123/tcp" + DefaultFaucetPortNum = "9123" + DefaultSuiNodePort = "9000" +) + +// SuiWalletInfo info about Sui account/wallet +type SuiWalletInfo struct { Alias *string `json:"alias"` // Alias key name, usually "null" Flag int `json:"flag"` // Flag is an integer KeyScheme string `json:"keyScheme"` // Key scheme is a string @@ -25,8 +32,11 @@ type SuiKeyInfo struct { SuiAddress string `json:"suiAddress"` // Sui address is a 0x prefixed hex string } +// funds provided key using local faucet +// we can't use the best client available - block-vision/sui-go-sdk for that, since some versions have old API and it is hardcoded +// https://github.com/block-vision/sui-go-sdk/blob/main/sui/faucet_api.go#L16 func fundAccount(url string, address string) error { - r := resty.New().SetBaseURL(url).EnableTrace().SetDebug(true) + r := resty.New().SetBaseURL(url) b := &models.FaucetRequest{ FixedAmountRequest: &models.FaucetFixedAmountRequest{ Recipient: address, @@ -40,18 +50,21 @@ func fundAccount(url string, address string) error { return nil } -func generateKeyData(containerName string, keyCipherType string) (*SuiKeyInfo, error) { +// generateKeyData generates a wallet and returns all the data +func generateKeyData(containerName string, keyCipherType string) (*SuiWalletInfo, error) { cmdStr := []string{"sui", "keytool", "generate", keyCipherType, "--json"} keyOut, err := framework.ExecContainer(containerName, cmdStr) if err != nil { return nil, err } + // formatted JSON with, no plain --json version, remove special symbols cleanKey := strings.ReplaceAll(keyOut, "\x00", "") cleanKey = strings.ReplaceAll(cleanKey, "\x01", "") cleanKey = strings.ReplaceAll(cleanKey, "\x02", "") cleanKey = strings.ReplaceAll(cleanKey, "\n", "") - var key *SuiKeyInfo - if err := json.Unmarshal([]byte("{"+cleanKey[2:]), &key); err != nil { + cleanKey = "{" + cleanKey[2:] + var key *SuiWalletInfo + if err := json.Unmarshal([]byte(cleanKey), &key); err != nil { return nil, err } framework.L.Info().Interface("Key", key).Msg("Test key") @@ -63,9 +76,9 @@ func defaultSui(in *Input) { in.Image = "mysten/sui-tools:devnet" } if in.Port != "" { - framework.L.Warn().Msg("'port' field is set but only default port can be used: 9000") + framework.L.Warn().Msgf("'port' field is set but only default port can be used: %s", DefaultSuiNodePort) } - in.Port = "9000" + in.Port = DefaultSuiNodePort } func newSui(in *Input) (*Output, error) { @@ -82,7 +95,7 @@ func newSui(in *Input) (*Output, error) { req := testcontainers.ContainerRequest{ Image: in.Image, - ExposedPorts: []string{in.Port, "9123/tcp"}, + ExposedPorts: []string{in.Port, DefaultFaucetPort}, Name: containerName, Labels: framework.DefaultTCLabels(), Networks: []string{framework.DefaultNetworkName}, @@ -90,7 +103,7 @@ func newSui(in *Input) (*Output, error) { framework.DefaultNetworkName: {containerName}, }, HostConfigModifier: func(h *container.HostConfig) { - h.PortBindings = framework.MapTheSamePort(bindPort, "9123/tcp") + h.PortBindings = framework.MapTheSamePort(bindPort, DefaultFaucetPort) }, ImagePlatform: "linux/amd64", Env: map[string]string{ @@ -109,7 +122,7 @@ func newSui(in *Input) (*Output, error) { }, }, // we need faucet for funding - WaitingFor: wait.ForListeningPort("9123/tcp").WithStartupTimeout(10 * time.Second).WithPollInterval(200 * time.Millisecond), + WaitingFor: wait.ForListeningPort(DefaultFaucetPort).WithStartupTimeout(10 * time.Second).WithPollInterval(200 * time.Millisecond), } c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ @@ -123,19 +136,18 @@ func newSui(in *Input) (*Output, error) { if err != nil { return nil, err } - keyData, err := generateKeyData(containerName, "ed25519") + suiAccount, err := generateKeyData(containerName, "ed25519") if err != nil { return nil, err } - err = fundAccount(fmt.Sprintf("http://%s:%s", "127.0.0.1", "9123"), keyData.SuiAddress) - if err != nil { + if err := fundAccount(fmt.Sprintf("http://%s:%s", "127.0.0.1", DefaultFaucetPortNum), suiAccount.SuiAddress); err != nil { return nil, err } return &Output{ - UseCache: true, - Family: "sui", - ContainerName: containerName, - GeneratedData: &GeneratedData{Mnemonic: keyData.Mnemonic}, + UseCache: true, + Family: "sui", + ContainerName: containerName, + NetworkSpecificData: &NetworkSpecificData{SuiAccount: suiAccount}, Nodes: []*Node{ { HostHTTPUrl: fmt.Sprintf("http://%s:%s", host, in.Port), diff --git a/framework/examples/myproject/smoke_sui.toml b/framework/examples/myproject/smoke_sui.toml index b72227ae8..9336aeb6f 100644 --- a/framework/examples/myproject/smoke_sui.toml +++ b/framework/examples/myproject/smoke_sui.toml @@ -1,2 +1,3 @@ [blockchain_a] type = "sui" + image = "mysten/sui-tools:mainnet" diff --git a/framework/examples/myproject/smoke_sui_test.go b/framework/examples/myproject/smoke_sui_test.go index 1ce652f05..78bc7d4fc 100644 --- a/framework/examples/myproject/smoke_sui_test.go +++ b/framework/examples/myproject/smoke_sui_test.go @@ -23,10 +23,12 @@ func TestSuiSmoke(t *testing.T) { bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA) require.NoError(t, err) - // execute any additional commands, to deploy contracts or set up // network is already funded, here are the keys - _ = bc.GeneratedData.Mnemonic + _ = bc.NetworkSpecificData.SuiAccount.Mnemonic + _ = bc.NetworkSpecificData.SuiAccount.PublicBase64Key + _ = bc.NetworkSpecificData.SuiAccount.SuiAddress + // execute any additional commands, to deploy contracts or set up _, err = framework.ExecContainer(bc.ContainerName, []string{"ls", "-lah"}) require.NoError(t, err) @@ -35,14 +37,17 @@ func TestSuiSmoke(t *testing.T) { _ = bc.Nodes[0].DockerInternalHTTPUrl // use host URL to interact _ = bc.Nodes[0].HostHTTPUrl - cli := sui.NewSuiClient("http://localhost:9000") - signerAccount, err := signer.NewSignertWithMnemonic(bc.GeneratedData.Mnemonic) + cli := sui.NewSuiClient(bc.Nodes[0].HostHTTPUrl) + + signerAccount, err := signer.NewSignertWithMnemonic(bc.NetworkSpecificData.SuiAccount.Mnemonic) require.NoError(t, err) rsp, err := cli.SuiXGetAllBalance(context.Background(), models.SuiXGetAllBalanceRequest{ Owner: signerAccount.Address, }) require.NoError(t, err) fmt.Printf("My funds: %v\n", rsp) + fmt.Printf("url1: %s\n", bc.Nodes[0].HostHTTPUrl) + fmt.Printf("url2: %s\n", bc.Nodes[0].DockerInternalHTTPUrl) }) } diff --git a/framework/examples/myproject_cll/go.mod b/framework/examples/myproject_cll/go.mod index 06c3c0b0f..0621d123c 100644 --- a/framework/examples/myproject_cll/go.mod +++ b/framework/examples/myproject_cll/go.mod @@ -34,6 +34,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 // indirect github.com/aws/smithy-go v1.22.1 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/block-vision/sui-go-sdk v1.0.6 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/consensys/bavard v0.1.13 // indirect @@ -100,6 +101,9 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/supranational/blst v0.3.13 // indirect github.com/testcontainers/testcontainers-go v0.35.0 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect diff --git a/framework/examples/myproject_cll/go.sum b/framework/examples/myproject_cll/go.sum index 59b3f6f82..be5972dfe 100644 --- a/framework/examples/myproject_cll/go.sum +++ b/framework/examples/myproject_cll/go.sum @@ -42,6 +42,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/block-vision/sui-go-sdk v1.0.6 h1:FysCc4TJC8v4BEBbCjJPDR4iR5eKqJT1dxGwsT67etg= +github.com/block-vision/sui-go-sdk v1.0.6/go.mod h1:FyK1vGE8lWm9QA1fdQpf1agfXQSMbPT8AV1BICgx6d8= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -282,6 +284,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= diff --git a/framework/go.mod b/framework/go.mod index 1e82bdbcf..e4ff87109 100644 --- a/framework/go.mod +++ b/framework/go.mod @@ -7,6 +7,7 @@ replace github.com/smartcontractkit/chainlink-testing-framework/seth => ../seth require ( github.com/aws/aws-sdk-go-v2/config v1.27.39 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.33.3 + github.com/block-vision/sui-go-sdk v1.0.6 github.com/charmbracelet/huh v0.6.0 github.com/charmbracelet/huh/spinner v0.0.0-20241028115900-20a4d21717a8 github.com/davecgh/go-spew v1.1.1 @@ -48,7 +49,6 @@ require ( github.com/aws/smithy-go v1.21.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect - github.com/block-vision/sui-go-sdk v1.0.6 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect