Skip to content

Commit

Permalink
Solana offchain plugin (#15974)
Browse files Browse the repository at this point in the history
* cleanup

* update

* fix mod

* update

* revert mod change

* add changeset

* fix smoke test

* fix lint

* revert go mod

* fix

* revert

* revert

* again

* update

* remove file

* revert

* update

* update

* update version

* revert

* downgrade version

* go mod tidy

* tidy

* add token indexes

* update comment

* add solana and remove evm specific

* add solana config

* grammar

* update go mod

* go mod

* update

* update

* changeset

* update

* refactor

* update

* refactor

* update lint

* fix lint

* add chain reader config to plugin creator

* update changeset

* Part 1 for Prashant comments

* refactor getTransmitterKeys

* go mod tidy

* minor

* fix lint

* go import

* gomod

* Blaž comments

* part 1

* fix lint

* data type

* add solana home chain merge func

* rephrase

* add entire config from example

* rm comments

* lint

* lint

* refactor

* update comments

* fix lint

* make generate

* Add solana support to contract transmitter (#15869)

* add solana support to contract transmitter

* add changeset

* one more place

* address unsupported chain family

* encode offramp addr

* fix breaking change

* rename

* test

* revert

* update version

* go mod

* go mod tidy

* go mod

* go.md

* go md

* remove hard coded

* go.md

* go import

* remove unused

* update mod

* update with new generated go binding

* Amit comments

* Solana estimate providers no-op  (#15909)

* no-op for solana estimate providers

* add changeset

* token data noop

* noop for solana rmn

* fix lint

* mod tidy

* Use SVM ABI, needs to check test

* add comment

* goimport

* update

* rename

* go mod tidy

* goimport

* tidy

* refactor

* fix lint

* step 1 for parse extra args map

* add comment

* fix test

* fix

* update

* improvement

* update

* update

* tidy

* update

* fix test

* Matt comments

* address Makram comments

* Makram comments part1

* refactor

* lint

* go mod

* update

* fix

* mod tidy

* go md

* gomd

* fix import

* fix broken integration test

* change commit to little endian, to do for execute

* update

* use LE

* update

* update

* update padding

* fix make

* fix test

* rename

* fix test

* update source chain

* update

* lower case

* update test

* goimport

* minor

* tidy

* implement and add test

* update comments

* goimport

* implement dest exec data handling

* fix conflicts

* fix

* fix

* fix test

* update

* fix test

* update

* add comment

* fix mod

* fix

* refactor

* refactor

* update

* update comment

* add unit test

* update

* lint

* mod

* mod tidy

* fix

* lint

* update

* update

* goimport

* update

* Solana MessageHasher (#15911)

* initial code

* update

* minor

* update test

* update

* lint

* change set

* update

* update

* update token data and test

* update

* fix test

---------

Co-authored-by: Joe Huang <[email protected]>

* mod

* update

* clean up

* update

* update

* add on-chain data decoding unit test coverage

* add comment

* fix import

* more test

* update

* update

* fix

* fix import

* rm unused

* remove unused

* downgrade

* downgrade

* rm

* update

* update

* rm

* update

* revert

* revert

* revert

* fix

* update

* fix

* update

* update

* mod clean

* fix

* mod tidy

* fix

* fix

* update

* fix

* update with develop

* fix

* go mod

* update

* update

* update

* update

* update

* more

* update

* update

* mod

* update

* refactor extradata codec

* update

* goimport

* update

* add error

* tidy

* fix import

* revert previous extra data codec refactor

* fix

---------

Co-authored-by: Prashant Yadav <[email protected]>
  • Loading branch information
huangzhen1997 and prashantkumar1982 authored Feb 7, 2025
1 parent bee407b commit 111fe84
Show file tree
Hide file tree
Showing 41 changed files with 2,280 additions and 132 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-waves-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

add solana chain reader config support to plugin creator, remove evm specific code #added
5 changes: 5 additions & 0 deletions .changeset/old-pets-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Add solana chain writer config initialization #added
5 changes: 5 additions & 0 deletions .changeset/rich-frogs-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Adding solana message hasher #added
5 changes: 5 additions & 0 deletions .changeset/twelve-games-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

add Solana estimate provider as a no-op #added
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
5 changes: 5 additions & 0 deletions .changeset/wise-items-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Add solana support for contract transmitter and remove evm depdendency for address encoding #added
4 changes: 2 additions & 2 deletions .github/actions/setup-solana/build-contracts/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ runs:
echo "ANCHOR_VERSION=${anchor}" >> $GITHUB_ENV
- name: cache docker build image
id: cache-image
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@v4 # v4.0.2
with:
lookup-only: true
path: chains/solana/contracts/docker-build.tar
key: ${{ runner.os }}-solana-build-${{ env.ANCHOR_VERSION }}-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo target dir
id: cache-target
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@v4 # v4.0.2
with:
lookup-only: true
path: chains/solana/contracts/target
Expand Down
56 changes: 56 additions & 0 deletions core/capabilities/ccip/ccipevm/extradatadecoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package ccipevm

import (
"fmt"

cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
)

func DecodeDestExecDataToMap(DestExecData cciptypes.Bytes) (map[string]interface{}, error) {
destGasAmount, err := abiDecodeUint32(DestExecData)
if err != nil {
return nil, fmt.Errorf("decode dest gas amount: %w", err)
}

return map[string]interface{}{
evmDestExecDataKey: destGasAmount,
}, nil
}

func DecodeExtraArgsToMap(extraArgs []byte) (map[string]any, error) {
if len(extraArgs) < 4 {
return nil, fmt.Errorf("extra args too short: %d, should be at least 4 (i.e the extraArgs tag)", len(extraArgs))
}

var method string
var extraByteOffset int
switch string(extraArgs[:4]) {
case string(evmExtraArgsV1Tag):
// for EVMExtraArgs, the first four bytes is the method name
method = evmV1DecodeName
extraByteOffset = 4
case string(evmExtraArgsV2Tag):
method = evmV2DecodeName
extraByteOffset = 4
case string(svmExtraArgsV1Tag):
// for SVMExtraArgs there's the four bytes plus another 32 bytes padding for the dynamic array
// TODO this is a temporary solution, the evm on-chain side will fix it, so the offset should just be 4 instead of 36
// https://smartcontract-it.atlassian.net/browse/BCI-4180
method = svmV1DecodeName
extraByteOffset = 36
default:
return nil, fmt.Errorf("unknown extra args tag: %x", extraArgs)
}

output := make(map[string]any)
args := make(map[string]interface{})
err := messageHasherABI.Methods[method].Inputs.UnpackIntoMap(args, extraArgs[extraByteOffset:])
if err != nil {
return nil, fmt.Errorf("abi decode extra args %v: %w", method, err)
}

for k, val := range args {
output[k] = val
}
return output, nil
}
105 changes: 105 additions & 0 deletions core/capabilities/ccip/ccipevm/extradatadecoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package ccipevm

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

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

"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/message_hasher"
)

func Test_decodeExtraData(t *testing.T) {
d := testSetup(t)
gasLimit := big.NewInt(rand.Int63())

t.Run("decode extra args into map evm v1", func(t *testing.T) {
encoded, err := d.contract.EncodeEVMExtraArgsV1(nil, message_hasher.ClientEVMExtraArgsV1{
GasLimit: gasLimit,
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 1)

gl, exist := m["gasLimit"]
require.True(t, exist)
require.Equal(t, gl, gasLimit)
})

t.Run("decode extra args into map evm v2", func(t *testing.T) {
encoded, err := d.contract.EncodeEVMExtraArgsV2(nil, message_hasher.ClientEVMExtraArgsV2{
GasLimit: gasLimit,
AllowOutOfOrderExecution: true,
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 2)

gl, exist := m["gasLimit"]
require.True(t, exist)
require.Equal(t, gl, gasLimit)

ooe, exist := m["allowOutOfOrderExecution"]
require.True(t, exist)
require.Equal(t, true, ooe)
})

t.Run("decode extra args into map svm", func(t *testing.T) {
key, err := solana.NewRandomPrivateKey()
require.NoError(t, err)
cu := uint32(10000)
bitmap := uint64(4)
ooe := false
tokenReceiver := [32]byte(key.PublicKey().Bytes())
accounts := [][32]byte{[32]byte(key.PublicKey().Bytes())}
decoded, err := d.contract.DecodeSVMExtraArgsV1(nil, cu, bitmap, ooe, tokenReceiver, accounts)
if err != nil {
return
}
encoded, err := d.contract.EncodeSVMExtraArgsV1(nil, decoded)
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 5)

cuDecoded, exist := m["computeUnits"]
require.True(t, exist)
require.Equal(t, cuDecoded, cu)

bitmapDecoded, exist := m["accountIsWritableBitmap"]
require.True(t, exist)
require.Equal(t, bitmapDecoded, bitmap)

ooeDecoded, exist := m["allowOutOfOrderExecution"]
require.True(t, exist)
require.Equal(t, ooeDecoded, ooe)

tokenReceiverDecoded, exist := m["tokenReceiver"]
require.True(t, exist)
require.Equal(t, tokenReceiverDecoded, tokenReceiver)

accountsDecoded, exist := m["accounts"]
require.True(t, exist)
require.Equal(t, accountsDecoded, accounts)
})

t.Run("decode dest exec data into map", func(t *testing.T) {
destGasAmount := uint32(10000)
encoded, err := abiEncodeUint32(destGasAmount)
require.NoError(t, err)
m, err := DecodeDestExecDataToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 1)

decoded, exist := m[evmDestExecDataKey]
require.True(t, exist)
require.Equal(t, destGasAmount, decoded)
})
}
11 changes: 9 additions & 2 deletions core/capabilities/ccip/ccipevm/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi"
)

const (
svmV1DecodeName = "decodeSVMExtraArgsV1"
evmV1DecodeName = "decodeEVMExtraArgsV1"
evmV2DecodeName = "decodeEVMExtraArgsV2"
evmDestExecDataKey = "destGasAmount"
)

var (
abiUint32 = ABITypeOrPanic("uint32")
TokenDestGasOverheadABI = abi.Arguments{
Expand All @@ -24,9 +31,9 @@ func decodeExtraArgsV1V2(extraArgs []byte) (gasLimit *big.Int, err error) {

var method string
if bytes.Equal(extraArgs[:4], evmExtraArgsV1Tag) {
method = "decodeEVMExtraArgsV1"
method = evmV1DecodeName
} else if bytes.Equal(extraArgs[:4], evmExtraArgsV2Tag) {
method = "decodeEVMExtraArgsV2"
method = evmV2DecodeName
} else {
return nil, fmt.Errorf("unknown extra args tag: %x", extraArgs)
}
Expand Down
35 changes: 35 additions & 0 deletions core/capabilities/ccip/ccipevm/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,39 @@ func Test_decodeExtraArgs(t *testing.T) {

require.Equal(t, gasLimit, decodedGasLimit)
})

t.Run("decode extra args into map evm v1", func(t *testing.T) {
encoded, err := d.contract.EncodeEVMExtraArgsV1(nil, message_hasher.ClientEVMExtraArgsV1{
GasLimit: gasLimit,
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 1)

gl, exist := m["gasLimit"]
require.True(t, exist)
require.Equal(t, gl, gasLimit)
})

t.Run("decode extra args into map evm v2", func(t *testing.T) {
encoded, err := d.contract.EncodeEVMExtraArgsV2(nil, message_hasher.ClientEVMExtraArgsV2{
GasLimit: gasLimit,
AllowOutOfOrderExecution: true,
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 2)

gl, exist := m["gasLimit"]
require.True(t, exist)
require.Equal(t, gl, gasLimit)

ooe, exist := m["allowOutOfOrderExecution"]
require.True(t, exist)
require.Equal(t, true, ooe)
})
}
3 changes: 3 additions & 0 deletions core/capabilities/ccip/ccipevm/msghasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ var (

// bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10;
evmExtraArgsV2Tag = hexutil.MustDecode("0x181dcf10")

// bytes4 public constant SVM_EXTRA_EXTRA_ARGS_V1_TAG = 0x1f3b3aba
svmExtraArgsV1Tag = hexutil.MustDecode("0x1f3b3aba")
)

// MessageHasherV1 implements the MessageHasher interface.
Expand Down
Loading

0 comments on commit 111fe84

Please sign in to comment.