Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solana plugin codec implementation #15816

Open
wants to merge 51 commits into
base: solana-offchain-plugin
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
71b06dc
add solana commit codec, need to add test
huangzhen1997 Nov 19, 2024
c79a406
ccipsolana: Import bindings
archseer Dec 24, 2024
0fe505c
Merge branch 'develop' into NONEVM-935/implement-ccip-solana-plugin-c…
huangzhen1997 Dec 30, 2024
de95bf1
update go list
huangzhen1997 Dec 30, 2024
e51bd46
add test and update commit price processing logic
huangzhen1997 Dec 31, 2024
86c9c17
fix lint
huangzhen1997 Dec 31, 2024
237e4f4
add exec, and test in progress
huangzhen1997 Dec 31, 2024
7ae43ed
added unit tests for execute, need clean up and verify requirements
huangzhen1997 Jan 1, 2025
4e2ea85
fix go mod in deployment
huangzhen1997 Jan 2, 2025
0d49c2e
fix lint
huangzhen1997 Jan 2, 2025
5849b3b
lint
huangzhen1997 Jan 2, 2025
70db87e
go mod
huangzhen1997 Jan 2, 2025
d2a7a39
update
huangzhen1997 Jan 2, 2025
ae3e43c
Merge branch 'develop' of github.com:smartcontractkit/chainlink into …
huangzhen1997 Jan 3, 2025
11cd2f3
add extra args and destExec support
huangzhen1997 Jan 3, 2025
40abc5c
minor
huangzhen1997 Jan 3, 2025
4e2f647
cleanup
huangzhen1997 Jan 3, 2025
558c0ff
update
huangzhen1997 Jan 3, 2025
07acdb1
fix mod
huangzhen1997 Jan 3, 2025
0c0b802
update
huangzhen1997 Jan 3, 2025
f0a5a0e
Merge branch 'develop' of github.com:smartcontractkit/chainlink into …
huangzhen1997 Jan 3, 2025
22ad68c
revert mod change
huangzhen1997 Jan 3, 2025
effd030
add changeset
huangzhen1997 Jan 3, 2025
26e9cb3
fix smoke test
huangzhen1997 Jan 3, 2025
b988745
fix lint
huangzhen1997 Jan 3, 2025
56d85d5
revert go mod
huangzhen1997 Jan 3, 2025
75ae059
fix
huangzhen1997 Jan 6, 2025
0272e75
revert
huangzhen1997 Jan 6, 2025
465d69c
revert
huangzhen1997 Jan 6, 2025
f314edc
again
huangzhen1997 Jan 6, 2025
a712c88
update
huangzhen1997 Jan 6, 2025
8c02e8b
remove file
huangzhen1997 Jan 6, 2025
bad1acb
revert
huangzhen1997 Jan 6, 2025
6023de7
update
huangzhen1997 Jan 6, 2025
23d9c80
update
huangzhen1997 Jan 6, 2025
82185a0
update version
huangzhen1997 Jan 6, 2025
8f42923
revert
huangzhen1997 Jan 6, 2025
4fdf7f7
downgrade version
huangzhen1997 Jan 6, 2025
b0b8d31
go mod tidy
huangzhen1997 Jan 6, 2025
85dfafd
tidy
huangzhen1997 Jan 6, 2025
9ec6505
add token indexes
huangzhen1997 Jan 6, 2025
a5b661d
update comment
huangzhen1997 Jan 6, 2025
e169af2
Merge branch 'solana-offchain-plugin' into NONEVM-935/implement-ccip-…
huangzhen1997 Jan 9, 2025
a52044d
gomod
huangzhen1997 Jan 9, 2025
3f4929a
part 1
huangzhen1997 Jan 10, 2025
d5413ae
fix lint
huangzhen1997 Jan 10, 2025
03ae3ee
data type
huangzhen1997 Jan 10, 2025
6ecbec2
make generate
huangzhen1997 Jan 13, 2025
67d689c
update with new generated go binding
huangzhen1997 Jan 16, 2025
e9abe63
Merge branch 'solana-offchain-plugin' into NONEVM-935/implement-ccip-…
huangzhen1997 Jan 17, 2025
9497f40
mod tidy
huangzhen1997 Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/violet-bears-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Solana CCIP plugin codec support for both commit and execute report #added
138 changes: 138 additions & 0 deletions core/capabilities/ccip/ccipsolana/commitcodec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package ccipsolana

import (
"bytes"
"context"
"fmt"
"math/big"

agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"

"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
"github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common"
cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
)

// CommitPluginCodecV1 is a codec for encoding and decoding commit plugin reports.
// Compatible with:
// - "OffRamp 1.6.0-dev"
type CommitPluginCodecV1 struct{}
Copy link
Contributor

