diff --git a/.golangci.yml b/.golangci.yml index d0542f400..6744196f9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,7 @@ run: linters: enable: - exhaustive - - exportloopref + - copyloopvar - revive - goimports - gosec diff --git a/.mockery.yaml b/.mockery.yaml index 1df96bfec..347d69c58 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -37,6 +37,7 @@ packages: config: filename: simple_keystore.go case: underscore + TxManager: github.com/smartcontractkit/chainlink-solana/pkg/solana/logpoller: interfaces: RPCClient: diff --git a/Makefile b/Makefile index 74ff0753c..e90ac4859 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ test_relay_unit: test_smoke: cd ./integration-tests &&\ - SELECTED_NETWORKS=SIMULATED go test -timeout 24h -count=1 -json $(args) -run TestSolanaOCRV2Smoke ./smoke 2>&1 | tee /tmp/gotest.log | gotestloghelper -json -tlogprefix -singlepackage -color + SELECTED_NETWORKS=SIMULATED go test -timeout 1h -count=1 -json $(args) -run TestSolanaOCRV2Smoke ./smoke 2>&1 | tee /tmp/gotest.log | gotestloghelper -json -tlogprefix -singlepackage -color test_relay_integration: cd ./integration-tests &&\ diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index f265cd8ce..0209f1dbb 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -7,7 +7,7 @@ name = "access-controller" version = "1.0.1" dependencies = [ "anchor-lang", - "arrayvec 1.0.0", + "arrayvec 1.0.1", "bytemuck", "static_assertions", ] @@ -379,7 +379,7 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "arrayvec" -version = "1.0.0" +version = "1.0.1" [[package]] name = "assert_matches" @@ -1374,7 +1374,7 @@ dependencies = [ "anchor-lang", "anchor-spl", "arrayref", - "arrayvec 1.0.0", + "arrayvec 1.0.1", "bytemuck", "solana-program", "static_assertions", diff --git a/contracts/crates/arrayvec/Cargo.toml b/contracts/crates/arrayvec/Cargo.toml index 4e9ad51cc..5570a5636 100644 --- a/contracts/crates/arrayvec/Cargo.toml +++ b/contracts/crates/arrayvec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arrayvec" -version = "1.0.0" +version = "1.0.1" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/contracts/crates/arrayvec/src/lib.rs b/contracts/crates/arrayvec/src/lib.rs index dfc8a12e3..9312c2338 100644 --- a/contracts/crates/arrayvec/src/lib.rs +++ b/contracts/crates/arrayvec/src/lib.rs @@ -90,6 +90,17 @@ macro_rules! arrayvec { self.xs[offset..offset + len].copy_from_slice(&data); self.len += len as $capacity_ty; } + + /// Removes the last element from the array and returns it. + /// Returns `None` if the array is empty. + pub fn pop(&mut self) -> Option<$ty> { + if self.len > 0 { + self.len -= 1; + Some(self.xs[self.len as usize]) + } else { + None + } + } } impl std::ops::Deref for $name { @@ -126,6 +137,38 @@ mod tests { } arrayvec!(ArrayVec, u8, u32); + #[test] + fn push_pop() { + let mut vec = ArrayVec::new(); + assert!(vec.is_empty()); + assert_eq!(vec.pop(), None); + + vec.push(10); + assert_eq!(vec.len(), 1); + assert_eq!(vec.as_slice(), &[10]); + + vec.push(20); + vec.push(30); + assert_eq!(vec.len(), 3); + assert_eq!(vec.as_slice(), &[10, 20, 30]); + + // Popping elements + assert_eq!(vec.pop(), Some(30)); + assert_eq!(vec.len(), 2); + assert_eq!(vec.as_slice(), &[10, 20]); + + assert_eq!(vec.pop(), Some(20)); + assert_eq!(vec.len(), 1); + assert_eq!(vec.as_slice(), &[10]); + + assert_eq!(vec.pop(), Some(10)); + assert_eq!(vec.len(), 0); + assert!(vec.is_empty()); + + // Popping from empty vec + assert_eq!(vec.pop(), None); + } + #[test] fn remove() { let mut vec = ArrayVec::new(); diff --git a/contracts/generated/contract_reader_interface/InitializeLookupTable.go b/contracts/generated/contract_reader_interface/InitializeLookupTable.go new file mode 100644 index 000000000..5b339f169 --- /dev/null +++ b/contracts/generated/contract_reader_interface/InitializeLookupTable.go @@ -0,0 +1,174 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package contract_reader_interface + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// InitializeLookupTable is the `initializeLookupTable` instruction. +type InitializeLookupTable struct { + LookupTable *ag_solanago.PublicKey + + // [0] = [WRITE] writeDataAccount + // ··········· PDA for LookupTableDataAccount, derived from seeds and created by the System Program + // + // [1] = [WRITE, SIGNER] admin + // ··········· Admin account that pays for PDA creation and signs the transaction + // + // [2] = [] systemProgram + // ··········· System Program required for PDA creation + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeLookupTableInstructionBuilder creates a new `InitializeLookupTable` instruction builder. +func NewInitializeLookupTableInstructionBuilder() *InitializeLookupTable { + nd := &InitializeLookupTable{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetLookupTable sets the "lookupTable" parameter. +func (inst *InitializeLookupTable) SetLookupTable(lookupTable ag_solanago.PublicKey) *InitializeLookupTable { + inst.LookupTable = &lookupTable + return inst +} + +// SetWriteDataAccountAccount sets the "writeDataAccount" account. +// PDA for LookupTableDataAccount, derived from seeds and created by the System Program +func (inst *InitializeLookupTable) SetWriteDataAccountAccount(writeDataAccount ag_solanago.PublicKey) *InitializeLookupTable { + inst.AccountMetaSlice[0] = ag_solanago.Meta(writeDataAccount).WRITE() + return inst +} + +// GetWriteDataAccountAccount gets the "writeDataAccount" account. +// PDA for LookupTableDataAccount, derived from seeds and created by the System Program +func (inst *InitializeLookupTable) GetWriteDataAccountAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetAdminAccount sets the "admin" account. +// Admin account that pays for PDA creation and signs the transaction +func (inst *InitializeLookupTable) SetAdminAccount(admin ag_solanago.PublicKey) *InitializeLookupTable { + inst.AccountMetaSlice[1] = ag_solanago.Meta(admin).WRITE().SIGNER() + return inst +} + +// GetAdminAccount gets the "admin" account. +// Admin account that pays for PDA creation and signs the transaction +func (inst *InitializeLookupTable) GetAdminAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetSystemProgramAccount sets the "systemProgram" account. +// System Program required for PDA creation +func (inst *InitializeLookupTable) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *InitializeLookupTable { + inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +// System Program required for PDA creation +func (inst *InitializeLookupTable) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst InitializeLookupTable) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_InitializeLookupTable, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeLookupTable) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeLookupTable) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.LookupTable == nil { + return errors.New("LookupTable parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.WriteDataAccount is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Admin is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *InitializeLookupTable) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeLookupTable")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("LookupTable", *inst.LookupTable)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" writeData", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" admin", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (obj InitializeLookupTable) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `LookupTable` param: + err = encoder.Encode(obj.LookupTable) + if err != nil { + return err + } + return nil +} +func (obj *InitializeLookupTable) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `LookupTable`: + err = decoder.Decode(&obj.LookupTable) + if err != nil { + return err + } + return nil +} + +// NewInitializeLookupTableInstruction declares a new InitializeLookupTable instruction with the provided parameters and accounts. +func NewInitializeLookupTableInstruction( + // Parameters: + lookupTable ag_solanago.PublicKey, + // Accounts: + writeDataAccount ag_solanago.PublicKey, + admin ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *InitializeLookupTable { + return NewInitializeLookupTableInstructionBuilder(). + SetLookupTable(lookupTable). + SetWriteDataAccountAccount(writeDataAccount). + SetAdminAccount(admin). + SetSystemProgramAccount(systemProgram) +} diff --git a/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go b/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go new file mode 100644 index 000000000..e30e2204d --- /dev/null +++ b/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package contract_reader_interface + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeLookupTable(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeLookupTable"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeLookupTable) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(InitializeLookupTable) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/contracts/generated/contract_reader_interface/accounts.go b/contracts/generated/contract_reader_interface/accounts.go index edf383de1..d02ccb874 100644 --- a/contracts/generated/contract_reader_interface/accounts.go +++ b/contracts/generated/contract_reader_interface/accounts.go @@ -5,8 +5,84 @@ package contract_reader_interface import ( "fmt" ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" ) +type LookupTableDataAccount struct { + Version uint8 + Administrator ag_solanago.PublicKey + PendingAdministrator ag_solanago.PublicKey + LookupTable ag_solanago.PublicKey +} + +var LookupTableDataAccountDiscriminator = [8]byte{220, 119, 44, 40, 237, 41, 223, 7} + +func (obj LookupTableDataAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Write account discriminator: + err = encoder.WriteBytes(LookupTableDataAccountDiscriminator[:], false) + if err != nil { + return err + } + // Serialize `Version` param: + err = encoder.Encode(obj.Version) + if err != nil { + return err + } + // Serialize `Administrator` param: + err = encoder.Encode(obj.Administrator) + if err != nil { + return err + } + // Serialize `PendingAdministrator` param: + err = encoder.Encode(obj.PendingAdministrator) + if err != nil { + return err + } + // Serialize `LookupTable` param: + err = encoder.Encode(obj.LookupTable) + if err != nil { + return err + } + return nil +} + +func (obj *LookupTableDataAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Read and check account discriminator: + { + discriminator, err := decoder.ReadTypeID() + if err != nil { + return err + } + if !discriminator.Equal(LookupTableDataAccountDiscriminator[:]) { + return fmt.Errorf( + "wrong discriminator: wanted %s, got %s", + "[220 119 44 40 237 41 223 7]", + fmt.Sprint(discriminator[:])) + } + } + // Deserialize `Version`: + err = decoder.Decode(&obj.Version) + if err != nil { + return err + } + // Deserialize `Administrator`: + err = decoder.Decode(&obj.Administrator) + if err != nil { + return err + } + // Deserialize `PendingAdministrator`: + err = decoder.Decode(&obj.PendingAdministrator) + if err != nil { + return err + } + // Deserialize `LookupTable`: + err = decoder.Decode(&obj.LookupTable) + if err != nil { + return err + } + return nil +} + type DataAccount struct { Idx uint64 Bump uint8 diff --git a/contracts/generated/contract_reader_interface/instructions.go b/contracts/generated/contract_reader_interface/instructions.go index 7eee5c9a6..c86960a41 100644 --- a/contracts/generated/contract_reader_interface/instructions.go +++ b/contracts/generated/contract_reader_interface/instructions.go @@ -29,6 +29,8 @@ func init() { var ( Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237}) + + Instruction_InitializeLookupTable = ag_binary.TypeID([8]byte{149, 120, 10, 249, 212, 185, 177, 216}) ) // InstructionIDToName returns the name of the instruction given its ID. @@ -36,6 +38,8 @@ func InstructionIDToName(id ag_binary.TypeID) string { switch id { case Instruction_Initialize: return "Initialize" + case Instruction_InitializeLookupTable: + return "InitializeLookupTable" default: return "" } @@ -59,6 +63,9 @@ var InstructionImplDef = ag_binary.NewVariantDefinition( { "initialize", (*Initialize)(nil), }, + { + "initialize_lookup_table", (*InitializeLookupTable)(nil), + }, }, ) diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 860108de1..5a742745d 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -1,903 +1,81 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -importers: - - .: - dependencies: - '@chainlink/solana-sdk': - specifier: link:../ts - version: link:../ts - '@coral-xyz/anchor': - specifier: ^0.29.0 - version: 0.29.0 - '@solana/spl-token': - specifier: ^0.3.5 - version: 0.3.11(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22) - '@solana/web3.js': - specifier: ^1.50.1 <=1.92.3 - version: 1.92.3 - '@types/chai': - specifier: ^4.2.22 - version: 4.3.12 - '@types/mocha': - specifier: ^9.0.0 - version: 9.1.1 - '@types/node': - specifier: ^14.14.37 - version: 14.18.63 - '@types/secp256k1': - specifier: ^4.0.3 - version: 4.0.6 - bn.js: - specifier: ^5.2.0 - version: 5.2.1 - borsh: - specifier: ^0.7.0 - version: 0.7.0 - chai: - specifier: ^4.3.4 - version: 4.4.1 - ethereum-cryptography: - specifier: ^0.1.3 - version: 0.1.3 - mocha: - specifier: ^9.0.0 - version: 9.2.2 - prettier: - specifier: ^2.5.1 - version: 2.8.8 - rpc-websockets: - specifier: <=7.10.0 - version: 7.10.0 - secp256k1: - specifier: ^4.0.2 - version: 4.0.3 - ts-mocha: - specifier: ^8.0.0 - version: 8.0.0(mocha@9.2.2) - typescript: - specifier: ^4.5.4 - version: 4.9.5 +dependencies: + '@chainlink/solana-sdk': + specifier: link:../ts + version: link:../ts + '@coral-xyz/anchor': + specifier: ^0.29.0 + version: 0.29.0 + '@solana/spl-token': + specifier: ^0.3.5 + version: 0.3.11(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) + '@solana/web3.js': + specifier: ^1.50.1 <=1.92.3 + version: 1.92.3 + '@types/chai': + specifier: ^4.2.22 + version: 4.3.20 + '@types/mocha': + specifier: ^9.0.0 + version: 9.1.1 + '@types/node': + specifier: ^14.14.37 + version: 14.18.63 + '@types/secp256k1': + specifier: ^4.0.3 + version: 4.0.6 + bn.js: + specifier: ^5.2.0 + version: 5.2.1 + borsh: + specifier: ^0.7.0 + version: 0.7.0 + chai: + specifier: ^4.3.4 + version: 4.5.0 + ethereum-cryptography: + specifier: ^0.1.3 + version: 0.1.3 + mocha: + specifier: ^9.0.0 + version: 9.2.2 + prettier: + specifier: ^2.5.1 + version: 2.8.8 + rpc-websockets: + specifier: <=7.10.0 + version: 7.10.0 + secp256k1: + specifier: ^4.0.2 + version: 4.0.4 + ts-mocha: + specifier: ^8.0.0 + version: 8.0.0(mocha@9.2.2) + typescript: + specifier: ^4.5.4 + version: 4.9.5 packages: - '@babel/runtime@7.25.6': - resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} + /@babel/runtime@7.26.0: + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} - - '@coral-xyz/anchor@0.29.0': - resolution: {integrity: sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==} - engines: {node: '>=11'} - - '@coral-xyz/borsh@0.29.0': - resolution: {integrity: sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==} - engines: {node: '>=10'} - peerDependencies: - '@solana/web3.js': ^1.68.0 - - '@noble/curves@1.6.0': - resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.5.0': - resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} - engines: {node: ^14.21.3 || >=16} - - '@solana/buffer-layout-utils@0.2.0': - resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==} - engines: {node: '>= 10'} - - '@solana/buffer-layout@4.0.1': - resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} - engines: {node: '>=5.10'} - - '@solana/codecs-core@2.0.0-experimental.8618508': - resolution: {integrity: sha512-JCz7mKjVKtfZxkuDtwMAUgA7YvJcA2BwpZaA1NOLcted4OMC4Prwa3DUe3f3181ixPYaRyptbF0Ikq2MbDkYEA==} - - '@solana/codecs-data-structures@2.0.0-experimental.8618508': - resolution: {integrity: sha512-sLpjL9sqzaDdkloBPV61Rht1tgaKq98BCtIKRuyscIrmVPu3wu0Bavk2n/QekmUzaTsj7K1pVSniM0YqCdnEBw==} - - '@solana/codecs-numbers@2.0.0-experimental.8618508': - resolution: {integrity: sha512-EXQKfzFr3CkKKNzKSZPOOOzchXsFe90TVONWsSnVkonO9z+nGKALE0/L9uBmIFGgdzhhU9QQVFvxBMclIDJo2Q==} - - '@solana/codecs-strings@2.0.0-experimental.8618508': - resolution: {integrity: sha512-b2yhinr1+oe+JDmnnsV0641KQqqDG8AQ16Z/x7GVWO+AWHMpRlHWVXOq8U1yhPMA4VXxl7i+D+C6ql0VGFp0GA==} - peerDependencies: - fastestsmallesttextencoderdecoder: ^1.0.22 - - '@solana/options@2.0.0-experimental.8618508': - resolution: {integrity: sha512-fy/nIRAMC3QHvnKi63KEd86Xr/zFBVxNW4nEpVEU2OT0gCEKwHY4Z55YHf7XujhyuM3PNpiBKg/YYw5QlRU4vg==} - - '@solana/spl-token-metadata@0.1.2': - resolution: {integrity: sha512-hJYnAJNkDrtkE2Q41YZhCpeOGU/0JgRFXbtrtOuGGeKc3pkEUHB9DDoxZAxx+XRno13GozUleyBi0qypz4c3bw==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.87.6 - - '@solana/spl-token@0.3.11': - resolution: {integrity: sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.88.0 - - '@solana/spl-type-length-value@0.1.0': - resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==} - engines: {node: '>=16'} - - '@solana/web3.js@1.92.3': - resolution: {integrity: sha512-NVBWvb9zdJIAx6X+caXaIICCEQfQaQ8ygykCjJW4u2z/sIKcvPj3ZIIllnx0MWMc3IxGq15ozGYDOQIMbwUcHw==} - - '@solana/web3.js@1.95.3': - resolution: {integrity: sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==} - - '@swc/helpers@0.5.13': - resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} - - '@types/chai@4.3.12': - resolution: {integrity: sha512-zNKDHG/1yxm8Il6uCCVsm+dRdEsJlFoDu73X17y09bId6UwoYww+vFBsAcRzl8knM1sab3Dp1VRikFQwDOtDDw==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - - '@types/mocha@9.1.1': - resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} - - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - - '@types/node@14.18.63': - resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} - - '@types/node@22.6.0': - resolution: {integrity: sha512-QyR8d5bmq+eR72TwQDfujwShHMcIrWIYsaQFtXRE58MHPTEKUNxjxvl0yS0qPMds5xbSDWtp7ZpvGFtd7dfMdQ==} - - '@types/pbkdf2@3.1.2': - resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} - - '@types/secp256k1@4.0.6': - resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==} - - '@types/uuid@8.3.4': - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} - - '@types/ws@7.4.7': - resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} - - '@types/ws@8.5.12': - resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} - - '@ungap/promise-all-settled@1.1.2': - resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} - - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - - agentkeepalive@4.5.0: - resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} - engines: {node: '>= 8.0.0'} - - ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} - engines: {node: '>=6'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base-x@3.0.10: - resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bigint-buffer@1.1.5: - resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} - engines: {node: '>= 10.0.0'} - - bignumber.js@9.1.2: - resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - blakejs@1.2.1: - resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} - - bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} - - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - - borsh@0.7.0: - resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - - brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - - browser-stdout@1.3.1: - resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - - browserify-aes@1.2.0: - resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} - - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - - bs58check@2.1.2: - resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer-layout@1.2.2: - resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==} - engines: {node: '>=4.5'} - - buffer-xor@1.0.3: - resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bufferutil@4.0.8: - resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} - engines: {node: '>=6.14.2'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - - chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - - cipher-base@1.0.4: - resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - create-hash@1.2.0: - resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} - - create-hmac@1.1.7: - resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - - cross-fetch@3.1.8: - resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} - - crypto-hash@1.3.0: - resolution: {integrity: sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==} - engines: {node: '>=8'} - - debug@4.3.3: - resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - - deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - - delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} - - diff@3.5.0: - resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} - engines: {node: '>=0.3.1'} - - diff@5.0.0: - resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} - engines: {node: '>=0.3.1'} - - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - - elliptic@6.5.5: - resolution: {integrity: sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - - es6-promisify@5.0.0: - resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - ethereum-cryptography@0.1.3: - resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - - evp_bytestokey@1.0.3: - resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} - - eyes@0.1.8: - resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} - engines: {node: '> 0.1.90'} - - fast-stable-stringify@1.0.0: - resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} - - fastestsmallesttextencoderdecoder@1.0.22: - resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} - - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob@7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - deprecated: Glob versions prior to v9 are no longer supported - - growl@1.10.5: - resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} - engines: {node: '>=4.x'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hash-base@3.1.0: - resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} - engines: {node: '>=4'} - - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - - hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isomorphic-ws@4.0.1: - resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} - peerDependencies: - ws: '*' - - jayson@4.1.2: - resolution: {integrity: sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA==} - engines: {node: '>=8'} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - - keccak@3.0.4: - resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} - engines: {node: '>=10.0.0'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - md5.js@1.3.5: - resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@4.2.1: - resolution: {integrity: sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==} - engines: {node: '>=10'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - mocha@9.2.2: - resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==} - engines: {node: '>= 12.0.0'} - hasBin: true - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.1: - resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-addon-api@2.0.2: - resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build@4.8.2: - resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} - hasBin: true - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - pako@2.1.0: - resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - - pbkdf2@3.1.2: - resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} - engines: {node: '>=0.12'} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - ripemd160@2.0.2: - resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} - - rpc-websockets@7.10.0: - resolution: {integrity: sha512-cemZ6RiDtYZpPiBzYijdOrkQQzmBCmug0E9SdRH2gIUNT15ql4mwCYWIp0VnSZq6Qrw/JkGUygp4PrK1y9KfwQ==} - - rpc-websockets@8.0.1: - resolution: {integrity: sha512-PptrPRK40uQvifq5sCcObmqInVcZXhy+RRrirzdE5KUPvDI47y1wPvfckD2QzqngOU9xaPW/dT+G+b+wj6M1MQ==} - - rpc-websockets@9.0.2: - resolution: {integrity: sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - scrypt-js@3.0.1: - resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - - secp256k1@4.0.3: - resolution: {integrity: sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==} - engines: {node: '>=10.0.0'} - - serialize-javascript@6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true - - snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - superstruct@0.15.5: - resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==} - - superstruct@1.0.4: - resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==} - engines: {node: '>=14.0.0'} - - superstruct@2.0.2: - resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} - engines: {node: '>=14.0.0'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - text-encoding-utf-8@1.0.2: - resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toml@3.0.0: - resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - ts-mocha@8.0.0: - resolution: {integrity: sha512-Kou1yxTlubLnD5C3unlCVO7nh0HERTezjoVhVw/M5S1SqoUec0WgllQvPk3vzPMc6by8m6xD1uR1yRf8lnVUbA==} - engines: {node: '>= 6.X.X'} - hasBin: true - peerDependencies: - mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X - - ts-node@7.0.1: - resolution: {integrity: sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==} - engines: {node: '>=4.2.0'} - hasBin: true - - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - - utf-8-validate@5.0.10: - resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} - engines: {node: '>=6.14.2'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - workerpool@6.2.0: - resolution: {integrity: sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yargs-parser@20.2.4: - resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} - engines: {node: '>=10'} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yn@2.0.0: - resolution: {integrity: sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==} - engines: {node: '>=4'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -snapshots: - - '@babel/runtime@7.25.6': dependencies: regenerator-runtime: 0.14.1 + dev: false - '@coral-xyz/anchor@0.29.0': + /@coral-xyz/anchor@0.29.0: + resolution: {integrity: sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==} + engines: {node: '>=11'} dependencies: - '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.95.3) - '@noble/hashes': 1.5.0 - '@solana/web3.js': 1.95.3 + '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.92.3) + '@noble/hashes': 1.6.1 + '@solana/web3.js': 1.92.3 bn.js: 5.2.1 bs58: 4.0.1 buffer-layout: 1.2.2 @@ -913,90 +91,179 @@ snapshots: - bufferutil - encoding - utf-8-validate + dev: false - '@coral-xyz/borsh@0.29.0(@solana/web3.js@1.95.3)': + /@coral-xyz/borsh@0.29.0(@solana/web3.js@1.92.3): + resolution: {integrity: sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==} + engines: {node: '>=10'} + peerDependencies: + '@solana/web3.js': ^1.68.0 dependencies: - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.92.3 bn.js: 5.2.1 buffer-layout: 1.2.2 + dev: false - '@noble/curves@1.6.0': + /@noble/curves@1.7.0: + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} dependencies: - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.6.0 + dev: false + + /@noble/hashes@1.6.0: + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + dev: false - '@noble/hashes@1.5.0': {} + /@noble/hashes@1.6.1: + resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==} + engines: {node: ^14.21.3 || >=16} + dev: false - '@solana/buffer-layout-utils@0.2.0': + /@solana/buffer-layout-utils@0.2.0: + resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==} + engines: {node: '>= 10'} dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.92.3 bigint-buffer: 1.1.5 bignumber.js: 9.1.2 transitivePeerDependencies: - bufferutil - encoding - utf-8-validate + dev: false - '@solana/buffer-layout@4.0.1': + /@solana/buffer-layout@4.0.1: + resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} + engines: {node: '>=5.10'} dependencies: buffer: 6.0.3 + dev: false - '@solana/codecs-core@2.0.0-experimental.8618508': {} + /@solana/codecs-core@2.0.0-rc.1(typescript@4.9.5): + resolution: {integrity: sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==} + peerDependencies: + typescript: '>=5' + dependencies: + '@solana/errors': 2.0.0-rc.1(typescript@4.9.5) + typescript: 4.9.5 + dev: false - '@solana/codecs-data-structures@2.0.0-experimental.8618508': + /@solana/codecs-data-structures@2.0.0-rc.1(typescript@4.9.5): + resolution: {integrity: sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==} + peerDependencies: + typescript: '>=5' dependencies: - '@solana/codecs-core': 2.0.0-experimental.8618508 - '@solana/codecs-numbers': 2.0.0-experimental.8618508 + '@solana/codecs-core': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@4.9.5) + '@solana/errors': 2.0.0-rc.1(typescript@4.9.5) + typescript: 4.9.5 + dev: false - '@solana/codecs-numbers@2.0.0-experimental.8618508': + /@solana/codecs-numbers@2.0.0-rc.1(typescript@4.9.5): + resolution: {integrity: sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==} + peerDependencies: + typescript: '>=5' dependencies: - '@solana/codecs-core': 2.0.0-experimental.8618508 + '@solana/codecs-core': 2.0.0-rc.1(typescript@4.9.5) + '@solana/errors': 2.0.0-rc.1(typescript@4.9.5) + typescript: 4.9.5 + dev: false - '@solana/codecs-strings@2.0.0-experimental.8618508(fastestsmallesttextencoderdecoder@1.0.22)': + /@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5): + resolution: {integrity: sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5' dependencies: - '@solana/codecs-core': 2.0.0-experimental.8618508 - '@solana/codecs-numbers': 2.0.0-experimental.8618508 + '@solana/codecs-core': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@4.9.5) + '@solana/errors': 2.0.0-rc.1(typescript@4.9.5) fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 4.9.5 + dev: false + + /@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5): + resolution: {integrity: sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==} + peerDependencies: + typescript: '>=5' + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) + '@solana/options': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + dev: false - '@solana/options@2.0.0-experimental.8618508': + /@solana/errors@2.0.0-rc.1(typescript@4.9.5): + resolution: {integrity: sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==} + hasBin: true + peerDependencies: + typescript: '>=5' dependencies: - '@solana/codecs-core': 2.0.0-experimental.8618508 - '@solana/codecs-numbers': 2.0.0-experimental.8618508 + chalk: 5.3.0 + commander: 12.1.0 + typescript: 4.9.5 + dev: false + + /@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5): + resolution: {integrity: sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==} + peerDependencies: + typescript: '>=5' + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@4.9.5) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) + '@solana/errors': 2.0.0-rc.1(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + dev: false - '@solana/spl-token-metadata@0.1.2(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22)': + /@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5): + resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.95.3 dependencies: - '@solana/codecs-core': 2.0.0-experimental.8618508 - '@solana/codecs-data-structures': 2.0.0-experimental.8618508 - '@solana/codecs-numbers': 2.0.0-experimental.8618508 - '@solana/codecs-strings': 2.0.0-experimental.8618508(fastestsmallesttextencoderdecoder@1.0.22) - '@solana/options': 2.0.0-experimental.8618508 - '@solana/spl-type-length-value': 0.1.0 + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) '@solana/web3.js': 1.92.3 transitivePeerDependencies: - fastestsmallesttextencoderdecoder + - typescript + dev: false - '@solana/spl-token@0.3.11(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22)': + /@solana/spl-token@0.3.11(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5): + resolution: {integrity: sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.88.0 dependencies: '@solana/buffer-layout': 4.0.1 '@solana/buffer-layout-utils': 0.2.0 - '@solana/spl-token-metadata': 0.1.2(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.92.3)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) '@solana/web3.js': 1.92.3 buffer: 6.0.3 transitivePeerDependencies: - bufferutil - encoding - fastestsmallesttextencoderdecoder + - typescript - utf-8-validate + dev: false - '@solana/spl-type-length-value@0.1.0': - dependencies: - buffer: 6.0.3 - - '@solana/web3.js@1.92.3': + /@solana/web3.js@1.92.3: + resolution: {integrity: sha512-NVBWvb9zdJIAx6X+caXaIICCEQfQaQ8ygykCjJW4u2z/sIKcvPj3ZIIllnx0MWMc3IxGq15ozGYDOQIMbwUcHw==} dependencies: - '@babel/runtime': 7.25.6 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 + '@babel/runtime': 7.26.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 '@solana/buffer-layout': 4.0.1 agentkeepalive: 4.5.0 bigint-buffer: 1.1.5 @@ -1005,212 +272,294 @@ snapshots: bs58: 4.0.1 buffer: 6.0.3 fast-stable-stringify: 1.0.0 - jayson: 4.1.2 + jayson: 4.1.3 node-fetch: 2.7.0 - rpc-websockets: 8.0.1 + rpc-websockets: 8.0.2 superstruct: 1.0.4 transitivePeerDependencies: - bufferutil - encoding - utf-8-validate + dev: false - '@solana/web3.js@1.95.3': - dependencies: - '@babel/runtime': 7.25.6 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 - '@solana/buffer-layout': 4.0.1 - agentkeepalive: 4.5.0 - bigint-buffer: 1.1.5 - bn.js: 5.2.1 - borsh: 0.7.0 - bs58: 4.0.1 - buffer: 6.0.3 - fast-stable-stringify: 1.0.0 - jayson: 4.1.2 - node-fetch: 2.7.0 - rpc-websockets: 9.0.2 - superstruct: 2.0.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@swc/helpers@0.5.13': - dependencies: - tslib: 2.7.0 - - '@types/chai@4.3.12': {} + /@types/chai@4.3.20: + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} + dev: false - '@types/connect@3.4.38': + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 22.6.0 + '@types/node': 14.18.63 + dev: false - '@types/json5@0.0.29': + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + requiresBuild: true + dev: false optional: true - '@types/mocha@9.1.1': {} - - '@types/node@12.20.55': {} - - '@types/node@14.18.63': {} + /@types/mocha@9.1.1: + resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} + dev: false - '@types/node@22.6.0': - dependencies: - undici-types: 6.19.8 + /@types/node@12.20.55: + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + dev: false - '@types/pbkdf2@3.1.2': - dependencies: - '@types/node': 22.6.0 + /@types/node@14.18.63: + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + dev: false - '@types/secp256k1@4.0.6': + /@types/pbkdf2@3.1.2: + resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} dependencies: - '@types/node': 22.6.0 + '@types/node': 14.18.63 + dev: false - '@types/uuid@8.3.4': {} - - '@types/ws@7.4.7': + /@types/secp256k1@4.0.6: + resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==} dependencies: - '@types/node': 22.6.0 + '@types/node': 14.18.63 + dev: false - '@types/ws@8.5.12': + /@types/ws@7.4.7: + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: - '@types/node': 22.6.0 + '@types/node': 14.18.63 + dev: false - '@ungap/promise-all-settled@1.1.2': {} + /@ungap/promise-all-settled@1.1.2: + resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} + dev: false - JSONStream@1.3.5: + /JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true dependencies: jsonparse: 1.3.1 through: 2.3.8 + dev: false - agentkeepalive@4.5.0: + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} dependencies: humanize-ms: 1.2.1 + dev: false - ansi-colors@4.1.1: {} + /ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: false - ansi-regex@5.0.1: {} + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: false - ansi-styles@4.3.0: + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 + dev: false - anymatch@3.1.3: + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: false - argparse@2.0.1: {} + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: false - arrify@1.0.1: {} + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: false - assertion-error@1.1.0: {} + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: false - balanced-match@1.0.2: {} + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false - base-x@3.0.10: + /base-x@3.0.10: + resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} dependencies: safe-buffer: 5.2.1 + dev: false - base64-js@1.5.1: {} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false - bigint-buffer@1.1.5: + /bigint-buffer@1.1.5: + resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} + engines: {node: '>= 10.0.0'} + requiresBuild: true dependencies: bindings: 1.5.0 + dev: false - bignumber.js@9.1.2: {} + /bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + dev: false - binary-extensions@2.3.0: {} + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: false - bindings@1.5.0: + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} dependencies: file-uri-to-path: 1.0.0 + dev: false - blakejs@1.2.1: {} + /blakejs@1.2.1: + resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} + dev: false - bn.js@4.12.0: {} + /bn.js@4.12.1: + resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} + dev: false - bn.js@5.2.1: {} + /bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + dev: false - borsh@0.7.0: + /borsh@0.7.0: + resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} dependencies: bn.js: 5.2.1 bs58: 4.0.1 text-encoding-utf-8: 1.0.2 + dev: false - brace-expansion@1.1.11: + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: false - braces@3.0.2: + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} dependencies: - fill-range: 7.0.1 + fill-range: 7.1.1 + dev: false - brorand@1.1.0: {} + /brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + dev: false - browser-stdout@1.3.1: {} + /browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: false - browserify-aes@1.2.0: + /browserify-aes@1.2.0: + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} dependencies: buffer-xor: 1.0.3 - cipher-base: 1.0.4 + cipher-base: 1.0.6 create-hash: 1.2.0 evp_bytestokey: 1.0.3 inherits: 2.0.4 safe-buffer: 5.2.1 + dev: false - bs58@4.0.1: + /bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} dependencies: base-x: 3.0.10 + dev: false - bs58check@2.1.2: + /bs58check@2.1.2: + resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} dependencies: bs58: 4.0.1 create-hash: 1.2.0 safe-buffer: 5.2.1 + dev: false - buffer-from@1.1.2: {} + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: false - buffer-layout@1.2.2: {} + /buffer-layout@1.2.2: + resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==} + engines: {node: '>=4.5'} + dev: false - buffer-xor@1.0.3: {} + /buffer-xor@1.0.3: + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} + dev: false - buffer@6.0.3: + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: false - bufferutil@4.0.8: + /bufferutil@4.0.8: + resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} + engines: {node: '>=6.14.2'} + requiresBuild: true dependencies: - node-gyp-build: 4.8.2 + node-gyp-build: 4.8.4 + dev: false - camelcase@6.3.0: {} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: false - chai@4.4.1: + /chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} dependencies: assertion-error: 1.1.0 check-error: 1.0.3 - deep-eql: 4.1.3 + deep-eql: 4.1.4 get-func-name: 2.0.2 loupe: 2.3.7 pathval: 1.1.1 - type-detect: 4.0.8 + type-detect: 4.1.0 + dev: false - chalk@4.1.2: + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + dev: false + + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false - check-error@1.0.3: + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: get-func-name: 2.0.2 + dev: false - chokidar@3.5.3: + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 - braces: 3.0.2 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -1218,98 +567,167 @@ snapshots: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: false - cipher-base@1.0.4: + /cipher-base@1.0.6: + resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} + engines: {node: '>= 0.10'} dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + dev: false - cliui@7.0.4: + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + dev: false - color-convert@2.0.1: + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false - color-name@1.1.4: {} + /commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + dev: false - commander@2.20.3: {} + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: false - concat-map@0.0.1: {} + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: false - create-hash@1.2.0: + /create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.6 inherits: 2.0.4 md5.js: 1.3.5 ripemd160: 2.0.2 sha.js: 2.4.11 + dev: false - create-hmac@1.1.7: + /create-hmac@1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.6 create-hash: 1.2.0 inherits: 2.0.4 ripemd160: 2.0.2 safe-buffer: 5.2.1 sha.js: 2.4.11 + dev: false - cross-fetch@3.1.8: + /cross-fetch@3.1.8: + resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: node-fetch: 2.7.0 transitivePeerDependencies: - encoding + dev: false - crypto-hash@1.3.0: {} + /crypto-hash@1.3.0: + resolution: {integrity: sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==} + engines: {node: '>=8'} + dev: false - debug@4.3.3(supports-color@8.1.1): + /debug@4.3.3(supports-color@8.1.1): + resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.2 supports-color: 8.1.1 + dev: false - decamelize@4.0.0: {} + /decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: false - deep-eql@4.1.3: + /deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} dependencies: - type-detect: 4.0.8 + type-detect: 4.1.0 + dev: false - delay@5.0.0: {} + /delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + dev: false - diff@3.5.0: {} + /diff@3.5.0: + resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} + engines: {node: '>=0.3.1'} + dev: false - diff@5.0.0: {} + /diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: false - dot-case@3.0.4: + /dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 + dev: false - elliptic@6.5.5: + /elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 brorand: 1.1.0 hash.js: 1.1.7 hmac-drbg: 1.0.1 inherits: 2.0.4 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + dev: false - emoji-regex@8.0.0: {} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false - es6-promise@4.2.8: {} + /es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + dev: false - es6-promisify@5.0.0: + /es6-promisify@5.0.0: + resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} dependencies: es6-promise: 4.2.8 + dev: false - escalade@3.1.2: {} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + dev: false - escape-string-regexp@4.0.0: {} + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: false - ethereum-cryptography@0.1.3: + /ethereum-cryptography@0.1.3: + resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} dependencies: '@types/pbkdf2': 3.1.2 '@types/secp256k1': 4.0.6 @@ -1324,51 +742,89 @@ snapshots: randombytes: 2.1.0 safe-buffer: 5.2.1 scrypt-js: 3.0.1 - secp256k1: 4.0.3 + secp256k1: 4.0.4 setimmediate: 1.0.5 + dev: false - eventemitter3@4.0.7: {} - - eventemitter3@5.0.1: {} + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false - evp_bytestokey@1.0.3: + /evp_bytestokey@1.0.3: + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} dependencies: md5.js: 1.3.5 safe-buffer: 5.2.1 + dev: false - eyes@0.1.8: {} + /eyes@0.1.8: + resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} + engines: {node: '> 0.1.90'} + dev: false - fast-stable-stringify@1.0.0: {} + /fast-stable-stringify@1.0.0: + resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} + dev: false - fastestsmallesttextencoderdecoder@1.0.22: {} + /fastestsmallesttextencoderdecoder@1.0.22: + resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} + dev: false - file-uri-to-path@1.0.0: {} + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false - fill-range@7.0.1: + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: false - find-up@5.0.0: + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: locate-path: 6.0.0 path-exists: 4.0.0 + dev: false - flat@5.0.2: {} + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: false - fs.realpath@1.0.0: {} + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: false - fsevents@2.3.3: + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false optional: true - get-caller-file@2.0.5: {} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: false - get-func-name@2.0.2: {} + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: false - glob-parent@5.1.2: + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: false - glob@7.2.0: + /glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1376,68 +832,124 @@ snapshots: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: false - growl@1.10.5: {} + /growl@1.10.5: + resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} + engines: {node: '>=4.x'} + dev: false - has-flag@4.0.0: {} + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: false - hash-base@3.1.0: + /hash-base@3.1.0: + resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} + engines: {node: '>=4'} dependencies: inherits: 2.0.4 readable-stream: 3.6.2 safe-buffer: 5.2.1 + dev: false - hash.js@1.1.7: + /hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 + dev: false - he@1.2.0: {} + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: false - hmac-drbg@1.0.1: + /hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} dependencies: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + dev: false - humanize-ms@1.2.1: + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} dependencies: ms: 2.1.3 + dev: false - ieee754@1.2.1: {} + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false - inflight@1.0.6: + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 wrappy: 1.0.2 + dev: false - inherits@2.0.4: {} + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false - is-binary-path@2.1.0: + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} dependencies: binary-extensions: 2.3.0 + dev: false - is-extglob@2.1.1: {} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: false - is-fullwidth-code-point@3.0.0: {} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false - is-glob@4.0.3: + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: false - is-number@7.0.0: {} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: false - is-plain-obj@2.1.0: {} + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: false - is-unicode-supported@0.1.0: {} + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: false - isexe@2.0.0: {} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: false - isomorphic-ws@4.0.1(ws@7.5.10): + /isomorphic-ws@4.0.1(ws@7.5.10): + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' dependencies: ws: 7.5.10 + dev: false - jayson@4.1.2: + /jayson@4.1.3: + resolution: {integrity: sha512-LtXh5aYZodBZ9Fc3j6f2w+MTNcnxteMOrb+QgIouguGOulWi0lieEkOUg+HkjjFs0DGoWDds6bi4E9hpNFLulQ==} + engines: {node: '>=8'} + hasBin: true dependencies: '@types/connect': 3.4.38 '@types/node': 12.20.55 @@ -1454,70 +966,118 @@ snapshots: transitivePeerDependencies: - bufferutil - utf-8-validate + dev: false - js-yaml@4.1.0: + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: argparse: 2.0.1 + dev: false - json-stringify-safe@5.0.1: {} + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: false - json5@1.0.2: + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + requiresBuild: true dependencies: minimist: 1.2.8 + dev: false optional: true - jsonparse@1.3.1: {} + /jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + dev: false - keccak@3.0.4: + /keccak@3.0.4: + resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} + engines: {node: '>=10.0.0'} + requiresBuild: true dependencies: node-addon-api: 2.0.2 - node-gyp-build: 4.8.2 + node-gyp-build: 4.8.4 readable-stream: 3.6.2 + dev: false - locate-path@6.0.0: + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} dependencies: p-locate: 5.0.0 + dev: false - log-symbols@4.1.0: + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 + dev: false - loupe@2.3.7: + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: get-func-name: 2.0.2 + dev: false - lower-case@2.0.2: + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.7.0 + tslib: 2.8.1 + dev: false - make-error@1.3.6: {} + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: false - md5.js@1.3.5: + /md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} dependencies: hash-base: 3.1.0 inherits: 2.0.4 safe-buffer: 5.2.1 + dev: false - minimalistic-assert@1.0.1: {} + /minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + dev: false - minimalistic-crypto-utils@1.0.1: {} + /minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + dev: false - minimatch@3.1.2: + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 + dev: false - minimatch@4.2.1: + /minimatch@4.2.1: + resolution: {integrity: sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==} + engines: {node: '>=10'} dependencies: brace-expansion: 1.1.11 + dev: false - minimist@1.2.8: {} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false - mkdirp@0.5.6: + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true dependencies: minimist: 1.2.8 + dev: false - mocha@9.2.2: + /mocha@9.2.2: + resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==} + engines: {node: '>= 12.0.0'} + hasBin: true dependencies: '@ungap/promise-all-settled': 1.1.2 ansi-colors: 4.1.1 @@ -1543,94 +1103,171 @@ snapshots: yargs: 16.2.0 yargs-parser: 20.2.4 yargs-unparser: 2.0.0 + dev: false - ms@2.1.2: {} + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false - ms@2.1.3: {} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false - nanoid@3.3.1: {} + /nanoid@3.3.1: + resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false - no-case@3.0.4: + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.7.0 + tslib: 2.8.1 + dev: false - node-addon-api@2.0.2: {} + /node-addon-api@2.0.2: + resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} + dev: false + + /node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + dev: false - node-fetch@2.7.0: + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true dependencies: whatwg-url: 5.0.0 + dev: false - node-gyp-build@4.8.2: {} + /node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + dev: false - normalize-path@3.0.0: {} + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: false - once@1.4.0: + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 + dev: false - p-limit@3.1.0: + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 + dev: false - p-locate@5.0.0: + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: p-limit: 3.1.0 + dev: false - pako@2.1.0: {} + /pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + dev: false - path-exists@4.0.0: {} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: false - path-is-absolute@1.0.1: {} + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: false - pathval@1.1.1: {} + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: false - pbkdf2@3.1.2: + /pbkdf2@3.1.2: + resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} + engines: {node: '>=0.12'} dependencies: create-hash: 1.2.0 create-hmac: 1.1.7 ripemd160: 2.0.2 safe-buffer: 5.2.1 sha.js: 2.4.11 + dev: false - picomatch@2.3.1: {} + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: false - prettier@2.8.8: {} + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: false - randombytes@2.1.0: + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 + dev: false - readable-stream@3.6.2: + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + dev: false - readdirp@3.6.0: + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: false - regenerator-runtime@0.14.1: {} + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: false - require-directory@2.1.1: {} + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: false - ripemd160@2.0.2: + /ripemd160@2.0.2: + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} dependencies: hash-base: 3.1.0 inherits: 2.0.4 + dev: false - rpc-websockets@7.10.0: + /rpc-websockets@7.10.0: + resolution: {integrity: sha512-cemZ6RiDtYZpPiBzYijdOrkQQzmBCmug0E9SdRH2gIUNT15ql4mwCYWIp0VnSZq6Qrw/JkGUygp4PrK1y9KfwQ==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 eventemitter3: 4.0.7 uuid: 8.3.2 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) optionalDependencies: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + dev: false - rpc-websockets@8.0.1: + /rpc-websockets@8.0.2: + resolution: {integrity: sha512-QZ8lneJTtIZTf9JBcdUn/im2qDynWRYPKtmF6P9DqtdzqSLebcllYWVQr5aQacAp7LBYPReOW9Ses98dNfO7cA==} dependencies: eventemitter3: 4.0.7 uuid: 8.3.2 @@ -1638,106 +1275,160 @@ snapshots: optionalDependencies: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + dev: false - rpc-websockets@9.0.2: - dependencies: - '@swc/helpers': 0.5.13 - '@types/uuid': 8.3.4 - '@types/ws': 8.5.12 - buffer: 6.0.3 - eventemitter3: 5.0.1 - uuid: 8.3.2 - ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - - safe-buffer@5.2.1: {} + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false - scrypt-js@3.0.1: {} + /scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + dev: false - secp256k1@4.0.3: + /secp256k1@4.0.4: + resolution: {integrity: sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==} + engines: {node: '>=18.0.0'} + requiresBuild: true dependencies: - elliptic: 6.5.5 - node-addon-api: 2.0.2 - node-gyp-build: 4.8.2 + elliptic: 6.6.1 + node-addon-api: 5.1.0 + node-gyp-build: 4.8.4 + dev: false - serialize-javascript@6.0.0: + /serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} dependencies: randombytes: 2.1.0 + dev: false - setimmediate@1.0.5: {} + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: false - sha.js@2.4.11: + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + dev: false - snake-case@3.0.4: + /snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 + dev: false - source-map-support@0.5.21: + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: false - source-map@0.6.1: {} + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false - string-width@4.2.3: + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + dev: false - string_decoder@1.3.0: + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 + dev: false - strip-ansi@6.0.1: + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 + dev: false - strip-bom@3.0.0: + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + requiresBuild: true + dev: false optional: true - strip-json-comments@3.1.1: {} - - superstruct@0.15.5: {} + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: false - superstruct@1.0.4: {} + /superstruct@0.15.5: + resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==} + dev: false - superstruct@2.0.2: {} + /superstruct@1.0.4: + resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==} + engines: {node: '>=14.0.0'} + dev: false - supports-color@7.2.0: + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 + dev: false - supports-color@8.1.1: + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} dependencies: has-flag: 4.0.0 + dev: false - text-encoding-utf-8@1.0.2: {} + /text-encoding-utf-8@1.0.2: + resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} + dev: false - through@2.3.8: {} + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: false - to-regex-range@5.0.1: + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: false - toml@3.0.0: {} + /toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + dev: false - tr46@0.0.3: {} + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false - ts-mocha@8.0.0(mocha@9.2.2): + /ts-mocha@8.0.0(mocha@9.2.2): + resolution: {integrity: sha512-Kou1yxTlubLnD5C3unlCVO7nh0HERTezjoVhVw/M5S1SqoUec0WgllQvPk3vzPMc6by8m6xD1uR1yRf8lnVUbA==} + engines: {node: '>= 6.X.X'} + hasBin: true + peerDependencies: + mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X dependencies: mocha: 9.2.2 ts-node: 7.0.1 optionalDependencies: tsconfig-paths: 3.15.0 + dev: false - ts-node@7.0.1: + /ts-node@7.0.1: + resolution: {integrity: sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==} + engines: {node: '>=4.2.0'} + hasBin: true dependencies: arrify: 1.0.1 buffer-from: 1.1.2 @@ -1747,82 +1438,155 @@ snapshots: mkdirp: 0.5.6 source-map-support: 0.5.21 yn: 2.0.0 + dev: false - tsconfig-paths@3.15.0: + /tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + requiresBuild: true dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 + dev: false optional: true - tslib@2.7.0: {} - - type-detect@4.0.8: {} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + dev: false - typescript@4.9.5: {} + /type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + dev: false - undici-types@6.19.8: {} + /typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: false - utf-8-validate@5.0.10: + /utf-8-validate@5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + requiresBuild: true dependencies: - node-gyp-build: 4.8.2 + node-gyp-build: 4.8.4 + dev: false - util-deprecate@1.0.2: {} + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false - uuid@8.3.2: {} + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false - webidl-conversions@3.0.1: {} + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false - whatwg-url@5.0.0: + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 + dev: false - which@2.0.2: + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 + dev: false - workerpool@6.2.0: {} + /workerpool@6.2.0: + resolution: {integrity: sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==} + dev: false - wrap-ansi@7.0.0: + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: false - wrappy@1.0.2: {} + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: false - ws@7.5.10: {} + /ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false - ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + /ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true dependencies: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + dev: false - y18n@5.0.8: {} - - yargs-parser@20.2.4: {} + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: false - yargs-parser@20.2.9: {} + /yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: false - yargs-unparser@2.0.0: + /yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} dependencies: camelcase: 6.3.0 decamelize: 4.0.0 flat: 5.0.2 is-plain-obj: 2.1.0 + dev: false - yargs@16.2.0: + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} dependencies: cliui: 7.0.4 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 - yargs-parser: 20.2.9 + yargs-parser: 20.2.4 + dev: false - yn@2.0.0: {} + /yn@2.0.0: + resolution: {integrity: sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==} + engines: {node: '>=4'} + dev: false - yocto-queue@0.1.0: {} + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: false diff --git a/contracts/programs/contract-reader-interface/src/lib.rs b/contracts/programs/contract-reader-interface/src/lib.rs index 838190fe9..b02b68888 100644 --- a/contracts/programs/contract-reader-interface/src/lib.rs +++ b/contracts/programs/contract-reader-interface/src/lib.rs @@ -17,6 +17,19 @@ pub mod contract_reader_interface { Ok(()) } + + pub fn initialize_lookup_table( + ctx: Context, + lookup_table: Pubkey, + ) -> Result<()> { + let account = &mut ctx.accounts.write_data_account; + account.version = 1; + account.administrator = ctx.accounts.admin.key(); + account.pending_administrator = Pubkey::default(); + account.lookup_table = lookup_table; + + Ok(()) + } } #[derive(Accounts)] @@ -37,6 +50,34 @@ pub struct Initialize<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct InitializeLookupTableData<'info> { + /// PDA for LookupTableDataAccount, derived from seeds and created by the System Program + #[account( + init, + payer = admin, + space = size_of::() + 8, + seeds = [b"data"], + bump + )] + pub write_data_account: Account<'info, LookupTableDataAccount>, + + /// Admin account that pays for PDA creation and signs the transaction + #[account(mut)] + pub admin: Signer<'info>, + + /// System Program required for PDA creation + pub system_program: Program<'info, System>, +} + +#[account] +pub struct LookupTableDataAccount { + pub version: u8, // Version of the data account + pub administrator: Pubkey, // Administrator public key + pub pending_administrator: Pubkey, // Pending administrator public key + pub lookup_table: Pubkey, // Address of the lookup table +} + #[account] pub struct DataAccount { pub idx: u64, diff --git a/go.mod b/go.mod index 22ff1dd69..6b58adcfe 100644 --- a/go.mod +++ b/go.mod @@ -12,22 +12,25 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-plugin v1.6.2 + github.com/jackc/pgx/v4 v4.18.3 github.com/jpillora/backoff v1.0.0 + github.com/lib/pq v1.10.9 github.com/pelletier/go-toml/v2 v2.2.0 github.com/prometheus/client_golang v1.17.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 + github.com/smartcontractkit/chainlink-common v0.4.1-0.20241223143929-db7919d60550 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 - golang.org/x/sync v0.8.0 - golang.org/x/text v0.18.0 + golang.org/x/sync v0.10.0 + golang.org/x/text v0.21.0 ) require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/apache/arrow-go/v18 v18.0.0 // indirect github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -46,9 +49,11 @@ require ( github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect @@ -57,27 +62,37 @@ require ( github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/invopop/jsonschema v0.12.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.0 // indirect - github.com/lib/pq v1.10.9 // indirect github.com/linkedin/goavro/v2 v2.12.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/marcboeker/go-duckdb v1.8.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.35 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/oklog/run v1.0.0 // indirect github.com/onsi/gomega v1.24.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect @@ -87,6 +102,7 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect + github.com/scylladb/go-reflectx v1.0.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 // indirect @@ -97,6 +113,7 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.mongodb.org/mongo-driver v1.15.0 // indirect go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect @@ -120,15 +137,18 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.26.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.1 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a44777df8..3cf34d679 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= @@ -47,6 +49,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -83,6 +91,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts= github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= @@ -93,8 +103,10 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -145,8 +157,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -165,6 +179,10 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -200,6 +218,8 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXi github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -271,6 +291,53 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= @@ -290,22 +357,32 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linkedin/goavro/v2 v2.9.7/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= @@ -318,13 +395,19 @@ github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamh github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -340,6 +423,10 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -350,6 +437,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= @@ -391,6 +480,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -427,6 +518,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -434,16 +528,23 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ= +github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 h1:ZA92CTX9JtEArrxgZw7PNctVxFS+/DmSXumkwf1WiMY= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-common v0.4.1-0.20241223143929-db7919d60550 h1:rRs74zjDJ7do5aYEXSU/sOnLnlbYCNqM8BrvEx/0NH8= +github.com/smartcontractkit/chainlink-common v0.4.1-0.20241223143929-db7919d60550/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360= @@ -464,6 +565,7 @@ github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 h1:ZqpS7rAhh github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -513,6 +615,11 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= @@ -564,6 +671,7 @@ go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8d go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -581,7 +689,9 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= @@ -590,15 +700,20 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -651,6 +766,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -661,12 +777,13 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -682,8 +799,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -694,13 +811,16 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -730,24 +850,26 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -761,12 +883,14 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -781,6 +905,7 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -793,12 +918,18 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -856,8 +987,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= -google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -869,8 +1000,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -878,6 +1009,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index bde3745bc..c66251505 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -14,7 +14,7 @@ require ( github.com/lib/pq v1.10.9 github.com/pelletier/go-toml/v2 v2.2.3 github.com/rs/zerolog v1.33.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 + github.com/smartcontractkit/chainlink-common v0.4.1-0.20241223143929-db7919d60550 github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 @@ -25,8 +25,8 @@ require ( github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.34.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c - golang.org/x/sync v0.8.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.10.0 + golang.org/x/text v0.21.0 gopkg.in/guregu/null.v4 v4.0.0 ) @@ -57,7 +57,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/XSAM/otelsql v0.27.0 // indirect github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect @@ -182,7 +182,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -258,8 +258,8 @@ require ( github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leanovate/gopter v0.2.11 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index cc45a70d9..7f20fb5c2 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -150,9 +150,11 @@ github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2uc github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -316,6 +318,8 @@ github.com/cometbft/cometbft-db v0.8.0 h1:vUMDaH3ApkX8m0KZvOFFy9b5DZHBAjsnEuo9AK github.com/cometbft/cometbft-db v0.8.0/go.mod h1:6ASCP4pfhmrCBpfk01/9E1SI29nD3HfVHrY4PG8x5c0= github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= +github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts= +github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= @@ -568,8 +572,8 @@ github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6l github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -634,6 +638,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -914,11 +920,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -949,6 +955,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/linkedin/goavro/v2 v2.12.0 h1:rIQQSj8jdAUlKQh6DttK8wCRv4t4QO09g1C4aBWXslg= +github.com/linkedin/goavro/v2 v2.12.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= github.com/linxGnu/grocksdb v1.7.16 h1:Q2co1xrpdkr5Hx3Fp+f+f7fRGhQFQhvi/+226dtLmA8= github.com/linxGnu/grocksdb v1.7.16/go.mod h1:JkS7pl5qWpGpuVb3bPqTz8nC12X3YtPZT+Xq7+QfQo4= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= @@ -968,6 +976,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1126,6 +1136,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1178,6 +1190,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/riferrei/srclient v0.5.4 h1:dfwyR5u23QF7beuVl2WemUY2KXh5+Sc4DHKyPXBNYuc= +github.com/riferrei/srclient v0.5.4/go.mod h1:vbkLmWcgYa7JgfPvuy/+K8fTS0p1bApqadxrxi/S1MI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -1257,8 +1271,8 @@ github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgB github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241211150100-7683331f64a0 h1:/1L+v4SxUD2K5RMRbfByyLfePMAgQKeD0onSetPnGmA= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241211150100-7683331f64a0/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 h1:ZA92CTX9JtEArrxgZw7PNctVxFS+/DmSXumkwf1WiMY= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-common v0.4.1-0.20241223143929-db7919d60550 h1:rRs74zjDJ7do5aYEXSU/sOnLnlbYCNqM8BrvEx/0NH8= +github.com/smartcontractkit/chainlink-common v0.4.1-0.20241223143929-db7919d60550/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= @@ -1425,6 +1439,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1434,6 +1450,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1692,8 +1710,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1804,8 +1822,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index 6af9c3444..c32507e49 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -6,9 +6,11 @@ package relayinterface import ( "context" "encoding/binary" + "encoding/json" "io" "os" "path/filepath" + "sync" "testing" "time" @@ -18,7 +20,7 @@ import ( "github.com/gagliardetto/solana-go/text" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/logger" commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -26,19 +28,40 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" + contract "github.com/smartcontractkit/chainlink-solana/contracts/generated/contract_reader_interface" "github.com/smartcontractkit/chainlink-solana/integration-tests/solclient" "github.com/smartcontractkit/chainlink-solana/integration-tests/utils" "github.com/smartcontractkit/chainlink-solana/pkg/solana/chainreader" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + solanautils "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" ) func TestChainComponents(t *testing.T) { t.Parallel() - it := &SolanaChainComponentsInterfaceTester[*testing.T]{Helper: &helper{}} - it.Init(t) + helper := &helper{} + helper.Init(t) + + t.Run("RunChainComponentsSolanaTests", func(t *testing.T) { + t.Parallel() + it := &SolanaChainComponentsInterfaceTester[*testing.T]{Helper: helper} + DisableTests(it) + it.Setup(t) + RunChainComponentsSolanaTests(t, it) + }) + + t.Run("RunChainComponentsInLoopSolanaTests", func(t *testing.T) { + t.Parallel() + it := &SolanaChainComponentsInterfaceTester[*testing.T]{Helper: helper} + DisableTests(it) + wrapped := commontestutils.WrapContractReaderTesterForLoop(it) + wrapped.Setup(t) + RunChainComponentsInLoopSolanaTests(t, wrapped) + }) +} +func DisableTests(it *SolanaChainComponentsInterfaceTester[*testing.T]) { it.DisableTests([]string{ // disable tests that set values ContractReaderGetLatestValueBasedOnConfidenceLevel, @@ -70,9 +93,6 @@ func TestChainComponents(t *testing.T) { ContractReaderQueryKeysCanFilterWithValueComparator, ContractReaderQueryKeysCanLimitResultsWithCursor, }) - - RunChainComponentsSolanaTests(t, it) - RunChainComponentsInLoopSolanaTests(t, commontestutils.WrapContractReaderTesterForLoop(it)) } func RunChainComponentsSolanaTests[T TestingT[T]](t T, it *SolanaChainComponentsInterfaceTester[T]) { @@ -86,17 +106,17 @@ func RunChainComponentsInLoopSolanaTests[T TestingT[T]](t T, it ChainComponentsI } func RunContractReaderSolanaTests[T TestingT[T]](t T, it *SolanaChainComponentsInterfaceTester[T]) { - RunContractReaderInterfaceTests(t, it, false) + RunContractReaderInterfaceTests(t, it, false, true) - testCases := []Testcase[T]{} + var testCases []Testcase[T] RunTests(t, it, testCases) } func RunContractReaderInLoopTests[T TestingT[T]](t T, it ChainComponentsInterfaceTester[T]) { - RunContractReaderInterfaceTests(t, it, false) + RunContractReaderInterfaceTests(t, it, false, true) - testCases := []Testcase[T]{} + var testCases []Testcase[T] RunTests(t, it, testCases) } @@ -112,61 +132,47 @@ type SolanaChainComponentsInterfaceTesterHelper[T TestingT[T]] interface { type SolanaChainComponentsInterfaceTester[T TestingT[T]] struct { TestSelectionSupport - Helper SolanaChainComponentsInterfaceTesterHelper[T] - cr *chainreader.SolanaChainReaderService - chainReaderConfig config.ChainReader - accountPubKey solana.PublicKey - secondAccountPubKey solana.PublicKey + Helper SolanaChainComponentsInterfaceTesterHelper[T] + cr *chainreader.SolanaChainReaderService + contractReaderConfig config.ContractReader } func (it *SolanaChainComponentsInterfaceTester[T]) Setup(t T) { t.Cleanup(func() {}) - it.chainReaderConfig = config.ChainReader{ - Namespaces: map[string]config.ChainReaderMethods{ + it.contractReaderConfig = config.ContractReader{ + Namespaces: map[string]config.ChainContractReader{ AnyContractName: { - Methods: map[string]config.ChainDataReader{ + IDL: mustUnmarshalIDL(t, string(it.Helper.GetJSONEncodedIDL(t))), + Reads: map[string]config.ReadDefinition{ MethodReturningUint64: { - AnchorIDL: string(it.Helper.GetJSONEncodedIDL(t)), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "DataAccount", - OutputModifications: codec.ModifiersConfig{ - &codec.PropertyExtractorConfig{FieldName: "U64value"}, - }, + ChainSpecificName: "DataAccount", + ReadType: config.Account, + OutputModifications: commoncodec.ModifiersConfig{ + &commoncodec.PropertyExtractorConfig{FieldName: "U64Value"}, }, }, MethodReturningUint64Slice: { - AnchorIDL: string(it.Helper.GetJSONEncodedIDL(t)), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "DataAccount", - OutputModifications: codec.ModifiersConfig{ - &codec.PropertyExtractorConfig{FieldName: "U64slice"}, - }, + ChainSpecificName: "DataAccount", + OutputModifications: commoncodec.ModifiersConfig{ + &commoncodec.PropertyExtractorConfig{FieldName: "U64Slice"}, }, }, }, }, AnySecondContractName: { - Methods: map[string]config.ChainDataReader{ + IDL: mustUnmarshalIDL(t, string(it.Helper.GetJSONEncodedIDL(t))), + Reads: map[string]config.ReadDefinition{ MethodReturningUint64: { - AnchorIDL: string(it.Helper.GetJSONEncodedIDL(t)), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "DataAccount", - OutputModifications: codec.ModifiersConfig{ - &codec.PropertyExtractorConfig{FieldName: "U64value"}, - }, + ChainSpecificName: "DataAccount", + OutputModifications: commoncodec.ModifiersConfig{ + &commoncodec.PropertyExtractorConfig{FieldName: "U64Value"}, }, }, }, }, }, } - - it.accountPubKey = it.Helper.CreateAccount(t, AnyValueToReadWithoutAnArgument) - it.secondAccountPubKey = it.Helper.CreateAccount(t, AnyDifferentValueToReadWithoutAnArgument) } func (it *SolanaChainComponentsInterfaceTester[T]) Name() string { @@ -187,7 +193,7 @@ func (it *SolanaChainComponentsInterfaceTester[T]) GetContractReader(t T) types. return it.cr } - svc, err := chainreader.NewChainReaderService(it.Helper.Logger(t), it.Helper.RPCClient(), it.chainReaderConfig) + svc, err := chainreader.NewChainReaderService(it.Helper.Logger(t), it.Helper.RPCClient(), it.contractReaderConfig) require.NoError(t, err) require.NoError(t, svc.Start(ctx)) @@ -202,10 +208,10 @@ func (it *SolanaChainComponentsInterfaceTester[T]) GetContractWriter(t T) types. } func (it *SolanaChainComponentsInterfaceTester[T]) GetBindings(t T) []types.BoundContract { - // at the moment, use only a single account address for everything + // Create a new account with fresh state for each test return []types.BoundContract{ - {Name: AnyContractName, Address: it.accountPubKey.String()}, - {Name: AnySecondContractName, Address: it.secondAccountPubKey.String()}, + {Name: AnyContractName, Address: it.Helper.CreateAccount(t, AnyValueToReadWithoutAnArgument).String()}, + {Name: AnySecondContractName, Address: it.Helper.CreateAccount(t, AnyDifferentValueToReadWithoutAnArgument).String()}, } } @@ -219,10 +225,6 @@ func (it *SolanaChainComponentsInterfaceTester[T]) GenerateBlocksTillConfidenceL } -func (it *SolanaChainComponentsInterfaceTester[T]) Init(t T) { - it.Helper.Init(t) -} - type helper struct { programID solana.PublicKey rpcURL string @@ -231,6 +233,7 @@ type helper struct { wsClient *ws.Client idlBts []byte nonce uint64 + nonceMu sync.Mutex } func (h *helper) Init(t *testing.T) { @@ -239,13 +242,13 @@ func (h *helper) Init(t *testing.T) { privateKey, err := solana.PrivateKeyFromBase58(solclient.DefaultPrivateKeysSolValidator[1]) require.NoError(t, err) - h.rpcURL, h.wsURL = setupTestValidator(t, privateKey.PublicKey().String()) + h.rpcURL, h.wsURL = solanautils.SetupTestValidatorWithAnchorPrograms(t, privateKey.PublicKey().String(), []string{"contract-reader-interface"}) h.wsClient, err = ws.Connect(tests.Context(t), h.wsURL) h.rpcClient = rpc.New(h.rpcURL) require.NoError(t, err) - client.FundTestAccounts(t, []solana.PublicKey{privateKey.PublicKey()}, h.rpcURL) + solanautils.FundAccounts(t, []solana.PrivateKey{privateKey}, h.rpcClient) pubkey, err := solana.PublicKeyFromBase58(programPubKey) require.NoError(t, err) @@ -292,10 +295,14 @@ func (h *helper) GetJSONEncodedIDL(t *testing.T) []byte { func (h *helper) CreateAccount(t *testing.T, value uint64) solana.PublicKey { t.Helper() + // avoid collisions in parallel tests + h.nonceMu.Lock() h.nonce++ + nonce := h.nonce + h.nonceMu.Unlock() bts := make([]byte, 8) - binary.LittleEndian.PutUint64(bts, h.nonce*value) + binary.LittleEndian.PutUint64(bts, nonce*value) pubKey, _, err := solana.FindProgramAddress([][]byte{[]byte("data"), bts}, h.programID) require.NoError(t, err) @@ -304,7 +311,7 @@ func (h *helper) CreateAccount(t *testing.T, value uint64) solana.PublicKey { privateKey, err := solana.PrivateKeyFromBase58(solclient.DefaultPrivateKeysSolValidator[1]) require.NoError(t, err) - h.runInitialize(t, value, pubKey, func(key solana.PublicKey) *solana.PrivateKey { + h.runInitialize(t, nonce, value, pubKey, func(key solana.PublicKey) *solana.PrivateKey { return &privateKey }, privateKey.PublicKey()) @@ -313,6 +320,7 @@ func (h *helper) CreateAccount(t *testing.T, value uint64) solana.PublicKey { func (h *helper) runInitialize( t *testing.T, + nonce uint64, value uint64, data solana.PublicKey, signerFunc func(key solana.PublicKey) *solana.PrivateKey, @@ -320,7 +328,7 @@ func (h *helper) runInitialize( ) { t.Helper() - inst, err := contract.NewInitializeInstruction(h.nonce*value, value, data, payer, solana.SystemProgramID).ValidateAndBuild() + inst, err := contract.NewInitializeInstruction(nonce*value, value, data, payer, solana.SystemProgramID).ValidateAndBuild() require.NoError(t, err) h.sendInstruction(t, inst, signerFunc, payer) @@ -385,27 +393,14 @@ func (h *helper) waitForTX(t *testing.T, sig solana.Signature, commitment rpc.Co } } -const programPubKey = "6AfuXF6HapDUhQfE4nQG9C1SGtA1YjP3icaJyRfU4RyE" - -// upgradeAuthority is admin solana.PrivateKey as string -func setupTestValidator(t *testing.T, upgradeAuthority string) (string, string) { - t.Helper() - - soPath := filepath.Join(utils.ContractsDir, "contract_reader_interface.so") - - _, err := os.Stat(soPath) - if err != nil { - t.Log(err.Error()) +func mustUnmarshalIDL[T TestingT[T]](t T, rawIDL string) codec.IDL { + var idl codec.IDL + if err := json.Unmarshal([]byte(rawIDL), &idl); err != nil { + t.Errorf("failed to unmarshal test IDL", err) t.FailNow() } - flags := []string{ - "--warp-slot", "42", - "--upgradeable-program", - programPubKey, - soPath, - upgradeAuthority, - } - - return client.SetupLocalSolNodeWithFlags(t, flags...) + return idl } + +const programPubKey = "6AfuXF6HapDUhQfE4nQG9C1SGtA1YjP3icaJyRfU4RyE" diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go new file mode 100644 index 000000000..fd148abff --- /dev/null +++ b/integration-tests/relayinterface/lookups_test.go @@ -0,0 +1,430 @@ +package relayinterface + +import ( + "reflect" + "testing" + "time" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/chainwriter" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" + keyMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" +) + +func TestAccountContant(t *testing.T) { + t.Run("AccountConstant resolves valid address", func(t *testing.T) { + expectedAddr := chainwriter.GetRandomPubKey(t) + expectedMeta := []*solana.AccountMeta{ + { + PublicKey: expectedAddr, + IsSigner: true, + IsWritable: true, + }, + } + constantConfig := chainwriter.AccountConstant{ + Name: "TestAccount", + Address: expectedAddr.String(), + IsSigner: true, + IsWritable: true, + } + result, err := constantConfig.Resolve(tests.Context(t), nil, nil, nil) + require.NoError(t, err) + require.Equal(t, expectedMeta, result) + }) +} +func TestAccountLookups(t *testing.T) { + ctx := tests.Context(t) + t.Run("AccountLookup resolves valid address with just one address", func(t *testing.T) { + expectedAddr := chainwriter.GetRandomPubKey(t) + testArgs := chainwriter.TestArgs{ + Inner: []chainwriter.InnerArgs{ + {Address: expectedAddr.Bytes()}, + }, + } + expectedMeta := []*solana.AccountMeta{ + { + PublicKey: expectedAddr, + IsSigner: true, + IsWritable: true, + }, + } + + lookupConfig := chainwriter.AccountLookup{ + Name: "TestAccount", + Location: "Inner.Address", + IsSigner: true, + IsWritable: true, + } + result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + require.NoError(t, err) + require.Equal(t, expectedMeta, result) + }) + + t.Run("AccountLookup resolves valid address with just multiple addresses", func(t *testing.T) { + expectedAddr1 := chainwriter.GetRandomPubKey(t) + expectedAddr2 := chainwriter.GetRandomPubKey(t) + + testArgs := chainwriter.TestArgs{ + Inner: []chainwriter.InnerArgs{ + {Address: expectedAddr1.Bytes()}, + {Address: expectedAddr2.Bytes()}, + }, + } + expectedMeta := []*solana.AccountMeta{ + { + PublicKey: expectedAddr1, + IsSigner: true, + IsWritable: true, + }, + { + PublicKey: expectedAddr2, + IsSigner: true, + IsWritable: true, + }, + } + + lookupConfig := chainwriter.AccountLookup{ + Name: "TestAccount", + Location: "Inner.Address", + IsSigner: true, + IsWritable: true, + } + result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + require.NoError(t, err) + for i, meta := range result { + require.Equal(t, expectedMeta[i], meta) + } + }) + + t.Run("AccountLookup fails when address isn't in args", func(t *testing.T) { + expectedAddr := chainwriter.GetRandomPubKey(t) + + testArgs := chainwriter.TestArgs{ + Inner: []chainwriter.InnerArgs{ + {Address: expectedAddr.Bytes()}, + }, + } + lookupConfig := chainwriter.AccountLookup{ + Name: "InvalidAccount", + Location: "Invalid.Directory", + IsSigner: true, + IsWritable: true, + } + _, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + require.Error(t, err) + }) +} + +func TestPDALookups(t *testing.T) { + programID := chainwriter.GetRandomPubKey(t) + + t.Run("PDALookup resolves valid PDA with constant address seeds", func(t *testing.T) { + seed := chainwriter.GetRandomPubKey(t) + + pda, _, err := solana.FindProgramAddress([][]byte{seed.Bytes()}, programID) + require.NoError(t, err) + + expectedMeta := []*solana.AccountMeta{ + { + PublicKey: pda, + IsSigner: false, + IsWritable: true, + }, + } + + pdaLookup := chainwriter.PDALookups{ + Name: "TestPDA", + PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountConstant{Name: "seed", Address: seed.String()}}, + }, + IsSigner: false, + IsWritable: true, + } + + ctx := tests.Context(t) + result, err := pdaLookup.Resolve(ctx, nil, nil, nil) + require.NoError(t, err) + require.Equal(t, expectedMeta, result) + }) + t.Run("PDALookup resolves valid PDA with non-address lookup seeds", func(t *testing.T) { + seed1 := []byte("test_seed") + seed2 := []byte("another_seed") + + pda, _, err := solana.FindProgramAddress([][]byte{seed1, seed2}, programID) + require.NoError(t, err) + + expectedMeta := []*solana.AccountMeta{ + { + PublicKey: pda, + IsSigner: false, + IsWritable: true, + }, + } + + pdaLookup := chainwriter.PDALookups{ + Name: "TestPDA", + PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, + }, + IsSigner: false, + IsWritable: true, + } + + ctx := tests.Context(t) + args := map[string]interface{}{ + "test_seed": seed1, + "another_seed": seed2, + } + + result, err := pdaLookup.Resolve(ctx, args, nil, nil) + require.NoError(t, err) + require.Equal(t, expectedMeta, result) + }) + + t.Run("PDALookup fails with missing seeds", func(t *testing.T) { + pdaLookup := chainwriter.PDALookups{ + Name: "TestPDA", + PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}}, + }, + IsSigner: false, + IsWritable: true, + } + + ctx := tests.Context(t) + args := map[string]interface{}{ + "test_seed": []byte("data"), + } + + _, err := pdaLookup.Resolve(ctx, args, nil, nil) + require.Error(t, err) + require.Contains(t, err.Error(), "key not found") + }) + + t.Run("PDALookup resolves valid PDA with address lookup seeds", func(t *testing.T) { + seed1 := chainwriter.GetRandomPubKey(t) + seed2 := chainwriter.GetRandomPubKey(t) + + pda, _, err := solana.FindProgramAddress([][]byte{seed1.Bytes(), seed2.Bytes()}, programID) + require.NoError(t, err) + + expectedMeta := []*solana.AccountMeta{ + { + PublicKey: pda, + IsSigner: false, + IsWritable: true, + }, + } + + pdaLookup := chainwriter.PDALookups{ + Name: "TestPDA", + PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, + }, + IsSigner: false, + IsWritable: true, + } + + ctx := tests.Context(t) + args := map[string]interface{}{ + "test_seed": seed1, + "another_seed": seed2, + } + + result, err := pdaLookup.Resolve(ctx, args, nil, nil) + require.NoError(t, err) + require.Equal(t, expectedMeta, result) + }) +} + +func TestLookupTables(t *testing.T) { + ctx := tests.Context(t) + + sender, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + + url, _ := utils.SetupTestValidatorWithAnchorPrograms(t, sender.PublicKey().String(), []string{"contract-reader-interface"}) + rpcClient := rpc.New(url) + + utils.FundAccounts(t, []solana.PrivateKey{sender}, rpcClient) + + cfg := config.NewDefault() + solanaClient, err := client.NewClient(url, cfg, 5*time.Second, nil) + require.NoError(t, err) + + loader := commonutils.NewLazyLoad(func() (client.ReaderWriter, error) { return solanaClient, nil }) + mkey := keyMocks.NewSimpleKeystore(t) + lggr := logger.Test(t) + + txm := txm.NewTxm("localnet", loader, nil, cfg, mkey, lggr) + + cw, err := chainwriter.NewSolanaChainWriterService(nil, solanaClient, txm, nil, chainwriter.ChainWriterConfig{}) + + t.Run("StaticLookup table resolves properly", func(t *testing.T) { + pubKeys := chainwriter.CreateTestPubKeys(t, 8) + table := chainwriter.CreateTestLookupTable(ctx, t, rpcClient, sender, pubKeys) + lookupConfig := chainwriter.LookupTables{ + DerivedLookupTables: nil, + StaticLookupTables: []solana.PublicKey{table}, + } + _, staticTableMap, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig) + require.NoError(t, resolveErr) + require.Equal(t, pubKeys, staticTableMap[table]) + }) + t.Run("Derived lookup table resolves properly with constant address", func(t *testing.T) { + pubKeys := chainwriter.CreateTestPubKeys(t, 8) + table := chainwriter.CreateTestLookupTable(ctx, t, rpcClient, sender, pubKeys) + lookupConfig := chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.AccountConstant{ + Name: "TestLookupTable", + Address: table.String(), + IsSigner: true, + IsWritable: true, + }, + }, + }, + StaticLookupTables: nil, + } + derivedTableMap, _, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig) + require.NoError(t, resolveErr) + + addresses, ok := derivedTableMap["DerivedTable"][table.String()] + require.True(t, ok) + for i, address := range addresses { + require.Equal(t, pubKeys[i], address.PublicKey) + } + }) + + t.Run("Derived lookup table fails with invalid address", func(t *testing.T) { + invalidTable := chainwriter.GetRandomPubKey(t) + + lookupConfig := chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.AccountConstant{ + Name: "InvalidTable", + Address: invalidTable.String(), + IsSigner: true, + IsWritable: true, + }, + }, + }, + StaticLookupTables: nil, + } + + _, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig) + require.Error(t, err) + require.Contains(t, err.Error(), "error fetching account info for table") // Example error message + }) + + t.Run("Static lookup table fails with invalid address", func(t *testing.T) { + invalidTable := chainwriter.GetRandomPubKey(t) + + lookupConfig := chainwriter.LookupTables{ + DerivedLookupTables: nil, + StaticLookupTables: []solana.PublicKey{invalidTable}, + } + + _, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig) + require.Error(t, err) + require.Contains(t, err.Error(), "error fetching account info for table") // Example error message + }) + + t.Run("Derived lookup table resolves properly with account lookup address", func(t *testing.T) { + pubKeys := chainwriter.CreateTestPubKeys(t, 8) + table := chainwriter.CreateTestLookupTable(ctx, t, rpcClient, sender, pubKeys) + lookupConfig := chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.AccountLookup{ + Name: "TestLookupTable", + Location: "Inner.Address", + IsSigner: true, + }, + }, + }, + StaticLookupTables: nil, + } + + testArgs := chainwriter.TestArgs{ + Inner: []chainwriter.InnerArgs{ + {Address: table.Bytes()}, + }, + } + + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, testArgs, lookupConfig) + require.NoError(t, err) + + addresses, ok := derivedTableMap["DerivedTable"][table.String()] + require.True(t, ok) + for i, address := range addresses { + require.Equal(t, pubKeys[i], address.PublicKey) + } + }) + + t.Run("Derived lookup table resolves properly with PDALookup address", func(t *testing.T) { + // Deployed contract_reader_interface contract + programID := solana.MustPublicKeyFromBase58("6AfuXF6HapDUhQfE4nQG9C1SGtA1YjP3icaJyRfU4RyE") + + lookupKeys := chainwriter.CreateTestPubKeys(t, 5) + lookupTable := chainwriter.CreateTestLookupTable(ctx, t, rpcClient, sender, lookupKeys) + + chainwriter.InitializeDataAccount(ctx, t, rpcClient, programID, sender, lookupTable) + + args := map[string]interface{}{ + "seed1": []byte("data"), + } + + lookupConfig := chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.PDALookups{ + Name: "DataAccountPDA", + PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, + }, + IsSigner: false, + IsWritable: false, + InternalField: chainwriter.InternalField{ + Type: reflect.TypeOf(chainwriter.DataAccount{}), + Location: "LookupTable", + }, + }, + }, + }, + StaticLookupTables: nil, + } + + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupConfig) + require.NoError(t, err) + + addresses, ok := derivedTableMap["DerivedTable"][lookupTable.String()] + require.True(t, ok) + for i, address := range addresses { + require.Equal(t, lookupKeys[i], address.PublicKey) + } + }) +} diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index 9b2645fcd..330222fdc 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -35,7 +35,6 @@ func TestSolanaOCRV2Smoke(t *testing.T) { t.Fatal(err) } - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() _, sg := startOCR2DataFeedsSmokeTest(t, test.name, test.env, config, "") diff --git a/integration-tests/soak/ocr2_test.go b/integration-tests/soak/ocr2_test.go index 1c53727af..b13eea188 100644 --- a/integration-tests/soak/ocr2_test.go +++ b/integration-tests/soak/ocr2_test.go @@ -35,7 +35,6 @@ func TestSolanaOCRV2Soak(t *testing.T) { t.Fatal(err) } - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/integration-tests/solclient/deployer.go b/integration-tests/solclient/deployer.go index f68650952..1d41600a6 100644 --- a/integration-tests/solclient/deployer.go +++ b/integration-tests/solclient/deployer.go @@ -503,7 +503,6 @@ func (c *ContractDeployer) DeployAnchorProgramsRemote(contractsDir string, env * log.Debug().Interface("Binaries", contractBinaries).Msg("Program binaries") g := errgroup.Group{} for _, bin := range contractBinaries { - bin := bin g.Go(func() error { return c.DeployProgramRemote(bin, env) }) @@ -519,7 +518,6 @@ func (c *ContractDeployer) DeployAnchorProgramsRemoteDocker(baseDir, subDir stri log.Info().Interface("Binaries", contractBinaries).Msg(fmt.Sprintf("Program binaries [%s]", filepath.Join("programs", subDir))) g := errgroup.Group{} for _, bin := range contractBinaries { - bin := bin g.Go(func() error { return c.DeployProgramRemoteLocal(filepath.Join(subDir, bin), sol, programIDBuilder) }) diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index 191bda38f..25e65019b 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -38,7 +38,7 @@ inside_k8 = false network = "localnet" user = "default" stateful_db = false -devnet_image = "anzaxyz/agave:v2.0.20" +devnet_image = "anzaxyz/agave:v2.0.21" [OCR2] node_count = 6 diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index 5ed5eb8cb..0ccdf0e46 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -30,6 +30,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/internal" "github.com/smartcontractkit/chainlink-solana/pkg/solana/monitor" "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) type Chain interface { @@ -576,12 +577,12 @@ func (c *chain) sendTx(ctx context.Context, from, to string, amount *big.Int, ba chainTxm := c.TxManager() err = chainTxm.Enqueue(ctx, "", tx, nil, blockhash.Value.LastValidBlockHeight, - txm.SetComputeUnitLimit(500), // reduce from default 200K limit - should only take 450 compute units + txmutils.SetComputeUnitLimit(500), // reduce from default 200K limit - should only take 450 compute units // no fee bumping and no additional fee - makes validating balance accurate - txm.SetComputeUnitPriceMax(0), - txm.SetComputeUnitPriceMin(0), - txm.SetBaseComputeUnitPrice(0), - txm.SetFeeBumpPeriod(0), + txmutils.SetComputeUnitPriceMax(0), + txmutils.SetComputeUnitPriceMin(0), + txmutils.SetBaseComputeUnitPrice(0), + txmutils.SetFeeBumpPeriod(0), ) if err != nil { return fmt.Errorf("transaction failed: %w", err) diff --git a/pkg/solana/chainreader/account_read_binding.go b/pkg/solana/chainreader/account_read_binding.go index 71ebb131b..eacd45fad 100644 --- a/pkg/solana/chainreader/account_read_binding.go +++ b/pkg/solana/chainreader/account_read_binding.go @@ -4,30 +4,32 @@ import ( "context" "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" ) -// accountReadBinding provides decoding and reading Solana Account data using a defined codec. The -// `idlAccount` refers to the account name in the IDL for which the codec has a type mapping. +// accountReadBinding provides decoding and reading Solana Account data using a defined codec. type accountReadBinding struct { - idlAccount string - codec types.RemoteCodec - key solana.PublicKey - opts *rpc.GetAccountInfoOpts + namespace, genericName string + codec types.RemoteCodec + key solana.PublicKey } -func newAccountReadBinding(acct string, codec types.RemoteCodec, opts *rpc.GetAccountInfoOpts) *accountReadBinding { +func newAccountReadBinding(namespace, genericName string) *accountReadBinding { return &accountReadBinding{ - idlAccount: acct, - codec: codec, - opts: opts, + namespace: namespace, + genericName: genericName, } } var _ readBinding = &accountReadBinding{} +func (b *accountReadBinding) SetCodec(codec types.RemoteCodec) { + b.codec = codec +} + func (b *accountReadBinding) SetAddress(key solana.PublicKey) { b.key = key } @@ -36,10 +38,10 @@ func (b *accountReadBinding) GetAddress() solana.PublicKey { return b.key } -func (b *accountReadBinding) CreateType(_ bool) (any, error) { - return b.codec.CreateType(b.idlAccount, false) +func (b *accountReadBinding) CreateType(forEncoding bool) (any, error) { + return b.codec.CreateType(codec.WrapItemType(forEncoding, b.namespace, b.genericName, codec.ChainConfigTypeAccountDef), forEncoding) } func (b *accountReadBinding) Decode(ctx context.Context, bts []byte, outVal any) error { - return b.codec.Decode(ctx, bts, outVal, b.idlAccount) + return b.codec.Decode(ctx, bts, outVal, codec.WrapItemType(false, b.namespace, b.genericName, codec.ChainConfigTypeAccountDef)) } diff --git a/pkg/solana/chainreader/batch.go b/pkg/solana/chainreader/batch.go index 43e4971b9..d5990601d 100644 --- a/pkg/solana/chainreader/batch.go +++ b/pkg/solana/chainreader/batch.go @@ -77,7 +77,6 @@ func doMethodBatchCall(ctx context.Context, client MultipleAccountGetter, bindin results[idx].err, binding.Decode(ctx, data[idx], results[idx].returnVal), ) - continue } diff --git a/pkg/solana/chainreader/bindings.go b/pkg/solana/chainreader/bindings.go index 51cc8980a..751a58fdd 100644 --- a/pkg/solana/chainreader/bindings.go +++ b/pkg/solana/chainreader/bindings.go @@ -12,6 +12,7 @@ import ( type readBinding interface { SetAddress(solana.PublicKey) GetAddress() solana.PublicKey + SetCodec(types.RemoteCodec) CreateType(bool) (any, error) Decode(context.Context, []byte, any) error } @@ -70,3 +71,11 @@ func (b namespaceBindings) Bind(binding types.BoundContract) error { return nil } + +func (b namespaceBindings) SetCodec(codec types.RemoteCodec) { + for _, nbs := range b { + for _, rb := range nbs { + rb.SetCodec(codec) + } + } +} diff --git a/pkg/solana/chainreader/bindings_test.go b/pkg/solana/chainreader/bindings_test.go index d8b510648..e8dbea89a 100644 --- a/pkg/solana/chainreader/bindings_test.go +++ b/pkg/solana/chainreader/bindings_test.go @@ -46,6 +46,8 @@ type mockBinding struct { mock.Mock } +func (_m *mockBinding) SetCodec(_ types.RemoteCodec) {} + func (_m *mockBinding) SetAddress(_ solana.PublicKey) {} func (_m *mockBinding) GetAddress() solana.PublicKey { diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go index d017eb25d..1edcb9b8e 100644 --- a/pkg/solana/chainreader/chain_reader.go +++ b/pkg/solana/chainreader/chain_reader.go @@ -2,7 +2,6 @@ package chainreader import ( "context" - "encoding/json" "errors" "fmt" "sync" @@ -10,7 +9,7 @@ import ( "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" - codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -33,6 +32,8 @@ type SolanaChainReaderService struct { // internal values bindings namespaceBindings lookup *lookup + parsed *codec.ParsedTypes + codec types.RemoteCodec // service state management wg sync.WaitGroup @@ -45,18 +46,27 @@ var ( ) // NewChainReaderService is a constructor for a new ChainReaderService for Solana. Returns a nil service on error. -func NewChainReaderService(lggr logger.Logger, dataReader MultipleAccountGetter, cfg config.ChainReader) (*SolanaChainReaderService, error) { +func NewChainReaderService(lggr logger.Logger, dataReader MultipleAccountGetter, cfg config.ContractReader) (*SolanaChainReaderService, error) { svc := &SolanaChainReaderService{ lggr: logger.Named(lggr, ServiceName), client: dataReader, bindings: namespaceBindings{}, lookup: newLookup(), + parsed: &codec.ParsedTypes{EncoderDefs: map[string]codec.Entry{}, DecoderDefs: map[string]codec.Entry{}}, } if err := svc.init(cfg.Namespaces); err != nil { return nil, err } + svcCodec, err := svc.parsed.ToCodec() + if err != nil { + return nil, err + } + + svc.codec = svcCodec + + svc.bindings.SetCodec(svcCodec) return svc, nil } @@ -106,15 +116,15 @@ func (s *SolanaChainReaderService) GetLatestValue(ctx context.Context, readIdent s.wg.Add(1) defer s.wg.Done() - vals, ok := s.lookup.getContractForReadIdentifiers(readIdentifier) + values, ok := s.lookup.getContractForReadIdentifiers(readIdentifier) if !ok { return fmt.Errorf("%w: no contract for read identifier %s", types.ErrInvalidType, readIdentifier) } batch := []call{ { - ContractName: vals.contract, - ReadName: vals.readName, + ContractName: values.contract, + ReadName: values.genericName, Params: params, ReturnVal: returnVal, }, @@ -217,78 +227,102 @@ func (s *SolanaChainReaderService) CreateContractType(readIdentifier string, for return nil, fmt.Errorf("%w: no contract for read identifier", types.ErrInvalidConfig) } - return s.bindings.CreateType(values.contract, values.readName, forEncoding) + return s.bindings.CreateType(values.contract, values.genericName, forEncoding) } -func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReaderMethods) error { - for namespace, methods := range namespaces { - for methodName, method := range methods.Methods { - var idl codec.IDL - if err := json.Unmarshal([]byte(method.AnchorIDL), &idl); err != nil { - return err - } - - idlCodec, err := codec.NewIDLAccountCodec(idl, config.BuilderForEncoding(method.Encoding)) - if err != nil { - return err - } - - s.lookup.addReadNameForContract(namespace, methodName) +func (s *SolanaChainReaderService) addCodecDef(forEncoding bool, namespace, genericName string, readType codec.ChainConfigType, idl codec.IDL, idlDefinition interface{}, modCfg commoncodec.ModifiersConfig) error { + mod, err := modCfg.ToModifier(codec.DecoderHooks...) + if err != nil { + return err + } - procedure := method.Procedure + cEntry, err := codec.CreateCodecEntry(idlDefinition, genericName, idl, mod) + if err != nil { + return err + } - injectAddressModifier(procedure.OutputModifications) + if forEncoding { + s.parsed.EncoderDefs[codec.WrapItemType(forEncoding, namespace, genericName, readType)] = cEntry + } else { + s.parsed.DecoderDefs[codec.WrapItemType(forEncoding, namespace, genericName, readType)] = cEntry + } + return nil +} - mod, err := procedure.OutputModifications.ToModifier(codec.DecoderHooks...) +func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainContractReader) error { + for namespace, nameSpaceDef := range namespaces { + for genericName, read := range nameSpaceDef.Reads { + injectAddressModifier(read.InputModifications, read.OutputModifications) + idlDef, err := codec.FindDefinitionFromIDL(codec.ChainConfigTypeAccountDef, read.ChainSpecificName, nameSpaceDef.IDL) if err != nil { return err } - codecWithModifiers, err := codec.NewNamedModifierCodec(idlCodec, procedure.IDLAccount, mod) - if err != nil { - return err + switch read.ReadType { + case config.Account: + accountIDLDef, isOk := idlDef.(codec.IdlTypeDef) + if !isOk { + return fmt.Errorf("unexpected type %T from IDL definition for account read: %q, with chainSpecificName: %q, of type: %q", accountIDLDef, genericName, read.ChainSpecificName, read.ReadType) + } + if err = s.addAccountRead(namespace, genericName, nameSpaceDef.IDL, accountIDLDef, read); err != nil { + return err + } + case config.Event: + eventIDlDef, isOk := idlDef.(codec.IdlEvent) + if !isOk { + return fmt.Errorf("unexpected type %T from IDL definition for log read: %q, with chainSpecificName: %q, of type: %q", eventIDlDef, genericName, read.ChainSpecificName, read.ReadType) + } + // TODO s.addLogRead() + return fmt.Errorf("implement me") + default: + return fmt.Errorf("unexpected read type %q for: %q in namespace: %q", read.ReadType, genericName, namespace) } - - s.bindings.AddReadBinding(namespace, methodName, newAccountReadBinding( - procedure.IDLAccount, - codecWithModifiers, - createRPCOpts(procedure.RPCOpts), - )) } } return nil } -// injectAddressModifier injects AddressModifier into OutputModifications. -// This is necessary because AddressModifier cannot be serialized and must be applied at runtime. -func injectAddressModifier(outputModifications codeccommon.ModifiersConfig) { - for i, modConfig := range outputModifications { - if addrModifierConfig, ok := modConfig.(*codeccommon.AddressBytesToStringModifierConfig); ok { - addrModifierConfig.Modifier = codec.SolanaAddressModifier{} - outputModifications[i] = addrModifierConfig - } +func (s *SolanaChainReaderService) addAccountRead(namespace string, genericName string, idl codec.IDL, idlType codec.IdlTypeDef, readDefinition config.ReadDefinition) error { + inputAccountIDLDef := codec.NilIdlTypeDefTy + // TODO: + // if hasPDA{ + // inputAccountIDLDef = pdaType + // } + if err := s.addCodecDef(true, namespace, genericName, codec.ChainConfigTypeAccountDef, idl, inputAccountIDLDef, readDefinition.InputModifications); err != nil { + return err } -} -func createRPCOpts(opts *config.RPCOpts) *rpc.GetAccountInfoOpts { - if opts == nil { - return nil + if err := s.addCodecDef(false, namespace, genericName, codec.ChainConfigTypeAccountDef, idl, idlType, readDefinition.OutputModifications); err != nil { + return err } - result := &rpc.GetAccountInfoOpts{ - DataSlice: opts.DataSlice, - } + s.lookup.addReadNameForContract(namespace, genericName) - if opts.Encoding != nil { - result.Encoding = *opts.Encoding - } + s.bindings.AddReadBinding(namespace, genericName, newAccountReadBinding( + namespace, + genericName, + )) - if opts.Commitment != nil { - result.Commitment = *opts.Commitment + return nil +} + +// injectAddressModifier injects AddressModifier into OutputModifications. +// This is necessary because AddressModifier cannot be serialized and must be applied at runtime. +func injectAddressModifier(inputModifications, outputModifications commoncodec.ModifiersConfig) { + for i, modConfig := range inputModifications { + if addrModifierConfig, ok := modConfig.(*commoncodec.AddressBytesToStringModifierConfig); ok { + addrModifierConfig.Modifier = codec.SolanaAddressModifier{} + outputModifications[i] = addrModifierConfig + } } - return result + for i, modConfig := range outputModifications { + if addrModifierConfig, ok := modConfig.(*commoncodec.AddressBytesToStringModifierConfig); ok { + addrModifierConfig.Modifier = codec.SolanaAddressModifier{} + outputModifications[i] = addrModifierConfig + } + } } type accountDataReader struct { diff --git a/pkg/solana/chainreader/chain_reader_test.go b/pkg/solana/chainreader/chain_reader_test.go index 4652f5199..de37567b6 100644 --- a/pkg/solana/chainreader/chain_reader_test.go +++ b/pkg/solana/chainreader/chain_reader_test.go @@ -14,7 +14,6 @@ import ( "github.com/gagliardetto/solana-go" ag_solana "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -54,17 +53,17 @@ func TestSolanaChainReaderService_ReaderInterface(t *testing.T) { ContractReaderQueryKeysCanLimitResultsWithCursor, }) - RunContractReaderInterfaceTests(t, it, true) + RunContractReaderInterfaceTests(t, it, true, false) lsIt := &skipEventsChainReaderTester{ChainComponentsInterfaceTester: commontestutils.WrapContractReaderTesterForLoop(it)} - RunContractReaderInterfaceTests(t, lsIt, true) + RunContractReaderInterfaceTests(t, lsIt, true, false) } func TestSolanaChainReaderService_ServiceCtx(t *testing.T) { t.Parallel() ctx := tests.Context(t) - svc, err := chainreader.NewChainReaderService(logger.Test(t), new(mockedRPCClient), config.ChainReader{}) + svc, err := chainreader.NewChainReaderService(logger.Test(t), new(mockedRPCClient), config.ContractReader{}) require.NoError(t, err) require.NotNil(t, svc) @@ -289,21 +288,19 @@ func newTestIDLAndCodec(t *testing.T) (string, codec.IDL, types.RemoteCodec) { return testutils.JSONIDLWithAllTypes, idl, entry } -func newTestConfAndCodec(t *testing.T) (types.RemoteCodec, config.ChainReader) { +func newTestConfAndCodec(t *testing.T) (types.RemoteCodec, config.ContractReader) { t.Helper() - rawIDL, _, testCodec := newTestIDLAndCodec(t) - conf := config.ChainReader{ - Namespaces: map[string]config.ChainReaderMethods{ + conf := config.ContractReader{ + Namespaces: map[string]config.ChainContractReader{ Namespace: { - Methods: map[string]config.ChainDataReader{ + IDL: mustUnmarshalIDL(t, rawIDL), + Reads: map[string]config.ReadDefinition{ NamedMethod: { - AnchorIDL: rawIDL, - Procedure: config.ChainReaderProcedure{ - IDLAccount: testutils.TestStructWithNestedStruct, - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.RenameModifierConfig{Fields: map[string]string{"Value": "V"}}, - }, + ChainSpecificName: testutils.TestStructWithNestedStruct, + ReadType: config.Account, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.RenameModifierConfig{Fields: map[string]string{"Value": "V"}}, }, }, }, @@ -385,7 +382,7 @@ func (_m *mockedRPCClient) SetForAddress(pk ag_solana.PublicKey, bts []byte, err type chainReaderInterfaceTester struct { TestSelectionSupport - conf config.ChainReader + conf config.ContractReader address []string reader *wrappedTestChainReader } @@ -415,75 +412,49 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) { r.address[idx] = ag_solana.NewWallet().PublicKey().String() } - encodingBase64 := solana.EncodingBase64 - commitment := rpc.CommitmentConfirmed - offset := uint64(1) - length := uint64(1) - - r.conf = config.ChainReader{ - Namespaces: map[string]config.ChainReaderMethods{ + r.conf = config.ContractReader{ + Namespaces: map[string]config.ChainContractReader{ AnyContractName: { - Methods: map[string]config.ChainDataReader{ + IDL: mustUnmarshalIDL(t, fullTestIDL(t)), + Reads: map[string]config.ReadDefinition{ MethodTakingLatestParamsReturningTestStruct: { - AnchorIDL: fullStructIDL(t), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "TestStruct", - RPCOpts: &config.RPCOpts{ - Encoding: &encodingBase64, - Commitment: &commitment, - DataSlice: &rpc.DataSlice{ - Offset: &offset, - Length: &length, - }, - }, - }, + ReadType: config.Account, + ChainSpecificName: "TestStruct", }, MethodReturningUint64: { - AnchorIDL: fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "SimpleUint64Value", - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.PropertyExtractorConfig{FieldName: "I"}, - }, + ReadType: config.Account, + ChainSpecificName: "SimpleUint64Value", + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.PropertyExtractorConfig{FieldName: "I"}, }, }, MethodReturningUint64Slice: { - AnchorIDL: fmt.Sprintf(baseIDL, uint64SliceBaseTypeIDL, ""), - Encoding: config.EncodingTypeBincode, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "Uint64Slice", - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.PropertyExtractorConfig{FieldName: "Vals"}, - }, + ChainSpecificName: "Uint64Slice", + ReadType: config.Account, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.PropertyExtractorConfig{FieldName: "Vals"}, }, }, MethodReturningSeenStruct: { - AnchorIDL: fullStructIDL(t), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "TestStruct", - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.AddressBytesToStringModifierConfig{ - Fields: []string{"Accountstruct.Accountstr"}, - }, - &codeccommon.HardCodeModifierConfig{OffChainValues: map[string]any{"ExtraField": AnyExtraValue}}, + ChainSpecificName: "TestStruct", + ReadType: config.Account, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.AddressBytesToStringModifierConfig{ + Fields: []string{"AccountStruct.AccountStr"}, }, + &codeccommon.HardCodeModifierConfig{OffChainValues: map[string]any{"ExtraField": AnyExtraValue}}, }, }, }, }, AnySecondContractName: { - Methods: map[string]config.ChainDataReader{ + IDL: mustUnmarshalIDL(t, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, "")), + Reads: map[string]config.ReadDefinition{ MethodReturningUint64: { - AnchorIDL: fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""), - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: "SimpleUint64Value", - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.PropertyExtractorConfig{FieldName: "I"}, - }, + ChainSpecificName: "SimpleUint64Value", + ReadType: config.Account, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.PropertyExtractorConfig{FieldName: "I"}, }, }, }, @@ -574,7 +545,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif case AnyContractName + EventName: r.test.Skip("Events are not yet supported in Solana") case AnyContractName + MethodReturningUint64: - cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""), config.EncodingTypeBorsh) + cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, "")) onChainStruct := struct { I uint64 }{ @@ -587,7 +558,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif r.test.FailNow() } case AnyContractName + MethodReturningUint64Slice: - cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64SliceBaseTypeIDL, ""), config.EncodingTypeBincode) + cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64SliceBaseTypeIDL, "")) onChainStruct := struct { Vals []uint64 }{ @@ -599,7 +570,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif r.test.FailNow() } case AnySecondContractName + MethodReturningUint64, AnyContractName: - cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""), config.EncodingTypeBorsh) + cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, "")) onChainStruct := struct { I uint64 }{ @@ -624,7 +595,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, readIdentif r.testStructQueue = r.testStructQueue[1:len(r.testStructQueue)] // split into two encoded parts to test the preloading function - cdc := makeTestCodec(r.test, fullStructIDL(r.test), config.EncodingTypeBorsh) + cdc := makeTestCodec(r.test, fullStructIDL(r.test)) if strings.Contains(r.test.Name(), "wraps_config_with_modifiers_using_its_own_mapstructure_overrides") { // TODO: This is a temporary solution. We are manually retyping this struct to avoid breaking unrelated tests. @@ -760,16 +731,10 @@ func (r *chainReaderInterfaceTester) MaxWaitTimeForEvents() time.Duration { return maxWaitTime } -func makeTestCodec(t *testing.T, rawIDL string, encoding config.EncodingType) types.RemoteCodec { +func makeTestCodec(t *testing.T, rawIDL string) types.RemoteCodec { t.Helper() - var idl codec.IDL - if err := json.Unmarshal([]byte(rawIDL), &idl); err != nil { - t.Logf("failed to unmarshal test IDL: %s", err.Error()) - t.FailNow() - } - - testCodec, err := codec.NewIDLAccountCodec(idl, config.BuilderForEncoding(encoding)) + testCodec, err := codec.NewIDLAccountCodec(mustUnmarshalIDL(t, rawIDL), binary.LittleEndian()) if err != nil { t.Logf("failed to create new codec from test IDL: %s", err.Error()) t.FailNow() @@ -788,6 +753,23 @@ func fullStructIDL(t *testing.T) string { ) } +func fullTestIDL(t *testing.T) string { + t.Helper() + + // Combine all of the type definitions into one comma-separated string. + allTypes := strings.Join([]string{ + testStructIDL, + uint64BaseTypeIDL, + uint64SliceBaseTypeIDL, + }, ",") + + return fmt.Sprintf( + baseIDL, + allTypes, + strings.Join([]string{midLevelDynamicStructIDL, midLevelStaticStructIDL, innerDynamicStructIDL, innerStaticStructIDL, accountStructIDL}, ","), + ) +} + const ( baseIDL = `{ "version": "0.1.0", @@ -808,7 +790,7 @@ const ( {"name": "nestedStaticStruct","type": {"defined": "MidLevelStaticStruct"}}, {"name": "oracleID","type": "u8"}, {"name": "oracleIDs","type": {"array": ["u8",32]}}, - {"name": "accountstruct","type": {"defined": "accountstruct"}}, + {"name": "accountStruct","type": {"defined": "accountStruct"}}, {"name": "accounts","type": {"vec": "bytes"}} ] @@ -816,12 +798,12 @@ const ( }` accountStructIDL = `{ - "name": "accountstruct", + "name": "accountStruct", "type": { "kind": "struct", "fields": [ {"name": "account", "type": "bytes"}, - {"name": "accountstr", "type": {"array": ["u8",32]}} + {"name": "accountStr", "type": {"array": ["u8",32]}} ] } }` @@ -933,3 +915,13 @@ func (s *skipEventsChainReader) QueryKey(_ context.Context, _ types.BoundContrac s.t.Skip("QueryKey is not yet supported in Solana") return nil, nil } + +func mustUnmarshalIDL(t *testing.T, rawIDL string) codec.IDL { + var idl codec.IDL + if err := json.Unmarshal([]byte(rawIDL), &idl); err != nil { + t.Logf("failed to unmarshal test IDL: %s", err.Error()) + t.FailNow() + } + + return idl +} diff --git a/pkg/solana/chainreader/lookup.go b/pkg/solana/chainreader/lookup.go index b4295d20f..ccd7b44db 100644 --- a/pkg/solana/chainreader/lookup.go +++ b/pkg/solana/chainreader/lookup.go @@ -7,19 +7,19 @@ import ( ) type readValues struct { - address string - contract string - readName string + address string + contract string + genericName string } // lookup provides basic utilities for mapping a complete readIdentifier to // finite contract read information type lookup struct { mu sync.RWMutex - // contractReadNames maps a contract name to all available readNames (method, log, event, etc.) + // contractReadNames maps a contract name to all available namePairs (method, log, event, etc.) contractReadNames map[string][]string // readIdentifiers maps from a complete readIdentifier string to finite read data - // a readIdentifier is a combination of address, contract, and readName as a concatenated string + // a readIdentifier is a combination of address, contract, and chainSpecificName as a concatenated string readIdentifiers map[string]readValues } @@ -30,7 +30,7 @@ func newLookup() *lookup { } } -func (l *lookup) addReadNameForContract(contract, readName string) { +func (l *lookup) addReadNameForContract(contract string, genericName string) { l.mu.Lock() defer l.mu.Unlock() @@ -39,23 +39,23 @@ func (l *lookup) addReadNameForContract(contract, readName string) { readNames = []string{} } - l.contractReadNames[contract] = append(readNames, readName) + l.contractReadNames[contract] = append(readNames, genericName) } func (l *lookup) bindAddressForContract(contract, address string) { l.mu.Lock() defer l.mu.Unlock() - for _, readName := range l.contractReadNames[contract] { + for _, genericName := range l.contractReadNames[contract] { readIdentifier := types.BoundContract{ Address: address, Name: contract, - }.ReadIdentifier(readName) + }.ReadIdentifier(genericName) l.readIdentifiers[readIdentifier] = readValues{ - address: address, - contract: contract, - readName: readName, + address: address, + contract: contract, + genericName: genericName, } } } @@ -64,11 +64,11 @@ func (l *lookup) unbindAddressForContract(contract, address string) { l.mu.Lock() defer l.mu.Unlock() - for _, readName := range l.contractReadNames[contract] { + for _, genericName := range l.contractReadNames[contract] { readIdentifier := types.BoundContract{ Address: address, Name: contract, - }.ReadIdentifier(readName) + }.ReadIdentifier(genericName) delete(l.readIdentifiers, readIdentifier) } diff --git a/pkg/solana/chainwriter/ccip_example_config.go b/pkg/solana/chainwriter/ccip_example_config.go new file mode 100644 index 000000000..fc46794a8 --- /dev/null +++ b/pkg/solana/chainwriter/ccip_example_config.go @@ -0,0 +1,333 @@ +package chainwriter + +import ( + "github.com/gagliardetto/solana-go" +) + +func TestConfig() { + // Fake constant addresses for the purpose of this example. + registryAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6A" + routerProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6B" + routerAccountConfigAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6C" + cpiSignerAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6D" + systemProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6E" + computeBudgetProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6F" + sysvarProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6G" + commonAddressesLookupTable := solana.MustPublicKeyFromBase58("4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6H") + routerLookupTable := solana.MustPublicKeyFromBase58("4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6I") + userAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6J" + + executionReportSingleChainIDL := `{"name":"ExecutionReportSingleChain","type":{"kind":"struct","fields":[{"name":"source_chain_selector","type":"u64"},{"name":"message","type":{"defined":"Any2SolanaRampMessage"}},{"name":"root","type":{"array":["u8",32]}},{"name":"proofs","type":{"vec":{"array":["u8",32]}}}]}},{"name":"Any2SolanaRampMessage","type":{"kind":"struct","fields":[{"name":"header","type":{"defined":"RampMessageHeader"}},{"name":"sender","type":{"vec":"u8"}},{"name":"data","type":{"vec":"u8"}},{"name":"receiver","type":{"array":["u8",32]}},{"name":"extra_args","type":{"defined":"SolanaExtraArgs"}}]}},{"name":"RampMessageHeader","type":{"kind":"struct","fields":[{"name":"message_id","type":{"array":["u8",32]}},{"name":"source_chain_selector","type":"u64"},{"name":"dest_chain_selector","type":"u64"},{"name":"sequence_number","type":"u64"},{"name":"nonce","type":"u64"}]}},{"name":"SolanaExtraArgs","type":{"kind":"struct","fields":[{"name":"compute_units","type":"u32"},{"name":"allow_out_of_order_execution","type":"bool"}]}}` + + executeConfig := MethodConfig{ + FromAddress: userAddress, + InputModifications: nil, + ChainSpecificName: "execute", + // LookupTables are on-chain stores of accounts. They can be used in two ways: + // 1. As a way to store a list of accounts that are all associated together (i.e. Token State registry) + // 2. To compress the transactions in a TX and reduce the size of the TX. (The traditional way) + LookupTables: LookupTables{ + // DerivedLookupTables are useful in both the ways described above. + // a. The user can configure any type of look up to get a list of lookupTables to read from. + // b. The ChainWriter reads from this lookup table and store the internal addresses in memory + // c. Later, in the []Accounts the user can specify which accounts to include in the TX with an AccountsFromLookupTable lookup. + // d. Lastly, the lookup table is used to compress the size of the transaction. + DerivedLookupTables: []DerivedLookupTable{ + { + Name: "RegistryTokenState", + // In this case, the user configured the lookup table accounts to use a PDALookup, which + // generates a list of one of more PDA accounts based on the input parameters. Specifically, + // there will be multiple PDA accounts if there are multiple addresses in the message, otherwise, + // there will only be one PDA account to read from. The PDA account corresponds to the lookup table. + Accounts: PDALookups{ + Name: "RegistryTokenState", + PublicKey: AccountConstant{ + Address: registryAddress, + IsSigner: false, + IsWritable: false, + }, + // Seeds would be used if the user needed to look up addresses to use as seeds, which isn't the case here. + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, + }, + IsSigner: false, + IsWritable: false, + }, + }, + }, + // Static lookup tables are the traditional use case (point 2 above) of Lookup tables. These are lookup + // tables which contain commonly used addresses in all CCIP execute transactions. The ChainWriter reads + // these lookup tables and appends them to the transaction to reduce the size of the transaction. + StaticLookupTables: []solana.PublicKey{ + commonAddressesLookupTable, + routerLookupTable, + }, + }, + // The Accounts field is where the user specifies which accounts to include in the transaction. Each Lookup + // resolves to one or more on-chain addresses. + Accounts: []Lookup{ + // The accounts can be of any of the following types: + // 1. Account constant + // 2. Account Lookup - Based on data from input parameters + // 3. Lookup Table content - Get all the accounts from a lookup table + // 4. PDA Account Lookup - Based on another account and a seed/s + // Nested PDA Account with seeds from: + // -> input parameters + // -> constant + // PDALookups can resolve to multiple addresses if: + // A) The PublicKey lookup resolves to multiple addresses (i.e. multiple token addresses) + // B) The Seeds or ValueSeeds resolve to multiple values + PDALookups{ + Name: "PerChainConfig", + // PublicKey is a constant account in this case, not a lookup. + PublicKey: AccountConstant{ + Address: registryAddress, + IsSigner: false, + IsWritable: false, + }, + // Similar to the RegistryTokenState above, the user is looking up PDA accounts based on the dest tokens. + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, + }, + IsSigner: false, + IsWritable: false, + }, + // Lookup Table content - Get the accounts from the derived lookup table above + AccountsFromLookupTable{ + LookupTableName: "RegistryTokenState", + IncludeIndexes: []int{}, // If left empty, all addresses will be included. Otherwise, only the specified indexes will be included. + }, + // Account Lookup - Based on data from input parameters + // In this case, the user wants to add the destination token addresses to the transaction. + // Once again, this can be one or multiple addresses. + AccountLookup{ + Name: "TokenAccount", + Location: "Message.TokenAmounts.DestTokenAddress", + IsSigner: false, + IsWritable: false, + }, + // PDA Account Lookup - Based on an account lookup and an address lookup + PDALookups{ + // In this case, the token address is the public key, and the receiver is the seed. + // Again, there could be multiple token addresses, in which case this would resolve to + // multiple PDA accounts. + Name: "ReceiverAssociatedTokenAccount", + PublicKey: AccountLookup{ + Name: "TokenAccount", + Location: "Message.TokenAmounts.DestTokenAddress", + IsSigner: false, + IsWritable: false, + }, + // The seed is the receiver address. + Seeds: []Seed{ + {Dynamic: AccountLookup{ + Name: "Receiver", + Location: "Message.Receiver", + IsSigner: false, + IsWritable: false, + }}, + }, + }, + // Account constant + AccountConstant{ + Name: "Registry", + Address: registryAddress, + IsSigner: false, + IsWritable: false, + }, + // PDA Lookup for the RegistryTokenConfig. + PDALookups{ + Name: "RegistryTokenConfig", + // constant public key + PublicKey: AccountConstant{ + Address: registryAddress, + IsSigner: false, + IsWritable: false, + }, + // The seed, once again, is the destination token address. + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, + }, + IsSigner: false, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "RouterProgram", + Address: routerProgramAddress, + IsSigner: false, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "RouterAccountConfig", + Address: routerAccountConfigAddress, + IsSigner: false, + IsWritable: false, + }, + // PDA lookup to get the Router Chain Config + PDALookups{ + Name: "RouterChainConfig", + // The public key is a constant Router address. + PublicKey: AccountConstant{ + Address: routerProgramAddress, + IsSigner: false, + IsWritable: false, + }, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Message.Header.SourceChainSelector"}}, + }, + IsSigner: false, + IsWritable: false, + }, + // PDA lookup to get the Router Report Accounts. + PDALookups{ + Name: "RouterReportAccount", + // The public key is a constant Router address. + PublicKey: AccountConstant{ + Address: routerProgramAddress, + IsSigner: false, + IsWritable: false, + }, + Seeds: []Seed{ + {Dynamic: AccountLookup{ + // The seed is the merkle root of the report, as passed into the input params. + Location: "args.MerkleRoot", + }}, + }, + IsSigner: false, + IsWritable: false, + }, + // PDA lookup to get UserNoncePerChain + PDALookups{ + Name: "UserNoncePerChain", + // The public key is a constant Router address. + PublicKey: AccountConstant{ + Address: routerProgramAddress, + IsSigner: false, + IsWritable: false, + }, + // In this case, the user configured multiple seeds. These will be used in conjunction + // with the public key to generate one or multiple PDA accounts. + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.Receiver"}}, + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, + }, + }, + // Account constant + AccountConstant{ + Name: "CPISigner", + Address: cpiSignerAddress, + IsSigner: true, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "SystemProgram", + Address: systemProgramAddress, + IsSigner: true, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "ComputeBudgetProgram", + Address: computeBudgetProgramAddress, + IsSigner: true, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "SysvarProgram", + Address: sysvarProgramAddress, + IsSigner: true, + IsWritable: false, + }, + }, + // TBD where this will be in the report + // This will be appended to every error message + DebugIDLocation: "Message.MessageID", + } + + commitConfig := MethodConfig{ + FromAddress: userAddress, + InputModifications: nil, + ChainSpecificName: "commit", + LookupTables: LookupTables{ + StaticLookupTables: []solana.PublicKey{ + commonAddressesLookupTable, + routerLookupTable, + }, + }, + Accounts: []Lookup{ + // Account constant + AccountConstant{ + Name: "RouterProgram", + Address: routerProgramAddress, + IsSigner: false, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "RouterAccountConfig", + Address: routerAccountConfigAddress, + IsSigner: false, + IsWritable: false, + }, + // PDA lookup to get the Router Report Accounts. + PDALookups{ + Name: "RouterReportAccount", + // The public key is a constant Router address. + PublicKey: AccountConstant{ + Address: routerProgramAddress, + IsSigner: false, + IsWritable: false, + }, + Seeds: []Seed{ + {Dynamic: AccountLookup{ + // The seed is the merkle root of the report, as passed into the input params. + Location: "args.MerkleRoots", + }}, + }, + IsSigner: false, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "SystemProgram", + Address: systemProgramAddress, + IsSigner: true, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "ComputeBudgetProgram", + Address: computeBudgetProgramAddress, + IsSigner: true, + IsWritable: false, + }, + // Account constant + AccountConstant{ + Name: "SysvarProgram", + Address: sysvarProgramAddress, + IsSigner: true, + IsWritable: false, + }, + }, + DebugIDLocation: "", + } + + chainWriterConfig := ChainWriterConfig{ + Programs: map[string]ProgramConfig{ + "ccip-router": { + Methods: map[string]MethodConfig{ + "execute": executeConfig, + "commit": commitConfig, + }, + IDL: executionReportSingleChainIDL, + }, + }, + } + _ = chainWriterConfig +} diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go new file mode 100644 index 000000000..e02148d89 --- /dev/null +++ b/pkg/solana/chainwriter/chain_writer.go @@ -0,0 +1,452 @@ +package chainwriter + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + + "github.com/gagliardetto/solana-go" + addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table" + "github.com/gagliardetto/solana-go/rpc" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" +) + +const ServiceName = "SolanaChainWriter" + +type SolanaChainWriterService struct { + lggr logger.Logger + reader client.Reader + txm txm.TxManager + ge fees.Estimator + config ChainWriterConfig + + parsed *codec.ParsedTypes + encoder types.Encoder + + services.StateMachine +} + +var ( + _ services.Service = &SolanaChainWriterService{} + _ types.ContractWriter = &SolanaChainWriterService{} +) + +// nolint // ignoring naming suggestion +type ChainWriterConfig struct { + Programs map[string]ProgramConfig +} + +type ProgramConfig struct { + Methods map[string]MethodConfig + IDL string +} + +type MethodConfig struct { + FromAddress string + InputModifications commoncodec.ModifiersConfig + ChainSpecificName string + LookupTables LookupTables + Accounts []Lookup + // Location in the args where the debug ID is stored + DebugIDLocation string +} + +func NewSolanaChainWriterService(logger logger.Logger, reader client.Reader, txm txm.TxManager, ge fees.Estimator, config ChainWriterConfig) (*SolanaChainWriterService, error) { + w := SolanaChainWriterService{ + lggr: logger, + reader: reader, + txm: txm, + ge: ge, + config: config, + parsed: &codec.ParsedTypes{EncoderDefs: map[string]codec.Entry{}, DecoderDefs: map[string]codec.Entry{}}, + } + + if err := w.parsePrograms(config); err != nil { + return nil, fmt.Errorf("failed to parse programs: %w", err) + } + + var err error + if w.encoder, err = w.parsed.ToCodec(); err != nil { + return nil, fmt.Errorf("%w: failed to create codec", err) + } + + return &w, nil +} + +func (s *SolanaChainWriterService) parsePrograms(config ChainWriterConfig) error { + for program, programConfig := range config.Programs { + var idl codec.IDL + if err := json.Unmarshal([]byte(programConfig.IDL), &idl); err != nil { + return fmt.Errorf("failed to unmarshal IDL for program: %s, error: %w", program, err) + } + for method, methodConfig := range programConfig.Methods { + idlDef, err := codec.FindDefinitionFromIDL(codec.ChainConfigTypeInstructionDef, methodConfig.ChainSpecificName, idl) + if err != nil { + return err + } + + inputMod, err := methodConfig.InputModifications.ToModifier(codec.DecoderHooks...) + if err != nil { + return fmt.Errorf("failed to create input modifications for method %s.%s, error: %w", program, method, err) + } + + input, err := codec.CreateCodecEntry(idlDef, methodConfig.ChainSpecificName, idl, inputMod) + if err != nil { + return fmt.Errorf("failed to create codec entry for method %s.%s, error: %w", program, method, err) + } + + s.parsed.EncoderDefs[codec.WrapItemType(true, program, method, "")] = input + } + } + + return nil +} + +/* +GetAddresses resolves account addresses from various `Lookup` configurations to build the required `solana.AccountMeta` list +for Solana transactions. It handles constant addresses, dynamic lookups, program-derived addresses (PDAs), and lookup tables. + +### Parameters: +- `ctx`: Context for request lifecycle management. +- `args`: Input arguments used for dynamic lookups. +- `accounts`: List of `Lookup` configurations specifying how addresses are derived. +- `derivedTableMap`: Map of pre-loaded lookup table addresses. +- `debugID`: Debug identifier for tracing errors. + +### Return: +- A slice of `solana.AccountMeta` containing derived addresses and associated metadata. + +### Account Types: +1. **AccountConstant**: + - A fixed address, provided in Base58 format, converted into a `solana.PublicKey`. + - Example: A pre-defined fee payer or system account. + +2. **AccountLookup**: + - Dynamically derived from input args using a specified location path (e.g., `user.walletAddress`). + - If the lookup table is pre-loaded, the address is fetched from `derivedTableMap`. + +3. **PDALookups**: + - Generates Program Derived Addresses (PDA) by combining a derived public key with one or more seeds. + - Seeds can be `AddressSeeds` (public keys from the input args) or `ValueSeeds` (byte arrays). + - Ensures there is only one public key if multiple seeds are provided. + +### Error Handling: +- Errors are wrapped with the `debugID` for easier tracing. +*/ +func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) { + var addresses []*solana.AccountMeta + for _, accountConfig := range accounts { + meta, err := accountConfig.Resolve(ctx, args, derivedTableMap, reader) + if err != nil { + return nil, err + } + addresses = append(addresses, meta...) + } + return addresses, nil +} + +// FilterLookupTableAddresses takes a list of accounts and two lookup table maps +// (one for derived tables, one for static tables) and filters out any addresses that are +// not used by the accounts. It returns a map of only those lookup table +// addresses that match entries in `accounts`. +func (s *SolanaChainWriterService) FilterLookupTableAddresses( + accounts []*solana.AccountMeta, + derivedTableMap map[string]map[string][]*solana.AccountMeta, + staticTableMap map[solana.PublicKey]solana.PublicKeySlice, +) map[solana.PublicKey]solana.PublicKeySlice { + filteredLookupTables := make(map[solana.PublicKey]solana.PublicKeySlice) + + // Build a hash set of account public keys for fast lookup + usedAccounts := make(map[string]struct{}) + for _, account := range accounts { + usedAccounts[account.PublicKey.String()] = struct{}{} + } + + // Filter derived lookup tables + for _, innerMap := range derivedTableMap { + for innerIdentifier, metas := range innerMap { + tableKey, err := solana.PublicKeyFromBase58(innerIdentifier) + if err != nil { + continue + } + + // Collect public keys that are actually used + var usedAddresses solana.PublicKeySlice + for _, meta := range metas { + if _, exists := usedAccounts[meta.PublicKey.String()]; exists { + usedAddresses = append(usedAddresses, meta.PublicKey) + } + } + + // Add to the filtered map if there are any used addresses + if len(usedAddresses) > 0 { + filteredLookupTables[tableKey] = usedAddresses + } + } + } + + // Filter static lookup tables + for tableKey, addresses := range staticTableMap { + var usedAddresses solana.PublicKeySlice + for _, staticAddress := range addresses { + if _, exists := usedAccounts[staticAddress.String()]; exists { + usedAddresses = append(usedAddresses, staticAddress) + } + } + + // Add to the filtered map if there are any used addresses + if len(usedAddresses) > 0 { + filteredLookupTables[tableKey] = usedAddresses + } + } + + return filteredLookupTables +} + +// SubmitTransaction builds, encodes, and enqueues a transaction using the provided program +// configuration and method details. It relies on the configured IDL, account lookups, and +// lookup tables to gather the necessary accounts and data. The function retrieves the latest +// blockhash and assigns it to the transaction, so callers do not need to provide one. +// +// Submissions and retries are handled by the underlying transaction manager. If a “debug ID” +// location is configured, SubmitTransaction extracts it from the provided `args` and attaches +// it to errors for easier troubleshooting. Only the first debug ID it encounters will be used. +// +// Parameters: +// - ctx: The context for cancellation and timeouts. +// - contractName: Identifies which Solana program config to use from `s.config.Programs`. +// - method: Specifies which method config to invoke within the chosen program config. +// - args: Arbitrary arguments that are encoded into the transaction payload and/or used for dynamic address lookups. +// - transactionID: A unique identifier for the transaction, used for tracking within the transaction manager. +// - toAddress: The on-chain address (program ID) to which the transaction is directed. +// - meta: Currently unused; included for interface compatibility. +// - value: Currently unused; included for interface compatibility. +// +// Returns: +// +// An error if any stage of the transaction preparation or enqueueing fails. A nil return +// indicates that the transaction was successfully submitted to the transaction manager. +func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contractName, method string, args any, transactionID string, toAddress string, _ *types.TxMeta, _ *big.Int) error { + programConfig, exists := s.config.Programs[contractName] + if !exists { + return fmt.Errorf("failed to find program config for contract name: %s", contractName) + } + methodConfig, exists := programConfig.Methods[method] + if !exists { + return fmt.Errorf("failed to find method config for method: %s", method) + } + + // Configure debug ID + debugID := "" + if methodConfig.DebugIDLocation != "" { + var err error + debugID, err = GetDebugIDAtLocation(args, methodConfig.DebugIDLocation) + if err != nil { + return errorWithDebugID(fmt.Errorf("error getting debug ID from input args: %w", err), debugID) + } + } + + encodedPayload, err := s.encoder.Encode(ctx, args, codec.WrapItemType(true, contractName, method, "")) + + if err != nil { + return errorWithDebugID(fmt.Errorf("error encoding transaction payload: %w", err), debugID) + } + + discriminator := GetDiscriminator(methodConfig.ChainSpecificName) + encodedPayload = append(discriminator[:], encodedPayload...) + + // Fetch derived and static table maps + derivedTableMap, staticTableMap, err := s.ResolveLookupTables(ctx, args, methodConfig.LookupTables) + if err != nil { + return errorWithDebugID(fmt.Errorf("error getting lookup tables: %w", err), debugID) + } + + // Resolve account metas + accounts, err := GetAddresses(ctx, args, methodConfig.Accounts, derivedTableMap, s.reader) + if err != nil { + return errorWithDebugID(fmt.Errorf("error resolving account addresses: %w", err), debugID) + } + + feePayer, err := solana.PublicKeyFromBase58(methodConfig.FromAddress) + if err != nil { + return errorWithDebugID(fmt.Errorf("error parsing fee payer address: %w", err), debugID) + } + + accounts = append([]*solana.AccountMeta{solana.Meta(feePayer).SIGNER().WRITE()}, accounts...) + accounts = append(accounts, solana.Meta(solana.SystemProgramID)) + + // Filter the lookup table addresses based on which accounts are actually used + filteredLookupTableMap := s.FilterLookupTableAddresses(accounts, derivedTableMap, staticTableMap) + + // Fetch latest blockhash + blockhash, err := s.reader.LatestBlockhash(ctx) + if err != nil { + return errorWithDebugID(fmt.Errorf("error fetching latest blockhash: %w", err), debugID) + } + + // Prepare transaction + programID, err := solana.PublicKeyFromBase58(toAddress) + if err != nil { + return errorWithDebugID(fmt.Errorf("error parsing program ID: %w", err), debugID) + } + + tx, err := solana.NewTransaction( + []solana.Instruction{ + solana.NewInstruction(programID, accounts, encodedPayload), + }, + blockhash.Value.Blockhash, + solana.TransactionPayer(feePayer), + solana.TransactionAddressTables(filteredLookupTableMap), + ) + if err != nil { + return errorWithDebugID(fmt.Errorf("error constructing transaction: %w", err), debugID) + } + + // Enqueue transaction + if err = s.txm.Enqueue(ctx, methodConfig.FromAddress, tx, &transactionID, blockhash.Value.LastValidBlockHeight); err != nil { + return errorWithDebugID(fmt.Errorf("error enqueuing transaction: %w", err), debugID) + } + + return nil +} + +// GetTransactionStatus returns the current status of a transaction in the underlying chain's TXM. +func (s *SolanaChainWriterService) GetTransactionStatus(ctx context.Context, transactionID string) (types.TransactionStatus, error) { + return s.txm.GetTransactionStatus(ctx, transactionID) +} + +// GetFeeComponents retrieves the associated gas costs for executing a transaction. +func (s *SolanaChainWriterService) GetFeeComponents(ctx context.Context) (*types.ChainFeeComponents, error) { + if s.ge == nil { + return nil, fmt.Errorf("gas estimator not available") + } + + fee := s.ge.BaseComputeUnitPrice() + return &types.ChainFeeComponents{ + ExecutionFee: new(big.Int).SetUint64(fee), + DataAvailabilityFee: nil, + }, nil +} + +func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args any, lookupTables LookupTables) (map[string]map[string][]*solana.AccountMeta, map[solana.PublicKey]solana.PublicKeySlice, error) { + derivedTableMap := make(map[string]map[string][]*solana.AccountMeta) + staticTableMap := make(map[solana.PublicKey]solana.PublicKeySlice) + + // Read derived lookup tables + for _, derivedLookup := range lookupTables.DerivedLookupTables { + // Load the lookup table - note: This could be multiple tables if the lookup is a PDALookups that resolves to more + // than one address + lookupTableMap, err := s.loadTable(ctx, args, derivedLookup) + if err != nil { + return nil, nil, fmt.Errorf("error loading derived lookup table: %w", err) + } + + // Merge the loaded table map into the result + for tableName, innerMap := range lookupTableMap { + if derivedTableMap[tableName] == nil { + derivedTableMap[tableName] = make(map[string][]*solana.AccountMeta) + } + for accountKey, metas := range innerMap { + derivedTableMap[tableName][accountKey] = metas + } + } + } + + // Read static lookup tables + for _, staticTable := range lookupTables.StaticLookupTables { + addressses, err := getLookupTableAddresses(ctx, s.reader, staticTable) + if err != nil { + return nil, nil, fmt.Errorf("error fetching static lookup table address: %w", err) + } + staticTableMap[staticTable] = addressses + } + + return derivedTableMap, staticTableMap, nil +} + +func (s *SolanaChainWriterService) loadTable(ctx context.Context, args any, rlt DerivedLookupTable) (map[string]map[string][]*solana.AccountMeta, error) { + // Resolve all addresses specified by the identifier + lookupTableAddresses, err := GetAddresses(ctx, args, []Lookup{rlt.Accounts}, nil, s.reader) + if err != nil { + return nil, fmt.Errorf("error resolving addresses for lookup table: %w", err) + } + + // Nested map in case the lookup table resolves to multiple addresses + resultMap := make(map[string]map[string][]*solana.AccountMeta) + + // Iterate over each address of the lookup table + for _, addressMeta := range lookupTableAddresses { + // Read the full list of addresses from the lookup table + addresses, err := getLookupTableAddresses(ctx, s.reader, addressMeta.PublicKey) + if err != nil { + return nil, fmt.Errorf("error fetching lookup table address: %s, error: %w", addressMeta.PublicKey, err) + } + + // Create the inner map for this lookup table + if resultMap[rlt.Name] == nil { + resultMap[rlt.Name] = make(map[string][]*solana.AccountMeta) + } + + // Populate the inner map (keyed by the account public key) + for _, addr := range addresses { + resultMap[rlt.Name][addressMeta.PublicKey.String()] = append(resultMap[rlt.Name][addressMeta.PublicKey.String()], &solana.AccountMeta{ + PublicKey: addr, + IsSigner: addressMeta.IsSigner, + IsWritable: addressMeta.IsWritable, + }) + } + } + + return resultMap, nil +} + +func getLookupTableAddresses(ctx context.Context, reader client.Reader, tableAddress solana.PublicKey) (solana.PublicKeySlice, error) { + // Fetch the account info for the static table + accountInfo, err := reader.GetAccountInfoWithOpts(ctx, tableAddress, &rpc.GetAccountInfoOpts{ + Encoding: "base64", + Commitment: rpc.CommitmentFinalized, + }) + + if err != nil || accountInfo == nil || accountInfo.Value == nil { + return nil, fmt.Errorf("error fetching account info for table: %s, error: %w", tableAddress.String(), err) + } + alt, err := addresslookuptable.DecodeAddressLookupTableState(accountInfo.GetBinary()) + if err != nil { + return nil, fmt.Errorf("error decoding address lookup table state: %w", err) + } + return alt.Addresses, nil +} + +func (s *SolanaChainWriterService) Start(_ context.Context) error { + return s.StartOnce(ServiceName, func() error { + return nil + }) +} + +func (s *SolanaChainWriterService) Close() error { + return s.StopOnce(ServiceName, func() error { + return nil + }) +} + +func (s *SolanaChainWriterService) HealthReport() map[string]error { + return map[string]error{s.Name(): s.Healthy()} +} + +func (s *SolanaChainWriterService) Name() string { + return s.lggr.Name() +} + +func (s *SolanaChainWriterService) Ready() error { + return s.StateMachine.Ready() +} diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go new file mode 100644 index 000000000..947674a2f --- /dev/null +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -0,0 +1,704 @@ +package chainwriter_test + +import ( + "bytes" + "errors" + "math/big" + "os" + "reflect" + "testing" + + ag_binary "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table" + "github.com/gagliardetto/solana-go/rpc" + "github.com/google/uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/testutils" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/chainwriter" + clientmocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/mocks" + feemocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees/mocks" + txmMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" +) + +type Arguments struct { + LookupTable solana.PublicKey + Seed1 []byte + Seed2 []byte +} + +func TestChainWriter_GetAddresses(t *testing.T) { + ctx := tests.Context(t) + + // mock client + rw := clientmocks.NewReaderWriter(t) + // mock estimator + ge := feemocks.NewEstimator(t) + // mock txm + txm := txmMocks.NewTxManager(t) + + // initialize chain writer + cw, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, chainwriter.ChainWriterConfig{}) + require.NoError(t, err) + + // expected account meta for constant account + constantAccountMeta := &solana.AccountMeta{ + IsSigner: true, + IsWritable: true, + } + + // expected account meta for account lookup + accountLookupMeta := &solana.AccountMeta{ + IsSigner: true, + IsWritable: false, + } + + // setup pda account address + seed1 := []byte("seed1") + pda1 := mustFindPdaProgramAddress(t, [][]byte{seed1}, solana.SystemProgramID) + // expected account meta for pda lookup + pdaLookupMeta := &solana.AccountMeta{ + PublicKey: pda1, + IsSigner: false, + IsWritable: false, + } + + // setup pda account with inner field lookup + programID := chainwriter.GetRandomPubKey(t) + seed2 := []byte("seed2") + pda2 := mustFindPdaProgramAddress(t, [][]byte{seed2}, programID) + // mock data account response from program + lookupTablePubkey := mockDataAccountLookupTable(t, rw, pda2) + // mock fetch lookup table addresses call + storedPubKeys := chainwriter.CreateTestPubKeys(t, 3) + mockFetchLookupTableAddresses(t, rw, lookupTablePubkey, storedPubKeys) + // expected account meta for derived table lookup + derivedTablePdaLookupMeta := &solana.AccountMeta{ + IsSigner: false, + IsWritable: true, + } + + lookupTableConfig := chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.PDALookups{ + Name: "DataAccountPDA", + PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + // extract seed2 for PDA lookup + {Dynamic: chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}, + }, + IsSigner: derivedTablePdaLookupMeta.IsSigner, + IsWritable: derivedTablePdaLookupMeta.IsWritable, + InternalField: chainwriter.InternalField{ + Type: reflect.TypeOf(chainwriter.DataAccount{}), + Location: "LookupTable", + }, + }, + }, + }, + StaticLookupTables: nil, + } + + t.Run("resolve addresses from different types of lookups", func(t *testing.T) { + constantAccountMeta.PublicKey = chainwriter.GetRandomPubKey(t) + accountLookupMeta.PublicKey = chainwriter.GetRandomPubKey(t) + // correlates to DerivedTable index in account lookup config + derivedTablePdaLookupMeta.PublicKey = storedPubKeys[0] + + args := Arguments{ + LookupTable: accountLookupMeta.PublicKey, + Seed1: seed1, + Seed2: seed2, + } + + accountLookupConfig := []chainwriter.Lookup{ + chainwriter.AccountConstant{ + Name: "Constant", + Address: constantAccountMeta.PublicKey.String(), + IsSigner: constantAccountMeta.IsSigner, + IsWritable: constantAccountMeta.IsWritable, + }, + chainwriter.AccountLookup{ + Name: "LookupTable", + Location: "LookupTable", + IsSigner: accountLookupMeta.IsSigner, + IsWritable: accountLookupMeta.IsWritable, + }, + chainwriter.PDALookups{ + Name: "DataAccountPDA", + PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, + Seeds: []chainwriter.Seed{ + // extract seed1 for PDA lookup + {Dynamic: chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}, + }, + IsSigner: pdaLookupMeta.IsSigner, + IsWritable: pdaLookupMeta.IsWritable, + // Just get the address of the account, nothing internal. + InternalField: chainwriter.InternalField{}, + }, + chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + IncludeIndexes: []int{0}, + }, + } + + // Fetch derived table map + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + require.NoError(t, err) + + // Resolve account metas + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + require.NoError(t, err) + + // account metas should be returned in the same order as the provided account lookup configs + require.Len(t, accounts, 4) + + // Validate account constant + require.Equal(t, constantAccountMeta.PublicKey, accounts[0].PublicKey) + require.Equal(t, constantAccountMeta.IsSigner, accounts[0].IsSigner) + require.Equal(t, constantAccountMeta.IsWritable, accounts[0].IsWritable) + + // Validate account lookup + require.Equal(t, accountLookupMeta.PublicKey, accounts[1].PublicKey) + require.Equal(t, accountLookupMeta.IsSigner, accounts[1].IsSigner) + require.Equal(t, accountLookupMeta.IsWritable, accounts[1].IsWritable) + + // Validate pda lookup + require.Equal(t, pdaLookupMeta.PublicKey, accounts[2].PublicKey) + require.Equal(t, pdaLookupMeta.IsSigner, accounts[2].IsSigner) + require.Equal(t, pdaLookupMeta.IsWritable, accounts[2].IsWritable) + + // Validate pda lookup with inner field from derived table + require.Equal(t, derivedTablePdaLookupMeta.PublicKey, accounts[3].PublicKey) + require.Equal(t, derivedTablePdaLookupMeta.IsSigner, accounts[3].IsSigner) + require.Equal(t, derivedTablePdaLookupMeta.IsWritable, accounts[3].IsWritable) + }) + + t.Run("resolve addresses for multiple indices from derived lookup table", func(t *testing.T) { + args := Arguments{ + Seed2: seed2, + } + + accountLookupConfig := []chainwriter.Lookup{ + chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + IncludeIndexes: []int{0, 2}, + }, + } + + // Fetch derived table map + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + require.NoError(t, err) + + // Resolve account metas + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + require.NoError(t, err) + + require.Len(t, accounts, 2) + require.Equal(t, storedPubKeys[0], accounts[0].PublicKey) + require.Equal(t, storedPubKeys[2], accounts[1].PublicKey) + }) + + t.Run("resolve all addresses from derived lookup table if indices not specified", func(t *testing.T) { + args := Arguments{ + Seed2: seed2, + } + + accountLookupConfig := []chainwriter.Lookup{ + chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + }, + } + + // Fetch derived table map + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + require.NoError(t, err) + + // Resolve account metas + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + require.NoError(t, err) + + require.Len(t, accounts, 3) + for i, storedPubkey := range storedPubKeys { + require.Equal(t, storedPubkey, accounts[i].PublicKey) + } + }) +} + +func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { + ctx := tests.Context(t) + + // mock client + rw := clientmocks.NewReaderWriter(t) + // mock estimator + ge := feemocks.NewEstimator(t) + // mock txm + txm := txmMocks.NewTxManager(t) + + // initialize chain writer + cw, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, chainwriter.ChainWriterConfig{}) + require.NoError(t, err) + + programID := chainwriter.GetRandomPubKey(t) + seed1 := []byte("seed1") + pda1 := mustFindPdaProgramAddress(t, [][]byte{seed1}, programID) + // mock data account response from program + lookupTablePubkey := mockDataAccountLookupTable(t, rw, pda1) + // mock fetch lookup table addresses call + storedPubKey := chainwriter.GetRandomPubKey(t) + mockFetchLookupTableAddresses(t, rw, lookupTablePubkey, []solana.PublicKey{storedPubKey}) + + unusedProgramID := chainwriter.GetRandomPubKey(t) + seed2 := []byte("seed2") + unusedPda := mustFindPdaProgramAddress(t, [][]byte{seed2}, unusedProgramID) + // mock data account response from program + unusedLookupTable := mockDataAccountLookupTable(t, rw, unusedPda) + // mock fetch lookup table addresses call + unusedKeys := chainwriter.GetRandomPubKey(t) + mockFetchLookupTableAddresses(t, rw, unusedLookupTable, []solana.PublicKey{unusedKeys}) + + // mock static lookup table calls + staticLookupTablePubkey1 := chainwriter.GetRandomPubKey(t) + mockFetchLookupTableAddresses(t, rw, staticLookupTablePubkey1, chainwriter.CreateTestPubKeys(t, 2)) + staticLookupTablePubkey2 := chainwriter.GetRandomPubKey(t) + mockFetchLookupTableAddresses(t, rw, staticLookupTablePubkey2, chainwriter.CreateTestPubKeys(t, 2)) + + lookupTableConfig := chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.PDALookups{ + Name: "DataAccountPDA", + PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + // extract seed1 for PDA lookup + {Dynamic: chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}, + }, + IsSigner: true, + IsWritable: true, + InternalField: chainwriter.InternalField{ + Type: reflect.TypeOf(chainwriter.DataAccount{}), + Location: "LookupTable", + }, + }, + }, + { + Name: "MiscDerivedTable", + Accounts: chainwriter.PDALookups{ + Name: "MiscPDA", + PublicKey: chainwriter.AccountConstant{Name: "UnusedAccount", Address: unusedProgramID.String()}, + Seeds: []chainwriter.Seed{ + // extract seed2 for PDA lookup + {Dynamic: chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}, + }, + IsSigner: true, + IsWritable: true, + InternalField: chainwriter.InternalField{ + Type: reflect.TypeOf(chainwriter.DataAccount{}), + Location: "LookupTable", + }, + }, + }, + }, + StaticLookupTables: []solana.PublicKey{staticLookupTablePubkey1, staticLookupTablePubkey2}, + } + + args := Arguments{ + Seed1: seed1, + Seed2: seed2, + } + + t.Run("returns filtered map with only relevant addresses required by account lookup config", func(t *testing.T) { + accountLookupConfig := []chainwriter.Lookup{ + chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + IncludeIndexes: []int{0}, + }, + } + + // Fetch derived table map + derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + require.NoError(t, err) + + // Resolve account metas + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + require.NoError(t, err) + + // Filter the lookup table addresses based on which accounts are actually used + filteredLookupTableMap := cw.FilterLookupTableAddresses(accounts, derivedTableMap, staticTableMap) + + // Filter map should only contain the address for the DerivedTable lookup defined in the account lookup config + require.Len(t, filteredLookupTableMap, len(accounts)) + entry, exists := filteredLookupTableMap[lookupTablePubkey] + require.True(t, exists) + require.Len(t, entry, 1) + require.Equal(t, storedPubKey, entry[0]) + }) + + t.Run("returns empty map if empty account lookup config provided", func(t *testing.T) { + accountLookupConfig := []chainwriter.Lookup{} + + // Fetch derived table map + derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + require.NoError(t, err) + + // Resolve account metas + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + require.NoError(t, err) + + // Filter the lookup table addresses based on which accounts are actually used + filteredLookupTableMap := cw.FilterLookupTableAddresses(accounts, derivedTableMap, staticTableMap) + require.Empty(t, filteredLookupTableMap) + }) + + t.Run("returns empty map if only constant account lookup required", func(t *testing.T) { + accountLookupConfig := []chainwriter.Lookup{ + chainwriter.AccountConstant{ + Name: "Constant", + Address: chainwriter.GetRandomPubKey(t).String(), + IsSigner: false, + IsWritable: false, + }, + } + + // Fetch derived table map + derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + require.NoError(t, err) + + // Resolve account metas + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + require.NoError(t, err) + + // Filter the lookup table addresses based on which accounts are actually used + filteredLookupTableMap := cw.FilterLookupTableAddresses(accounts, derivedTableMap, staticTableMap) + require.Empty(t, filteredLookupTableMap) + }) +} + +func TestChainWriter_SubmitTransaction(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + // mock client + rw := clientmocks.NewReaderWriter(t) + // mock estimator + ge := feemocks.NewEstimator(t) + // mock txm + txm := txmMocks.NewTxManager(t) + + // setup admin key + adminPk, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + admin := adminPk.PublicKey() + + account1 := chainwriter.GetRandomPubKey(t) + account2 := chainwriter.GetRandomPubKey(t) + + seed1 := []byte("seed1") + account3 := mustFindPdaProgramAddress(t, [][]byte{seed1}, solana.SystemProgramID) + + // create lookup table addresses + seed2 := []byte("seed2") + programID := solana.MustPublicKeyFromBase58("6AfuXF6HapDUhQfE4nQG9C1SGtA1YjP3icaJyRfU4RyE") + derivedTablePda := mustFindPdaProgramAddress(t, [][]byte{seed2}, programID) + // mock data account response from program + derivedLookupTablePubkey := mockDataAccountLookupTable(t, rw, derivedTablePda) + // mock fetch lookup table addresses call + derivedLookupKeys := chainwriter.CreateTestPubKeys(t, 1) + mockFetchLookupTableAddresses(t, rw, derivedLookupTablePubkey, derivedLookupKeys) + + // mock static lookup table call + staticLookupTablePubkey := chainwriter.GetRandomPubKey(t) + staticLookupKeys := chainwriter.CreateTestPubKeys(t, 2) + mockFetchLookupTableAddresses(t, rw, staticLookupTablePubkey, staticLookupKeys) + + data, err := os.ReadFile("testContractIDL.json") + require.NoError(t, err) + + testContractIDLJson := string(data) + + cwConfig := chainwriter.ChainWriterConfig{ + Programs: map[string]chainwriter.ProgramConfig{ + "contract_reader_interface": { + Methods: map[string]chainwriter.MethodConfig{ + "initializeLookupTable": { + FromAddress: admin.String(), + ChainSpecificName: "initializeLookupTable", + LookupTables: chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "DerivedTable", + Accounts: chainwriter.PDALookups{ + Name: "DataAccountPDA", + PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + Seeds: []chainwriter.Seed{ + // extract seed2 for PDA lookup + {Dynamic: chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}, + }, + IsSigner: false, + IsWritable: false, + InternalField: chainwriter.InternalField{ + Type: reflect.TypeOf(chainwriter.DataAccount{}), + Location: "LookupTable", + }, + }, + }, + }, + StaticLookupTables: []solana.PublicKey{staticLookupTablePubkey}, + }, + Accounts: []chainwriter.Lookup{ + chainwriter.AccountConstant{ + Name: "Constant", + Address: account1.String(), + IsSigner: false, + IsWritable: false, + }, + chainwriter.AccountLookup{ + Name: "LookupTable", + Location: "LookupTable", + IsSigner: false, + IsWritable: false, + }, + chainwriter.PDALookups{ + Name: "DataAccountPDA", + PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, + Seeds: []chainwriter.Seed{ + // extract seed1 for PDA lookup + {Dynamic: chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}, + }, + IsSigner: false, + IsWritable: false, + // Just get the address of the account, nothing internal. + InternalField: chainwriter.InternalField{}, + }, + chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + IncludeIndexes: []int{0}, + }, + }, + }, + }, + IDL: testContractIDLJson, + }, + }, + } + + // initialize chain writer + cw, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, cwConfig) + require.NoError(t, err) + + t.Run("fails with invalid ABI", func(t *testing.T) { + invalidCWConfig := chainwriter.ChainWriterConfig{ + Programs: map[string]chainwriter.ProgramConfig{ + "invalid_program": { + Methods: map[string]chainwriter.MethodConfig{ + "invalid": { + ChainSpecificName: "invalid", + }, + }, + IDL: "", + }, + }, + } + + _, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, invalidCWConfig) + require.Error(t, err) + }) + + t.Run("fails to encode payload if args with missing values provided", func(t *testing.T) { + txID := uuid.NewString() + type InvalidArgs struct{} + args := InvalidArgs{} + submitErr := cw.SubmitTransaction(ctx, "contract_reader_interface", "initializeLookupTable", args, txID, programID.String(), nil, nil) + require.Error(t, submitErr) + }) + + t.Run("fails if invalid contract name provided", func(t *testing.T) { + txID := uuid.NewString() + args := Arguments{} + submitErr := cw.SubmitTransaction(ctx, "badContract", "initializeLookupTable", args, txID, programID.String(), nil, nil) + require.Error(t, submitErr) + }) + + t.Run("fails if invalid method provided", func(t *testing.T) { + txID := uuid.NewString() + + args := Arguments{} + submitErr := cw.SubmitTransaction(ctx, "contract_reader_interface", "badMethod", args, txID, programID.String(), nil, nil) + require.Error(t, submitErr) + }) + + t.Run("submits transaction successfully", func(t *testing.T) { + recentBlockHash := solana.Hash{} + rw.On("LatestBlockhash", mock.Anything).Return(&rpc.GetLatestBlockhashResult{Value: &rpc.LatestBlockhashResult{Blockhash: recentBlockHash, LastValidBlockHeight: uint64(100)}}, nil).Once() + txID := uuid.NewString() + + txm.On("Enqueue", mock.Anything, admin.String(), mock.MatchedBy(func(tx *solana.Transaction) bool { + // match transaction fields to ensure it was built as expected + require.Equal(t, recentBlockHash, tx.Message.RecentBlockhash) + require.Len(t, tx.Message.Instructions, 1) + require.Len(t, tx.Message.AccountKeys, 6) // fee payer + derived accounts + require.Equal(t, admin, tx.Message.AccountKeys[0]) // fee payer + require.Equal(t, account1, tx.Message.AccountKeys[1]) // account constant + require.Equal(t, account2, tx.Message.AccountKeys[2]) // account lookup + require.Equal(t, account3, tx.Message.AccountKeys[3]) // pda lookup + require.Equal(t, solana.SystemProgramID, tx.Message.AccountKeys[4]) // system program ID + require.Equal(t, programID, tx.Message.AccountKeys[5]) // instruction program ID + // instruction program ID + require.Len(t, tx.Message.AddressTableLookups, 1) // address table look contains entry + require.Equal(t, derivedLookupTablePubkey, tx.Message.AddressTableLookups[0].AccountKey) // address table + return true + }), &txID, mock.Anything).Return(nil).Once() + + args := Arguments{ + LookupTable: account2, + Seed1: seed1, + Seed2: seed2, + } + + submitErr := cw.SubmitTransaction(ctx, "contract_reader_interface", "initializeLookupTable", args, txID, programID.String(), nil, nil) + require.NoError(t, submitErr) + }) +} + +func TestChainWriter_GetTransactionStatus(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + rw := clientmocks.NewReaderWriter(t) + ge := feemocks.NewEstimator(t) + + // mock txm + txm := txmMocks.NewTxManager(t) + + // initialize chain writer + cw, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, chainwriter.ChainWriterConfig{}) + require.NoError(t, err) + + t.Run("returns unknown with error if ID not found", func(t *testing.T) { + txID := uuid.NewString() + txm.On("GetTransactionStatus", mock.Anything, txID).Return(types.Unknown, errors.New("tx not found")).Once() + status, err := cw.GetTransactionStatus(ctx, txID) + require.Error(t, err) + require.Equal(t, types.Unknown, status) + }) + + t.Run("returns pending when transaction is pending", func(t *testing.T) { + txID := uuid.NewString() + txm.On("GetTransactionStatus", mock.Anything, txID).Return(types.Pending, nil).Once() + status, err := cw.GetTransactionStatus(ctx, txID) + require.NoError(t, err) + require.Equal(t, types.Pending, status) + }) + + t.Run("returns unconfirmed when transaction is unconfirmed", func(t *testing.T) { + txID := uuid.NewString() + txm.On("GetTransactionStatus", mock.Anything, txID).Return(types.Unconfirmed, nil).Once() + status, err := cw.GetTransactionStatus(ctx, txID) + require.NoError(t, err) + require.Equal(t, types.Unconfirmed, status) + }) + + t.Run("returns finalized when transaction is finalized", func(t *testing.T) { + txID := uuid.NewString() + txm.On("GetTransactionStatus", mock.Anything, txID).Return(types.Finalized, nil).Once() + status, err := cw.GetTransactionStatus(ctx, txID) + require.NoError(t, err) + require.Equal(t, types.Finalized, status) + }) + + t.Run("returns failed when transaction error classfied as failed", func(t *testing.T) { + txID := uuid.NewString() + txm.On("GetTransactionStatus", mock.Anything, txID).Return(types.Failed, nil).Once() + status, err := cw.GetTransactionStatus(ctx, txID) + require.NoError(t, err) + require.Equal(t, types.Failed, status) + }) + + t.Run("returns fatal when transaction error classfied as fatal", func(t *testing.T) { + txID := uuid.NewString() + txm.On("GetTransactionStatus", mock.Anything, txID).Return(types.Fatal, nil).Once() + status, err := cw.GetTransactionStatus(ctx, txID) + require.NoError(t, err) + require.Equal(t, types.Fatal, status) + }) +} + +func TestChainWriter_GetFeeComponents(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + rw := clientmocks.NewReaderWriter(t) + ge := feemocks.NewEstimator(t) + ge.On("BaseComputeUnitPrice").Return(uint64(100)) + + // mock txm + txm := txmMocks.NewTxManager(t) + + cw, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, chainwriter.ChainWriterConfig{}) + require.NoError(t, err) + + t.Run("returns valid compute unit price", func(t *testing.T) { + feeComponents, err := cw.GetFeeComponents(ctx) + require.NoError(t, err) + require.Equal(t, big.NewInt(100), feeComponents.ExecutionFee) + require.Nil(t, feeComponents.DataAvailabilityFee) // always nil for Solana + }) + + t.Run("fails if gas estimator not set", func(t *testing.T) { + cwNoEstimator, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, nil, chainwriter.ChainWriterConfig{}) + require.NoError(t, err) + _, err = cwNoEstimator.GetFeeComponents(ctx) + require.Error(t, err) + }) +} + +func mustBorshEncodeStruct(t *testing.T, data interface{}) []byte { + buf := new(bytes.Buffer) + err := ag_binary.NewBorshEncoder(buf).Encode(data) + require.NoError(t, err) + return buf.Bytes() +} + +func mustFindPdaProgramAddress(t *testing.T, seeds [][]byte, programID solana.PublicKey) solana.PublicKey { + pda, _, err := solana.FindProgramAddress(seeds, programID) + require.NoError(t, err) + return pda +} + +func mockDataAccountLookupTable(t *testing.T, rw *clientmocks.ReaderWriter, pda solana.PublicKey) solana.PublicKey { + lookupTablePubkey := chainwriter.GetRandomPubKey(t) + dataAccount := chainwriter.DataAccount{ + Discriminator: [8]byte{}, + Version: 1, + Administrator: chainwriter.GetRandomPubKey(t), + PendingAdministrator: chainwriter.GetRandomPubKey(t), + LookupTable: lookupTablePubkey, + } + dataAccountBytes := mustBorshEncodeStruct(t, dataAccount) + rw.On("GetAccountInfoWithOpts", mock.Anything, pda, mock.Anything).Return(&rpc.GetAccountInfoResult{ + RPCContext: rpc.RPCContext{}, + Value: &rpc.Account{Data: rpc.DataBytesOrJSONFromBytes(dataAccountBytes)}, + }, nil) + return lookupTablePubkey +} + +func mockFetchLookupTableAddresses(t *testing.T, rw *clientmocks.ReaderWriter, lookupTablePubkey solana.PublicKey, storedPubkeys []solana.PublicKey) { + var lookupTablePubkeySlice solana.PublicKeySlice + lookupTablePubkeySlice.Append(storedPubkeys...) + lookupTableState := addresslookuptable.AddressLookupTableState{ + Addresses: lookupTablePubkeySlice, + } + lookupTableStateBytes := mustBorshEncodeStruct(t, lookupTableState) + rw.On("GetAccountInfoWithOpts", mock.Anything, lookupTablePubkey, mock.Anything).Return(&rpc.GetAccountInfoResult{ + RPCContext: rpc.RPCContext{}, + Value: &rpc.Account{Data: rpc.DataBytesOrJSONFromBytes(lookupTableStateBytes)}, + }, nil) +} diff --git a/pkg/solana/chainwriter/helpers.go b/pkg/solana/chainwriter/helpers.go new file mode 100644 index 000000000..a4b18e4d5 --- /dev/null +++ b/pkg/solana/chainwriter/helpers.go @@ -0,0 +1,205 @@ +package chainwriter + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" +) + +type TestArgs struct { + Inner []InnerArgs +} + +type InnerArgs struct { + Address []byte +} + +type DataAccount struct { + Discriminator [8]byte + Version uint8 + Administrator solana.PublicKey + PendingAdministrator solana.PublicKey + LookupTable solana.PublicKey +} + +// GetValuesAtLocation parses through nested types and arrays to find all locations of values +func GetValuesAtLocation(args any, location string) ([][]byte, error) { + var vals [][]byte + path := strings.Split(location, ".") + + addressList, err := traversePath(args, path) + if err != nil { + return nil, err + } + for _, value := range addressList { + if byteArray, ok := value.([]byte); ok { + vals = append(vals, byteArray) + } else if address, ok := value.(solana.PublicKey); ok { + vals = append(vals, address.Bytes()) + } else if num, ok := value.(uint64); ok { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, num) + vals = append(vals, buf) + } else { + return nil, fmt.Errorf("invalid value format at path: %s", location) + } + } + + return vals, nil +} + +func GetDebugIDAtLocation(args any, location string) (string, error) { + debugIDList, err := GetValuesAtLocation(args, location) + if err != nil { + return "", err + } + + if len(debugIDList) == 0 { + return "", errors.New("no debug ID found at location: " + location) + } + // there should only be one debug ID, others will be ignored. + debugID := string(debugIDList[0]) + + return debugID, nil +} + +func errorWithDebugID(err error, debugID string) error { + if debugID == "" { + return err + } + return fmt.Errorf("Debug ID: %s: Error: %s", debugID, err) +} + +// traversePath recursively traverses the given structure based on the provided path. +func traversePath(data any, path []string) ([]any, error) { + if len(path) == 0 { + return []any{data}, nil + } + + var result []any + + val := reflect.ValueOf(data) + + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + switch val.Kind() { + case reflect.Struct: + field := val.FieldByName(path[0]) + if !field.IsValid() { + return nil, errors.New("field not found: " + path[0]) + } + return traversePath(field.Interface(), path[1:]) + + case reflect.Slice, reflect.Array: + for i := 0; i < val.Len(); i++ { + element := val.Index(i).Interface() + elements, err := traversePath(element, path) + if err == nil { + result = append(result, elements...) + } + } + if len(result) > 0 { + return result, nil + } + return nil, errors.New("no matching field found in array") + + case reflect.Map: + key := reflect.ValueOf(path[0]) + value := val.MapIndex(key) + if !value.IsValid() { + return nil, errors.New("key not found: " + path[0]) + } + return traversePath(value.Interface(), path[1:]) + default: + if len(path) == 1 && val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8 { + return []any{val.Interface()}, nil + } + return nil, errors.New("unexpected type encountered at path: " + path[0]) + } +} + +func InitializeDataAccount( + ctx context.Context, + t *testing.T, + client *rpc.Client, + programID solana.PublicKey, + admin solana.PrivateKey, + lookupTable solana.PublicKey, +) { + pda, _, err := solana.FindProgramAddress([][]byte{[]byte("data")}, programID) + require.NoError(t, err) + + discriminator := GetDiscriminator("initialize_lookup_table") + + instructionData := append(discriminator[:], lookupTable.Bytes()...) + + instruction := solana.NewInstruction( + programID, + solana.AccountMetaSlice{ + solana.Meta(pda).WRITE(), + solana.Meta(admin.PublicKey()).SIGNER().WRITE(), + solana.Meta(solana.SystemProgramID), + }, + instructionData, + ) + + // Send and confirm the transaction + utils.SendAndConfirm(ctx, t, client, []solana.Instruction{instruction}, admin, rpc.CommitmentFinalized) +} + +func GetDiscriminator(instruction string) [8]byte { + fullHash := sha256.Sum256([]byte("global:" + instruction)) + var discriminator [8]byte + copy(discriminator[:], fullHash[:8]) + return discriminator +} + +func GetRandomPubKey(t *testing.T) solana.PublicKey { + privKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + return privKey.PublicKey() +} + +func CreateTestPubKeys(t *testing.T, num int) solana.PublicKeySlice { + addresses := make([]solana.PublicKey, num) + for i := 0; i < num; i++ { + addresses[i] = GetRandomPubKey(t) + } + return addresses +} + +func CreateTestLookupTable(ctx context.Context, t *testing.T, c *rpc.Client, sender solana.PrivateKey, addresses []solana.PublicKey) solana.PublicKey { + // Create lookup tables + slot, serr := c.GetSlot(ctx, rpc.CommitmentFinalized) + require.NoError(t, serr) + table, instruction, ierr := utils.NewCreateLookupTableInstruction( + sender.PublicKey(), + sender.PublicKey(), + slot, + ) + require.NoError(t, ierr) + utils.SendAndConfirm(ctx, t, c, []solana.Instruction{instruction}, sender, rpc.CommitmentConfirmed) + + // add entries to lookup table + utils.SendAndConfirm(ctx, t, c, []solana.Instruction{ + utils.NewExtendLookupTableInstruction( + table, sender.PublicKey(), sender.PublicKey(), + addresses, + ), + }, sender, rpc.CommitmentConfirmed) + + return table +} diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go new file mode 100644 index 000000000..f875ade3a --- /dev/null +++ b/pkg/solana/chainwriter/lookups.go @@ -0,0 +1,272 @@ +package chainwriter + +import ( + "context" + "fmt" + "reflect" + + ag_binary "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" +) + +// Lookup is an interface that defines a method to resolve an address (or multiple addresses) from a given definition. +type Lookup interface { + Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) +} + +// AccountConstant represents a fixed address, provided in Base58 format, converted into a `solana.PublicKey`. +type AccountConstant struct { + Name string + Address string + IsSigner bool + IsWritable bool +} + +// AccountLookup dynamically derives an account address from args using a specified location path. +type AccountLookup struct { + Name string + Location string + IsSigner bool + IsWritable bool +} + +type Seed struct { + Static []byte // Static seed value + Dynamic Lookup // Dynamic lookup for seed +} + +// PDALookups generates Program Derived Addresses (PDA) by combining a derived public key with one or more seeds. +type PDALookups struct { + Name string + // The public key of the PDA to be combined with seeds. If there are multiple PublicKeys + // there will be multiple PDAs generated by combining each PublicKey with the seeds. + PublicKey Lookup + // Seeds to be derived from an additional lookup + Seeds []Seed + IsSigner bool + IsWritable bool + // OPTIONAL: On-chain location and type of desired data from PDA (e.g. a sub-account of the data account) + InternalField InternalField +} + +type InternalField struct { + Type reflect.Type + Location string +} + +// LookupTables represents a list of lookup tables that are used to derive addresses for a program. +type LookupTables struct { + DerivedLookupTables []DerivedLookupTable + StaticLookupTables []solana.PublicKey +} + +// DerivedLookupTable represents a lookup table that is used to derive addresses for a program. +type DerivedLookupTable struct { + Name string + Accounts Lookup +} + +// AccountsFromLookupTable extracts accounts from a lookup table that was previously read and stored in memory. +type AccountsFromLookupTable struct { + LookupTableName string + IncludeIndexes []int +} + +func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { + address, err := solana.PublicKeyFromBase58(ac.Address) + if err != nil { + return nil, fmt.Errorf("error getting account from constant: %w", err) + } + return []*solana.AccountMeta{ + { + PublicKey: address, + IsSigner: ac.IsSigner, + IsWritable: ac.IsWritable, + }, + }, nil +} + +func (al AccountLookup) Resolve(_ context.Context, args any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { + derivedValues, err := GetValuesAtLocation(args, al.Location) + if err != nil { + return nil, fmt.Errorf("error getting account from lookup: %w", err) + } + + var metas []*solana.AccountMeta + for _, address := range derivedValues { + metas = append(metas, &solana.AccountMeta{ + PublicKey: solana.PublicKeyFromBytes(address), + IsSigner: al.IsSigner, + IsWritable: al.IsWritable, + }) + } + return metas, nil +} + +func (alt AccountsFromLookupTable) Resolve(_ context.Context, _ any, derivedTableMap map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { + // Fetch the inner map for the specified lookup table name + innerMap, ok := derivedTableMap[alt.LookupTableName] + if !ok { + return nil, fmt.Errorf("lookup table not found: %s", alt.LookupTableName) + } + + var result []*solana.AccountMeta + + // If no indices are specified, include all addresses + if len(alt.IncludeIndexes) == 0 { + for _, metas := range innerMap { + result = append(result, metas...) + } + return result, nil + } + + // Otherwise, include only addresses at the specified indices + for publicKey, metas := range innerMap { + for _, index := range alt.IncludeIndexes { + if index < 0 || index >= len(metas) { + return nil, fmt.Errorf("invalid index %d for account %s in lookup table %s", index, publicKey, alt.LookupTableName) + } + result = append(result, metas[index]) + } + } + + return result, nil +} + +func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) { + publicKeys, err := GetAddresses(ctx, args, []Lookup{pda.PublicKey}, derivedTableMap, reader) + if err != nil { + return nil, fmt.Errorf("error getting public key for PDALookups: %w", err) + } + + seeds, err := getSeedBytes(ctx, pda, args, derivedTableMap, reader) + if err != nil { + return nil, fmt.Errorf("error getting seeds for PDALookups: %w", err) + } + + pdas, err := generatePDAs(publicKeys, seeds, pda) + if err != nil { + return nil, fmt.Errorf("error generating PDAs: %w", err) + } + + if pda.InternalField.Location == "" { + return pdas, nil + } + + // If a decoded location is specified, fetch the data at that location + var result []*solana.AccountMeta + for _, accountMeta := range pdas { + accountInfo, err := reader.GetAccountInfoWithOpts(ctx, accountMeta.PublicKey, &rpc.GetAccountInfoOpts{ + Encoding: "base64", + Commitment: rpc.CommitmentFinalized, + }) + + if err != nil || accountInfo == nil || accountInfo.Value == nil { + return nil, fmt.Errorf("error fetching account info for PDA account: %s, error: %w", accountMeta.PublicKey.String(), err) + } + + decoded, err := decodeBorshIntoType(accountInfo.GetBinary(), pda.InternalField.Type) + if err != nil { + return nil, fmt.Errorf("error decoding Borsh data dynamically: %w", err) + } + + value, err := GetValuesAtLocation(decoded, pda.InternalField.Location) + if err != nil { + return nil, fmt.Errorf("error getting value at location: %w", err) + } + if len(value) > 1 { + return nil, fmt.Errorf("multiple values found at location: %s", pda.InternalField.Location) + } + + result = append(result, &solana.AccountMeta{ + PublicKey: solana.PublicKeyFromBytes(value[0]), + IsSigner: accountMeta.IsSigner, + IsWritable: accountMeta.IsWritable, + }) + } + return result, nil +} + +func decodeBorshIntoType(data []byte, typ reflect.Type) (interface{}, error) { + // Ensure the type is a struct + if typ.Kind() != reflect.Struct { + return nil, fmt.Errorf("provided type is not a struct: %s", typ.Kind()) + } + + // Create a new instance of the type + instance := reflect.New(typ).Interface() + + // Decode using Borsh + err := ag_binary.NewBorshDecoder(data).Decode(instance) + if err != nil { + return nil, fmt.Errorf("error decoding Borsh data: %w", err) + } + + // Return the underlying value (not a pointer) + return reflect.ValueOf(instance).Elem().Interface(), nil +} + +// getSeedBytes extracts the seeds for the PDALookups. +// It handles both AddressSeeds (which are public keys) and ValueSeeds (which are byte arrays from input args). +func getSeedBytes(ctx context.Context, lookup PDALookups, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([][]byte, error) { + var seedBytes [][]byte + + for _, seed := range lookup.Seeds { + if seed.Static != nil { + seedBytes = append(seedBytes, seed.Static) + } + if seed.Dynamic != nil { + dynamicSeed := seed.Dynamic + if lookupSeed, ok := dynamicSeed.(AccountLookup); ok { + // Get value from a location (This doens't have to be an address, it can be any value) + bytes, err := GetValuesAtLocation(args, lookupSeed.Location) + if err != nil { + return nil, fmt.Errorf("error getting address seed: %w", err) + } + // validate seed length + for _, b := range bytes { + if len(b) > solana.MaxSeedLength { + return nil, fmt.Errorf("seed byte array exceeds maximum length of %d: got %d bytes", solana.MaxSeedLength, len(b)) + } + seedBytes = append(seedBytes, b) + } + } else { + // Get address seeds from the lookup + seedAddresses, err := GetAddresses(ctx, args, []Lookup{dynamicSeed}, derivedTableMap, reader) + if err != nil { + return nil, fmt.Errorf("error getting address seed: %w", err) + } + + // Add each address seed as bytes + for _, address := range seedAddresses { + seedBytes = append(seedBytes, address.PublicKey.Bytes()) + } + } + } + } + + return seedBytes, nil +} + +// generatePDAs generates program-derived addresses (PDAs) from public keys and seeds. +func generatePDAs(publicKeys []*solana.AccountMeta, seeds [][]byte, lookup PDALookups) ([]*solana.AccountMeta, error) { + if len(seeds) > solana.MaxSeeds { + return nil, fmt.Errorf("seed maximum exceeded: %d", len(seeds)) + } + var addresses []*solana.AccountMeta + for _, publicKeyMeta := range publicKeys { + address, _, err := solana.FindProgramAddress(seeds, publicKeyMeta.PublicKey) + if err != nil { + return nil, fmt.Errorf("error finding program address: %w", err) + } + addresses = append(addresses, &solana.AccountMeta{ + PublicKey: address, + IsSigner: lookup.IsSigner, + IsWritable: lookup.IsWritable, + }) + } + return addresses, nil +} diff --git a/pkg/solana/chainwriter/testContractIDL.json b/pkg/solana/chainwriter/testContractIDL.json new file mode 100644 index 000000000..9631e4acf --- /dev/null +++ b/pkg/solana/chainwriter/testContractIDL.json @@ -0,0 +1 @@ +{"version":"0.1.0","name":"contractReaderInterface","instructions":[{"name":"initialize","accounts":[{"name":"data","isMut":true,"isSigner":false},{"name":"signer","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"testIdx","type":"u64"},{"name":"value","type":"u64"}]},{"name":"initializeLookupTable","accounts":[{"name":"writeDataAccount","isMut":true,"isSigner":false,"docs":["PDA for LookupTableDataAccount, derived from seeds and created by the System Program"]},{"name":"admin","isMut":true,"isSigner":true,"docs":["Admin account that pays for PDA creation and signs the transaction"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["System Program required for PDA creation"]}],"args":[{"name":"lookupTable","type":"publicKey"}]}],"accounts":[{"name":"LookupTableDataAccount","type":{"kind":"struct","fields":[{"name":"version","type":"u8"},{"name":"administrator","type":"publicKey"},{"name":"pendingAdministrator","type":"publicKey"},{"name":"lookupTable","type":"publicKey"}]}},{"name":"DataAccount","type":{"kind":"struct","fields":[{"name":"idx","type":"u64"},{"name":"bump","type":"u8"},{"name":"u64Value","type":"u64"},{"name":"u64Slice","type":{"vec":"u64"}}]}}]} \ No newline at end of file diff --git a/pkg/solana/client/test_helpers.go b/pkg/solana/client/test_helpers.go index 5bb8b1cde..1659cfacd 100644 --- a/pkg/solana/client/test_helpers.go +++ b/pkg/solana/client/test_helpers.go @@ -66,6 +66,7 @@ func SetupLocalSolNodeWithFlags(t *testing.T, flags ...string) (string, string) out, err := client.GetHealth(tests.Context(t)) if err != nil || out != rpc.HealthOk { t.Logf("API server not ready yet (attempt %d)\n", i+1) + t.Logf("Cmd output: %s\nCmd error: %s\n", stdOut.String(), stdErr.String()) continue } ready = true diff --git a/pkg/solana/codec/anchoridl.go b/pkg/solana/codec/anchoridl.go index e54710c5c..3fc296e97 100644 --- a/pkg/solana/codec/anchoridl.go +++ b/pkg/solana/codec/anchoridl.go @@ -312,6 +312,11 @@ type IdlTypeDefTyEnum struct { Variants IdlEnumVariantSlice `json:"variants,omitempty"` } +var NilIdlTypeDefTy = IdlTypeDef{Type: IdlTypeDefTy{ + Kind: "struct", + Fields: &IdlTypeDefStruct{}, +}} + type IdlTypeDefTy struct { Kind IdlTypeDefTyKind `json:"kind"` diff --git a/pkg/solana/codec/byte_string_modifier.go b/pkg/solana/codec/byte_string_modifier.go index 8a30b33d5..f33080ed4 100644 --- a/pkg/solana/codec/byte_string_modifier.go +++ b/pkg/solana/codec/byte_string_modifier.go @@ -22,15 +22,19 @@ func (s SolanaAddressModifier) EncodeAddress(bytes []byte) (string, error) { // DecodeAddress decodes a Base58-encoded Solana address into a 32-byte array. func (s SolanaAddressModifier) DecodeAddress(str string) ([]byte, error) { - if len(str) != 44 { - return nil, fmt.Errorf("%w: got length %d, expected 44 for address %s", commontypes.ErrInvalidType, len(str), str) - } - pubkey, err := solana.PublicKeyFromBase58(str) if err != nil { return nil, fmt.Errorf("%w: failed to decode Base58 address: %s", commontypes.ErrInvalidType, err) } + if pubkey.IsZero() { + return nil, fmt.Errorf("%w: zero-value address", commontypes.ErrInvalidType) + } + + if !pubkey.IsOnCurve() { + return nil, fmt.Errorf("%w: address %q with length of %d is not on the ed25519 curve", commontypes.ErrInvalidType, str, len(str)) + } + return pubkey.Bytes(), nil } diff --git a/pkg/solana/codec/byte_string_modifier_test.go b/pkg/solana/codec/byte_string_modifier_test.go index 83d1bf189..e0be1ce2c 100644 --- a/pkg/solana/codec/byte_string_modifier_test.go +++ b/pkg/solana/codec/byte_string_modifier_test.go @@ -51,6 +51,19 @@ func TestSolanaAddressModifier(t *testing.T) { assert.Contains(t, err.Error(), commontypes.ErrInvalidType.Error()) }) + t.Run("DecodeAddress returns error for address under 32 chars", func(t *testing.T) { + // < than 32 chars + _, err := modifier.DecodeAddress("1111111111111111111111111111111") + assert.Error(t, err) + assert.Contains(t, err.Error(), commontypes.ErrInvalidType.Error()) + }) + + t.Run("DecodeAddress returns error for valid length address not on the ed25519 curve", func(t *testing.T) { + _, err := modifier.DecodeAddress("AddressLookupTab1e11111111111111111111111111") + assert.Error(t, err) + assert.Contains(t, err.Error(), commontypes.ErrInvalidType.Error()) + }) + t.Run("Length returns 32 for Solana addresses", func(t *testing.T) { assert.Equal(t, solana.PublicKeyLength, modifier.Length()) }) diff --git a/pkg/solana/codec/codec_entry.go b/pkg/solana/codec/codec_entry.go new file mode 100644 index 000000000..f22b05984 --- /dev/null +++ b/pkg/solana/codec/codec_entry.go @@ -0,0 +1,207 @@ +package codec + +import ( + "bytes" + "fmt" + "reflect" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + commonencodings "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type Entry interface { + Encode(value any, into []byte) ([]byte, error) + Decode(encoded []byte) (any, []byte, error) + GetCodecType() commonencodings.TypeCodec + GetType() reflect.Type + Modifier() codec.Modifier + Size(numItems int) (int, error) + FixedSize() (int, error) +} + +type entry struct { + // TODO this might not be needed in the end, it was handy to make tests simpler + genericName string + chainSpecificName string + reflectType reflect.Type + typeCodec commonencodings.TypeCodec + mod codec.Modifier + // includeDiscriminator during Encode adds a discriminator to the encoded bytes under an assumption that the provided value didn't have a discriminator. + // During Decode includeDiscriminator removes discriminator from bytes under an assumption that the provided struct doesn't need a discriminator. + includeDiscriminator bool + discriminator Discriminator +} + +type AccountIDLTypes struct { + Account IdlTypeDef + Types IdlTypeDefSlice +} + +func NewAccountEntry(offchainName string, idlTypes AccountIDLTypes, includeDiscriminator bool, mod codec.Modifier, builder commonencodings.Builder) (Entry, error) { + _, accCodec, err := createCodecType(idlTypes.Account, createRefs(idlTypes.Types, builder), false) + if err != nil { + return nil, err + } + + return newEntry( + offchainName, + idlTypes.Account.Name, + accCodec, + includeDiscriminator, + mod, + ), nil +} + +type InstructionArgsIDLTypes struct { + Instruction IdlInstruction + Types IdlTypeDefSlice +} + +func NewInstructionArgsEntry(offChainName string, idlTypes InstructionArgsIDLTypes, mod codec.Modifier, builder commonencodings.Builder) (Entry, error) { + _, instructionCodecArgs, err := asStruct(idlTypes.Instruction.Args, createRefs(idlTypes.Types, builder), idlTypes.Instruction.Name, false, true) + if err != nil { + return nil, err + } + + return newEntry( + offChainName, + idlTypes.Instruction.Name, + instructionCodecArgs, + // Instruction arguments don't need a discriminator by default + false, + mod, + ), nil +} + +type EventIDLTypes struct { + Event IdlEvent + Types IdlTypeDefSlice +} + +func NewEventArgsEntry(offChainName string, idlTypes EventIDLTypes, includeDiscriminator bool, mod codec.Modifier, builder commonencodings.Builder) (Entry, error) { + _, eventCodec, err := asStruct(eventFieldsToFields(idlTypes.Event.Fields), createRefs(idlTypes.Types, builder), idlTypes.Event.Name, false, false) + if err != nil { + return nil, err + } + + return newEntry( + offChainName, + idlTypes.Event.Name, + eventCodec, + includeDiscriminator, + mod, + ), nil +} + +func newEntry( + genericName, chainSpecificName string, + typeCodec commonencodings.TypeCodec, + includeDiscriminator bool, + mod codec.Modifier, +) Entry { + return &entry{ + genericName: genericName, + chainSpecificName: chainSpecificName, + reflectType: typeCodec.GetType(), + typeCodec: typeCodec, + mod: ensureModifier(mod), + includeDiscriminator: includeDiscriminator, + discriminator: *NewDiscriminator(chainSpecificName), + } +} + +func createRefs(idlTypes IdlTypeDefSlice, builder commonencodings.Builder) *codecRefs { + return &codecRefs{ + builder: builder, + codecs: make(map[string]commonencodings.TypeCodec), + typeDefs: idlTypes, + dependencies: make(map[string][]string), + } +} + +func (e *entry) Encode(value any, into []byte) ([]byte, error) { + // Special handling for encoding a nil pointer to an empty struct. + t := e.reflectType + if value == nil { + if t.Kind() == reflect.Pointer { + elem := t.Elem() + if elem.Kind() == reflect.Struct && elem.NumField() == 0 { + return []byte{}, nil + } + } + return nil, fmt.Errorf("%w: cannot encode nil value for genericName: %q, chainSpecificName: %q", + commontypes.ErrInvalidType, e.genericName, e.chainSpecificName) + } + + encodedVal, err := e.typeCodec.Encode(value, into) + if err != nil { + return nil, err + } + + if e.includeDiscriminator { + var byt []byte + encodedDisc, err := e.discriminator.Encode(&e.discriminator.hashPrefix, byt) + if err != nil { + return nil, err + } + return append(encodedDisc, encodedVal...), nil + } + + return encodedVal, nil +} + +func (e *entry) Decode(encoded []byte) (any, []byte, error) { + if e.includeDiscriminator { + if len(encoded) < discriminatorLength { + return nil, nil, fmt.Errorf("%w: encoded data too short to contain discriminator for genericName: %q, chainSpecificName: %q", + commontypes.ErrInvalidType, e.genericName, e.chainSpecificName) + } + + if !bytes.Equal(e.discriminator.hashPrefix, encoded[:discriminatorLength]) { + return nil, nil, fmt.Errorf("%w: encoded data has a bad discriminator %v for genericName: %q, chainSpecificName: %q", + commontypes.ErrInvalidType, encoded[:discriminatorLength], e.genericName, e.chainSpecificName) + } + + encoded = encoded[discriminatorLength:] + } + return e.typeCodec.Decode(encoded) +} + +func (e *entry) GetCodecType() commonencodings.TypeCodec { + return e.typeCodec +} + +func (e *entry) GetType() reflect.Type { + return e.reflectType +} + +func (e *entry) Modifier() codec.Modifier { + return e.mod +} + +func (e *entry) Size(numItems int) (int, error) { + return e.typeCodec.Size(numItems) +} + +func (e *entry) FixedSize() (int, error) { + return e.typeCodec.FixedSize() +} + +func ensureModifier(mod codec.Modifier) codec.Modifier { + if mod == nil { + return codec.MultiModifier{} + } + return mod +} + +func eventFieldsToFields(evFields []IdlEventField) []IdlField { + var idlFields []IdlField + for _, evField := range evFields { + idlFields = append(idlFields, IdlField{ + Name: evField.Name, + Type: evField.Type, + }) + } + return idlFields +} diff --git a/pkg/solana/codec/codec_test.go b/pkg/solana/codec/codec_test.go new file mode 100644 index 000000000..50b0366ac --- /dev/null +++ b/pkg/solana/codec/codec_test.go @@ -0,0 +1,166 @@ +package codec_test + +import ( + "bytes" + _ "embed" + "slices" + "sync" + "testing" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" + clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec/testutils" +) + +const anyExtraValue = 3 + +func TestCodec(t *testing.T) { + tester := &codecInterfaceTester{} + RunCodecInterfaceTests(t, tester) + RunCodecInterfaceTests(t, looptestutils.WrapCodecTesterForLoop(tester)) + + t.Run("Events are encode-able and decode-able for a single item", func(t *testing.T) { + ctx := tests.Context(t) + item := CreateTestStruct[*testing.T](0, tester) + req := &EncodeRequest{TestStructs: []TestStruct{item}, TestOn: testutils.TestEventItem} + resp := tester.EncodeFields(t, req) + + codec := tester.GetCodec(t) + actualEncoding, err := codec.Encode(ctx, item, testutils.TestEventItem) + require.NoError(t, err) + assert.Equal(t, resp, actualEncoding) + + into := TestStruct{} + require.NoError(t, codec.Decode(ctx, actualEncoding, &into, testutils.TestEventItem)) + assert.Equal(t, item, into) + }) +} + +func FuzzCodec(f *testing.F) { + tester := &codecInterfaceTester{} + RunCodecInterfaceFuzzTests(f, tester) +} + +type codecInterfaceTester struct { + accountBytesMu sync.Mutex + accountBytes []byte + TestSelectionSupport +} + +func (it *codecInterfaceTester) Setup(_ *testing.T) {} + +func (it *codecInterfaceTester) GetAccountBytes(_ int) []byte { + it.accountBytesMu.Lock() + defer it.accountBytesMu.Unlock() + if len(it.accountBytes) != 32 { + pk, _ := solana.NewRandomPrivateKey() + it.accountBytes = pk.PublicKey().Bytes() + } + + return it.accountBytes +} + +func (it *codecInterfaceTester) GetAccountString(i int) string { + return solana.PublicKeyFromBytes(it.GetAccountBytes(i)).String() +} + +func (it *codecInterfaceTester) EncodeFields(t *testing.T, request *EncodeRequest) []byte { + if request.TestOn == TestItemType || request.TestOn == testutils.TestEventItem { + return encodeFieldsOnItem(t, request) + } + + return encodeFieldsOnSliceOrArray(t, request) +} + +func encodeFieldsOnItem(t *testing.T, request *EncodeRequest) ocr2types.Report { + buf := new(bytes.Buffer) + // The underlying TestItemAsAccount adds a discriminator by default while being Borsh encoded. + if err := testutils.EncodeRequestToTestItemAsAccount(request.TestStructs[0]).MarshalWithEncoder(bin.NewBorshEncoder(buf)); err != nil { + require.NoError(t, err) + } + return buf.Bytes() +} + +func encodeFieldsOnSliceOrArray(t *testing.T, request *EncodeRequest) []byte { + var toEncode interface{} + buf := new(bytes.Buffer) + switch request.TestOn { + case TestItemArray1Type: + toEncode = [1]testutils.TestItemAsArgs{testutils.EncodeRequestToTestItemAsArgs(request.TestStructs[0])} + case TestItemArray2Type: + toEncode = [2]testutils.TestItemAsArgs{testutils.EncodeRequestToTestItemAsArgs(request.TestStructs[0]), testutils.EncodeRequestToTestItemAsArgs(request.TestStructs[1])} + default: + // encode TestItemSliceType as instruction args (similar to accounts, but no discriminator) because accounts can't be just a vector + var itemSliceType []testutils.TestItemAsArgs + for _, req := range request.TestStructs { + itemSliceType = append(itemSliceType, testutils.EncodeRequestToTestItemAsArgs(req)) + } + toEncode = itemSliceType + } + + if err := bin.NewBorshEncoder(buf).Encode(toEncode); err != nil { + require.NoError(t, err) + } + return buf.Bytes() +} + +func (it *codecInterfaceTester) GetCodec(t *testing.T) clcommontypes.Codec { + codecConfig := codec.Config{Configs: map[string]codec.ChainConfig{}} + TestItem := CreateTestStruct[*testing.T](0, it) + for offChainName, v := range testutils.CodecDefs { + codecEntryCfg := codecConfig.Configs[offChainName] + codecEntryCfg.IDL = v.IDL + codecEntryCfg.Type = v.ItemType + codecEntryCfg.ChainSpecificName = v.IDLTypeName + + if offChainName != NilType { + codecEntryCfg.ModifierConfigs = commoncodec.ModifiersConfig{ + &commoncodec.RenameModifierConfig{Fields: map[string]string{"NestedDynamicStruct.Inner.IntVal": "I"}}, + &commoncodec.RenameModifierConfig{Fields: map[string]string{"NestedStaticStruct.Inner.IntVal": "I"}}, + } + } + + if slices.Contains([]string{TestItemType, TestItemSliceType, TestItemArray1Type, TestItemArray2Type, testutils.TestItemWithConfigExtraType, testutils.TestEventItem}, offChainName) { + addressByteModifier := &commoncodec.AddressBytesToStringModifierConfig{ + Fields: []string{"AccountStruct.AccountStr"}, + Modifier: codec.SolanaAddressModifier{}, + } + codecEntryCfg.ModifierConfigs = append(codecEntryCfg.ModifierConfigs, addressByteModifier) + } + + if offChainName == testutils.TestItemWithConfigExtraType { + hardCode := &commoncodec.HardCodeModifierConfig{ + OnChainValues: map[string]any{ + "BigField": TestItem.BigField.String(), + "AccountStruct.Account": solana.PublicKeyFromBytes(TestItem.AccountStruct.Account), + }, + OffChainValues: map[string]any{"ExtraField": anyExtraValue}, + } + codecEntryCfg.ModifierConfigs = append(codecEntryCfg.ModifierConfigs, hardCode) + } + codecConfig.Configs[offChainName] = codecEntryCfg + } + + c, err := codec.NewCodec(codecConfig) + require.NoError(t, err) + + return c +} + +func (it *codecInterfaceTester) IncludeArrayEncodingSizeEnforcement() bool { + return true +} +func (it *codecInterfaceTester) Name() string { + return "Solana" +} diff --git a/pkg/solana/codec/decoder.go b/pkg/solana/codec/decoder.go new file mode 100644 index 000000000..ec734b36a --- /dev/null +++ b/pkg/solana/codec/decoder.go @@ -0,0 +1,41 @@ +package codec + +import ( + "context" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type Decoder struct { + definitions map[string]Entry + lenientFromTypeCodec encodings.LenientCodecFromTypeCodec +} + +var _ commontypes.Decoder = &Decoder{} + +func (d *Decoder) Decode(ctx context.Context, raw []byte, into any, itemType string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("recovered from: %v, while decoding %q", r, itemType) + } + }() + + if d.lenientFromTypeCodec == nil { + d.lenientFromTypeCodec = make(encodings.LenientCodecFromTypeCodec) + for k, v := range d.definitions { + d.lenientFromTypeCodec[k] = v + } + } + + return d.lenientFromTypeCodec.Decode(ctx, raw, into, itemType) +} + +func (d *Decoder) GetMaxDecodingSize(_ context.Context, n int, itemType string) (int, error) { + codecEntry, ok := d.definitions[itemType] + if !ok { + return 0, fmt.Errorf("%w: nil entry", commontypes.ErrInvalidType) + } + return codecEntry.GetCodecType().Size(n) +} diff --git a/pkg/solana/codec/decoder_test.go b/pkg/solana/codec/decoder_test.go new file mode 100644 index 000000000..0f21c43e8 --- /dev/null +++ b/pkg/solana/codec/decoder_test.go @@ -0,0 +1,90 @@ +package codec + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + commonencodings "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +type testErrDecodeEntry struct { + entry +} + +func (t *testErrDecodeEntry) Decode(_ []byte) (interface{}, []byte, error) { + return nil, nil, fmt.Errorf("decode error") +} + +type testErrDecodeRemainingBytes struct { + entry +} + +func (t *testErrDecodeRemainingBytes) Decode(_ []byte) (interface{}, []byte, error) { + return struct{}{}, []byte{1}, nil +} + +func TestDecoder_Decode_Errors(t *testing.T) { + var into interface{} + someType := "some-type" + t.Run("error when item type not found", func(t *testing.T) { + d := &Decoder{definitions: map[string]Entry{}} + d.definitions[someType] = &entry{} + + nonExistentType := "non-existent" + err := d.Decode(tests.Context(t), []byte{}, &into, nonExistentType) + require.ErrorIs(t, err, fmt.Errorf("%w: cannot find type %s", commontypes.ErrInvalidType, nonExistentType)) + }) + + t.Run("error when underlying entry decode fails", func(t *testing.T) { + d := &Decoder{definitions: map[string]Entry{}} + d.definitions[someType] = &testErrDecodeEntry{} + require.Error(t, d.Decode(tests.Context(t), []byte{}, &into, someType)) + }) + + t.Run("remaining bytes exist after decode is ok", func(t *testing.T) { + d := &Decoder{definitions: map[string]Entry{}} + d.definitions[someType] = &testErrDecodeRemainingBytes{} + require.NoError(t, d.Decode(tests.Context(t), []byte{}, &into, someType)) + }) +} + +type testErrGetMaxDecodingSize struct { + entry +} + +type testErrGetMaxDecodingSizeCodecType struct { + commonencodings.Empty +} + +func (t testErrGetMaxDecodingSizeCodecType) Size(_ int) (int, error) { + return 0, fmt.Errorf("error") +} + +func (t *testErrGetMaxDecodingSize) GetCodecType() commonencodings.TypeCodec { + return testErrGetMaxDecodingSizeCodecType{} +} + +func TestDecoder_GetMaxDecodingSize_Errors(t *testing.T) { + someType := "some-type" + + t.Run("error when entry for item type is missing", func(t *testing.T) { + d := &Decoder{definitions: map[string]Entry{}} + d.definitions[someType] = &entry{} + + nonExistentType := "non-existent" + _, err := d.GetMaxDecodingSize(tests.Context(t), 0, nonExistentType) + require.ErrorIs(t, err, fmt.Errorf("%w: cannot find type %s", commontypes.ErrInvalidType, nonExistentType)) + }) + + t.Run("error when underlying entry decode fails", func(t *testing.T) { + d := &Decoder{definitions: map[string]Entry{}} + d.definitions[someType] = &testErrGetMaxDecodingSize{} + + _, err := d.GetMaxDecodingSize(tests.Context(t), 0, someType) + require.Error(t, err) + }) +} diff --git a/pkg/solana/codec/discriminator.go b/pkg/solana/codec/discriminator.go index f712a3f68..9bc363ae7 100644 --- a/pkg/solana/codec/discriminator.go +++ b/pkg/solana/codec/discriminator.go @@ -12,16 +12,16 @@ import ( const discriminatorLength = 8 -func NewDiscriminator(name string) encodings.TypeCodec { +func NewDiscriminator(name string) *Discriminator { sum := sha256.Sum256([]byte("account:" + name)) - return &discriminator{hashPrefix: sum[:discriminatorLength]} + return &Discriminator{hashPrefix: sum[:discriminatorLength]} } -type discriminator struct { +type Discriminator struct { hashPrefix []byte } -func (d discriminator) Encode(value any, into []byte) ([]byte, error) { +func (d Discriminator) Encode(value any, into []byte) ([]byte, error) { if value == nil { return append(into, d.hashPrefix...), nil } @@ -44,7 +44,7 @@ func (d discriminator) Encode(value any, into []byte) ([]byte, error) { return append(into, *raw...), nil } -func (d discriminator) Decode(encoded []byte) (any, []byte, error) { +func (d Discriminator) Decode(encoded []byte) (any, []byte, error) { raw, remaining, err := encodings.SafeDecode(encoded, discriminatorLength, func(raw []byte) []byte { return raw }) if err != nil { return nil, nil, err @@ -57,15 +57,15 @@ func (d discriminator) Decode(encoded []byte) (any, []byte, error) { return &raw, remaining, nil } -func (d discriminator) GetType() reflect.Type { +func (d Discriminator) GetType() reflect.Type { // Pointer type so that nil can inject values and so that the NamedCodec won't wrap with no-nil pointer. return reflect.TypeOf(&[]byte{}) } -func (d discriminator) Size(_ int) (int, error) { +func (d Discriminator) Size(_ int) (int, error) { return discriminatorLength, nil } -func (d discriminator) FixedSize() (int, error) { +func (d Discriminator) FixedSize() (int, error) { return discriminatorLength, nil } diff --git a/pkg/solana/codec/encoder.go b/pkg/solana/codec/encoder.go new file mode 100644 index 000000000..e3f734768 --- /dev/null +++ b/pkg/solana/codec/encoder.go @@ -0,0 +1,41 @@ +package codec + +import ( + "context" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type Encoder struct { + definitions map[string]Entry + lenientCodecFromTypeCodec encodings.LenientCodecFromTypeCodec +} + +var _ commontypes.Encoder = &Encoder{} + +func (e *Encoder) Encode(ctx context.Context, item any, itemType string) (res []byte, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("recovered from: %v, while encoding %q", r, itemType) + } + }() + + if e.lenientCodecFromTypeCodec == nil { + e.lenientCodecFromTypeCodec = make(encodings.LenientCodecFromTypeCodec) + for k, v := range e.definitions { + e.lenientCodecFromTypeCodec[k] = v + } + } + + return e.lenientCodecFromTypeCodec.Encode(ctx, item, itemType) +} + +func (e *Encoder) GetMaxEncodingSize(_ context.Context, n int, itemType string) (int, error) { + entry, ok := e.definitions[itemType] + if !ok { + return 0, fmt.Errorf("%w: nil entry", commontypes.ErrInvalidType) + } + return entry.GetCodecType().Size(n) +} diff --git a/pkg/solana/codec/encoder_test.go b/pkg/solana/codec/encoder_test.go new file mode 100644 index 000000000..fb098d884 --- /dev/null +++ b/pkg/solana/codec/encoder_test.go @@ -0,0 +1,103 @@ +package codec + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + commonencodings "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +type testErrEncodeEntry struct { + entry + codecType commonencodings.TypeCodec +} + +func (t *testErrEncodeEntry) Encode(_ interface{}, _ []byte) ([]byte, error) { + return nil, fmt.Errorf("encode error") +} + +func (t *testErrEncodeEntry) GetType() reflect.Type { + return commonencodings.Empty{}.GetType() +} + +type testErrEncodeTypeEntry struct { + entry + tCodec commonencodings.TypeCodec +} + +func (e *testErrEncodeTypeEntry) GetCodecType() commonencodings.TypeCodec { + return e.tCodec +} + +func TestEncoder_Encode_Errors(t *testing.T) { + someType := "some-type" + + t.Run("error when item type not found", func(t *testing.T) { + e := &Encoder{definitions: map[string]Entry{}} + _, err := e.Encode(tests.Context(t), nil, "non-existent-type") + require.Error(t, err) + require.ErrorIs(t, err, commontypes.ErrInvalidType) + require.Contains(t, err.Error(), "cannot find type non-existent-type") + }) + + t.Run("error when convert fails because of unexpected type", func(t *testing.T) { + e := &Encoder{ + definitions: map[string]Entry{ + someType: &testErrEncodeEntry{}, + }, + } + _, err := e.Encode(tests.Context(t), nil, someType) + require.Error(t, err) + }) + + t.Run("error when entry encode fails", func(t *testing.T) { + e := &Encoder{ + definitions: map[string]Entry{ + someType: &testErrEncodeEntry{codecType: commonencodings.Empty{}}, + }, + } + _, err := e.Encode(tests.Context(t), make(map[string]interface{}), someType) + require.ErrorContains(t, err, "encode error") + }) +} + +type testErrGetSize struct { + commonencodings.Empty + retType reflect.Type +} + +func (t testErrGetSize) GetType() reflect.Type { + return t.retType +} + +func (t testErrGetSize) Size(_ int) (int, error) { + return 0, fmt.Errorf("size error") +} + +func TestEncoder_GetMaxEncodingSize_Errors(t *testing.T) { + t.Run("error when entry for item type is missing", func(t *testing.T) { + e := &Encoder{definitions: map[string]Entry{}} + _, err := e.GetMaxEncodingSize(tests.Context(t), 10, "no-entry-type") + require.Error(t, err) + require.ErrorIs(t, err, commontypes.ErrInvalidType) + require.Contains(t, err.Error(), "nil entry") + }) + + t.Run("error when size calculation fails", func(t *testing.T) { + someType := "some-type" + e := &Encoder{ + definitions: map[string]Entry{ + someType: &testErrEncodeTypeEntry{tCodec: testErrGetSize{}}, + }, + } + + _, err := e.GetMaxEncodingSize(tests.Context(t), 0, someType) + require.Error(t, err) + require.Contains(t, err.Error(), "size error") + }) +} diff --git a/pkg/solana/codec/parsed_types.go b/pkg/solana/codec/parsed_types.go new file mode 100644 index 000000000..3144f6dd0 --- /dev/null +++ b/pkg/solana/codec/parsed_types.go @@ -0,0 +1,48 @@ +package codec + +import ( + "fmt" + "reflect" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type ParsedTypes struct { + EncoderDefs map[string]Entry + DecoderDefs map[string]Entry +} + +func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { + modByTypeName := map[string]commoncodec.Modifier{} + if err := AddEntries(parsed.EncoderDefs, modByTypeName); err != nil { + return nil, err + } + if err := AddEntries(parsed.DecoderDefs, modByTypeName); err != nil { + return nil, err + } + + mod, err := commoncodec.NewByItemTypeModifier(modByTypeName) + if err != nil { + return nil, err + } + underlying := &solanaCodec{ + Encoder: &Encoder{definitions: parsed.EncoderDefs}, + Decoder: &Decoder{definitions: parsed.DecoderDefs}, + ParsedTypes: parsed, + } + return commoncodec.NewModifierCodec(underlying, mod, DecoderHooks...) +} + +// AddEntries extracts the mods from entry and adds them to modByTypeName use with codec.NewByItemTypeModifier +// Since each input/output can have its own modifications, we need to keep track of them by type name +func AddEntries(defs map[string]Entry, modByTypeName map[string]commoncodec.Modifier) error { + for k, def := range defs { + modByTypeName[k] = def.Modifier() + _, err := def.Modifier().RetypeToOffChain(reflect.PointerTo(def.GetType()), k) + if err != nil { + return fmt.Errorf("%w: cannot retype %v: %w", commontypes.ErrInvalidConfig, k, err) + } + } + return nil +} diff --git a/pkg/solana/codec/solana.go b/pkg/solana/codec/solana.go index 71e2f7f06..3a19a1683 100644 --- a/pkg/solana/codec/solana.go +++ b/pkg/solana/codec/solana.go @@ -20,58 +20,148 @@ Modifiers can be provided to assist in modifying property names, adding properti package codec import ( + "encoding/json" "fmt" "math" + "reflect" "github.com/go-viper/mapstructure/v2" "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/smartcontractkit/chainlink-common/pkg/codec" - "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" - "github.com/smartcontractkit/chainlink-common/pkg/types" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commonencodings "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" ) const ( DefaultHashBitLength = 32 - unknownIDLFormat = "%w: unknown IDL type def %s" + unknownIDLFormat = "%w: unknown IDL type def %q" ) +// DecoderHooks +// // BigIntHook allows *big.Int to be represented as any integer type or a string and to go back to them. // Useful for config, or if when a model may use a go type that isn't a *big.Int when Pack expects one. // Eg: int32 in a go struct from a plugin could require a *big.Int in Pack for int24, if it fits, we shouldn't care. // SliceToArrayVerifySizeHook verifies that slices have the correct size when converting to an array // EpochToTimeHook allows multiple conversions: time.Time -> int64; int64 -> time.Time; *big.Int -> time.Time; and more -var DecoderHooks = []mapstructure.DecodeHookFunc{codec.EpochToTimeHook, codec.BigIntHook, codec.SliceToArrayVerifySizeHook} +var DecoderHooks = []mapstructure.DecodeHookFunc{commoncodec.EpochToTimeHook, commoncodec.BigIntHook, commoncodec.SliceToArrayVerifySizeHook} -func NewNamedModifierCodec(original types.RemoteCodec, itemType string, modifier codec.Modifier) (types.RemoteCodec, error) { - mod, err := codec.NewByItemTypeModifier(map[string]codec.Modifier{itemType: modifier}) - if err != nil { - return nil, err +type solanaCodec struct { + *Encoder + *Decoder + *ParsedTypes +} + +// NewCodec creates a new [commontypes.RemoteCodec] for Solana. +func NewCodec(conf Config) (commontypes.RemoteCodec, error) { + parsed := &ParsedTypes{ + EncoderDefs: map[string]Entry{}, + DecoderDefs: map[string]Entry{}, } - modCodec, err := codec.NewModifierCodec(original, mod, DecoderHooks...) + for offChainName, cfg := range conf.Configs { + var idl IDL + if err := json.Unmarshal([]byte(cfg.IDL), &idl); err != nil { + return nil, err + } + + mod, err := cfg.ModifierConfigs.ToModifier(DecoderHooks...) + if err != nil { + return nil, err + } + + definition, err := FindDefinitionFromIDL(cfg.Type, cfg.ChainSpecificName, idl) + if err != nil { + return nil, err + } + + cEntry, err := CreateCodecEntry(definition, offChainName, idl, mod) + if err != nil { + return nil, err + } + + parsed.EncoderDefs[offChainName] = cEntry + parsed.DecoderDefs[offChainName] = cEntry + } + + return parsed.ToCodec() +} + +func CreateCodecEntry(idlDefinition interface{}, offChainName string, idl IDL, mod commoncodec.Modifier) (entry Entry, err error) { + switch v := idlDefinition.(type) { + case IdlTypeDef: + entry, err = NewAccountEntry(offChainName, AccountIDLTypes{Account: v, Types: idl.Types}, true, mod, binary.LittleEndian()) + case IdlInstruction: + entry, err = NewInstructionArgsEntry(offChainName, InstructionArgsIDLTypes{Instruction: v, Types: idl.Types}, mod, binary.LittleEndian()) + case IdlEvent: + entry, err = NewEventArgsEntry(offChainName, EventIDLTypes{Event: v, Types: idl.Types}, true, mod, binary.LittleEndian()) + default: + return nil, fmt.Errorf("unknown codec IDL definition: %T", idlDefinition) + } if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create %q codec entry: %w", offChainName, err) } - _, err = modCodec.CreateType(itemType, true) + return entry, nil +} - return modCodec, err +func FindDefinitionFromIDL(cfgType ChainConfigType, chainSpecificName string, idl IDL) (interface{}, error) { + // not the most efficient way to do this, but these slices should always be very, very small + switch cfgType { + case ChainConfigTypeAccountDef: + for i := range idl.Accounts { + if idl.Accounts[i].Name == chainSpecificName { + return idl.Accounts[i], nil + } + } + return nil, fmt.Errorf("failed to find account %q in IDL", chainSpecificName) + + case ChainConfigTypeInstructionDef: + for i := range idl.Instructions { + if idl.Instructions[i].Name == chainSpecificName { + return idl.Instructions[i], nil + } + } + return nil, fmt.Errorf("failed to find instruction %q in IDL", chainSpecificName) + + case ChainConfigTypeEventDef: + for i := range idl.Events { + if idl.Events[i].Name == chainSpecificName { + return idl.Events[i], nil + } + } + return nil, fmt.Errorf("failed to find event %q in IDL", chainSpecificName) + } + return nil, fmt.Errorf("unknown type: %q", cfgType) +} + +func WrapItemType(forEncoding bool, contractName, itemType string, readType ChainConfigType) string { + if forEncoding { + return fmt.Sprintf("input.%s.%s", contractName, itemType) + } + + return fmt.Sprintf("output.%s.%s.%s", readType, contractName, itemType) +} + +// NewIDLAccountCodec is for Anchor custom types +func NewIDLAccountCodec(idl IDL, builder commonencodings.Builder) (commontypes.RemoteCodec, error) { + return newIDLCoded(idl, builder, idl.Accounts, true) } -func NewIDLInstructionsCodec(idl IDL, builder encodings.Builder) (types.RemoteCodec, error) { - typeCodecs := make(encodings.LenientCodecFromTypeCodec) - caser := cases.Title(language.English) +func NewIDLInstructionsCodec(idl IDL, builder commonencodings.Builder) (commontypes.RemoteCodec, error) { + typeCodecs := make(commonencodings.LenientCodecFromTypeCodec) refs := &codecRefs{ builder: builder, - codecs: make(map[string]encodings.TypeCodec), + codecs: make(map[string]commonencodings.TypeCodec), typeDefs: idl.Types, dependencies: make(map[string][]string), } for _, instruction := range idl.Instructions { - name, instCodec, err := asStruct(instruction.Args, refs, instruction.Name, caser, false) + name, instCodec, err := asStruct(instruction.Args, refs, instruction.Name, false, false) if err != nil { return nil, err } @@ -82,22 +172,54 @@ func NewIDLInstructionsCodec(idl IDL, builder encodings.Builder) (types.RemoteCo return typeCodecs, nil } -// NewIDLAccountCodec is for Anchor custom types -func NewIDLAccountCodec(idl IDL, builder encodings.Builder) (types.RemoteCodec, error) { - return newIDLCoded(idl, builder, idl.Accounts, true) +func NewNamedModifierCodec(original commontypes.RemoteCodec, itemType string, modifier commoncodec.Modifier) (commontypes.RemoteCodec, error) { + mod, err := commoncodec.NewByItemTypeModifier(map[string]commoncodec.Modifier{itemType: modifier}) + if err != nil { + return nil, err + } + + modCodec, err := commoncodec.NewModifierCodec(original, mod, DecoderHooks...) + if err != nil { + return nil, err + } + + _, err = modCodec.CreateType(itemType, true) + + return modCodec, err } -func NewIDLDefinedTypesCodec(idl IDL, builder encodings.Builder) (types.RemoteCodec, error) { +func NewIDLDefinedTypesCodec(idl IDL, builder commonencodings.Builder) (commontypes.RemoteCodec, error) { return newIDLCoded(idl, builder, idl.Types, false) } +func (s solanaCodec) CreateType(itemType string, forEncoding bool) (any, error) { + var itemTypes map[string]Entry + if forEncoding { + itemTypes = s.EncoderDefs + } else { + itemTypes = s.DecoderDefs + } + + def, ok := itemTypes[itemType] + if !ok { + return nil, fmt.Errorf("%w: cannot find type name %q", commontypes.ErrInvalidType, itemType) + } + + // we don't need double pointers, and they can also mess up reflection variable creation and mapstruct decode + if def.GetType().Kind() == reflect.Pointer { + return reflect.New(def.GetCodecType().GetType().Elem()).Interface(), nil + } + + return reflect.New(def.GetType()).Interface(), nil +} + func newIDLCoded( - idl IDL, builder encodings.Builder, from IdlTypeDefSlice, includeDiscriminator bool) (types.RemoteCodec, error) { - typeCodecs := make(encodings.LenientCodecFromTypeCodec) + idl IDL, builder commonencodings.Builder, from IdlTypeDefSlice, includeDiscriminator bool) (commontypes.RemoteCodec, error) { + typeCodecs := make(commonencodings.LenientCodecFromTypeCodec) refs := &codecRefs{ builder: builder, - codecs: make(map[string]encodings.TypeCodec), + codecs: make(map[string]commonencodings.TypeCodec), typeDefs: idl.Types, dependencies: make(map[string][]string), } @@ -105,11 +227,11 @@ func newIDLCoded( for _, def := range from { var ( name string - accCodec encodings.TypeCodec + accCodec commonencodings.TypeCodec err error ) - name, accCodec, err = createNamedCodec(def, refs, includeDiscriminator) + name, accCodec, err = createCodecType(def, refs, includeDiscriminator) if err != nil { return nil, err } @@ -121,32 +243,29 @@ func newIDLCoded( } type codecRefs struct { - builder encodings.Builder - codecs map[string]encodings.TypeCodec + builder commonencodings.Builder + codecs map[string]commonencodings.TypeCodec typeDefs IdlTypeDefSlice dependencies map[string][]string } -func createNamedCodec( +func createCodecType( def IdlTypeDef, refs *codecRefs, includeDiscriminator bool, -) (string, encodings.TypeCodec, error) { - caser := cases.Title(language.English) +) (string, commonencodings.TypeCodec, error) { name := def.Name - switch def.Type.Kind { case IdlTypeDefTyKindStruct: - return asStruct(*def.Type.Fields, refs, name, caser, includeDiscriminator) + return asStruct(*def.Type.Fields, refs, name, includeDiscriminator, false) case IdlTypeDefTyKindEnum: variants := def.Type.Variants if !variants.IsAllUint8() { - return name, nil, fmt.Errorf("%w: variants are not supported", types.ErrInvalidConfig) + return name, nil, fmt.Errorf("%w: variants are not supported", commontypes.ErrInvalidConfig) } - return name, refs.builder.Uint8(), nil default: - return name, nil, fmt.Errorf(unknownIDLFormat, types.ErrInvalidConfig, def.Type.Kind) + return name, nil, fmt.Errorf(unknownIDLFormat, commontypes.ErrInvalidConfig, def.Type.Kind) } } @@ -154,17 +273,18 @@ func asStruct( fields []IdlField, refs *codecRefs, name string, // name is the struct name and can be used in dependency checks - caser cases.Caser, includeDiscriminator bool, -) (string, encodings.TypeCodec, error) { + isInstructionArgs bool, +) (string, commonencodings.TypeCodec, error) { desLen := 0 if includeDiscriminator { desLen = 1 } - named := make([]encodings.NamedTypeCodec, len(fields)+desLen) + + named := make([]commonencodings.NamedTypeCodec, len(fields)+desLen) if includeDiscriminator { - named[0] = encodings.NamedTypeCodec{Name: "Discriminator" + name, Codec: NewDiscriminator(name)} + named[0] = commonencodings.NamedTypeCodec{Name: "Discriminator" + name, Codec: NewDiscriminator(name)} } for idx, field := range fields { @@ -175,10 +295,15 @@ func asStruct( return name, nil, err } - named[idx+desLen] = encodings.NamedTypeCodec{Name: caser.String(fieldName), Codec: typedCodec} + named[idx+desLen] = commonencodings.NamedTypeCodec{Name: cases.Title(language.English, cases.NoLower).String(fieldName), Codec: typedCodec} + } + + // accounts have to be in a struct, instruction args don't if they're an array + if len(named) == 1 && isInstructionArgs && (fields[0].Type.IsIdlTypeVec() || fields[0].Type.IsArray()) { + return name, named[0].Codec, nil } - structCodec, err := encodings.NewStructCodec(named) + structCodec, err := commonencodings.NewStructCodec(named) if err != nil { return name, nil, err } @@ -186,7 +311,7 @@ func asStruct( return name, structCodec, nil } -func processFieldType(parentTypeName string, idlType IdlType, refs *codecRefs) (encodings.TypeCodec, error) { +func processFieldType(parentTypeName string, idlType IdlType, refs *codecRefs) (commonencodings.TypeCodec, error) { switch true { case idlType.IsString(): return getCodecByStringType(idlType.GetString(), refs.builder) @@ -201,13 +326,13 @@ func processFieldType(parentTypeName string, idlType IdlType, refs *codecRefs) ( case idlType.IsIdlTypeVec(): return asVec(parentTypeName, idlType.GetIdlTypeVec(), refs) default: - return nil, fmt.Errorf("%w: unknown IDL type def", types.ErrInvalidConfig) + return nil, fmt.Errorf("%w: unknown IDL type def", commontypes.ErrInvalidConfig) } } -func asDefined(parentTypeName string, definedName *IdlTypeDefined, refs *codecRefs) (encodings.TypeCodec, error) { +func asDefined(parentTypeName string, definedName *IdlTypeDefined, refs *codecRefs) (commonencodings.TypeCodec, error) { if definedName == nil { - return nil, fmt.Errorf("%w: defined type name should not be nil", types.ErrInvalidConfig) + return nil, fmt.Errorf("%w: defined type name should not be nil", commontypes.ErrInvalidConfig) } // already exists as a type in the typed codecs @@ -217,19 +342,19 @@ func asDefined(parentTypeName string, definedName *IdlTypeDefined, refs *codecRe // nextDef should not have a dependency on definedName if !validDependency(refs, parentTypeName, definedName.Defined) { - return nil, fmt.Errorf("%w: circular dependency detected on %s -> %s relation", types.ErrInvalidConfig, parentTypeName, definedName.Defined) + return nil, fmt.Errorf("%w: circular dependency detected on %q -> %q relation", commontypes.ErrInvalidConfig, parentTypeName, definedName.Defined) } // codec by defined type doesn't exist // process it using the provided typeDefs nextDef := refs.typeDefs.GetByName(definedName.Defined) if nextDef == nil { - return nil, fmt.Errorf("%w: IDL type does not exist for name %s", types.ErrInvalidConfig, definedName.Defined) + return nil, fmt.Errorf("%w: IDL type does not exist for name %q", commontypes.ErrInvalidConfig, definedName.Defined) } saveDependency(refs, parentTypeName, definedName.Defined) - newTypeName, newTypeCodec, err := createNamedCodec(*nextDef, refs, false) + newTypeName, newTypeCodec, err := createCodecType(*nextDef, refs, false) if err != nil { return nil, err } @@ -240,16 +365,16 @@ func asDefined(parentTypeName string, definedName *IdlTypeDefined, refs *codecRe return newTypeCodec, nil } -func asArray(parentTypeName string, idlArray *IdlTypeArray, refs *codecRefs) (encodings.TypeCodec, error) { +func asArray(parentTypeName string, idlArray *IdlTypeArray, refs *codecRefs) (commonencodings.TypeCodec, error) { codec, err := processFieldType(parentTypeName, idlArray.Thing, refs) if err != nil { return nil, err } - return encodings.NewArray(idlArray.Num, codec) + return commonencodings.NewArray(idlArray.Num, codec) } -func asVec(parentTypeName string, idlVec *IdlTypeVec, refs *codecRefs) (encodings.TypeCodec, error) { +func asVec(parentTypeName string, idlVec *IdlTypeVec, refs *codecRefs) (commonencodings.TypeCodec, error) { codec, err := processFieldType(parentTypeName, idlVec.Vec, refs) if err != nil { return nil, err @@ -260,10 +385,10 @@ func asVec(parentTypeName string, idlVec *IdlTypeVec, refs *codecRefs) (encoding return nil, err } - return encodings.NewSlice(codec, b) + return commonencodings.NewSlice(codec, b) } -func getCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { +func getCodecByStringType(curType IdlTypeAsString, builder commonencodings.Builder) (commonencodings.TypeCodec, error) { switch curType { case IdlTypeBool: return builder.Bool(), nil @@ -278,11 +403,11 @@ func getCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (e case IdlTypeBytes, IdlTypePublicKey, IdlTypeHash: return getByteCodecByStringType(curType, builder) default: - return nil, fmt.Errorf(unknownIDLFormat, types.ErrInvalidConfig, curType) + return nil, fmt.Errorf(unknownIDLFormat, commontypes.ErrInvalidConfig, curType) } } -func getIntCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { +func getIntCodecByStringType(curType IdlTypeAsString, builder commonencodings.Builder) (commonencodings.TypeCodec, error) { switch curType { case IdlTypeI8: return builder.Int8(), nil @@ -295,11 +420,11 @@ func getIntCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) case IdlTypeI128: return builder.BigInt(16, true) default: - return nil, fmt.Errorf(unknownIDLFormat, types.ErrInvalidConfig, curType) + return nil, fmt.Errorf(unknownIDLFormat, commontypes.ErrInvalidConfig, curType) } } -func getUIntCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { +func getUIntCodecByStringType(curType IdlTypeAsString, builder commonencodings.Builder) (commonencodings.TypeCodec, error) { switch curType { case IdlTypeU8: return builder.Uint8(), nil @@ -312,22 +437,22 @@ func getUIntCodecByStringType(curType IdlTypeAsString, builder encodings.Builder case IdlTypeU128: return builder.BigInt(16, true) default: - return nil, fmt.Errorf(unknownIDLFormat, types.ErrInvalidConfig, curType) + return nil, fmt.Errorf(unknownIDLFormat, commontypes.ErrInvalidConfig, curType) } } -func getTimeCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { +func getTimeCodecByStringType(curType IdlTypeAsString, builder commonencodings.Builder) (commonencodings.TypeCodec, error) { switch curType { case IdlTypeUnixTimestamp: return builder.Int64(), nil case IdlTypeDuration: return NewDuration(builder), nil default: - return nil, fmt.Errorf(unknownIDLFormat, types.ErrInvalidConfig, curType) + return nil, fmt.Errorf(unknownIDLFormat, commontypes.ErrInvalidConfig, curType) } } -func getByteCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { +func getByteCodecByStringType(curType IdlTypeAsString, builder commonencodings.Builder) (commonencodings.TypeCodec, error) { switch curType { case IdlTypeBytes: b, err := builder.Int(4) @@ -335,11 +460,11 @@ func getByteCodecByStringType(curType IdlTypeAsString, builder encodings.Builder return nil, err } - return encodings.NewSlice(builder.Uint8(), b) + return commonencodings.NewSlice(builder.Uint8(), b) case IdlTypePublicKey, IdlTypeHash: - return encodings.NewArray(DefaultHashBitLength, builder.Uint8()) + return commonencodings.NewArray(DefaultHashBitLength, builder.Uint8()) default: - return nil, fmt.Errorf(unknownIDLFormat, types.ErrInvalidConfig, curType) + return nil, fmt.Errorf(unknownIDLFormat, commontypes.ErrInvalidConfig, curType) } } diff --git a/pkg/solana/codec/testutils/eventItemTypeIDL.json b/pkg/solana/codec/testutils/eventItemTypeIDL.json new file mode 100644 index 000000000..f98f27671 --- /dev/null +++ b/pkg/solana/codec/testutils/eventItemTypeIDL.json @@ -0,0 +1,73 @@ +{ + "version": "0.1.0", + "name": "test_item_event_type", + "instructions": [], + "events": [ + { + "name": "TestItem", + "fields": [ + { "name": "Field", "type": "i32" }, + { "name": "OracleId", "type": "u8" }, + { "name": "OracleIds", "type": { "array": ["u8", 32] } }, + { "name": "AccountStruct", "type": { "defined": "AccountStruct" } }, + { "name": "Accounts", "type": { "vec": "publicKey" } }, + { "name": "DifferentField", "type": "string" }, + { "name": "BigField", "type": "i128" }, + { "name": "NestedDynamicStruct", "type": { "defined": "NestedDynamic" } }, + { "name": "NestedStaticStruct", "type": { "defined": "NestedStatic" } } + ] + } + ], + "types": [ + { + "name": "AccountStruct", + "type": { + "kind": "struct", + "fields": [ + { "name": "Account", "type": "publicKey" }, + { "name": "AccountStr", "type": "publicKey" } + ] + } + }, + { + "name": "InnerDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "S", "type": "string" } + ] + } + }, + { + "name": "NestedDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerDynamic" } } + ] + } + }, + { + "name": "InnerStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "A", "type": "publicKey" } + ] + } + }, + { + "name": "NestedStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerStatic" } } + ] + } + } + ] +} diff --git a/pkg/solana/codec/testutils/itemArray1TypeIDL.json b/pkg/solana/codec/testutils/itemArray1TypeIDL.json new file mode 100644 index 000000000..8d061aed2 --- /dev/null +++ b/pkg/solana/codec/testutils/itemArray1TypeIDL.json @@ -0,0 +1,92 @@ +{ + "version": "0.1.0", + "name": "test_item_array1", + "instructions": [ + { + "name": "TestItemArray1Type", + "accounts": [], + "args": [ + { + "name": "TestItemArray1Type", + "type": { + "array": [ + { + "defined": "TestItem" + }, + 1 + ] + } + } + ] + } + ], + "types": [ + { + "name": "TestItem", + "type": { + "kind": "struct", + "fields": [ + { "name": "Field", "type": "i32" }, + { "name": "OracleId", "type": "u8" }, + { "name": "OracleIds", "type": { "array": ["u8", 32] } }, + { "name": "AccountStruct", "type": { "defined": "AccountStruct" } }, + { "name": "Accounts", "type": { "vec": "publicKey" } }, + { "name": "DifferentField", "type": "string" }, + { "name": "BigField", "type": "i128" }, + { "name": "NestedDynamicStruct", "type": { "defined": "NestedDynamic" } }, + { "name": "NestedStaticStruct", "type": { "defined": "NestedStatic" } } + ] + } + }, + { + "name": "AccountStruct", + "type": { + "kind": "struct", + "fields": [ + { "name": "Account", "type": "publicKey" }, + { "name": "AccountStr", "type": "publicKey" } + ] + } + }, + { + "name": "InnerDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "S", "type": "string" } + ] + } + }, + { + "name": "NestedDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerDynamic" } } + ] + } + }, + { + "name": "InnerStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "A", "type": "publicKey" } + ] + } + }, + { + "name": "NestedStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerStatic" } } + ] + } + } + ] +} \ No newline at end of file diff --git a/pkg/solana/codec/testutils/itemArray2TypeIDL.json b/pkg/solana/codec/testutils/itemArray2TypeIDL.json new file mode 100644 index 000000000..c20c55785 --- /dev/null +++ b/pkg/solana/codec/testutils/itemArray2TypeIDL.json @@ -0,0 +1,92 @@ +{ + "version": "0.1.0", + "name": "test_item_array2", + "instructions": [ + { + "name": "TestItemArray2Type", + "accounts": [], + "args": [ + { + "name": "TestItemArray2Type", + "type": { + "array": [ + { + "defined": "TestItem" + }, + 2 + ] + } + } + ] + } + ], + "types": [ + { + "name": "TestItem", + "type": { + "kind": "struct", + "fields": [ + { "name": "Field", "type": "i32" }, + { "name": "OracleId", "type": "u8" }, + { "name": "OracleIds", "type": { "array": ["u8", 32] } }, + { "name": "AccountStruct", "type": { "defined": "AccountStruct" } }, + { "name": "Accounts", "type": { "vec": "publicKey" } }, + { "name": "DifferentField", "type": "string" }, + { "name": "BigField", "type": "i128" }, + { "name": "NestedDynamicStruct", "type": { "defined": "NestedDynamic" } }, + { "name": "NestedStaticStruct", "type": { "defined": "NestedStatic" } } + ] + } + }, + { + "name": "AccountStruct", + "type": { + "kind": "struct", + "fields": [ + { "name": "Account", "type": "publicKey" }, + { "name": "AccountStr", "type": "publicKey" } + ] + } + }, + { + "name": "InnerDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "S", "type": "string" } + ] + } + }, + { + "name": "NestedDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerDynamic" } } + ] + } + }, + { + "name": "InnerStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "A", "type": "publicKey" } + ] + } + }, + { + "name": "NestedStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerStatic" } } + ] + } + } + ] +} diff --git a/pkg/solana/codec/testutils/itemIDL.json b/pkg/solana/codec/testutils/itemIDL.json new file mode 100644 index 000000000..ee2a719cc --- /dev/null +++ b/pkg/solana/codec/testutils/itemIDL.json @@ -0,0 +1,77 @@ +{ + "version": "0.1.0", + "name": "test_item_type", + "instructions": [ + ], + "accounts": [ + { + "name": "TestItem", + "type": { + "kind": "struct", + "fields": [ + { "name": "Field", "type": "i32" }, + { "name": "OracleId", "type": "u8" }, + { "name": "OracleIds", "type": { "array": ["u8", 32] } }, + { "name": "AccountStruct", "type": { "defined": "AccountStruct" } }, + { "name": "Accounts", "type": { "vec": "publicKey" } }, + { "name": "DifferentField", "type": "string" }, + { "name": "BigField", "type": "i128" }, + { "name": "NestedDynamicStruct", "type": { "defined": "NestedDynamic" } }, + { "name": "NestedStaticStruct", "type": { "defined": "NestedStatic" } } + ] + } + } + ], + "types": [ + { + "name": "AccountStruct", + "type": { + "kind": "struct", + "fields": [ + { "name": "Account", "type": "publicKey" }, + { "name": "AccountStr", "type": "publicKey" } + ] + } + }, + { + "name": "InnerDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "S", "type": "string" } + ] + } + }, + { + "name": "NestedDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerDynamic" } } + ] + } + }, + { + "name": "InnerStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "A", "type": "publicKey" } + ] + } + }, + { + "name": "NestedStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerStatic" } } + ] + } + } + ] +} diff --git a/pkg/solana/codec/testutils/itemSliceTypeIDL.json b/pkg/solana/codec/testutils/itemSliceTypeIDL.json new file mode 100644 index 000000000..491d3e12f --- /dev/null +++ b/pkg/solana/codec/testutils/itemSliceTypeIDL.json @@ -0,0 +1,89 @@ +{ + "version": "0.1.0", + "name": "test_item_slice_type", + "instructions": [ + { + "name": "TestItemSliceType", + "accounts": [], + "args": [ + { + "name": "TestItemSliceType", + "type": { + "vec": { + "defined": "TestItem" + } + } + } + ] + } + ], + "types": [ + { + "name": "TestItem", + "type": { + "kind": "struct", + "fields": [ + { "name": "Field", "type": "i32" }, + { "name": "OracleId", "type": "u8" }, + { "name": "OracleIds", "type": { "array": ["u8", 32] } }, + { "name": "AccountStruct", "type": { "defined": "AccountStruct" } }, + { "name": "Accounts", "type": { "vec": "publicKey" } }, + { "name": "DifferentField", "type": "string" }, + { "name": "BigField", "type": "i128" }, + { "name": "NestedDynamicStruct", "type": { "defined": "NestedDynamic" } }, + { "name": "NestedStaticStruct", "type": { "defined": "NestedStatic" } } + ] + } + }, + { + "name": "AccountStruct", + "type": { + "kind": "struct", + "fields": [ + { "name": "Account", "type": "publicKey" }, + { "name": "AccountStr", "type": "publicKey" } + ] + } + }, + { + "name": "InnerDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "S", "type": "string" } + ] + } + }, + { + "name": "NestedDynamic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerDynamic" } } + ] + } + }, + { + "name": "InnerStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "IntVal", "type": "i64" }, + { "name": "A", "type": "publicKey" } + ] + } + }, + { + "name": "NestedStatic", + "type": { + "kind": "struct", + "fields": [ + { "name": "FixedBytes", "type": { "array": ["u8", 2] } }, + { "name": "Inner", "type": { "defined": "InnerStatic" } } + ] + } + } + ] +} diff --git a/pkg/solana/codec/testutils/nilTypeIDL.json b/pkg/solana/codec/testutils/nilTypeIDL.json new file mode 100644 index 000000000..47b169428 --- /dev/null +++ b/pkg/solana/codec/testutils/nilTypeIDL.json @@ -0,0 +1,12 @@ +{ + "name": "NilType", + "accounts": [ + { + "name": "NilType", + "type": { + "kind": "struct", + "fields": [] + } + } + ] +} \ No newline at end of file diff --git a/pkg/solana/codec/testutils/sizeItemTypeIDL.json b/pkg/solana/codec/testutils/sizeItemTypeIDL.json new file mode 100644 index 000000000..fdb73115e --- /dev/null +++ b/pkg/solana/codec/testutils/sizeItemTypeIDL.json @@ -0,0 +1,38 @@ +{ + "version": "0.1.0", + "name": "item_for_size", + "instructions": [ + { + "name": "ProcessItemForSize", + "accounts": [ + { + "name": "ItemForSize", + "isMut": true, + "isSigner": false + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "ItemForSize", + "type": { + "kind": "struct", + "fields": [ + { + "name": "Stuff", + "type": { + "vec": "i128" + } + }, + { + "name": "OtherStuff", + "type": "i128" + } + ] + } + } + ], + "types": [] +} diff --git a/pkg/solana/codec/testutils/types.go b/pkg/solana/codec/testutils/types.go index 533e88b0b..3c52adb0f 100644 --- a/pkg/solana/codec/testutils/types.go +++ b/pkg/solana/codec/testutils/types.go @@ -2,10 +2,16 @@ package testutils import ( _ "embed" + "fmt" "math/big" "time" - ag_solana "github.com/gagliardetto/solana-go" + agbinary "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + + "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" ) var ( @@ -45,9 +51,11 @@ var ( BasicVector: []string{"some string", "another string"}, TimeVal: 683_100_000, DurationVal: 42 * time.Second, - PublicKey: ag_solana.NewWallet().PublicKey(), + PublicKey: solana.NewWallet().PublicKey(), EnumVal: 0, } + TestItemWithConfigExtraType = "TestItemWithConfigExtra" + TestEventItem = "TestEventItem" ) type StructWithNestedStruct struct { @@ -59,7 +67,7 @@ type StructWithNestedStruct struct { BasicVector []string TimeVal int64 DurationVal time.Duration - PublicKey ag_solana.PublicKey + PublicKey solana.PublicKey EnumVal uint8 } @@ -86,3 +94,550 @@ var JSONIDLWithAllTypes string //go:embed circularDepIDL.json var CircularDepIDL string + +//go:embed itemIDL.json +var itemTypeJSONIDL string + +//go:embed eventItemTypeIDL.json +var eventItemTypeJSONIDL string + +//go:embed itemSliceTypeIDL.json +var itemSliceTypeJSONIDL string + +//go:embed itemArray1TypeIDL.json +var itemArray1TypeJSONIDL string + +//go:embed itemArray2TypeIDL.json +var itemArray2TypeJSONIDL string + +//go:embed nilTypeIDL.json +var nilTypeJSONIDL string + +type CodecDef struct { + IDL string + IDLTypeName string + ItemType codec.ChainConfigType +} + +// CodecDefs key is codec offchain type name +var CodecDefs = map[string]CodecDef{ + interfacetests.TestItemType: { + IDL: itemTypeJSONIDL, + IDLTypeName: interfacetests.TestItemType, + ItemType: codec.ChainConfigTypeAccountDef, + }, + interfacetests.TestItemSliceType: { + IDL: itemSliceTypeJSONIDL, + IDLTypeName: interfacetests.TestItemSliceType, + ItemType: codec.ChainConfigTypeInstructionDef, + }, + interfacetests.TestItemArray1Type: { + IDL: itemArray1TypeJSONIDL, + IDLTypeName: interfacetests.TestItemArray1Type, + ItemType: codec.ChainConfigTypeInstructionDef, + }, + interfacetests.TestItemArray2Type: { + IDL: itemArray2TypeJSONIDL, + IDLTypeName: interfacetests.TestItemArray2Type, + ItemType: codec.ChainConfigTypeInstructionDef, + }, + TestItemWithConfigExtraType: { + IDL: itemTypeJSONIDL, + IDLTypeName: interfacetests.TestItemType, + ItemType: codec.ChainConfigTypeAccountDef, + }, + interfacetests.NilType: { + IDL: nilTypeJSONIDL, + IDLTypeName: interfacetests.NilType, + ItemType: codec.ChainConfigTypeAccountDef, + }, + TestEventItem: { + IDL: eventItemTypeJSONIDL, + IDLTypeName: interfacetests.TestItemType, + ItemType: codec.ChainConfigTypeEventDef, + }, +} + +type TestItemAsAccount struct { + Field int32 + OracleID uint8 + OracleIDs [32]uint8 + AccountStruct AccountStruct + Accounts []solana.PublicKey + DifferentField string + BigField agbinary.Int128 + NestedDynamicStruct NestedDynamic + NestedStaticStruct NestedStatic +} + +var TestItemDiscriminator = [8]byte{148, 105, 105, 155, 26, 167, 212, 149} + +func (obj TestItemAsAccount) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Write account discriminator: + err = encoder.WriteBytes(TestItemDiscriminator[:], false) + if err != nil { + return err + } + // Serialize `Field` param: + err = encoder.Encode(obj.Field) + if err != nil { + return err + } + // Serialize `OracleID` param: + err = encoder.Encode(obj.OracleID) + if err != nil { + return err + } + // Serialize `OracleIDs` param: + err = encoder.Encode(obj.OracleIDs) + if err != nil { + return err + } + // Serialize `AccountStruct` param: + err = encoder.Encode(obj.AccountStruct) + if err != nil { + return err + } + // Serialize `Accounts` param: + err = encoder.Encode(obj.Accounts) + if err != nil { + return err + } + // Serialize `DifferentField` param: + err = encoder.Encode(obj.DifferentField) + if err != nil { + return err + } + // Serialize `BigField` param: + err = encoder.Encode(obj.BigField) + if err != nil { + return err + } + // Serialize `NestedDynamicStruct` param: + err = encoder.Encode(obj.NestedDynamicStruct) + if err != nil { + return err + } + // Serialize `NestedStaticStruct` param: + err = encoder.Encode(obj.NestedStaticStruct) + if err != nil { + return err + } + return nil +} + +func (obj *TestItemAsAccount) UnmarshalWithDecoder(decoder *agbinary.Decoder) error { + // Read and check account discriminator: + { + discriminator, err := decoder.ReadTypeID() + if err != nil { + return err + } + if !discriminator.Equal(TestItemDiscriminator[:]) { + return fmt.Errorf( + "wrong discriminator: wanted %s, got %s", + "[148 105 105 155 26 167 212 149]", + fmt.Sprint(discriminator[:])) + } + } + // Deserialize `Field`: + err := decoder.Decode(&obj.Field) + if err != nil { + return err + } + // Deserialize `OracleID`: + err = decoder.Decode(&obj.OracleID) + if err != nil { + return err + } + // Deserialize `OracleIDs`: + err = decoder.Decode(&obj.OracleIDs) + if err != nil { + return err + } + // Deserialize `AccountStruct`: + err = decoder.Decode(&obj.AccountStruct) + if err != nil { + return err + } + // Deserialize `Accounts`: + err = decoder.Decode(&obj.Accounts) + if err != nil { + return err + } + // Deserialize `DifferentField`: + err = decoder.Decode(&obj.DifferentField) + if err != nil { + return err + } + // Deserialize `BigField`: + err = decoder.Decode(&obj.BigField) + if err != nil { + return err + } + // Deserialize `NestedDynamicStruct`: + err = decoder.Decode(&obj.NestedDynamicStruct) + if err != nil { + return err + } + // Deserialize `NestedStaticStruct`: + err = decoder.Decode(&obj.NestedStaticStruct) + if err != nil { + return err + } + return nil +} + +type TestItemAsArgs struct { + Field int32 + OracleID uint8 + OracleIDs [32]uint8 + AccountStruct AccountStruct + Accounts []solana.PublicKey + DifferentField string + BigField agbinary.Int128 + NestedDynamicStruct NestedDynamic + NestedStaticStruct NestedStatic +} + +func (obj TestItemAsArgs) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Serialize `Field` param: + err = encoder.Encode(obj.Field) + if err != nil { + return err + } + // Serialize `OracleID` param: + err = encoder.Encode(obj.OracleID) + if err != nil { + return err + } + // Serialize `OracleIDs` param: + err = encoder.Encode(obj.OracleIDs) + if err != nil { + return err + } + // Serialize `AccountStruct` param: + err = encoder.Encode(obj.AccountStruct) + if err != nil { + return err + } + // Serialize `Accounts` param: + err = encoder.Encode(obj.Accounts) + if err != nil { + return err + } + // Serialize `DifferentField` param: + err = encoder.Encode(obj.DifferentField) + if err != nil { + return err + } + // Serialize `BigField` param: + err = encoder.Encode(obj.BigField) + if err != nil { + return err + } + // Serialize `NestedDynamicStruct` param: + err = encoder.Encode(obj.NestedDynamicStruct) + if err != nil { + return err + } + // Serialize `NestedStaticStruct` param: + err = encoder.Encode(obj.NestedStaticStruct) + if err != nil { + return err + } + return nil +} + +func (obj *TestItemAsArgs) UnmarshalWithDecoder(decoder *agbinary.Decoder) (err error) { + // Deserialize `Field`: + err = decoder.Decode(&obj.Field) + if err != nil { + return err + } + // Deserialize `OracleID`: + err = decoder.Decode(&obj.OracleID) + if err != nil { + return err + } + // Deserialize `OracleIDs`: + err = decoder.Decode(&obj.OracleIDs) + if err != nil { + return err + } + // Deserialize `AccountStruct`: + err = decoder.Decode(&obj.AccountStruct) + if err != nil { + return err + } + // Deserialize `Accounts`: + err = decoder.Decode(&obj.Accounts) + if err != nil { + return err + } + // Deserialize `DifferentField`: + err = decoder.Decode(&obj.DifferentField) + if err != nil { + return err + } + // Deserialize `BigField`: + err = decoder.Decode(&obj.BigField) + if err != nil { + return err + } + // Deserialize `NestedDynamicStruct`: + err = decoder.Decode(&obj.NestedDynamicStruct) + if err != nil { + return err + } + // Deserialize `NestedStaticStruct`: + err = decoder.Decode(&obj.NestedStaticStruct) + if err != nil { + return err + } + return nil +} + +type AccountStruct struct { + Account solana.PublicKey + AccountStr solana.PublicKey +} + +func (obj AccountStruct) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Serialize `Account` param: + err = encoder.Encode(obj.Account) + if err != nil { + return err + } + // Serialize `AccountStr` param: + err = encoder.Encode(obj.AccountStr) + if err != nil { + return err + } + return nil +} + +func (obj *AccountStruct) UnmarshalWithDecoder(decoder *agbinary.Decoder) (err error) { + // Deserialize `Account`: + err = decoder.Decode(&obj.Account) + if err != nil { + return err + } + // Deserialize `AccountStr`: + err = decoder.Decode(&obj.AccountStr) + if err != nil { + return err + } + return nil +} + +type InnerDynamic struct { + IntVal int64 + S string +} + +func (obj InnerDynamic) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Serialize `IntVal` param: + err = encoder.Encode(obj.IntVal) + if err != nil { + return err + } + // Serialize `S` param: + err = encoder.Encode(obj.S) + if err != nil { + return err + } + return nil +} + +func (obj *InnerDynamic) UnmarshalWithDecoder(decoder *agbinary.Decoder) (err error) { + // Deserialize `IntVal`: + err = decoder.Decode(&obj.IntVal) + if err != nil { + return err + } + // Deserialize `S`: + err = decoder.Decode(&obj.S) + if err != nil { + return err + } + return nil +} + +type NestedDynamic struct { + FixedBytes [2]uint8 + Inner InnerDynamic +} + +func (obj NestedDynamic) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Serialize `FixedBytes` param: + err = encoder.Encode(obj.FixedBytes) + if err != nil { + return err + } + // Serialize `Inner` param: + err = encoder.Encode(obj.Inner) + if err != nil { + return err + } + return nil +} + +func (obj *NestedDynamic) UnmarshalWithDecoder(decoder *agbinary.Decoder) (err error) { + // Deserialize `FixedBytes`: + err = decoder.Decode(&obj.FixedBytes) + if err != nil { + return err + } + // Deserialize `Inner`: + err = decoder.Decode(&obj.Inner) + if err != nil { + return err + } + return nil +} + +type InnerStatic struct { + IntVal int64 + A solana.PublicKey +} + +func (obj InnerStatic) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Serialize `IntVal` param: + err = encoder.Encode(obj.IntVal) + if err != nil { + return err + } + // Serialize `A` param: + err = encoder.Encode(obj.A) + if err != nil { + return err + } + return nil +} + +func (obj *InnerStatic) UnmarshalWithDecoder(decoder *agbinary.Decoder) (err error) { + // Deserialize `IntVal`: + err = decoder.Decode(&obj.IntVal) + if err != nil { + return err + } + // Deserialize `A`: + err = decoder.Decode(&obj.A) + if err != nil { + return err + } + return nil +} + +type NestedStatic struct { + FixedBytes [2]uint8 + Inner InnerStatic +} + +func (obj NestedStatic) MarshalWithEncoder(encoder *agbinary.Encoder) (err error) { + // Serialize `FixedBytes` param: + err = encoder.Encode(obj.FixedBytes) + if err != nil { + return err + } + // Serialize `Inner` param: + err = encoder.Encode(obj.Inner) + if err != nil { + return err + } + return nil +} + +func (obj *NestedStatic) UnmarshalWithDecoder(decoder *agbinary.Decoder) (err error) { + // Deserialize `FixedBytes`: + err = decoder.Decode(&obj.FixedBytes) + if err != nil { + return err + } + // Deserialize `Inner`: + err = decoder.Decode(&obj.Inner) + if err != nil { + return err + } + return nil +} + +func EncodeRequestToTestItemAsAccount(testStruct interfacetests.TestStruct) TestItemAsAccount { + return TestItemAsAccount{ + Field: *testStruct.Field, + OracleID: uint8(testStruct.OracleID), + OracleIDs: getOracleIDs(testStruct), + AccountStruct: getAccountStruct(testStruct), + Accounts: getAccounts(testStruct), + DifferentField: testStruct.DifferentField, + BigField: bigIntToBinInt128(testStruct.BigField), + NestedDynamicStruct: getNestedDynamic(testStruct), + NestedStaticStruct: getNestedStatic(testStruct), + } +} + +func EncodeRequestToTestItemAsArgs(testStruct interfacetests.TestStruct) TestItemAsArgs { + return TestItemAsArgs{ + Field: *testStruct.Field, + OracleID: uint8(testStruct.OracleID), + OracleIDs: getOracleIDs(testStruct), + AccountStruct: getAccountStruct(testStruct), + Accounts: getAccounts(testStruct), + DifferentField: testStruct.DifferentField, + BigField: bigIntToBinInt128(testStruct.BigField), + NestedDynamicStruct: getNestedDynamic(testStruct), + NestedStaticStruct: getNestedStatic(testStruct), + } +} + +func getOracleIDs(testStruct interfacetests.TestStruct) [32]byte { + var oracleIDs [32]byte + for i, v := range testStruct.OracleIDs { + oracleIDs[i] = byte(v) + } + return oracleIDs +} + +func getAccountStruct(testStruct interfacetests.TestStruct) AccountStruct { + k, _ := solana.PublicKeyFromBase58(testStruct.AccountStruct.AccountStr) + return AccountStruct{ + Account: solana.PublicKeyFromBytes(testStruct.AccountStruct.Account), + AccountStr: k, + } +} + +func getAccounts(testStruct interfacetests.TestStruct) []solana.PublicKey { + accs := make([]solana.PublicKey, len(testStruct.Accounts)) + for i, v := range testStruct.Accounts { + accs[i] = solana.PublicKeyFromBytes(v) + } + return accs +} + +func getNestedDynamic(testStruct interfacetests.TestStruct) NestedDynamic { + return NestedDynamic{ + FixedBytes: testStruct.NestedDynamicStruct.FixedBytes, + Inner: InnerDynamic{ + IntVal: int64(testStruct.NestedDynamicStruct.Inner.I), + S: testStruct.NestedDynamicStruct.Inner.S, + }, + } +} + +func getNestedStatic(testStruct interfacetests.TestStruct) NestedStatic { + return NestedStatic{ + FixedBytes: testStruct.NestedStaticStruct.FixedBytes, + Inner: InnerStatic{ + IntVal: int64(testStruct.NestedStaticStruct.Inner.I), + A: solana.PublicKeyFromBytes(testStruct.NestedStaticStruct.Inner.A), + }, + } +} + +func bigIntToBinInt128(val *big.Int) agbinary.Int128 { + return agbinary.Int128{ + Lo: val.Uint64(), + Hi: new(big.Int).Rsh(val, 64).Uint64(), + } +} diff --git a/pkg/solana/codec/types.go b/pkg/solana/codec/types.go new file mode 100644 index 000000000..40852398e --- /dev/null +++ b/pkg/solana/codec/types.go @@ -0,0 +1,24 @@ +package codec + +import commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + +type ChainConfigType string + +const ( + ChainConfigTypeAccountDef ChainConfigType = "account" + ChainConfigTypeInstructionDef ChainConfigType = "instruction" + ChainConfigTypeEventDef ChainConfigType = "event" +) + +type Config struct { + // Configs key is the type's offChainName for the codec + Configs map[string]ChainConfig `json:"configs" toml:"configs"` +} + +type ChainConfig struct { + IDL string `json:"typeIdl" toml:"typeIdl"` + ChainSpecificName string `json:"chainSpecificName" toml:"chainSpecificName"` + // Type can be Solana Account, Instruction args, or TODO Event + Type ChainConfigType `json:"type" toml:"type"` + ModifierConfigs commoncodec.ModifiersConfig `json:"modifierConfigs,omitempty" toml:"modifierConfigs,omitempty"` +} diff --git a/pkg/solana/config/chain_reader.go b/pkg/solana/config/chain_reader.go index dbe9ef4ab..57ccb9040 100644 --- a/pkg/solana/config/chain_reader.go +++ b/pkg/solana/config/chain_reader.go @@ -4,98 +4,79 @@ import ( "encoding/json" "fmt" - "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/rpc" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/codec" - "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" - "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" - "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" ) -type ChainReader struct { - Namespaces map[string]ChainReaderMethods `json:"namespaces" toml:"namespaces"` +type ContractReader struct { + Namespaces map[string]ChainContractReader `json:"namespaces"` } -type ChainReaderMethods struct { - Methods map[string]ChainDataReader `json:"methods" toml:"methods"` +type ChainContractReader struct { + codec.IDL `json:"anchorIDL"` + // Reads key is the off-chain name for this read. + Reads map[string]ReadDefinition `json:"reads"` + // TODO ContractPollingFilter same as EVM? } -type ChainDataReader struct { - AnchorIDL string `json:"anchorIDL" toml:"anchorIDL"` - // Encoding defines the type of encoding used for on-chain data. Currently supported - // are 'borsh' and 'bincode'. - Encoding EncodingType `json:"encoding" toml:"encoding"` - Procedure ChainReaderProcedure `json:"procedure" toml:"procedure"` +type ReadDefinition struct { + ChainSpecificName string `json:"chainSpecificName"` + ReadType ReadType `json:"readType,omitempty"` + InputModifications commoncodec.ModifiersConfig `json:"inputModifications,omitempty"` + OutputModifications commoncodec.ModifiersConfig `json:"outputModifications,omitempty"` } -type EncodingType int +type ReadType int const ( - EncodingTypeBorsh EncodingType = iota - EncodingTypeBincode - - encodingTypeBorshStr = "borsh" - encodingTypeBincodeStr = "bincode" + Account ReadType = iota + Event ) -func (t EncodingType) MarshalJSON() ([]byte, error) { - switch t { - case EncodingTypeBorsh: - return json.Marshal(encodingTypeBorshStr) - case EncodingTypeBincode: - return json.Marshal(encodingTypeBincodeStr) +func (r ReadType) String() string { + switch r { + case Account: + return "Account" + case Event: + return "Event" default: - return nil, fmt.Errorf("%w: unrecognized encoding type: %d", types.ErrInvalidConfig, t) + return fmt.Sprintf("Unknown(%d)", r) } } -func (t *EncodingType) UnmarshalJSON(data []byte) error { - var str string - - if err := json.Unmarshal(data, &str); err != nil { - return fmt.Errorf("%w: %s", types.ErrInvalidConfig, err.Error()) +func (c *ChainContractReader) UnmarshalJSON(bytes []byte) error { + rawJSON := make(map[string]json.RawMessage) + if err := json.Unmarshal(bytes, &rawJSON); err != nil { + return err } - switch str { - case encodingTypeBorshStr: - *t = EncodingTypeBorsh - case encodingTypeBincodeStr: - *t = EncodingTypeBincode - default: - return fmt.Errorf("%w: unrecognized encoding type: %s", types.ErrInvalidConfig, str) + idlBytes := rawJSON["anchorIDL"] + var rawString string + if err := json.Unmarshal(idlBytes, &rawString); err == nil { + if err = json.Unmarshal([]byte(rawString), &c.IDL); err != nil { + return fmt.Errorf("failed to parse anchorIDL string as IDL struct: %w", err) + } + return nil } - return nil -} + // If we didn't get a string, attempt to parse directly as an IDL object + if err := json.Unmarshal(idlBytes, &c.IDL); err != nil { + return fmt.Errorf("anchorIDL field is neither a valid JSON string nor a valid IDL object: %w", err) + } -type RPCOpts struct { - Encoding *solana.EncodingType `json:"encoding,omitempty"` - Commitment *rpc.CommitmentType `json:"commitment,omitempty"` - DataSlice *rpc.DataSlice `json:"dataSlice,omitempty"` -} + if len(c.Accounts) == 0 && len(c.Events) == 0 { + return fmt.Errorf("namespace idl must have at least one account or event: %w", commontypes.ErrInvalidConfig) + } -type ChainReaderProcedure chainDataProcedureFields - -type chainDataProcedureFields struct { - // IDLAccount refers to the account defined in the IDL. - IDLAccount string `json:"idlAccount,omitempty"` - // OutputModifications provides modifiers to convert chain data format to custom - // output formats. - OutputModifications codec.ModifiersConfig `json:"outputModifications,omitempty"` - // RPCOpts provides optional configurations for commitment, encoding, and data - // slice offsets. - RPCOpts *RPCOpts `json:"rpcOpts,omitempty"` -} + if err := json.Unmarshal(rawJSON["reads"], &c.Reads); err != nil { + return err + } -// BuilderForEncoding returns a builder for the encoding configuration. Defaults to little endian. -func BuilderForEncoding(eType EncodingType) encodings.Builder { - switch eType { - case EncodingTypeBorsh: - return binary.LittleEndian() - case EncodingTypeBincode: - return binary.BigEndian() - default: - return binary.LittleEndian() + if len(c.Reads) == 0 { + return fmt.Errorf("namespace must have at least one read: %w", commontypes.ErrInvalidConfig) } + + return nil } diff --git a/pkg/solana/config/chain_reader_test.go b/pkg/solana/config/chain_reader_test.go index 7d290b50c..19bcedbe3 100644 --- a/pkg/solana/config/chain_reader_test.go +++ b/pkg/solana/config/chain_reader_test.go @@ -5,15 +5,14 @@ import ( "encoding/json" "testing" - "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec" - "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec/testutils" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" ) @@ -21,24 +20,33 @@ import ( //go:embed testChainReader_valid.json var validJSON string +//go:embed testChainReader_valid_with_IDL_as_string.json +var validJSONWithIDLAsString string + //go:embed testChainReader_invalid.json var invalidJSON string func TestChainReaderConfig(t *testing.T) { t.Parallel() - t.Run("valid unmarshal", func(t *testing.T) { + t.Run("valid unmarshal with idl as struct", func(t *testing.T) { t.Parallel() - var result config.ChainReader + var result config.ContractReader require.NoError(t, json.Unmarshal([]byte(validJSON), &result)) assert.Equal(t, validChainReaderConfig, result) }) + t.Run("valid unmarshal with idl as string", func(t *testing.T) { + var result config.ContractReader + require.NoError(t, json.Unmarshal([]byte(validJSONWithIDLAsString), &result)) + assert.Equal(t, validChainReaderConfig, result) + }) + t.Run("invalid unmarshal", func(t *testing.T) { t.Parallel() - var result config.ChainReader + var result config.ContractReader require.ErrorIs(t, json.Unmarshal([]byte(invalidJSON), &result), types.ErrInvalidConfig) }) @@ -49,79 +57,42 @@ func TestChainReaderConfig(t *testing.T) { require.NoError(t, err) - var conf config.ChainReader + var conf config.ContractReader require.NoError(t, json.Unmarshal(result, &conf)) assert.Equal(t, validChainReaderConfig, conf) }) } -func TestEncodingType_Fail(t *testing.T) { - t.Parallel() - - _, err := json.Marshal(config.EncodingType(100)) - - require.NotNil(t, err) - - var tp config.EncodingType - - require.ErrorIs(t, json.Unmarshal([]byte(`42`), &tp), types.ErrInvalidConfig) - require.ErrorIs(t, json.Unmarshal([]byte(`"invalid"`), &tp), types.ErrInvalidConfig) -} - -func TestBuilderForEncoding_Default(t *testing.T) { - t.Parallel() - - builder := config.BuilderForEncoding(config.EncodingType(100)) - require.Equal(t, binary.LittleEndian(), builder) +var nilIDL = codec.IDL{ + Version: "0.1.0", + Name: "myProgram", + Accounts: codec.IdlTypeDefSlice{ + {Name: "NilType", Type: codec.IdlTypeDefTy{Kind: codec.IdlTypeDefTyKindStruct, Fields: &codec.IdlTypeDefStruct{}}}, + }, } -var ( - encodingBase64 = solana.EncodingBase64 - commitment = rpc.CommitmentFinalized - offset = uint64(10) - length = uint64(10) -) - -var validChainReaderConfig = config.ChainReader{ - Namespaces: map[string]config.ChainReaderMethods{ +var validChainReaderConfig = config.ContractReader{ + Namespaces: map[string]config.ChainContractReader{ "Contract": { - Methods: map[string]config.ChainDataReader{ + IDL: nilIDL, + Reads: map[string]config.ReadDefinition{ "Method": { - AnchorIDL: "test idl 1", - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: testutils.TestStructWithNestedStruct, - }, + ChainSpecificName: testutils.TestStructWithNestedStruct, }, "MethodWithOpts": { - AnchorIDL: "test idl 2", - Encoding: config.EncodingTypeBorsh, - Procedure: config.ChainReaderProcedure{ - IDLAccount: testutils.TestStructWithNestedStruct, - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.PropertyExtractorConfig{FieldName: "DurationVal"}, - }, - RPCOpts: &config.RPCOpts{ - Encoding: &encodingBase64, - Commitment: &commitment, - DataSlice: &rpc.DataSlice{ - Offset: &offset, - Length: &length, - }, - }, + ChainSpecificName: testutils.TestStructWithNestedStruct, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.PropertyExtractorConfig{FieldName: "DurationVal"}, }, }, }, }, "OtherContract": { - Methods: map[string]config.ChainDataReader{ + IDL: nilIDL, + Reads: map[string]config.ReadDefinition{ "Method": { - AnchorIDL: "test idl 3", - Encoding: config.EncodingTypeBincode, - Procedure: config.ChainReaderProcedure{ - IDLAccount: testutils.TestStructWithNestedStruct, - }, + ChainSpecificName: testutils.TestStructWithNestedStruct, }, }, }, diff --git a/pkg/solana/config/testChainReader_invalid.json b/pkg/solana/config/testChainReader_invalid.json index 98caa8fcc..925f8eba0 100644 --- a/pkg/solana/config/testChainReader_invalid.json +++ b/pkg/solana/config/testChainReader_invalid.json @@ -1,13 +1,14 @@ { "namespaces": { "Contract": { - "methods": { - "Method": { - "anchorIDL": "test idl 1", - "encoding": "invalid", - "procedure": { - "idlAccount": "StructWithNestedStruct" - } + "anchorIDL": { + "version": "0.1.0", + "name": "myProgram" + }, + "OtherContract": { + "anchorIDL": { + "version": "0.1.0", + "name": "myProgram" } } } diff --git a/pkg/solana/config/testChainReader_valid.json b/pkg/solana/config/testChainReader_valid.json index ca75a936b..a813988c0 100644 --- a/pkg/solana/config/testChainReader_valid.json +++ b/pkg/solana/config/testChainReader_valid.json @@ -1,43 +1,54 @@ { - "namespaces": { - "Contract": { - "methods": { - "Method": { - "anchorIDL": "test idl 1", - "encoding": "borsh", - "procedure": { - "idlAccount": "StructWithNestedStruct" + "namespaces":{ + "Contract":{ + "anchorIDL":{ + "version":"0.1.0", + "name":"myProgram", + "accounts":[ + { + "name":"NilType", + "type":{ + "kind":"struct", + "fields":[ + + ] + } } + ] + }, + "reads":{ + "Method":{ + "chainSpecificName":"StructWithNestedStruct" }, - "MethodWithOpts": { - "anchorIDL": "test idl 2", - "encoding": "borsh", - "procedure": { - "idlAccount": "StructWithNestedStruct", - "outputModifications": [{ - "Type": "extract property", - "FieldName": "DurationVal" - }], - "rpcOpts": { - "encoding": "base64", - "commitment": "finalized", - "dataSlice": { - "offset": 10, - "length": 10 - } + "MethodWithOpts":{ + "chainSpecificName":"StructWithNestedStruct", + "outputModifications":[ + { + "FieldName":"DurationVal", + "Type":"extract property" } - } + ] } } }, - "OtherContract": { - "methods": { - "Method": { - "anchorIDL": "test idl 3", - "encoding": "bincode", - "procedure": { - "idlAccount": "StructWithNestedStruct" + "OtherContract":{ + "anchorIDL":{ + "version":"0.1.0", + "name":"myProgram", + "accounts":[ + { + "name":"NilType", + "type":{ + "kind":"struct", + "fields":[ + ] + } } + ] + }, + "reads":{ + "Method":{ + "chainSpecificName":"StructWithNestedStruct" } } } diff --git a/pkg/solana/config/testChainReader_valid_with_IDL_as_string.json b/pkg/solana/config/testChainReader_valid_with_IDL_as_string.json new file mode 100644 index 000000000..700c7b768 --- /dev/null +++ b/pkg/solana/config/testChainReader_valid_with_IDL_as_string.json @@ -0,0 +1,57 @@ +{ + "namespaces":{ + "Contract":{ + "anchorIDL":{ + "version":"0.1.0", + "name":"myProgram", + "accounts":[ + { + "name":"NilType", + "type":{ + "kind":"struct", + "fields":[ + + ] + } + } + ] + }, + "reads":{ + "Method":{ + "chainSpecificName":"StructWithNestedStruct" + }, + "MethodWithOpts":{ + "chainSpecificName":"StructWithNestedStruct", + "outputModifications":[ + { + "FieldName":"DurationVal", + "Type":"extract property" + } + ] + } + } + }, + "OtherContract":{ + "anchorIDL":{ + "version":"0.1.0", + "name":"myProgram", + "accounts":[ + { + "name":"NilType", + "type":{ + "kind":"struct", + "fields":[ + + ] + } + } + ] + }, + "reads":{ + "Method":{ + "chainSpecificName":"StructWithNestedStruct" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/solana/config_tracker.go b/pkg/solana/config_tracker.go index 3ddff2715..425e62d46 100644 --- a/pkg/solana/config_tracker.go +++ b/pkg/solana/config_tracker.go @@ -31,7 +31,6 @@ func ConfigFromState(ctx context.Context, state State) (types.ContractConfig, er return types.ContractConfig{}, err } for _, o := range oracles { - o := o // https://github.com/golang/go/wiki/CommonMistakes#using-reference-to-loop-iterator-variable pubKeys = append(pubKeys, o.Signer.Key[:]) accounts = append(accounts, types.Account(o.Transmitter.String())) } diff --git a/pkg/solana/logpoller/models.go b/pkg/solana/logpoller/models.go new file mode 100644 index 000000000..7afb0a50e --- /dev/null +++ b/pkg/solana/logpoller/models.go @@ -0,0 +1,38 @@ +package logpoller + +import ( + "time" + + "github.com/lib/pq" +) + +type Filter struct { + ID int64 + Name string + Address PublicKey + EventName string + EventSig []byte + StartingBlock int64 + EventIDL string + SubkeyPaths SubkeyPaths + Retention time.Duration + MaxLogsKept int64 +} + +type Log struct { + ID int64 + FilterID int64 + ChainID string + LogIndex int64 + BlockHash Hash + BlockNumber int64 + BlockTimestamp time.Time + Address PublicKey + EventSig []byte + SubkeyValues pq.ByteaArray + TxHash Signature + Data []byte + CreatedAt time.Time + ExpiresAt *time.Time + SequenceNum int64 +} diff --git a/pkg/solana/logpoller/orm.go b/pkg/solana/logpoller/orm.go new file mode 100644 index 000000000..12035438f --- /dev/null +++ b/pkg/solana/logpoller/orm.go @@ -0,0 +1,158 @@ +package logpoller + +import ( + "context" + "errors" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" +) + +type DSORM struct { + chainID string + ds sqlutil.DataSource + lggr logger.Logger +} + +// NewORM creates an DSORM scoped to chainID. +func NewORM(chainID string, ds sqlutil.DataSource, lggr logger.Logger) *DSORM { + return &DSORM{ + chainID: chainID, + ds: ds, + lggr: lggr, + } +} + +func (o *DSORM) Transact(ctx context.Context, fn func(*DSORM) error) (err error) { + return sqlutil.Transact(ctx, o.new, o.ds, nil, fn) +} + +// new returns a NewORM like o, but backed by ds. +func (o *DSORM) new(ds sqlutil.DataSource) *DSORM { return NewORM(o.chainID, ds, o.lggr) } + +// InsertFilter is idempotent. +// +// Each address/event pair must have a unique job id, so it may be removed when the job is deleted. +// Returns ID for updated or newly inserted filter. +func (o *DSORM) InsertFilter(ctx context.Context, filter Filter) (id int64, err error) { + args, err := newQueryArgs(o.chainID). + withField("name", filter.Name). + withRetention(filter.Retention). + withMaxLogsKept(filter.MaxLogsKept). + withName(filter.Name). + withAddress(filter.Address). + withEventName(filter.EventName). + withEventSig(filter.EventSig). + withStartingBlock(filter.StartingBlock). + withEventIDL(filter.EventIDL). + withSubkeyPaths(filter.SubkeyPaths). + toArgs() + if err != nil { + return 0, err + } + + // '::' has to be escaped in the query string + // https://github.com/jmoiron/sqlx/issues/91, https://github.com/jmoiron/sqlx/issues/428 + query := ` + INSERT INTO solana.log_poller_filters + (chain_id, name, address, event_name, event_sig, starting_block, event_idl, subkey_paths, retention, max_logs_kept) + VALUES (:chain_id, :name, :address, :event_name, :event_sig, :starting_block, :event_idl, :subkey_paths, :retention, :max_logs_kept) + RETURNING id;` + + query, sqlArgs, err := o.ds.BindNamed(query, args) + if err != nil { + return 0, err + } + if err = o.ds.GetContext(ctx, &id, query, sqlArgs...); err != nil { + return 0, err + } + return id, nil +} + +// GetFilterByID returns filter by ID +func (o *DSORM) GetFilterByID(ctx context.Context, id int64) (Filter, error) { + query := `SELECT id, name, address, event_name, event_sig, starting_block, event_idl, subkey_paths, retention, max_logs_kept + FROM solana.log_poller_filters WHERE id = $1` + var result Filter + err := o.ds.GetContext(ctx, &result, query, id) + return result, err +} + +// InsertLogs is idempotent to support replays. +func (o *DSORM) InsertLogs(ctx context.Context, logs []Log) error { + if err := o.validateLogs(logs); err != nil { + return err + } + return o.Transact(ctx, func(orm *DSORM) error { + return orm.insertLogsWithinTx(ctx, logs, orm.ds) + }) +} + +func (o *DSORM) insertLogsWithinTx(ctx context.Context, logs []Log, tx sqlutil.DataSource) error { + batchInsertSize := 4000 + for i := 0; i < len(logs); i += batchInsertSize { + start, end := i, i+batchInsertSize + if end > len(logs) { + end = len(logs) + } + + query := `INSERT INTO solana.logs + (filter_id, chain_id, log_index, block_hash, block_number, block_timestamp, address, event_sig, subkey_values, tx_hash, data, created_at, expires_at, sequence_num) + VALUES + (:filter_id, :chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :subkey_values, :tx_hash, :data, NOW(), :expires_at, :sequence_num) + ON CONFLICT DO NOTHING` + + _, err := tx.NamedExecContext(ctx, query, logs[start:end]) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) && batchInsertSize > 500 { + // In case of DB timeouts, try to insert again with a smaller batch upto a limit + batchInsertSize /= 2 + i -= batchInsertSize // counteract +=batchInsertSize on next loop iteration + continue + } + return err + } + } + return nil +} + +func (o *DSORM) validateLogs(logs []Log) error { + for _, log := range logs { + if o.chainID != log.ChainID { + return fmt.Errorf("invalid chainID in log got %v want %v", log.ChainID, o.chainID) + } + } + return nil +} + +// SelectLogs finds the logs in a given block range. +func (o *DSORM) SelectLogs(ctx context.Context, start, end int64, address PublicKey, eventSig []byte) ([]Log, error) { + args, err := newQueryArgsForEvent(o.chainID, address, eventSig). + withStartBlock(start). + withEndBlock(end). + toArgs() + if err != nil { + return nil, err + } + + query := logsQuery(` + WHERE chain_id = :chain_id + AND address = :address + AND event_sig = :event_sig + AND block_number >= :start_block + AND block_number <= :end_block + ORDER BY block_number, log_index`) + + var logs []Log + query, sqlArgs, err := o.ds.BindNamed(query, args) + if err != nil { + return nil, err + } + + err = o.ds.SelectContext(ctx, &logs, query, sqlArgs...) + if err != nil { + return nil, err + } + return logs, nil +} diff --git a/pkg/solana/logpoller/orm_test.go b/pkg/solana/logpoller/orm_test.go new file mode 100644 index 000000000..626f17bdd --- /dev/null +++ b/pkg/solana/logpoller/orm_test.go @@ -0,0 +1,159 @@ +//go:build db_tests + +package logpoller + +import ( + "testing" + "time" + + "github.com/gagliardetto/solana-go" + "github.com/google/uuid" + _ "github.com/jackc/pgx/v4/stdlib" + "github.com/lib/pq" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +// NOTE: at the moment it's not possible to run all db tests at once. This issue will be addressed separately + +func TestLogPollerFilters(t *testing.T) { + lggr := logger.Test(t) + chainID := uuid.NewString() + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm := NewORM(chainID, dbx, lggr) + + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + t.Run("Ensure all fields are readable/writable", func(t *testing.T) { + filters := []Filter{ + { + Name: "happy path", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: SubkeyPaths([][]string{{"a", "b"}, {"c"}}), + Retention: 1000, + MaxLogsKept: 3, + }, + { + Name: "empty sub key paths", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: SubkeyPaths([][]string{}), + Retention: 1000, + MaxLogsKept: 3, + }, + { + Name: "nil sub key paths", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: nil, + Retention: 1000, + MaxLogsKept: 3, + }, + } + + for _, filter := range filters { + t.Run("Read/write filter: "+filter.Name, func(t *testing.T) { + ctx := tests.Context(t) + id, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + filter.ID = id + dbFilter, err := orm.GetFilterByID(ctx, id) + require.NoError(t, err) + require.Equal(t, filter, dbFilter) + }) + } + }) + t.Run("Returns and error if name is not unique", func(t *testing.T) { + filter := newRandomFilter(t) + ctx := tests.Context(t) + _, err = orm.InsertFilter(ctx, filter) + require.NoError(t, err) + filter.EventSig = []byte(uuid.NewString()) + _, err = orm.InsertFilter(ctx, filter) + require.EqualError(t, err, `ERROR: duplicate key value violates unique constraint "solana_log_poller_filter_name" (SQLSTATE 23505)`) + }) +} + +func newRandomFilter(t *testing.T) Filter { + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + return Filter{ + Name: uuid.NewString(), + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: [][]string{{"a", "b"}, {"c"}}, + Retention: 1000, + MaxLogsKept: 3, + } +} + +func TestLogPollerLogs(t *testing.T) { + lggr := logger.Test(t) + chainID := uuid.NewString() + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm := NewORM(chainID, dbx, lggr) + + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + + ctx := tests.Context(t) + // create filter as it's required for a log + filterID, err := orm.InsertFilter(ctx, Filter{ + Name: "awesome filter", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: [][]string{{"a", "b"}, {"c"}}, + Retention: 1000, + MaxLogsKept: 3, + }) + require.NoError(t, err) + data := []byte("solana is fun") + signature, err := privateKey.Sign(data) + require.NoError(t, err) + log := Log{ + FilterID: filterID, + ChainID: chainID, + LogIndex: 1, + BlockHash: Hash(pubKey), + BlockNumber: 10, + BlockTimestamp: time.Unix(1731590113, 0), + Address: PublicKey(pubKey), + EventSig: []byte{3, 2, 1}, + SubkeyValues: pq.ByteaArray([][]byte{{3, 2, 1}, {1}, {1, 2}, pubKey.Bytes()}), + TxHash: Signature(signature), + Data: data, + } + err = orm.InsertLogs(ctx, []Log{log}) + require.NoError(t, err) + // insert of the same Log should not produce two instances + err = orm.InsertLogs(ctx, []Log{log}) + require.NoError(t, err) + dbLogs, err := orm.SelectLogs(ctx, 0, 100, log.Address, log.EventSig) + require.NoError(t, err) + require.Len(t, dbLogs, 1) + log.ID = dbLogs[0].ID + log.CreatedAt = dbLogs[0].CreatedAt + require.Equal(t, log, dbLogs[0]) +} diff --git a/pkg/solana/logpoller/parser.go b/pkg/solana/logpoller/parser.go new file mode 100644 index 000000000..a0dd44b52 --- /dev/null +++ b/pkg/solana/logpoller/parser.go @@ -0,0 +1,5 @@ +package logpoller + +var ( + logsFields = [...]string{"id", "filter_id", "chain_id", "log_index", "block_hash", "block_number", "block_timestamp", "address", "event_sig", "subkey_values", "tx_hash", "data", "created_at", "expires_at", "sequence_num"} +) diff --git a/pkg/solana/logpoller/query.go b/pkg/solana/logpoller/query.go new file mode 100644 index 000000000..36e5fb159 --- /dev/null +++ b/pkg/solana/logpoller/query.go @@ -0,0 +1,126 @@ +package logpoller + +import ( + "errors" + "fmt" + "strings" + "time" +) + +// queryArgs is a helper for building the arguments to a postgres query created by DSORM +// Besides the convenience methods, it also keeps track of arguments validation and sanitization. +type queryArgs struct { + args map[string]any + idxLookup map[string]uint8 + err []error +} + +func newQueryArgs(chainID string) *queryArgs { + return &queryArgs{ + args: map[string]any{ + "chain_id": chainID, + }, + idxLookup: make(map[string]uint8), + err: []error{}, + } +} + +func (q *queryArgs) withField(fieldName string, value any) *queryArgs { + _, args := q.withIndexableField(fieldName, value, false) + + return args +} + +func (q *queryArgs) withIndexableField(fieldName string, value any, addIndex bool) (string, *queryArgs) { + if addIndex { + idx := q.nextIdx(fieldName) + idxName := fmt.Sprintf("%s_%d", fieldName, idx) + + q.idxLookup[fieldName] = idx + fieldName = idxName + } + + q.args[fieldName] = value + + return fieldName, q +} + +func (q *queryArgs) nextIdx(baseFieldName string) uint8 { + idx, ok := q.idxLookup[baseFieldName] + if !ok { + return 0 + } + + return idx + 1 +} + +// withName sets the Name field in queryArgs. +func (q *queryArgs) withName(name string) *queryArgs { + return q.withField("name", name) +} + +// withAddress sets the Address field in queryArgs. +func (q *queryArgs) withAddress(address PublicKey) *queryArgs { + return q.withField("address", address) +} + +// withEventName sets the EventName field in queryArgs. +func (q *queryArgs) withEventName(eventName string) *queryArgs { + return q.withField("event_name", eventName) +} + +// withEventSig sets the EventSig field in queryArgs. +func (q *queryArgs) withEventSig(eventSig []byte) *queryArgs { + return q.withField("event_sig", eventSig) +} + +// withStartingBlock sets the StartingBlock field in queryArgs. +func (q *queryArgs) withStartingBlock(startingBlock int64) *queryArgs { + return q.withField("starting_block", startingBlock) +} + +// withEventIDL sets the EventIDL field in queryArgs. +func (q *queryArgs) withEventIDL(eventIDL string) *queryArgs { + return q.withField("event_idl", eventIDL) +} + +// withSubkeyPaths sets the SubkeyPaths field in queryArgs. +func (q *queryArgs) withSubkeyPaths(subkeyPaths [][]string) *queryArgs { + return q.withField("subkey_paths", subkeyPaths) +} + +// withRetention sets the Retention field in queryArgs. +func (q *queryArgs) withRetention(retention time.Duration) *queryArgs { + return q.withField("retention", retention) +} + +// withMaxLogsKept sets the MaxLogsKept field in queryArgs. +func (q *queryArgs) withMaxLogsKept(maxLogsKept int64) *queryArgs { + return q.withField("max_logs_kept", maxLogsKept) +} + +func newQueryArgsForEvent(chainID string, address PublicKey, eventSig []byte) *queryArgs { + return newQueryArgs(chainID). + withAddress(address). + withEventSig(eventSig) +} + +func (q *queryArgs) withStartBlock(startBlock int64) *queryArgs { + return q.withField("start_block", startBlock) +} + +func (q *queryArgs) withEndBlock(endBlock int64) *queryArgs { + return q.withField("end_block", endBlock) +} + +func logsQuery(clause string) string { + return fmt.Sprintf(`SELECT %s FROM solana.logs %s`, strings.Join(logsFields[:], ", "), clause) +} + +func (q *queryArgs) toArgs() (map[string]any, error) { + if len(q.err) > 0 { + return nil, errors.Join(q.err...) + } + + return q.args, nil +} diff --git a/pkg/solana/logpoller/types.go b/pkg/solana/logpoller/types.go new file mode 100644 index 000000000..0581a7630 --- /dev/null +++ b/pkg/solana/logpoller/types.go @@ -0,0 +1,98 @@ +package logpoller + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + + "github.com/gagliardetto/solana-go" +) + +type PublicKey solana.PublicKey + +// Scan implements Scanner for database/sql. +func (k *PublicKey) Scan(src interface{}) error { + return scanFixedLengthArray("PublicKey", solana.PublicKeyLength, src, k[:]) +} + +// Value implements valuer for database/sql. +func (k PublicKey) Value() (driver.Value, error) { + return k[:], nil +} + +func (k PublicKey) ToSolana() solana.PublicKey { + return solana.PublicKey(k) +} + +type Hash solana.Hash + +// Scan implements Scanner for database/sql. +func (h *Hash) Scan(src interface{}) error { + return scanFixedLengthArray("Hash", solana.PublicKeyLength, src, h[:]) +} + +// Value implements valuer for database/sql. +func (h Hash) Value() (driver.Value, error) { + return h[:], nil +} + +func (h Hash) ToSolana() solana.Hash { + return solana.Hash(h) +} + +type Signature solana.Signature + +// Scan implements Scanner for database/sql. +func (s *Signature) Scan(src interface{}) error { + return scanFixedLengthArray("Signature", solana.SignatureLength, src, s[:]) +} + +// Value implements valuer for database/sql. +func (s Signature) Value() (driver.Value, error) { + return s[:], nil +} + +func (s Signature) ToSolana() solana.Signature { + return solana.Signature(s) +} + +func scanFixedLengthArray(name string, maxLength int, src interface{}, dest []byte) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into %s", src, name) + } + if len(srcB) != maxLength { + return fmt.Errorf("can't scan []byte of len %d into %s, want %d", len(srcB), name, maxLength) + } + copy(dest, srcB) + return nil +} + +type SubkeyPaths [][]string + +func (p SubkeyPaths) Value() (driver.Value, error) { + return json.Marshal([][]string(p)) +} + +func (p *SubkeyPaths) Scan(src interface{}) error { + var bSrc []byte + switch src := src.(type) { + case string: + bSrc = []byte(src) + case []byte: + bSrc = src + default: + return fmt.Errorf("can't scan %T into SubkeyPaths", src) + } + + if len(bSrc) == 0 || string(bSrc) == "null" { + return nil + } + + err := json.Unmarshal(bSrc, p) + if err != nil { + return fmt.Errorf("failed to scan %v into SubkeyPaths: %w", string(bSrc), err) + } + + return nil +} diff --git a/pkg/solana/relay.go b/pkg/solana/relay.go index fca61ba9f..5dfe8c836 100644 --- a/pkg/solana/relay.go +++ b/pkg/solana/relay.go @@ -19,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) var _ TxManager = (*txm.Txm)(nil) @@ -32,7 +33,7 @@ type TxManager interface { // - txCfgs can be used to set custom tx configurations. // - If a txID is provided, it will be used to identify the tx. Otherwise, a random UUID will be generated. // - The caller needs to set the tx.Message.RecentBlockhash and provide the corresponding lastValidBlockHeight. These values are obtained from the GetLatestBlockhash RPC call. - Enqueue(ctx context.Context, accountID string, tx *solana.Transaction, txID *string, lastValidBlockHeight uint64, txCfgs ...txm.SetTxConfig) error + Enqueue(ctx context.Context, accountID string, tx *solana.Transaction, txID *string, lastValidBlockHeight uint64, txCfgs ...txmutils.SetTxConfig) error } var _ relaytypes.Relayer = &Relayer{} //nolint:staticcheck diff --git a/pkg/solana/report_test.go b/pkg/solana/report_test.go index 84452dada..eab16e26b 100644 --- a/pkg/solana/report_test.go +++ b/pkg/solana/report_test.go @@ -143,7 +143,6 @@ func TestMedianFromReport(t *testing.T) { } for _, tc := range tt { - tc := tc t.Run(tc.name, func(t *testing.T) { ctx := tests.Context(t) var pos []median.ParsedAttributedObservation diff --git a/pkg/solana/transmitter_test.go b/pkg/solana/transmitter_test.go index f6db01d6c..ba1ec9cc3 100644 --- a/pkg/solana/transmitter_test.go +++ b/pkg/solana/transmitter_test.go @@ -17,7 +17,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" clientmocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/mocks" "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) // custom mock txm instead of mockery generated because SetTxConfig causes circular imports @@ -27,7 +27,7 @@ type verifyTxSize struct { s *solana.PrivateKey } -func (txm verifyTxSize) Enqueue(_ context.Context, _ string, tx *solana.Transaction, txID *string, _ uint64, _ ...txm.SetTxConfig) error { +func (txm verifyTxSize) Enqueue(_ context.Context, _ string, tx *solana.Transaction, txID *string, _ uint64, _ ...txmutils.SetTxConfig) error { // additional components that transaction manager adds to the transaction require.NoError(txm.t, fees.SetComputeUnitPrice(tx, 0)) require.NoError(txm.t, fees.SetComputeUnitLimit(tx, 0)) diff --git a/pkg/solana/txm/mocks/tx_manager.go b/pkg/solana/txm/mocks/tx_manager.go new file mode 100644 index 000000000..7694703a3 --- /dev/null +++ b/pkg/solana/txm/mocks/tx_manager.go @@ -0,0 +1,391 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + solana "github.com/gagliardetto/solana-go" + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" + + utils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" +) + +// TxManager is an autogenerated mock type for the TxManager type +type TxManager struct { + mock.Mock +} + +type TxManager_Expecter struct { + mock *mock.Mock +} + +func (_m *TxManager) EXPECT() *TxManager_Expecter { + return &TxManager_Expecter{mock: &_m.Mock} +} + +// Close provides a mock function with given fields: +func (_m *TxManager) Close() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxManager_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type TxManager_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *TxManager_Expecter) Close() *TxManager_Close_Call { + return &TxManager_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *TxManager_Close_Call) Run(run func()) *TxManager_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *TxManager_Close_Call) Return(_a0 error) *TxManager_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxManager_Close_Call) RunAndReturn(run func() error) *TxManager_Close_Call { + _c.Call.Return(run) + return _c +} + +// Enqueue provides a mock function with given fields: ctx, accountID, tx, txID, txLastValidBlockHeight, txCfgs +func (_m *TxManager) Enqueue(ctx context.Context, accountID string, tx *solana.Transaction, txID *string, txLastValidBlockHeight uint64, txCfgs ...utils.SetTxConfig) error { + _va := make([]interface{}, len(txCfgs)) + for _i := range txCfgs { + _va[_i] = txCfgs[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, accountID, tx, txID, txLastValidBlockHeight) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Enqueue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, *solana.Transaction, *string, uint64, ...utils.SetTxConfig) error); ok { + r0 = rf(ctx, accountID, tx, txID, txLastValidBlockHeight, txCfgs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxManager_Enqueue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Enqueue' +type TxManager_Enqueue_Call struct { + *mock.Call +} + +// Enqueue is a helper method to define mock.On call +// - ctx context.Context +// - accountID string +// - tx *solana.Transaction +// - txID *string +// - txLastValidBlockHeight uint64 +// - txCfgs ...utils.SetTxConfig +func (_e *TxManager_Expecter) Enqueue(ctx interface{}, accountID interface{}, tx interface{}, txID interface{}, txLastValidBlockHeight interface{}, txCfgs ...interface{}) *TxManager_Enqueue_Call { + return &TxManager_Enqueue_Call{Call: _e.mock.On("Enqueue", + append([]interface{}{ctx, accountID, tx, txID, txLastValidBlockHeight}, txCfgs...)...)} +} + +func (_c *TxManager_Enqueue_Call) Run(run func(ctx context.Context, accountID string, tx *solana.Transaction, txID *string, txLastValidBlockHeight uint64, txCfgs ...utils.SetTxConfig)) *TxManager_Enqueue_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]utils.SetTxConfig, len(args)-5) + for i, a := range args[5:] { + if a != nil { + variadicArgs[i] = a.(utils.SetTxConfig) + } + } + run(args[0].(context.Context), args[1].(string), args[2].(*solana.Transaction), args[3].(*string), args[4].(uint64), variadicArgs...) + }) + return _c +} + +func (_c *TxManager_Enqueue_Call) Return(_a0 error) *TxManager_Enqueue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxManager_Enqueue_Call) RunAndReturn(run func(context.Context, string, *solana.Transaction, *string, uint64, ...utils.SetTxConfig) error) *TxManager_Enqueue_Call { + _c.Call.Return(run) + return _c +} + +// GetTransactionStatus provides a mock function with given fields: ctx, transactionID +func (_m *TxManager) GetTransactionStatus(ctx context.Context, transactionID string) (types.TransactionStatus, error) { + ret := _m.Called(ctx, transactionID) + + if len(ret) == 0 { + panic("no return value specified for GetTransactionStatus") + } + + var r0 types.TransactionStatus + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (types.TransactionStatus, error)); ok { + return rf(ctx, transactionID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) types.TransactionStatus); ok { + r0 = rf(ctx, transactionID) + } else { + r0 = ret.Get(0).(types.TransactionStatus) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, transactionID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TxManager_GetTransactionStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTransactionStatus' +type TxManager_GetTransactionStatus_Call struct { + *mock.Call +} + +// GetTransactionStatus is a helper method to define mock.On call +// - ctx context.Context +// - transactionID string +func (_e *TxManager_Expecter) GetTransactionStatus(ctx interface{}, transactionID interface{}) *TxManager_GetTransactionStatus_Call { + return &TxManager_GetTransactionStatus_Call{Call: _e.mock.On("GetTransactionStatus", ctx, transactionID)} +} + +func (_c *TxManager_GetTransactionStatus_Call) Run(run func(ctx context.Context, transactionID string)) *TxManager_GetTransactionStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *TxManager_GetTransactionStatus_Call) Return(_a0 types.TransactionStatus, _a1 error) *TxManager_GetTransactionStatus_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TxManager_GetTransactionStatus_Call) RunAndReturn(run func(context.Context, string) (types.TransactionStatus, error)) *TxManager_GetTransactionStatus_Call { + _c.Call.Return(run) + return _c +} + +// HealthReport provides a mock function with given fields: +func (_m *TxManager) HealthReport() map[string]error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for HealthReport") + } + + var r0 map[string]error + if rf, ok := ret.Get(0).(func() map[string]error); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]error) + } + } + + return r0 +} + +// TxManager_HealthReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HealthReport' +type TxManager_HealthReport_Call struct { + *mock.Call +} + +// HealthReport is a helper method to define mock.On call +func (_e *TxManager_Expecter) HealthReport() *TxManager_HealthReport_Call { + return &TxManager_HealthReport_Call{Call: _e.mock.On("HealthReport")} +} + +func (_c *TxManager_HealthReport_Call) Run(run func()) *TxManager_HealthReport_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *TxManager_HealthReport_Call) Return(_a0 map[string]error) *TxManager_HealthReport_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxManager_HealthReport_Call) RunAndReturn(run func() map[string]error) *TxManager_HealthReport_Call { + _c.Call.Return(run) + return _c +} + +// Name provides a mock function with given fields: +func (_m *TxManager) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// TxManager_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' +type TxManager_Name_Call struct { + *mock.Call +} + +// Name is a helper method to define mock.On call +func (_e *TxManager_Expecter) Name() *TxManager_Name_Call { + return &TxManager_Name_Call{Call: _e.mock.On("Name")} +} + +func (_c *TxManager_Name_Call) Run(run func()) *TxManager_Name_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *TxManager_Name_Call) Return(_a0 string) *TxManager_Name_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxManager_Name_Call) RunAndReturn(run func() string) *TxManager_Name_Call { + _c.Call.Return(run) + return _c +} + +// Ready provides a mock function with given fields: +func (_m *TxManager) Ready() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Ready") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxManager_Ready_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ready' +type TxManager_Ready_Call struct { + *mock.Call +} + +// Ready is a helper method to define mock.On call +func (_e *TxManager_Expecter) Ready() *TxManager_Ready_Call { + return &TxManager_Ready_Call{Call: _e.mock.On("Ready")} +} + +func (_c *TxManager_Ready_Call) Run(run func()) *TxManager_Ready_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *TxManager_Ready_Call) Return(_a0 error) *TxManager_Ready_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxManager_Ready_Call) RunAndReturn(run func() error) *TxManager_Ready_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: _a0 +func (_m *TxManager) Start(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxManager_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type TxManager_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - _a0 context.Context +func (_e *TxManager_Expecter) Start(_a0 interface{}) *TxManager_Start_Call { + return &TxManager_Start_Call{Call: _e.mock.On("Start", _a0)} +} + +func (_c *TxManager_Start_Call) Run(run func(_a0 context.Context)) *TxManager_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *TxManager_Start_Call) Return(_a0 error) *TxManager_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxManager_Start_Call) RunAndReturn(run func(context.Context) error) *TxManager_Start_Call { + _c.Call.Return(run) + return _c +} + +// NewTxManager creates a new instance of TxManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTxManager(t interface { + mock.TestingT + Cleanup(func()) +}) *TxManager { + mock := &TxManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/solana/txm/pendingtx.go b/pkg/solana/txm/pendingtx.go index b08039ab7..7784c47cd 100644 --- a/pkg/solana/txm/pendingtx.go +++ b/pkg/solana/txm/pendingtx.go @@ -9,6 +9,9 @@ import ( "github.com/gagliardetto/solana-go" "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) var ( @@ -40,11 +43,11 @@ type PendingTxContext interface { // OnFinalized marks transaction as Finalized, moves it from the broadcasted or confirmed map to finalized map, removes signatures from signature map to stop confirmation checks OnFinalized(sig solana.Signature, retentionTimeout time.Duration) (string, error) // OnPrebroadcastError adds transaction that has not yet been broadcasted to the finalized/errored map as errored, matches err type using enum - OnPrebroadcastError(id string, retentionTimeout time.Duration, txState TxState, errType TxErrType) error + OnPrebroadcastError(id string, retentionTimeout time.Duration, txState utils.TxState, errType TxErrType) error // OnError marks transaction as errored, matches err type using enum, moves it from the broadcasted or confirmed map to finalized/errored map, removes signatures from signature map to stop confirmation checks - OnError(sig solana.Signature, retentionTimeout time.Duration, txState TxState, errType TxErrType) (string, error) + OnError(sig solana.Signature, retentionTimeout time.Duration, txState utils.TxState, errType TxErrType) (string, error) // GetTxState returns the transaction state for the provided ID if it exists - GetTxState(id string) (TxState, error) + GetTxState(id string) (utils.TxState, error) // TrimFinalizedErroredTxs removes transactions that have reached their retention time TrimFinalizedErroredTxs() int } @@ -52,18 +55,18 @@ type PendingTxContext interface { // finishedTx is used to store info required to track transactions to finality or error type pendingTx struct { tx solana.Transaction - cfg TxConfig + cfg txmutils.TxConfig signatures []solana.Signature id string createTs time.Time - state TxState + state txmutils.TxState lastValidBlockHeight uint64 // to track expiration, equivalent to last valid block number. } // finishedTx is used to store minimal info specifically for finalized or errored transactions for external status checks type finishedTx struct { retentionTs time.Time - state TxState + state utils.TxState } var _ PendingTxContext = &pendingTxContext{} @@ -133,7 +136,7 @@ func (c *pendingTxContext) New(tx pendingTx, sig solana.Signature, cancel contex // add signature to tx tx.signatures = append(tx.signatures, sig) tx.createTs = time.Now() - tx.state = Broadcasted + tx.state = utils.Broadcasted // save to the broadcasted map since transaction was just broadcasted c.broadcastedProcessedTxs[tx.id] = tx return "", nil @@ -232,7 +235,7 @@ func (c *pendingTxContext) ListAllExpiredBroadcastedTxs(currBlockNumber uint64) defer c.lock.RUnlock() expiredBroadcastedTxs := make([]pendingTx, 0, len(c.broadcastedProcessedTxs)) // worst case, all of them for _, tx := range c.broadcastedProcessedTxs { - if tx.state == Broadcasted && tx.lastValidBlockHeight < currBlockNumber { + if tx.state == txmutils.Broadcasted && tx.lastValidBlockHeight < currBlockNumber { expiredBroadcastedTxs = append(expiredBroadcastedTxs, tx) } } @@ -273,7 +276,7 @@ func (c *pendingTxContext) OnProcessed(sig solana.Signature) (string, error) { return ErrTransactionNotFound } // Check if tranasction already in processed state - if tx.state == Processed { + if tx.state == utils.Processed { return ErrAlreadyInExpectedState } return nil @@ -293,7 +296,7 @@ func (c *pendingTxContext) OnProcessed(sig solana.Signature) (string, error) { return id, ErrTransactionNotFound } // update tx state to Processed - tx.state = Processed + tx.state = utils.Processed // save updated tx back to the broadcasted map c.broadcastedProcessedTxs[id] = tx return id, nil @@ -308,7 +311,7 @@ func (c *pendingTxContext) OnConfirmed(sig solana.Signature) (string, error) { return ErrSigDoesNotExist } // Check if transaction already in confirmed state - if tx, exists := c.confirmedTxs[id]; exists && tx.state == Confirmed { + if tx, exists := c.confirmedTxs[id]; exists && tx.state == utils.Confirmed { return ErrAlreadyInExpectedState } // Transactions should only move to confirmed from broadcasted/processed @@ -337,7 +340,7 @@ func (c *pendingTxContext) OnConfirmed(sig solana.Signature) (string, error) { delete(c.cancelBy, id) } // update tx state to Confirmed - tx.state = Confirmed + tx.state = utils.Confirmed // move tx to confirmed map c.confirmedTxs[id] = tx // remove tx from broadcasted map @@ -401,7 +404,7 @@ func (c *pendingTxContext) OnFinalized(sig solana.Signature, retentionTimeout ti return id, nil } finalizedTx := finishedTx{ - state: Finalized, + state: utils.Finalized, retentionTs: time.Now().Add(retentionTimeout), } // move transaction from confirmed to finalized map @@ -410,7 +413,7 @@ func (c *pendingTxContext) OnFinalized(sig solana.Signature, retentionTimeout ti }) } -func (c *pendingTxContext) OnPrebroadcastError(id string, retentionTimeout time.Duration, txState TxState, _ TxErrType) error { +func (c *pendingTxContext) OnPrebroadcastError(id string, retentionTimeout time.Duration, txState utils.TxState, _ TxErrType) error { // nothing to do if retention timeout is 0 since transaction is not stored yet. if retentionTimeout == 0 { return nil @@ -452,7 +455,7 @@ func (c *pendingTxContext) OnPrebroadcastError(id string, retentionTimeout time. return err } -func (c *pendingTxContext) OnError(sig solana.Signature, retentionTimeout time.Duration, txState TxState, _ TxErrType) (string, error) { +func (c *pendingTxContext) OnError(sig solana.Signature, retentionTimeout time.Duration, txState utils.TxState, _ TxErrType) (string, error) { err := c.withReadLock(func() error { id, sigExists := c.sigToID[sig] if !sigExists { @@ -517,7 +520,7 @@ func (c *pendingTxContext) OnError(sig solana.Signature, retentionTimeout time.D }) } -func (c *pendingTxContext) GetTxState(id string) (TxState, error) { +func (c *pendingTxContext) GetTxState(id string) (utils.TxState, error) { c.lock.RLock() defer c.lock.RUnlock() if tx, exists := c.broadcastedProcessedTxs[id]; exists { @@ -529,7 +532,7 @@ func (c *pendingTxContext) GetTxState(id string) (TxState, error) { if tx, exists := c.finalizedErroredTxs[id]; exists { return tx.state, nil } - return NotFound, fmt.Errorf("failed to find transaction for id: %s", id) + return utils.NotFound, fmt.Errorf("failed to find transaction for id: %s", id) } // TrimFinalizedErroredTxs deletes transactions from the finalized/errored map and the allTxs map after the retention period has passed @@ -644,7 +647,7 @@ func (c *pendingTxContextWithProm) OnFinalized(sig solana.Signature, retentionTi return id, err } -func (c *pendingTxContextWithProm) OnError(sig solana.Signature, retentionTimeout time.Duration, txState TxState, errType TxErrType) (string, error) { +func (c *pendingTxContextWithProm) OnError(sig solana.Signature, retentionTimeout time.Duration, txState utils.TxState, errType TxErrType) (string, error) { id, err := c.pendingTx.OnError(sig, retentionTimeout, txState, errType) // err indicates transaction not found so may already be removed if err == nil { incrementErrorMetrics(errType, c.chainID) @@ -652,7 +655,7 @@ func (c *pendingTxContextWithProm) OnError(sig solana.Signature, retentionTimeou return id, err } -func (c *pendingTxContextWithProm) OnPrebroadcastError(id string, retentionTimeout time.Duration, txState TxState, errType TxErrType) error { +func (c *pendingTxContextWithProm) OnPrebroadcastError(id string, retentionTimeout time.Duration, txState utils.TxState, errType TxErrType) error { err := c.pendingTx.OnPrebroadcastError(id, retentionTimeout, txState, errType) // err indicates transaction not found so may already be removed if err == nil { incrementErrorMetrics(errType, c.chainID) @@ -679,7 +682,7 @@ func incrementErrorMetrics(errType TxErrType, chainID string) { promSolTxmErrorTxs.WithLabelValues(chainID).Inc() } -func (c *pendingTxContextWithProm) GetTxState(id string) (TxState, error) { +func (c *pendingTxContextWithProm) GetTxState(id string) (utils.TxState, error) { return c.pendingTx.GetTxState(id) } diff --git a/pkg/solana/txm/pendingtx_test.go b/pkg/solana/txm/pendingtx_test.go index a79f9f7aa..f9e956513 100644 --- a/pkg/solana/txm/pendingtx_test.go +++ b/pkg/solana/txm/pendingtx_test.go @@ -13,6 +13,8 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) func TestPendingTxContext_add_remove_multiple(t *testing.T) { @@ -92,7 +94,7 @@ func TestPendingTxContext_new(t *testing.T) { require.Equal(t, sig, tx.signatures[0], "signature should match") // Check status is Broadcasted - require.Equal(t, Broadcasted, tx.state, "transaction state should be Broadcasted") + require.Equal(t, utils.Broadcasted, tx.state, "transaction state should be Broadcasted") // Check it does not exist in confirmed nor finalized maps _, exists = txs.confirmedTxs[msg.id] @@ -250,7 +252,7 @@ func TestPendingTxContext_on_broadcasted_processed(t *testing.T) { require.Equal(t, sig, tx.signatures[0]) // Check status is Processed - require.Equal(t, Processed, tx.state) + require.Equal(t, utils.Processed, tx.state) // Check it does not exist in confirmed map _, exists = txs.confirmedTxs[msg.id] @@ -321,7 +323,7 @@ func TestPendingTxContext_on_broadcasted_processed(t *testing.T) { require.NoError(t, err) // Transition to errored state - id, err := txs.OnError(sig, retentionTimeout, Errored, 0) + id, err := txs.OnError(sig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -389,7 +391,7 @@ func TestPendingTxContext_on_confirmed(t *testing.T) { require.Equal(t, sig, tx.signatures[0]) // Check status is Confirmed - require.Equal(t, Confirmed, tx.state) + require.Equal(t, utils.Confirmed, tx.state) // Check it does not exist in finalized map _, exists = txs.finalizedErroredTxs[msg.id] @@ -433,7 +435,7 @@ func TestPendingTxContext_on_confirmed(t *testing.T) { require.NoError(t, err) // Transition to errored state - id, err := txs.OnError(sig, retentionTimeout, Errored, 0) + id, err := txs.OnError(sig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -503,7 +505,7 @@ func TestPendingTxContext_on_finalized(t *testing.T) { require.True(t, exists) // Check status is Finalized - require.Equal(t, Finalized, tx.state) + require.Equal(t, utils.Finalized, tx.state) // Check sigs do no exist in signature map _, exists = txs.sigToID[sig1] @@ -553,7 +555,7 @@ func TestPendingTxContext_on_finalized(t *testing.T) { require.True(t, exists) // Check status is Finalized - require.Equal(t, Finalized, tx.state) + require.Equal(t, utils.Finalized, tx.state) // Check sigs do no exist in signature map _, exists = txs.sigToID[sig1] @@ -611,7 +613,7 @@ func TestPendingTxContext_on_finalized(t *testing.T) { require.NoError(t, err) // Transition to errored state - id, err := txs.OnError(sig, retentionTimeout, Errored, 0) + id, err := txs.OnError(sig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -636,7 +638,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.NoError(t, err) // Transition to errored state - id, err := txs.OnError(sig, retentionTimeout, Errored, 0) + id, err := txs.OnError(sig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -653,7 +655,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.True(t, exists) // Check status is Finalized - require.Equal(t, Errored, tx.state) + require.Equal(t, utils.Errored, tx.state) // Check sigs do no exist in signature map _, exists = txs.sigToID[sig] @@ -674,7 +676,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.Equal(t, msg.id, id) // Transition to errored state - id, err = txs.OnError(sig, retentionTimeout, Errored, 0) + id, err = txs.OnError(sig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -691,7 +693,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.True(t, exists) // Check status is Finalized - require.Equal(t, Errored, tx.state) + require.Equal(t, utils.Errored, tx.state) // Check sigs do no exist in signature map _, exists = txs.sigToID[sig] @@ -707,7 +709,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.NoError(t, err) // Transition to fatally errored state - id, err := txs.OnError(sig, retentionTimeout, FatallyErrored, 0) + id, err := txs.OnError(sig, retentionTimeout, utils.FatallyErrored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -720,7 +722,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.True(t, exists) // Check status is Errored - require.Equal(t, FatallyErrored, tx.state) + require.Equal(t, utils.FatallyErrored, tx.state) // Check sigs do no exist in signature map _, exists = txs.sigToID[sig] @@ -741,7 +743,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.Equal(t, msg.id, id) // Transition to errored state - id, err = txs.OnError(sig, 0*time.Second, Errored, 0) + id, err = txs.OnError(sig, 0*time.Second, utils.Errored, 0) require.NoError(t, err) require.Equal(t, msg.id, id) @@ -776,7 +778,7 @@ func TestPendingTxContext_on_error(t *testing.T) { require.Equal(t, msg.id, id) // Transition back to confirmed state - id, err = txs.OnError(sig, retentionTimeout, Errored, 0) + id, err = txs.OnError(sig, retentionTimeout, utils.Errored, 0) require.Error(t, err) require.Equal(t, "", id) }) @@ -792,7 +794,7 @@ func TestPendingTxContext_on_prebroadcast_error(t *testing.T) { // Create new transaction msg := pendingTx{id: uuid.NewString()} // Transition to errored state - err := txs.OnPrebroadcastError(msg.id, retentionTimeout, Errored, 0) + err := txs.OnPrebroadcastError(msg.id, retentionTimeout, utils.Errored, 0) require.NoError(t, err) // Check it exists in errored map @@ -800,7 +802,7 @@ func TestPendingTxContext_on_prebroadcast_error(t *testing.T) { require.True(t, exists) // Check status is Errored - require.Equal(t, Errored, tx.state) + require.Equal(t, utils.Errored, tx.state) }) t.Run("successfully adds transaction with fatally errored state", func(t *testing.T) { @@ -808,7 +810,7 @@ func TestPendingTxContext_on_prebroadcast_error(t *testing.T) { msg := pendingTx{id: uuid.NewString()} // Transition to fatally errored state - err := txs.OnPrebroadcastError(msg.id, retentionTimeout, FatallyErrored, 0) + err := txs.OnPrebroadcastError(msg.id, retentionTimeout, utils.FatallyErrored, 0) require.NoError(t, err) // Check it exists in errored map @@ -816,7 +818,7 @@ func TestPendingTxContext_on_prebroadcast_error(t *testing.T) { require.True(t, exists) // Check status is Errored - require.Equal(t, FatallyErrored, tx.state) + require.Equal(t, utils.FatallyErrored, tx.state) }) t.Run("fails to add transaction to errored map if id exists in another map already", func(t *testing.T) { @@ -829,7 +831,7 @@ func TestPendingTxContext_on_prebroadcast_error(t *testing.T) { require.NoError(t, err) // Transition to errored state - err = txs.OnPrebroadcastError(msg.id, retentionTimeout, FatallyErrored, 0) + err = txs.OnPrebroadcastError(msg.id, retentionTimeout, utils.FatallyErrored, 0) require.ErrorIs(t, err, ErrIDAlreadyExists) }) @@ -837,11 +839,11 @@ func TestPendingTxContext_on_prebroadcast_error(t *testing.T) { txID := uuid.NewString() // Transition to errored state - err := txs.OnPrebroadcastError(txID, retentionTimeout, Errored, 0) + err := txs.OnPrebroadcastError(txID, retentionTimeout, utils.Errored, 0) require.NoError(t, err) // Transition back to errored state - err = txs.OnPrebroadcastError(txID, retentionTimeout, Errored, 0) + err = txs.OnPrebroadcastError(txID, retentionTimeout, utils.Errored, 0) require.ErrorIs(t, err, ErrAlreadyInExpectedState) }) } @@ -900,7 +902,7 @@ func TestPendingTxContext_remove(t *testing.T) { erroredMsg := pendingTx{id: erroredID} err = txs.New(erroredMsg, erroredSig, cancel) require.NoError(t, err) - id, err = txs.OnError(erroredSig, retentionTimeout, Errored, 0) + id, err = txs.OnError(erroredSig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, erroredMsg.id, id) @@ -1097,7 +1099,7 @@ func TestGetTxState(t *testing.T) { err := txs.New(broadcastedMsg, broadcastedSig, cancel) require.NoError(t, err) - var state TxState + var state utils.TxState // Create new processed transaction processedMsg := pendingTx{id: uuid.NewString()} err = txs.New(processedMsg, processedSig, cancel) @@ -1108,7 +1110,7 @@ func TestGetTxState(t *testing.T) { // Check Processed state is returned state, err = txs.GetTxState(processedMsg.id) require.NoError(t, err) - require.Equal(t, Processed, state) + require.Equal(t, utils.Processed, state) // Create new confirmed transaction confirmedMsg := pendingTx{id: uuid.NewString()} @@ -1120,7 +1122,7 @@ func TestGetTxState(t *testing.T) { // Check Confirmed state is returned state, err = txs.GetTxState(confirmedMsg.id) require.NoError(t, err) - require.Equal(t, Confirmed, state) + require.Equal(t, utils.Confirmed, state) // Create new finalized transaction finalizedMsg := pendingTx{id: uuid.NewString()} @@ -1132,36 +1134,36 @@ func TestGetTxState(t *testing.T) { // Check Finalized state is returned state, err = txs.GetTxState(finalizedMsg.id) require.NoError(t, err) - require.Equal(t, Finalized, state) + require.Equal(t, utils.Finalized, state) // Create new errored transaction erroredMsg := pendingTx{id: uuid.NewString()} err = txs.New(erroredMsg, erroredSig, cancel) require.NoError(t, err) - id, err = txs.OnError(erroredSig, retentionTimeout, Errored, 0) + id, err = txs.OnError(erroredSig, retentionTimeout, utils.Errored, 0) require.NoError(t, err) require.Equal(t, erroredMsg.id, id) // Check Errored state is returned state, err = txs.GetTxState(erroredMsg.id) require.NoError(t, err) - require.Equal(t, Errored, state) + require.Equal(t, utils.Errored, state) // Create new fatally errored transaction fatallyErroredMsg := pendingTx{id: uuid.NewString()} err = txs.New(fatallyErroredMsg, fatallyErroredSig, cancel) require.NoError(t, err) - id, err = txs.OnError(fatallyErroredSig, retentionTimeout, FatallyErrored, 0) + id, err = txs.OnError(fatallyErroredSig, retentionTimeout, utils.FatallyErrored, 0) require.NoError(t, err) require.Equal(t, fatallyErroredMsg.id, id) // Check Errored state is returned state, err = txs.GetTxState(fatallyErroredMsg.id) require.NoError(t, err) - require.Equal(t, FatallyErrored, state) + require.Equal(t, utils.FatallyErrored, state) // Check NotFound state is returned if unknown id provided state, err = txs.GetTxState("unknown id") require.Error(t, err) - require.Equal(t, NotFound, state) + require.Equal(t, utils.NotFound, state) } func randomSignature(t *testing.T) solana.Signature { @@ -1193,12 +1195,12 @@ func TestPendingTxContext_ListAllExpiredBroadcastedTxs(t *testing.T) { setup: func(t *testing.T, ctx *pendingTxContext) { tx1 := pendingTx{ id: "tx1", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1500, } tx2 := pendingTx{ id: "tx2", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1600, } ctx.broadcastedProcessedTxs["tx1"] = tx1 @@ -1212,17 +1214,17 @@ func TestPendingTxContext_ListAllExpiredBroadcastedTxs(t *testing.T) { setup: func(t *testing.T, ctx *pendingTxContext) { tx1 := pendingTx{ id: "tx1", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1000, } tx2 := pendingTx{ id: "tx2", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1500, } tx3 := pendingTx{ id: "tx3", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 900, } ctx.broadcastedProcessedTxs["tx1"] = tx1 @@ -1237,12 +1239,12 @@ func TestPendingTxContext_ListAllExpiredBroadcastedTxs(t *testing.T) { setup: func(t *testing.T, ctx *pendingTxContext) { tx1 := pendingTx{ id: "tx1", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1000, } tx2 := pendingTx{ id: "tx2", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1500, } ctx.broadcastedProcessedTxs["tx1"] = tx1 @@ -1256,17 +1258,17 @@ func TestPendingTxContext_ListAllExpiredBroadcastedTxs(t *testing.T) { setup: func(t *testing.T, ctx *pendingTxContext) { tx1 := pendingTx{ id: "tx1", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 800, } tx2 := pendingTx{ id: "tx2", - state: Processed, // Not Broadcasted + state: utils.Processed, // Not Broadcasted lastValidBlockHeight: 700, } tx3 := pendingTx{ id: "tx3", - state: Processed, // Not Broadcasted + state: utils.Processed, // Not Broadcasted lastValidBlockHeight: 600, } ctx.broadcastedProcessedTxs["tx1"] = tx1 @@ -1281,17 +1283,17 @@ func TestPendingTxContext_ListAllExpiredBroadcastedTxs(t *testing.T) { setup: func(t *testing.T, ctx *pendingTxContext) { tx1 := pendingTx{ id: "tx1", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1000, } tx2 := pendingTx{ id: "tx2", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 999, } tx3 := pendingTx{ id: "tx3", - state: Broadcasted, + state: utils.Broadcasted, lastValidBlockHeight: 1, } ctx.broadcastedProcessedTxs["tx1"] = tx1 @@ -1304,7 +1306,6 @@ func TestPendingTxContext_ListAllExpiredBroadcastedTxs(t *testing.T) { } for _, tt := range tests { - tt := tt // capture range variable t.Run(tt.name, func(t *testing.T) { // Initialize a new PendingTxContext ctx := newPendingTxContext() diff --git a/pkg/solana/txm/txm.go b/pkg/solana/txm/txm.go index 3e169d88a..c87089060 100644 --- a/pkg/solana/txm/txm.go +++ b/pkg/solana/txm/txm.go @@ -25,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" "github.com/smartcontractkit/chainlink-solana/pkg/solana/internal" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) const ( @@ -36,8 +37,6 @@ const ( MaxComputeUnitLimit = 1_400_000 // max compute unit limit a transaction can have ) -var _ services.Service = (*Txm)(nil) - type SimpleKeystore interface { Sign(ctx context.Context, account string, data []byte) (signature []byte, err error) Accounts(ctx context.Context) (accounts []string, err error) @@ -45,6 +44,14 @@ type SimpleKeystore interface { var _ loop.Keystore = (SimpleKeystore)(nil) +type TxManager interface { + services.Service + Enqueue(ctx context.Context, accountID string, tx *solanaGo.Transaction, txID *string, txLastValidBlockHeight uint64, txCfgs ...txmutils.SetTxConfig) error + GetTransactionStatus(ctx context.Context, transactionID string) (commontypes.TransactionStatus, error) +} + +var _ TxManager = (*Txm)(nil) + // Txm manages transactions for the solana blockchain. // simple implementation with no persistently stored txs type Txm struct { @@ -64,19 +71,6 @@ type Txm struct { sendTx func(ctx context.Context, tx *solanaGo.Transaction) (solanaGo.Signature, error) } -type TxConfig struct { - Timeout time.Duration // transaction broadcast timeout - - // compute unit price config - FeeBumpPeriod time.Duration // how often to bump fee - BaseComputeUnitPrice uint64 // starting price - ComputeUnitPriceMin uint64 // min price - ComputeUnitPriceMax uint64 // max price - - EstimateComputeUnitLimit bool // enable compute limit estimations using simulation - ComputeUnitLimit uint32 // compute unit limit -} - // NewTxm creates a txm. Uses simulation so should only be used to send txes to trusted contracts i.e. OCR. func NewTxm(chainID string, client internal.Loader[client.ReaderWriter], sendTx func(ctx context.Context, tx *solanaGo.Transaction) (solanaGo.Signature, error), @@ -195,7 +189,7 @@ func (txm *Txm) sendWithRetry(ctx context.Context, msg pendingTx) (solanaGo.Tran if initSendErr != nil { // Do not retry and exit early if fails cancel() - stateTransitionErr := txm.txs.OnPrebroadcastError(msg.id, txm.cfg.TxRetentionTimeout(), Errored, TxFailReject) + stateTransitionErr := txm.txs.OnPrebroadcastError(msg.id, txm.cfg.TxRetentionTimeout(), txmutils.Errored, TxFailReject) return solanaGo.Transaction{}, "", solanaGo.Signature{}, fmt.Errorf("tx failed initial transmit: %w", errors.Join(initSendErr, stateTransitionErr)) } @@ -208,7 +202,7 @@ func (txm *Txm) sendWithRetry(ctx context.Context, msg pendingTx) (solanaGo.Tran txm.lggr.Debugw("tx initial broadcast", "id", msg.id, "fee", msg.cfg.BaseComputeUnitPrice, "signature", sig, "lastValidBlockHeight", msg.lastValidBlockHeight) // Initialize signature list with initialTx signature. This list will be used to add new signatures and track retry attempts. - sigs := &signatureList{} + sigs := &txmutils.SignatureList{} sigs.Allocate() if initSetErr := sigs.Set(0, sig); initSetErr != nil { return solanaGo.Transaction{}, "", solanaGo.Signature{}, fmt.Errorf("failed to save initial signature in signature list: %w", initSetErr) @@ -269,7 +263,7 @@ func (txm *Txm) buildTx(ctx context.Context, msg pendingTx, retryCount int) (sol // retryTx contains the logic for retrying the transaction, including exponential backoff and fee bumping. // Retries until context cancelled by timeout or called externally. // It uses handleRetry helper function to handle each retry attempt. -func (txm *Txm) retryTx(ctx context.Context, msg pendingTx, currentTx solanaGo.Transaction, sigs *signatureList) { +func (txm *Txm) retryTx(ctx context.Context, msg pendingTx, currentTx solanaGo.Transaction, sigs *txmutils.SignatureList) { deltaT := 1 // initial delay in ms tick := time.After(0) bumpCount := 0 @@ -306,7 +300,7 @@ func (txm *Txm) retryTx(ctx context.Context, msg pendingTx, currentTx solanaGo.T } // Start a goroutine to handle the retry attempt - // takes currentTx and rebroadcast. If needs bumping it will new signature to already allocated space in signatureList. + // takes currentTx and rebroadcast. If needs bumping it will new signature to already allocated space in txmutils.SignatureList. wg.Add(1) go func(bump bool, count int, retryTx solanaGo.Transaction) { defer wg.Done() @@ -324,7 +318,7 @@ func (txm *Txm) retryTx(ctx context.Context, msg pendingTx, currentTx solanaGo.T } // handleRetry handles the logic for each retry attempt, including sending the transaction, updating signatures, and logging. -func (txm *Txm) handleRetry(ctx context.Context, msg pendingTx, bump bool, count int, retryTx solanaGo.Transaction, sigs *signatureList) { +func (txm *Txm) handleRetry(ctx context.Context, msg pendingTx, bump bool, count int, retryTx solanaGo.Transaction, sigs *txmutils.SignatureList) { // send retry transaction retrySig, err := txm.sendTx(ctx, &retryTx) if err != nil { @@ -426,7 +420,7 @@ func (txm *Txm) processConfirmations(ctx context.Context, client client.ReaderWr defer wg.Done() // to process successful first - sortedSigs, sortedRes, err := SortSignaturesAndResults(sigsBatch[i], statuses) + sortedSigs, sortedRes, err := txmutils.SortSignaturesAndResults(sigsBatch[i], statuses) if err != nil { txm.lggr.Errorw("sorting error", "error", err) return @@ -474,7 +468,7 @@ func (txm *Txm) processConfirmations(ctx context.Context, client client.ReaderWr func (txm *Txm) handleNotFoundSignatureStatus(sig solanaGo.Signature) { txm.lggr.Debugw("tx state: not found", "signature", sig) if txm.cfg.TxConfirmTimeout() != 0*time.Second && txm.txs.Expired(sig, txm.cfg.TxConfirmTimeout()) { - id, err := txm.txs.OnError(sig, txm.cfg.TxRetentionTimeout(), Errored, TxFailDrop) + id, err := txm.txs.OnError(sig, txm.cfg.TxRetentionTimeout(), txmutils.Errored, TxFailDrop) if err != nil { txm.lggr.Infow("failed to mark transaction as errored", "id", id, "signature", sig, "timeoutSeconds", txm.cfg.TxConfirmTimeout(), "error", err) } else { @@ -518,7 +512,7 @@ func (txm *Txm) handleProcessedSignatureStatus(sig solanaGo.Signature) { } // check confirm timeout exceeded if TxConfirmTimeout set if txm.cfg.TxConfirmTimeout() != 0*time.Second && txm.txs.Expired(sig, txm.cfg.TxConfirmTimeout()) { - id, err := txm.txs.OnError(sig, txm.cfg.TxRetentionTimeout(), Errored, TxFailDrop) + id, err := txm.txs.OnError(sig, txm.cfg.TxRetentionTimeout(), txmutils.Errored, TxFailDrop) if err != nil { txm.lggr.Infow("failed to mark transaction as errored", "id", id, "signature", sig, "timeoutSeconds", txm.cfg.TxConfirmTimeout(), "error", err) } else { @@ -597,7 +591,7 @@ func (txm *Txm) rebroadcastExpiredTxs(ctx context.Context, client client.ReaderW // call sendWithRetry directly to avoid enqueuing _, _, _, sendErr := txm.sendWithRetry(ctx, rebroadcastTx) if sendErr != nil { - stateTransitionErr := txm.txs.OnPrebroadcastError(tx.id, txm.cfg.TxRetentionTimeout(), Errored, TxFailReject) + stateTransitionErr := txm.txs.OnPrebroadcastError(tx.id, txm.cfg.TxRetentionTimeout(), txmutils.Errored, TxFailReject) txm.lggr.Errorw("failed to rebroadcast transaction", "id", tx.id, "error", errors.Join(sendErr, stateTransitionErr)) continue } @@ -673,7 +667,7 @@ func (txm *Txm) reap() { } // Enqueue enqueues a msg destined for the solana chain. -func (txm *Txm) Enqueue(ctx context.Context, accountID string, tx *solanaGo.Transaction, txID *string, txLastValidBlockHeight uint64, txCfgs ...SetTxConfig) error { +func (txm *Txm) Enqueue(ctx context.Context, accountID string, tx *solanaGo.Transaction, txID *string, txLastValidBlockHeight uint64, txCfgs ...txmutils.SetTxConfig) error { if err := txm.Ready(); err != nil { return fmt.Errorf("error in soltxm.Enqueue: %w", err) } @@ -744,15 +738,15 @@ func (txm *Txm) GetTransactionStatus(ctx context.Context, transactionID string) } switch state { - case Broadcasted: + case txmutils.Broadcasted: return commontypes.Pending, nil - case Processed, Confirmed: + case txmutils.Processed, txmutils.Confirmed: return commontypes.Unconfirmed, nil - case Finalized: + case txmutils.Finalized: return commontypes.Finalized, nil - case Errored: + case txmutils.Errored: return commontypes.Failed, nil - case FatallyErrored: + case txmutils.FatallyErrored: return commontypes.Fatal, nil default: return commontypes.Unknown, fmt.Errorf("found unknown transaction state: %s", state.String()) @@ -840,7 +834,7 @@ func (txm *Txm) simulateTx(ctx context.Context, tx *solanaGo.Transaction) (res * } // ProcessError parses and handles relevant errors found in simulation results -func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulation bool) (txState TxState, errType TxErrType) { +func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulation bool) (txState txmutils.TxState, errType TxErrType) { if resErr != nil { // handle various errors // https://github.com/solana-labs/solana/blob/master/sdk/src/transaction/error.rs @@ -866,7 +860,7 @@ func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulat if simulation { return txState, NoFailure } - return Errored, errType + return txmutils.Errored, errType // transaction is already processed in the chain case strings.Contains(errStr, "AlreadyProcessed"): txm.lggr.Debugw("AlreadyProcessed", logValues...) @@ -875,39 +869,39 @@ func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulat if simulation { return txState, NoFailure } - return Errored, errType + return txmutils.Errored, errType // transaction will encounter execution error/revert case strings.Contains(errStr, "InstructionError"): txm.lggr.Debugw("InstructionError", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // transaction contains an invalid account reference case strings.Contains(errStr, "InvalidAccountIndex"): txm.lggr.Debugw("InvalidAccountIndex", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // transaction loads a writable account that cannot be written case strings.Contains(errStr, "InvalidWritableAccount"): txm.lggr.Debugw("InvalidWritableAccount", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // address lookup table not found case strings.Contains(errStr, "AddressLookupTableNotFound"): txm.lggr.Debugw("AddressLookupTableNotFound", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // attempted to lookup addresses from an invalid account case strings.Contains(errStr, "InvalidAddressLookupTableData"): txm.lggr.Debugw("InvalidAddressLookupTableData", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // address table lookup uses an invalid index case strings.Contains(errStr, "InvalidAddressLookupTableIndex"): txm.lggr.Debugw("InvalidAddressLookupTableIndex", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // attempt to debit an account but found no record of a prior credit. case strings.Contains(errStr, "AccountNotFound"): txm.lggr.Debugw("AccountNotFound", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // attempt to load a program that does not exist case strings.Contains(errStr, "ProgramAccountNotFound"): txm.lggr.Debugw("ProgramAccountNotFound", logValues...) - return FatallyErrored, errType + return txmutils.FatallyErrored, errType // unrecognized errors (indicates more concerning failures) default: // if simulating, return TxFailSimOther if error unknown @@ -915,7 +909,7 @@ func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulat errType = TxFailSimOther } txm.lggr.Errorw("unrecognized error", logValues...) - return Errored, errType + return txmutils.Errored, errType } } return @@ -938,8 +932,8 @@ func (txm *Txm) Name() string { return txm.lggr.Name() } func (txm *Txm) HealthReport() map[string]error { return map[string]error{txm.Name(): txm.Healthy()} } -func (txm *Txm) defaultTxConfig() TxConfig { - return TxConfig{ +func (txm *Txm) defaultTxConfig() txmutils.TxConfig { + return txmutils.TxConfig{ Timeout: txm.cfg.TxRetryTimeout(), FeeBumpPeriod: txm.cfg.FeeBumpPeriod(), BaseComputeUnitPrice: txm.fee.BaseComputeUnitPrice(), diff --git a/pkg/solana/txm/txm_internal_test.go b/pkg/solana/txm/txm_internal_test.go index 13c861362..15e4631a3 100644 --- a/pkg/solana/txm/txm_internal_test.go +++ b/pkg/solana/txm/txm_internal_test.go @@ -24,6 +24,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" keyMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" relayconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -687,7 +688,7 @@ func TestTxm(t *testing.T) { // send tx - with disabled fee bumping testTxID := uuid.New().String() lastValidBlockHeight := uint64(100) - assert.NoError(t, txm.Enqueue(ctx, t.Name(), tx, &testTxID, lastValidBlockHeight, SetFeeBumpPeriod(0))) + assert.NoError(t, txm.Enqueue(ctx, t.Name(), tx, &testTxID, lastValidBlockHeight, txmutils.SetFeeBumpPeriod(0))) wg.Wait() // no transactions stored inflight txs list @@ -740,7 +741,7 @@ func TestTxm(t *testing.T) { // send tx - with disabled fee bumping and disabled compute unit limit testTxID := uuid.New().String() lastValidBlockHeight := uint64(100) - assert.NoError(t, txm.Enqueue(ctx, t.Name(), tx, &testTxID, lastValidBlockHeight, SetFeeBumpPeriod(0), SetComputeUnitLimit(0))) + assert.NoError(t, txm.Enqueue(ctx, t.Name(), tx, &testTxID, lastValidBlockHeight, txmutils.SetFeeBumpPeriod(0), txmutils.SetComputeUnitLimit(0))) wg.Wait() // no transactions stored inflight txs list diff --git a/pkg/solana/txm/txm_unit_test.go b/pkg/solana/txm/txm_unit_test.go index 7dfec6c57..e8dfcb584 100644 --- a/pkg/solana/txm/txm_unit_test.go +++ b/pkg/solana/txm/txm_unit_test.go @@ -18,6 +18,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" solanatxm "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" keyMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils" @@ -174,12 +175,12 @@ func TestTxm_ProcessError(t *testing.T) { // returns no failure if BlockhashNotFound encountered during simulation txState, errType := txm.ProcessError(solana.Signature{}, err, true) require.Equal(t, solanatxm.NoFailure, errType) - require.Equal(t, solanatxm.NotFound, txState) // default enum value + require.Equal(t, txmutils.NotFound, txState) // default enum value // returns error if BlockhashNotFound encountered during normal processing txState, errType = txm.ProcessError(solana.Signature{}, err, false) require.Equal(t, solanatxm.TxFailRevert, errType) - require.Equal(t, solanatxm.Errored, txState) // default enum value + require.Equal(t, txmutils.Errored, txState) // default enum value }) t.Run("process AlreadyProcessed error", func(t *testing.T) { t.Parallel() @@ -191,12 +192,12 @@ func TestTxm_ProcessError(t *testing.T) { // returns no failure if AlreadyProcessed encountered during simulation txState, errType := txm.ProcessError(solana.Signature{}, err, true) require.Equal(t, solanatxm.NoFailure, errType) - require.Equal(t, solanatxm.NotFound, txState) // default enum value + require.Equal(t, txmutils.NotFound, txState) // default enum value // returns error if AlreadyProcessed encountered during normal processing txState, errType = txm.ProcessError(solana.Signature{}, err, false) require.Equal(t, solanatxm.TxFailRevert, errType) - require.Equal(t, solanatxm.Errored, txState) // default enum value + require.Equal(t, txmutils.Errored, txState) // default enum value }) t.Run("process fatal error cases", func(t *testing.T) { t.Parallel() @@ -212,12 +213,12 @@ func TestTxm_ProcessError(t *testing.T) { // returns fatal error if InstructionError encountered during simulation txState, errType := txm.ProcessError(solana.Signature{}, err, true) require.Equal(t, solanatxm.TxFailSimRevert, errType) - require.Equal(t, solanatxm.FatallyErrored, txState) // default enum value + require.Equal(t, txmutils.FatallyErrored, txState) // default enum value // returns fatal error if InstructionError encountered during normal processing txState, errType = txm.ProcessError(solana.Signature{}, err, false) require.Equal(t, solanatxm.TxFailRevert, errType) - require.Equal(t, solanatxm.FatallyErrored, txState) // default enum value + require.Equal(t, txmutils.FatallyErrored, txState) // default enum value }) } }) @@ -231,12 +232,12 @@ func TestTxm_ProcessError(t *testing.T) { // returns fatal error if InstructionError encountered during simulation txState, errType := txm.ProcessError(solana.Signature{}, err, true) require.Equal(t, solanatxm.TxFailSimOther, errType) - require.Equal(t, solanatxm.Errored, txState) // default enum value + require.Equal(t, txmutils.Errored, txState) // default enum value // returns fatal error if InstructionError encountered during normal processing txState, errType = txm.ProcessError(solana.Signature{}, err, false) require.Equal(t, solanatxm.TxFailRevert, errType) - require.Equal(t, solanatxm.Errored, txState) // default enum value + require.Equal(t, txmutils.Errored, txState) // default enum value }) } diff --git a/pkg/solana/txm/utils.go b/pkg/solana/txm/utils/utils.go similarity index 82% rename from pkg/solana/txm/utils.go rename to pkg/solana/txm/utils/utils.go index fef260e3d..7f3ffb9e2 100644 --- a/pkg/solana/txm/utils.go +++ b/pkg/solana/txm/utils/utils.go @@ -1,4 +1,4 @@ -package txm +package utils import ( "errors" @@ -111,39 +111,39 @@ func convertStatus(res *rpc.SignatureStatusesResult) TxState { return NotFound } -type signatureList struct { +type SignatureList struct { sigs []solana.Signature lock sync.RWMutex wg []*sync.WaitGroup } // internal function that should be called using the proper lock -func (s *signatureList) get(index int) (sig solana.Signature, err error) { +func (s *SignatureList) get(index int) (sig solana.Signature, err error) { if index >= len(s.sigs) { return sig, errors.New("invalid index") } return s.sigs[index], nil } -func (s *signatureList) Get(index int) (sig solana.Signature, err error) { +func (s *SignatureList) Get(index int) (sig solana.Signature, err error) { s.lock.RLock() defer s.lock.RUnlock() return s.get(index) } -func (s *signatureList) List() []solana.Signature { +func (s *SignatureList) List() []solana.Signature { s.lock.RLock() defer s.lock.RUnlock() return s.sigs } -func (s *signatureList) Length() int { +func (s *SignatureList) Length() int { s.lock.RLock() defer s.lock.RUnlock() return len(s.sigs) } -func (s *signatureList) Allocate() (index int) { +func (s *SignatureList) Allocate() (index int) { s.lock.Lock() defer s.lock.Unlock() @@ -156,7 +156,7 @@ func (s *signatureList) Allocate() (index int) { return len(s.sigs) - 1 } -func (s *signatureList) Set(index int, sig solana.Signature) error { +func (s *SignatureList) Set(index int, sig solana.Signature) error { s.lock.Lock() defer s.lock.Unlock() @@ -174,7 +174,7 @@ func (s *signatureList) Set(index int, sig solana.Signature) error { return nil } -func (s *signatureList) Wait(index int) { +func (s *SignatureList) Wait(index int) { wg := &sync.WaitGroup{} s.lock.RLock() if index < len(s.wg) { @@ -185,6 +185,19 @@ func (s *signatureList) Wait(index int) { wg.Wait() } +type TxConfig struct { + Timeout time.Duration // transaction broadcast timeout + + // compute unit price config + FeeBumpPeriod time.Duration // how often to bump fee + BaseComputeUnitPrice uint64 // starting price + ComputeUnitPriceMin uint64 // min price + ComputeUnitPriceMax uint64 // max price + + EstimateComputeUnitLimit bool // enable compute limit estimations using simulation + ComputeUnitLimit uint32 // compute unit limit +} + type SetTxConfig func(*TxConfig) func SetTimeout(t time.Duration) SetTxConfig { diff --git a/pkg/solana/txm/utils_test.go b/pkg/solana/txm/utils/utils_test.go similarity index 98% rename from pkg/solana/txm/utils_test.go rename to pkg/solana/txm/utils/utils_test.go index f4ac868ff..676f04202 100644 --- a/pkg/solana/txm/utils_test.go +++ b/pkg/solana/txm/utils/utils_test.go @@ -1,4 +1,4 @@ -package txm +package utils import ( "sync" @@ -42,7 +42,7 @@ func TestSortSignaturesAndResults(t *testing.T) { } func TestSignatureList_AllocateWaitSet(t *testing.T) { - sigs := signatureList{} + sigs := SignatureList{} assert.Equal(t, 0, sigs.Length()) // can't set without pre-allocating diff --git a/pkg/solana/utils.go b/pkg/solana/utils.go deleted file mode 100644 index a4387aea8..000000000 --- a/pkg/solana/utils.go +++ /dev/null @@ -1,5 +0,0 @@ -package solana - -import "github.com/smartcontractkit/chainlink-solana/pkg/solana/internal" - -func LamportsToSol(lamports uint64) float64 { return internal.LamportsToSol(lamports) } diff --git a/pkg/solana/utils/utils.go b/pkg/solana/utils/utils.go new file mode 100644 index 000000000..764c236de --- /dev/null +++ b/pkg/solana/utils/utils.go @@ -0,0 +1,219 @@ +package utils + +import ( + "context" + "encoding/binary" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/internal" +) + +var ( + _, b, _, _ = runtime.Caller(0) + // ProjectRoot Root folder of this project + ProjectRoot = filepath.Join(filepath.Dir(b), "/../../..") + // ContractsDir path to our contracts + ContractsDir = filepath.Join(ProjectRoot, "contracts", "target", "deploy") + PathToAnchorConfig = filepath.Join(ProjectRoot, "contracts", "Anchor.toml") +) + +func LamportsToSol(lamports uint64) float64 { return internal.LamportsToSol(lamports) } + +// TxModifier is a dynamic function used to flexibly add components to a transaction such as additional signers, and compute budget parameters +type TxModifier func(tx *solana.Transaction, signers map[solana.PublicKey]solana.PrivateKey) error + +func SendAndConfirm(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction, + signer solana.PrivateKey, commitment rpc.CommitmentType, opts ...TxModifier) *rpc.GetTransactionResult { + txres := sendTransaction(ctx, rpcClient, t, instructions, signer, commitment, false, opts...) // do not skipPreflight when expected to pass, preflight can help debug + + require.NotNil(t, txres.Meta) + require.Nil(t, txres.Meta.Err, fmt.Sprintf("tx failed with: %+v", txres.Meta)) // tx should not err, print meta if it does (contains logs) + return txres +} + +func sendTransaction(ctx context.Context, rpcClient *rpc.Client, t *testing.T, instructions []solana.Instruction, + signerAndPayer solana.PrivateKey, commitment rpc.CommitmentType, skipPreflight bool, opts ...TxModifier) *rpc.GetTransactionResult { + hashRes, err := rpcClient.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + require.NoError(t, err) + + tx, err := solana.NewTransaction( + instructions, + hashRes.Value.Blockhash, + solana.TransactionPayer(signerAndPayer.PublicKey()), + ) + require.NoError(t, err) + + // build signers map + signers := map[solana.PublicKey]solana.PrivateKey{} + signers[signerAndPayer.PublicKey()] = signerAndPayer + + // set options before signing transaction + for _, o := range opts { + require.NoError(t, o(tx, signers)) + } + + _, err = tx.Sign(func(pub solana.PublicKey) *solana.PrivateKey { + priv, ok := signers[pub] + require.True(t, ok, fmt.Sprintf("Missing signer private key for %s", pub)) + return &priv + }) + require.NoError(t, err) + + txsig, err := rpcClient.SendTransactionWithOpts(ctx, tx, rpc.TransactionOpts{SkipPreflight: skipPreflight, PreflightCommitment: rpc.CommitmentProcessed}) + require.NoError(t, err) + + var txStatus rpc.ConfirmationStatusType + count := 0 + for txStatus != rpc.ConfirmationStatusType(commitment) && txStatus != rpc.ConfirmationStatusFinalized { + count++ + statusRes, sigErr := rpcClient.GetSignatureStatuses(ctx, true, txsig) + require.NoError(t, sigErr) + if statusRes != nil && len(statusRes.Value) > 0 && statusRes.Value[0] != nil { + txStatus = statusRes.Value[0].ConfirmationStatus + } + time.Sleep(100 * time.Millisecond) + if count > 500 { + require.NoError(t, fmt.Errorf("unable to find transaction within timeout")) + } + } + + txres, err := rpcClient.GetTransaction(ctx, txsig, &rpc.GetTransactionOpts{ + Commitment: commitment, + }) + require.NoError(t, err) + return txres +} + +var ( + AddressLookupTableProgram = solana.MustPublicKeyFromBase58("AddressLookupTab1e1111111111111111111111111") +) + +const ( + InstructionCreateLookupTable uint32 = iota + InstructionFreezeLookupTable + InstructionExtendLookupTable + InstructionDeactiveLookupTable + InstructionCloseLookupTable +) + +func NewCreateLookupTableInstruction( + authority, funder solana.PublicKey, + slot uint64, +) (solana.PublicKey, solana.Instruction, error) { + // https://github.com/solana-labs/solana-web3.js/blob/c1c98715b0c7900ce37c59bffd2056fa0037213d/src/programs/address-lookup-table/index.ts#L274 + slotLE := make([]byte, 8) + binary.LittleEndian.PutUint64(slotLE, slot) + account, bumpSeed, err := solana.FindProgramAddress([][]byte{authority.Bytes(), slotLE}, AddressLookupTableProgram) + if err != nil { + return solana.PublicKey{}, nil, err + } + + data := binary.LittleEndian.AppendUint32([]byte{}, InstructionCreateLookupTable) + data = binary.LittleEndian.AppendUint64(data, slot) + data = append(data, bumpSeed) + return account, solana.NewInstruction( + AddressLookupTableProgram, + solana.AccountMetaSlice{ + solana.Meta(account).WRITE(), + solana.Meta(authority).SIGNER(), + solana.Meta(funder).SIGNER().WRITE(), + solana.Meta(solana.SystemProgramID), + }, + data, + ), nil +} + +func NewExtendLookupTableInstruction( + table, authority, funder solana.PublicKey, + accounts []solana.PublicKey, +) solana.Instruction { + // https://github.com/solana-labs/solana-web3.js/blob/c1c98715b0c7900ce37c59bffd2056fa0037213d/src/programs/address-lookup-table/index.ts#L113 + + data := binary.LittleEndian.AppendUint32([]byte{}, InstructionExtendLookupTable) + data = binary.LittleEndian.AppendUint64(data, uint64(len(accounts))) // note: this is usually u32 + 8 byte buffer + for _, a := range accounts { + data = append(data, a.Bytes()...) + } + + return solana.NewInstruction( + AddressLookupTableProgram, + solana.AccountMetaSlice{ + solana.Meta(table).WRITE(), + solana.Meta(authority).SIGNER(), + solana.Meta(funder).SIGNER().WRITE(), + solana.Meta(solana.SystemProgramID), + }, + data, + ) +} + +func FundAccounts(t *testing.T, accounts []solana.PrivateKey, solanaGoClient *rpc.Client) { + ctx := tests.Context(t) + sigs := []solana.Signature{} + for _, v := range accounts { + sig, err := solanaGoClient.RequestAirdrop(ctx, v.PublicKey(), 1000*solana.LAMPORTS_PER_SOL, rpc.CommitmentFinalized) + require.NoError(t, err) + sigs = append(sigs, sig) + } + + // wait for confirmation so later transactions don't fail + remaining := len(sigs) + count := 0 + for remaining > 0 { + count++ + statusRes, sigErr := solanaGoClient.GetSignatureStatuses(ctx, true, sigs...) + require.NoError(t, sigErr) + require.NotNil(t, statusRes) + require.NotNil(t, statusRes.Value) + + unconfirmedTxCount := 0 + for _, res := range statusRes.Value { + if res == nil || res.ConfirmationStatus == rpc.ConfirmationStatusProcessed || res.ConfirmationStatus == rpc.ConfirmationStatusConfirmed { + unconfirmedTxCount++ + } + } + remaining = unconfirmedTxCount + + time.Sleep(500 * time.Millisecond) + if count > 60 { + require.NoError(t, fmt.Errorf("unable to find transaction within timeout")) + } + } +} + +func SetupTestValidatorWithAnchorPrograms(t *testing.T, upgradeAuthority string, programs []string) (string, string) { + anchorData := struct { + Programs struct { + Localnet map[string]string + } + }{} + + // upload programs to validator + anchorBytes, err := os.ReadFile(PathToAnchorConfig) + require.NoError(t, err) + require.NoError(t, toml.Unmarshal(anchorBytes, &anchorData)) + + flags := []string{"--warp-slot", "42"} + for i := range programs { + k := programs[i] + v := anchorData.Programs.Localnet[k] + k = strings.Replace(k, "-", "_", -1) + flags = append(flags, "--upgradeable-program", v, filepath.Join(ContractsDir, k+".so"), upgradeAuthority) + } + rpcURL, wsURL := client.SetupLocalSolNodeWithFlags(t, flags...) + return rpcURL, wsURL +} diff --git a/pkg/solana/utils_test.go b/pkg/solana/utils/utils_test.go similarity index 71% rename from pkg/solana/utils_test.go rename to pkg/solana/utils/utils_test.go index 67efc932b..0f41f80c9 100644 --- a/pkg/solana/utils_test.go +++ b/pkg/solana/utils/utils_test.go @@ -1,9 +1,11 @@ -package solana +package utils_test import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" ) func TestLamportsToSol(t *testing.T) { @@ -19,7 +21,7 @@ func TestLamportsToSol(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.out, LamportsToSol(test.in)) + assert.Equal(t, test.out, utils.LamportsToSol(test.in)) }) } } diff --git a/scripts/install-solana-ci.sh b/scripts/install-solana-ci.sh index 30dd83c1f..8828b7d7f 100755 --- a/scripts/install-solana-ci.sh +++ b/scripts/install-solana-ci.sh @@ -2,5 +2,5 @@ set -euxo pipefail -sh -c "$(curl -sSfL https://release.anza.xyz/v2.0.20/install)" +sh -c "$(curl -sSfL https://release.anza.xyz/v2.0.21/install)" echo "PATH=$HOME/.local/share/solana/install/active_release/bin:$PATH" >> $GITHUB_ENV diff --git a/scripts/setup-localnet/localnet.sh b/scripts/setup-localnet/localnet.sh index ed0ef1429..3d998d0e7 100755 --- a/scripts/setup-localnet/localnet.sh +++ b/scripts/setup-localnet/localnet.sh @@ -6,7 +6,7 @@ cpu_struct="linux"; # Clean up first bash "$(dirname -- "$0";)/localnet.down.sh" -container_version=v2.0.20 +container_version=v2.0.21 container_name="chainlink-solana.test-validator" echo "Starting $container_name@$container_version" diff --git a/solana.nix b/solana.nix index 6a94a7619..b63511d5e 100644 --- a/solana.nix +++ b/solana.nix @@ -5,7 +5,7 @@ # Solana integration let - version = "v2.0.20"; + version = "v2.0.21"; getBinDerivation = { name, @@ -37,14 +37,14 @@ let name = "solana-cli-x86_64-linux"; filename = "solana-release-x86_64-unknown-linux-gnu.tar.bz2"; ### BEGIN_LINUX_SHA256 ### - sha256 = "sha256-6w624hoeRZM/HaM43I1eUeyf6oTbRfrVcb85m0Oww4s="; + sha256 = "sha256-RaQRpBbVAZIPSGp5ZNAj//oNBFC0mLePJq6/KjtBeq0="; ### END_LINUX_SHA256 ### }; aarch64-apple-darwin = getBinDerivation { name = "solana-cli-aarch64-apple-darwin"; filename = "solana-release-aarch64-apple-darwin.tar.bz2"; ### BEGIN_DARWIN_SHA256 ### - sha256 = "sha256-9jJtes7CHb5XR+Zklh3asDiyC+intzzDd6Bb69rhHRk="; + sha256 = "sha256-BftV8BkacZy/w/DZ1cBC1G4J9bvGM8jZ6fuy14nPd0U="; ### END_DARWIN_SHA256 ### }; }; diff --git a/sonar-project.properties b/sonar-project.properties index 0434465b5..bec9533fd 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,9 +5,10 @@ sonar.sources=. # Full exclusions from the static analysis sonar.exclusions=**/node_modules/**/*, **/contracts/artifacts/**/*, **/generated/**/*, **/docs/**/*, **/*.config.ts, **/*.config.js, **/*.txt, pkg/solana/codec/anchoridl.go # Coverage exclusions -sonar.coverage.exclusions=**/*.test.ts, **/*_test.go, **/contracts/tests/**/*, **/integration-tests/**/* +sonar.coverage.exclusions=**/*.test.ts, **/*_test.go, **/contracts/tests/**/*, **/integration-tests/**/*, **/pkg/solana/codec/testutils/**/* # Tests' root folder, inclusions (tests to check and count) and exclusions sonar.tests=. sonar.test.inclusions=**/*_test.go, **/contracts/tests/**/* -sonar.test.exclusions=**/integration-tests/*, **/gauntlet/* \ No newline at end of file +sonar.test.exclusions=**/integration-tests/*, **/gauntlet/* +sonar.cpd.exclusions=**/pkg/solana/codec/testutils/**/* \ No newline at end of file