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 router deploy (D) #15871

Open
wants to merge 13 commits into
base: solana-state-updates
Choose a base branch
from
187 changes: 165 additions & 22 deletions deployment/ccip/changeset/cs_deploy_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gagliardetto/solana-go"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"golang.org/x/sync/errgroup"

"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"

solBinary "github.com/gagliardetto/binary"
solRpc "github.com/gagliardetto/solana-go/rpc"
chainsel "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home"
Expand All @@ -25,6 +32,10 @@ import (

var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts

var (
EnableExecutionAfter = int64(1800) // 30min
)

// DeployChainContracts deploys all new CCIP v1.6 or later contracts for the given chains.
// It returns the new addresses for the contracts.
// DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the
Expand Down Expand Up @@ -68,17 +79,12 @@ func (c DeployChainContractsConfig) Validate() error {
return nil
}

func deployChainContractsForChains(
e deployment.Environment,
ab deployment.AddressBook,
homeChainSel uint64,
chainsToDeploy []uint64) error {
func validateHomeChainState(e deployment.Environment, homeChainSel uint64, existingState CCIPOnChainState) error {
existingState, err := LoadOnchainState(e)
if err != nil {
e.Logger.Errorw("Failed to load existing onchain state", "err")
return err
}

capReg := existingState.Chains[homeChainSel].CapabilityRegistry
if capReg == nil {
e.Logger.Errorw("Failed to get capability registry")
Expand Down Expand Up @@ -113,24 +119,67 @@ func deployChainContractsForChains(
e.Logger.Errorw("Failed to get rmn home", "err", err)
return errors.New("rmn home not found")
}
return nil
}

func deployChainContractsForChains(
e deployment.Environment,
ab deployment.AddressBook,
homeChainSel uint64,
chainsToDeploy []uint64) error {
existingEVMState, err := LoadOnchainState(e)
if err != nil {
e.Logger.Errorw("Failed to load existing onchain state", "err")
return err
}

err = validateHomeChainState(e, homeChainSel, existingEVMState)
if err != nil {
return err
}

err = deployment.ValidateSelectorsInEnvironment(e, chainsToDeploy)
if err != nil {
return err
}

rmnHome := existingEVMState.Chains[homeChainSel].RMNHome

existingSolState, err := LoadOnchainStateSolana(e)
if err != nil {
e.Logger.Errorw("Failed to load existing onchain solanastate", "err")
return err
}

deployGrp := errgroup.Group{}

for _, chainSel := range chainsToDeploy {
chain, ok := e.Chains[chainSel]
if !ok {
return fmt.Errorf("chain %d not found", chainSel)
}
if existingState.Chains[chainSel].LinkToken == nil || existingState.Chains[chainSel].Weth9 == nil {
return fmt.Errorf("fee tokens not found for chain %d", chainSel)
// already validated family
family, _ := chainsel.GetSelectorFamily(chainSel)
var deployFn func() error
switch family {
case chainsel.FamilyEVM:
chain := e.Chains[chainSel]
if existingEVMState.Chains[chainSel].LinkToken == nil || existingEVMState.Chains[chainSel].Weth9 == nil {
return fmt.Errorf("fee tokens not found for chain %d", chainSel)
}
deployFn = func() error { return deployChainContractsEVM(e, chain, ab, rmnHome) }

case chainsel.FamilySolana:
chain := e.SolChains[chainSel]
if existingSolState.SolChains[chainSel].LinkToken.IsZero() {
return fmt.Errorf("fee tokens not found for chain %d", chainSel)
}
deployFn = func() error { return deployChainContractsSolana(e, chain, ab) }
}
deployGrp.Go(
func() error {
err := deployChainContracts(e, chain, ab, rmnHome)
if err != nil {
e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err)
return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err)
}
return nil
})
deployGrp.Go(func() error {
err := deployFn()
if err != nil {
e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err)
return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err)
}
return nil
})
}
if err := deployGrp.Wait(); err != nil {
e.Logger.Errorw("Failed to deploy chain contracts", "err", err)
Expand All @@ -139,7 +188,7 @@ func deployChainContractsForChains(
return nil
}

func deployChainContracts(
func deployChainContractsEVM(
e deployment.Environment,
chain deployment.Chain,
ab deployment.AddressBook,
Expand Down Expand Up @@ -401,3 +450,97 @@ func deployChainContracts(
e.Logger.Infow("Added nonce manager authorized callers", "chain", chain.String(), "callers", []common.Address{offRampContract.Address(), onRampContract.Address()})
return nil
}

func solRouterProgramData(e deployment.Environment, chain deployment.SolChain, ccipRouterProgram solana.PublicKey) (struct {
DataType uint32
Address solana.PublicKey
}, error) {
var programData struct {
DataType uint32
Address solana.PublicKey
}
data, err := chain.Client.GetAccountInfoWithOpts(e.GetContext(), ccipRouterProgram, &solRpc.GetAccountInfoOpts{
Commitment: solRpc.CommitmentConfirmed,
})
if err != nil {
return programData, fmt.Errorf("failed to deploy program: %w", err)
}

err = solBinary.UnmarshalBorsh(&programData, data.Bytes())
if err != nil {
return programData, fmt.Errorf("failed to unmarshal program data: %w", err)
}
return programData, nil
}

func deployChainContractsSolana(
e deployment.Environment,
chain deployment.SolChain,
ab deployment.AddressBook,
) error {
state, err := LoadOnchainStateSolana(e)
if err != nil {
e.Logger.Errorw("Failed to load existing onchain state", "err")
return err
}
chainState, chainExists := state.SolChains[chain.Selector]
if !chainExists {
return fmt.Errorf("chain %s not found in existing state, deploy the prerequisites first", chain.String())
}
linkTokenContract := chainState.LinkToken
e.Logger.Infow("link token", "addr", linkTokenContract.String())

if chainState.SolCcipRouter.IsZero() {
// deploy and initialize router
programID, err := chain.DeployProgram(e.Logger, "ccip_router")
if err != nil {
return fmt.Errorf("failed to deploy program: %w", err)
}

tv := deployment.NewTypeAndVersion("SolCcipRouter", deployment.Version1_0_0)
e.Logger.Infow("Deployed contract", "Contract", tv.String(), "addr", programID, "chain", chain.String())

ccipRouterProgram := solana.MustPublicKeyFromBase58(programID)
programData, err := solRouterProgramData(e, chain, ccipRouterProgram)
if err != nil {
return fmt.Errorf("failed to get solana router program data: %w", err)
}

ccip_router.SetProgramID(ccipRouterProgram)

defaultGasLimit := solBinary.Uint128{Lo: 3000, Hi: 0, Endianness: nil}

instruction, err := ccip_router.NewInitializeInstruction(
chain.Selector, // chain selector
defaultGasLimit, // default gas limit
true, // allow out of order execution
EnableExecutionAfter, // period to wait before allowing manual execution
solana.PublicKey{},
GetRouterConfigPDA(ccipRouterProgram),
GetRouterStatePDA(ccipRouterProgram),
chain.DeployerKey.PublicKey(),
solana.SystemProgramID,
ccipRouterProgram,
programData.Address,
GetExternalExecutionConfigPDA(ccipRouterProgram),
GetExternalTokenPoolsSignerPDA(ccipRouterProgram),
).ValidateAndBuild()

if err != nil {
return fmt.Errorf("failed to build instruction: %w", err)
}
err = chain.Confirm([]solana.Instruction{instruction})

if err != nil {
return fmt.Errorf("failed to confirm instructions: %w", err)
}

err = ab.Save(chain.Selector, programID, tv)
if err != nil {
return fmt.Errorf("failed to save address: %w", err)
}
//TODO: deploy token pool contract
//TODO: log errors
}
return nil
}
59 changes: 56 additions & 3 deletions deployment/ccip/changeset/cs_deploy_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package changeset
import (
"encoding/json"
"fmt"
"os"
"testing"

"github.com/mr-tron/base58"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/gagliardetto/solana-go"

"github.com/smartcontractkit/chainlink/deployment"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
Expand All @@ -22,10 +26,15 @@ func TestDeployChainContractsChangeset(t *testing.T) {
e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{
Bootstraps: 1,
Chains: 2,
SolChains: 1,
Nodes: 4,
})
selectors := e.AllChainSelectors()
homeChainSel := selectors[0]
evmSelectors := e.AllChainSelectors()
homeChainSel := evmSelectors[0]
solChainSelectors := e.AllChainSelectorsSolana()
selectors := make([]uint64, 0, len(evmSelectors)+len(solChainSelectors))
selectors = append(selectors, evmSelectors...)
selectors = append(selectors, solChainSelectors...)
nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain)
require.NoError(t, err)
p2pIds := nodes.NonBootstraps().PeerIDs()
Expand Down Expand Up @@ -84,7 +93,7 @@ func TestDeployChainContractsChangeset(t *testing.T) {
require.NotNil(t, state.Chains[homeChainSel].CapabilityRegistry)
require.NotNil(t, state.Chains[homeChainSel].CCIPHome)
require.NotNil(t, state.Chains[homeChainSel].RMNHome)
for _, sel := range selectors {
for _, sel := range evmSelectors {
require.NotNil(t, state.Chains[sel].LinkToken)
require.NotNil(t, state.Chains[sel].Weth9)
require.NotNil(t, state.Chains[sel].TokenAdminRegistry)
Expand All @@ -97,6 +106,14 @@ func TestDeployChainContractsChangeset(t *testing.T) {
require.NotNil(t, state.Chains[sel].OffRamp)
require.NotNil(t, state.Chains[sel].OnRamp)
}

solState, err := LoadOnchainStateSolana(e)
require.NoError(t, err)
for _, sel := range solChainSelectors {
require.NotNil(t, solState.SolChains[sel].LinkToken)
require.NotNil(t, solState.SolChains[sel].SolCcipRouter)
}

}

func TestDeployCCIPContracts(t *testing.T) {
Expand All @@ -114,3 +131,39 @@ func TestDeployCCIPContracts(t *testing.T) {
require.NoError(t, err)
fmt.Println(string(b))
}

// IGNORE
func TestSolanaKeygen(t *testing.T) {
privateKey, _ := solana.NewRandomPrivateKey()
fmt.Println(privateKey.String())

// Decode the Base58 private key
privateKeyBytes, err := base58.Decode(privateKey.String())
if err != nil {
fmt.Printf("Error decoding Base58 private key: %v\n", err)
return
}
fmt.Printf("Bytes after decode: %v\n", privateKeyBytes)

// Convert bytes to array of integers
intArray := make([]int, len(privateKeyBytes))
for i, b := range privateKeyBytes {
intArray[i] = int(b)
}

// Marshal the integer array to JSON
keypairJSON, err := json.Marshal(intArray)
if err != nil {
fmt.Printf("Error marshaling to JSON: %v\n", err)
return
}
outputFilePath := "/Users/yashvardhan/.config/solana/myid.json"
if err := os.WriteFile(outputFilePath, keypairJSON, 0600); err != nil {
fmt.Printf("Error writing keypair to file: %v\n", err)
return
}

pk, err := solana.PrivateKeyFromSolanaKeygenFile(outputFilePath)
require.NoError(t, err)
require.Equal(t, pk.String(), privateKey.String())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[101,238,189,81,99,141,117,176,11,49,33,2,218,239,163,125,209,246,2,133,177,222,143,89,48,80,244,247,247,141,216,183,145,28,85,130,47,80,170,11,121,40,143,49,171,88,54,235,39,125,182,141,1,69,71,62,148,230,124,191,22,218,25,173]
Binary file not shown.
Loading
Loading