@ilija42 ilija42 Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can't we use the Solana Codec for this? Just define the IDL for these types, I haven't looked closely into the types being encodec, so I may be missing something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try Solana codec, @archseer suggested to use the generated gobinding for this earlier, and I also think it makes sense as the EVM codec right now is using similar approach with the generated type.


func NewCommitPluginCodecV1() *CommitPluginCodecV1 {
return &CommitPluginCodecV1{}
}

func (c *CommitPluginCodecV1) Encode(ctx context.Context, report cciptypes.CommitPluginReport) ([]byte, error) {
var buf bytes.Buffer
encoder := agbinary.NewBorshEncoder(&buf)
mr := ccip_router.MerkleRoot{}

if len(report.MerkleRoots) != 0 {
huangzhen1997 marked this conversation as resolved.
Show resolved Hide resolved
mr = ccip_router.MerkleRoot{
SourceChainSelector: uint64(report.MerkleRoots[0].ChainSel),
OnRampAddress: report.MerkleRoots[0].OnRampAddress,
MinSeqNr: uint64(report.MerkleRoots[0].SeqNumsRange.Start()),
MaxSeqNr: uint64(report.MerkleRoots[0].SeqNumsRange.End()),
MerkleRoot: report.MerkleRoots[0].MerkleRoot,
}
}

tpu := make([]ccip_router.TokenPriceUpdate, 0, len(report.PriceUpdates.TokenPriceUpdates))
for _, update := range report.PriceUpdates.TokenPriceUpdates {
token, err := solana.PublicKeyFromBase58(string(update.TokenID))
if err != nil {
return nil, fmt.Errorf("invalid token address: %s, %w", update.TokenID, err)
}
if update.Price.IsEmpty() {
return nil, fmt.Errorf("empty price for token: %s", update.TokenID)
}
tpu = append(tpu, ccip_router.TokenPriceUpdate{
SourceToken: token,
UsdPerToken: common.To28BytesBE(update.Price.Int.Uint64()),
})
}

gpu := make([]ccip_router.GasPriceUpdate, 0, len(report.PriceUpdates.GasPriceUpdates))
for _, update := range report.PriceUpdates.GasPriceUpdates {
gpu = append(gpu, ccip_router.GasPriceUpdate{
DestChainSelector: uint64(update.ChainSel),
UsdPerUnitGas: common.To28BytesBE(update.GasPrice.Int.Uint64()),
huangzhen1997 marked this conversation as resolved.
Show resolved Hide resolved
})
}

commit := ccip_router.CommitInput{
MerkleRoot: mr,
PriceUpdates: ccip_router.PriceUpdates{
TokenPriceUpdates: tpu,
GasPriceUpdates: gpu,
},
}

err := commit.MarshalWithEncoder(encoder)
if err != nil {
return nil, err
}

return buf.Bytes(), nil
}

func (c *CommitPluginCodecV1) Decode(ctx context.Context, bytes []byte) (cciptypes.CommitPluginReport, error) {
decoder := agbinary.NewBorshDecoder(bytes)
commitReport := ccip_router.CommitInput{}
err := commitReport.UnmarshalWithDecoder(decoder)
if err != nil {
return cciptypes.CommitPluginReport{}, err
}

merkleRoots := []cciptypes.MerkleRootChain{
{
ChainSel: cciptypes.ChainSelector(commitReport.MerkleRoot.SourceChainSelector),
OnRampAddress: commitReport.MerkleRoot.OnRampAddress,
SeqNumsRange: cciptypes.NewSeqNumRange(
cciptypes.SeqNum(commitReport.MerkleRoot.MinSeqNr),
cciptypes.SeqNum(commitReport.MerkleRoot.MaxSeqNr),
),
MerkleRoot: commitReport.MerkleRoot.MerkleRoot,
},
}

tokenPriceUpdates := make([]cciptypes.TokenPrice, 0, len(commitReport.PriceUpdates.TokenPriceUpdates))
for _, update := range commitReport.PriceUpdates.TokenPriceUpdates {
tokenPriceUpdates = append(tokenPriceUpdates, cciptypes.TokenPrice{
TokenID: cciptypes.UnknownEncodedAddress(update.SourceToken.String()),
Price: priceHelper(update.UsdPerToken[:]),
})
}

gasPriceUpdates := make([]cciptypes.GasPriceChain, 0, len(commitReport.PriceUpdates.GasPriceUpdates))
for _, update := range commitReport.PriceUpdates.GasPriceUpdates {
gasPriceUpdates = append(gasPriceUpdates, cciptypes.GasPriceChain{
GasPrice: priceHelper(update.UsdPerUnitGas[:]),
ChainSel: cciptypes.ChainSelector(update.DestChainSelector),
})
}

return cciptypes.CommitPluginReport{
MerkleRoots: merkleRoots,
PriceUpdates: cciptypes.PriceUpdates{
TokenPriceUpdates: tokenPriceUpdates,
GasPriceUpdates: gasPriceUpdates,
},
}, nil
}

