-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add pda finders and contract address parser to the Solana SDK (#…
…225) Add a few helpers to the Solana SDK: * PDA finders, similar to the ones found in the chainlink-ccip repository * A pair of functions to encode and decode contract addresses (from `string` to `program_id + seed` and vice-versa) <!-- DON'T DELETE. add your comments above llm generated contents -->
- Loading branch information
1 parent
4adb968
commit 7c9cd3d
Showing
8 changed files
with
287 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@smartcontractkit/mcms": patch | ||
--- | ||
|
||
Add PDA finders and ContractAddress parser to the Solana SDK |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package solana | ||
|
||
import ( | ||
"encoding/binary" | ||
"fmt" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/gagliardetto/solana-go" | ||
) | ||
|
||
func FindSignerPDA(programID solana.PublicKey, pdaSeed PDASeed) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("multisig_signer"), pdaSeed[:]} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func FindConfigPDA(programID solana.PublicKey, pdaSeed PDASeed) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("multisig_config"), pdaSeed[:]} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func FindConfigSignersPDA(programID solana.PublicKey, pdaSeed PDASeed) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("multisig_config_signers"), pdaSeed[:]} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func FindRootMetadataPDA(programID solana.PublicKey, pdaSeed PDASeed) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("root_metadata"), pdaSeed[:]} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func FindExpiringRootAndOpCountPDA(programID solana.PublicKey, pdaSeed PDASeed) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("expiring_root_and_op_count"), pdaSeed[:]} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func FindRootSignaturesPDA( | ||
programID solana.PublicKey, pdaSeed PDASeed, root common.Hash, validUntil uint32, | ||
) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("root_signatures"), pdaSeed[:], root[:], validUntilBytes(validUntil)} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func FindSeenSignedHashesPDA( | ||
programID solana.PublicKey, pdaSeed PDASeed, root common.Hash, validUntil uint32, | ||
) (solana.PublicKey, error) { | ||
seeds := [][]byte{[]byte("seen_signed_hashes"), pdaSeed[:], root[:], validUntilBytes(validUntil)} | ||
return findPDA(programID, seeds) | ||
} | ||
|
||
func findPDA(programID solana.PublicKey, seeds [][]byte) (solana.PublicKey, error) { | ||
pda, _, err := solana.FindProgramAddress(seeds, programID) | ||
if err != nil { | ||
return solana.PublicKey{}, fmt.Errorf("unable to find %s pda: %w", string(seeds[0]), err) | ||
} | ||
|
||
return pda, nil | ||
} | ||
|
||
func validUntilBytes(validUntil uint32) []byte { | ||
const uint32Size = 4 | ||
vuBytes := make([]byte, uint32Size) | ||
binary.LittleEndian.PutUint32(vuBytes, validUntil) | ||
|
||
return vuBytes | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package solana | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/gagliardetto/solana-go" | ||
"github.com/google/go-cmp/cmp" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var ( | ||
testProgramID = solana.MustPublicKeyFromBase58("6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX") | ||
testPDASeed = PDASeed{'t', 'e', 's', 't', '-', 'm', 'c', 'm'} | ||
testRoot = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000") | ||
) | ||
|
||
func Test_FindSignerPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindSignerPDA(testProgramID, testPDASeed) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("62gDM6BRLf2w1yXfmpePUTsuvbeBbu4QqdjV32wcc4UG"))) | ||
} | ||
|
||
func Test_FindConfigPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindConfigPDA(testProgramID, testPDASeed) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("CiPYshUKNDV9i4p4MLaqXSRqYWtnMtW6b1aYjh4Lw9nP"))) | ||
} | ||
|
||
func Test_getConfigSignersPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindConfigSignersPDA(testProgramID, testPDASeed) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("EZJdMB7TCRcSTP6KMp1HPnzwNtW6wvqKXHLAZh1Jn81w"))) | ||
} | ||
|
||
func Test_FindRootMetadataPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindRootMetadataPDA(testProgramID, testPDASeed) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("H45XH8Z1zpcLUHLLQzUwEgB1s3mZQcRvCYfcHriRcMxN"))) | ||
} | ||
|
||
func Test_FindExpiringRootAndOpCountPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindExpiringRootAndOpCountPDA(testProgramID, testPDASeed) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("7nh2qGybwNRxL3zKpiSUzk2yc9CjCb5MhrB61B98hYZu"))) | ||
} | ||
|
||
func Test_FindRootSignaturesPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindRootSignaturesPDA(testProgramID, testPDASeed, testRoot, 1735689600) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("528jBx5Mn1EPt4vG47CRkr1zhj8QVfSMvfvBZksZdrHr"))) | ||
} | ||
|
||
func Test_FindSeenSignedHashesPDA(t *testing.T) { | ||
t.Parallel() | ||
pda, err := FindSeenSignedHashesPDA(testProgramID, testPDASeed, testRoot, 1735689600) | ||
require.NoError(t, err) | ||
require.Empty(t, cmp.Diff(pda, solana.MustPublicKeyFromBase58("FxPYSHG9tm35T43zpAuVDdNY8uMPQfaaVBftxVrLyXVq"))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package solana | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/gagliardetto/solana-go" | ||
) | ||
|
||
type PDASeed [32]byte | ||
|
||
// ContractAddress returns a string representation of a solana contract id | ||
// which is a combination of the program id and the seed <PROGRAM_ID>.<SEED> | ||
func ContractAddress(programID solana.PublicKey, pdaSeed PDASeed) string { | ||
return fmt.Sprintf("%s.%s", programID.String(), bytes.Trim(pdaSeed[:], "\x00")) | ||
} | ||
|
||
func ParseContractAddress(address string) (solana.PublicKey, PDASeed, error) { | ||
const numParts = 2 | ||
parts := strings.SplitN(address, ".", numParts) | ||
if len(parts) != numParts { | ||
return solana.PublicKey{}, PDASeed{}, fmt.Errorf("invalid solana contract address format: %q", address) | ||
} | ||
|
||
programID, err := solana.PublicKeyFromBase58(parts[0]) | ||
if err != nil { | ||
return solana.PublicKey{}, PDASeed{}, fmt.Errorf("unable to parse solana program id: %w", err) | ||
} | ||
|
||
allSeedBytes := []byte(parts[1]) | ||
if len(allSeedBytes) > len(PDASeed{}) { | ||
return solana.PublicKey{}, PDASeed{}, fmt.Errorf("pda seed is too long (max %d bytes)", len(PDASeed{})) | ||
} | ||
|
||
var pdaSeed PDASeed | ||
copy(pdaSeed[:], []byte(parts[1])[:]) | ||
|
||
return programID, pdaSeed, nil | ||
} |
Oops, something went wrong.