func priceHelper(input []byte) cciptypes.BigInt {
var tokenPrice cciptypes.BigInt
price := new(big.Int).SetBytes(input)
if price.Int64() == 0 {
tokenPrice = cciptypes.NewBigInt(big.NewInt(0))
} else {
tokenPrice = cciptypes.NewBigInt(price)
}

return tokenPrice
}

// Ensure CommitPluginCodec implements the CommitPluginCodec interface
var _ cciptypes.CommitPluginCodec = (*CommitPluginCodecV1)(nil)
133 changes: 133 additions & 0 deletions core/capabilities/ccip/ccipsolana/commitcodec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package ccipsolana

import (
"math/big"
"math/rand"
"testing"

solanago "github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
)

var randomCommitReport = func() cciptypes.CommitPluginReport {
pubkey, err := solanago.NewRandomPrivateKey()
if err != nil {
panic(err)
}

return cciptypes.CommitPluginReport{
MerkleRoots: []cciptypes.MerkleRootChain{
{
OnRampAddress: cciptypes.UnknownAddress(pubkey.PublicKey().String()),
ChainSel: cciptypes.ChainSelector(rand.Uint64()),
SeqNumsRange: cciptypes.NewSeqNumRange(
cciptypes.SeqNum(rand.Uint64()),
cciptypes.SeqNum(rand.Uint64()),
),
MerkleRoot: utils.RandomBytes32(),
},
},
PriceUpdates: cciptypes.PriceUpdates{
TokenPriceUpdates: []cciptypes.TokenPrice{
{
TokenID: "C8WSPj3yyus1YN3yNB6YA5zStYtbjQWtpmKadmvyUXq8",
Price: cciptypes.NewBigInt(big.NewInt(rand.Int63())),
},
},
GasPriceUpdates: []cciptypes.GasPriceChain{
{GasPrice: cciptypes.NewBigInt(big.NewInt(rand.Int63())), ChainSel: cciptypes.ChainSelector(rand.Uint64())},
{GasPrice: cciptypes.NewBigInt(big.NewInt(rand.Int63())), ChainSel: cciptypes.ChainSelector(rand.Uint64())},
{GasPrice: cciptypes.NewBigInt(big.NewInt(rand.Int63())), ChainSel: cciptypes.ChainSelector(rand.Uint64())},
},
},
}
}

func TestCommitPluginCodecV1(t *testing.T) {
testCases := []struct {
name string
report func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport
expErr bool
}{
{
name: "base report",
report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport {
return report
},
},
{
name: "empty token address",
report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport {
report.PriceUpdates.TokenPriceUpdates[0].TokenID = ""
return report
},
expErr: true,
},
{
name: "empty merkle root",
report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport {
report.MerkleRoots[0].MerkleRoot = cciptypes.Bytes32{}
return report
},
},
{
name: "zero token price",
report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport {
report.PriceUpdates.TokenPriceUpdates[0].Price = cciptypes.NewBigInt(big.NewInt(0))
return report
},
},
{
name: "zero gas price",
huangzhen1997 marked this conversation as resolved.
Show resolved Hide resolved
report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport {
report.PriceUpdates.GasPriceUpdates[0].GasPrice = cciptypes.NewBigInt(big.NewInt(0))
return report
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
report := tc.report(randomCommitReport())
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(t)
encodedReport, err := commitCodec.Encode(ctx, report)
if tc.expErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
decodedReport, err := commitCodec.Decode(ctx, encodedReport)
require.NoError(t, err)
require.Equal(t, report, decodedReport)
})
}
}

func BenchmarkCommitPluginCodecV1_Encode(b *testing.B) {
huangzhen1997 marked this conversation as resolved.
Show resolved Hide resolved
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(b)

rep := randomCommitReport()
for i := 0; i < b.N; i++ {
_, err := commitCodec.Encode(ctx, rep)
require.NoError(b, err)
}
}

func BenchmarkCommitPluginCodecV1_Decode(b *testing.B) {
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(b)
encodedReport, err := commitCodec.Encode(ctx, randomCommitReport())
require.NoError(b, err)

for i := 0; i < b.N; i++ {
_, err := commitCodec.Decode(ctx, encodedReport)
require.NoError(b, err)
}
}
Loading
Loading