From ff400f84da18aa727db05302c117e589861cbe4d Mon Sep 17 00:00:00 2001 From: ilija42 Date: Tue, 1 Sep 2026 20:56:29 +0200 Subject: [PATCH] Add mcms to forwarder --- .../cre/forwarder/stellar/add_forwarder.go | 35 ++++ .../cre/forwarder/stellar/clear_config.go | 149 +++++++++++++++++ .../forwarder/stellar/configure_forwarder.go | 38 +++++ deployment/cre/forwarder/stellar/mcms.go | 156 ++++++++++++++++++ deployment/cre/forwarder/stellar/mcms_test.go | 139 ++++++++++++++++ deployment/cre/stellar/artifacts.go | 31 ++-- deployment/go.mod | 4 +- 7 files changed, 538 insertions(+), 14 deletions(-) create mode 100644 deployment/cre/forwarder/stellar/clear_config.go create mode 100644 deployment/cre/forwarder/stellar/mcms.go create mode 100644 deployment/cre/forwarder/stellar/mcms_test.go diff --git a/deployment/cre/forwarder/stellar/add_forwarder.go b/deployment/cre/forwarder/stellar/add_forwarder.go index 520c5b0fd17..4301d52bec4 100644 --- a/deployment/cre/forwarder/stellar/add_forwarder.go +++ b/deployment/cre/forwarder/stellar/add_forwarder.go @@ -6,6 +6,8 @@ import ( "github.com/Masterminds/semver/v3" + mcmstypes "github.com/smartcontractkit/mcms/types" + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" stellardeployment "github.com/smartcontractkit/chainlink-stellar/deployment" @@ -30,6 +32,8 @@ type AddForwardersRequest struct { Chains map[uint64]struct{} Qualifier string Version string + + MCMS *cldf.MCMSTimelockProposalInput } func (cs AddForwarders) VerifyPreconditions(env cldf.Environment, req *AddForwardersRequest) error { @@ -49,6 +53,11 @@ func (cs AddForwarders) VerifyPreconditions(env cldf.Environment, req *AddForwar if err != nil { return fmt.Errorf("invalid forwarder version %q: %w", req.Version, err) } + if req.MCMS != nil { + if err := req.MCMS.Validate(); err != nil { + return fmt.Errorf("invalid MCMS timelock proposal input: %w", err) + } + } chains := req.Chains if len(chains) == 0 { @@ -81,6 +90,8 @@ func (cs AddForwarders) Apply(env cldf.Environment, req *AddForwardersRequest) ( } } + var batchOps []mcmstypes.BatchOperation + for sel := range chains { ch, ok := env.BlockChains.StellarChains()[sel] if !ok { @@ -93,6 +104,24 @@ func (cs AddForwarders) Apply(env cldf.Environment, req *AddForwardersRequest) ( return out, fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err) } + // The governed path only reads (owner check) and encodes; it must not + // require the deployer signing key. + if req.MCMS != nil { + if err := requireTimelockOwnership(env.GetContext(), env, ch, addrRef.Address, *req.MCMS); err != nil { + return out, err + } + + batchOp, err := addForwardersBatchOp(sel, addrRef.Address, req.Transmitters) + if err != nil { + return out, fmt.Errorf("failed to build add_forwarder batch operation for stellar forwarder %s on chain selector %d: %w", addrRef.Address, sel, err) + } + batchOps = append(batchOps, batchOp) + + env.Logger.Infow("Built governed Stellar forwarder add_forwarder operations", "chainSelector", sel, "forwarder", addrRef.Address, "transmitters", len(req.Transmitters)) + + continue + } + deployer, err := stellardeployment.NewDeployerFromChain(ch) if err != nil { return out, fmt.Errorf("failed to build stellar deployer for chain selector %d: %w", sel, err) @@ -106,5 +135,11 @@ func (cs AddForwarders) Apply(env cldf.Environment, req *AddForwardersRequest) ( } } + if req.MCMS != nil { + return cldf.NewOutputBuilder(env, nil). + WithTimelockProposal(*req.MCMS, batchOps). + Build() + } + return out, nil } diff --git a/deployment/cre/forwarder/stellar/clear_config.go b/deployment/cre/forwarder/stellar/clear_config.go new file mode 100644 index 00000000000..714eb00f8ee --- /dev/null +++ b/deployment/cre/forwarder/stellar/clear_config.go @@ -0,0 +1,149 @@ +package stellar + +import ( + "errors" + "fmt" + + "github.com/Masterminds/semver/v3" + + mcmstypes "github.com/smartcontractkit/mcms/types" + + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + crebindings "github.com/smartcontractkit/chainlink-stellar/bindings/contracts/cre" + stellardeployment "github.com/smartcontractkit/chainlink-stellar/deployment" +) + +var _ cldf.ChangeSetV2[*ClearConfigRequest] = ClearConfigs{} + +// ClearConfigs removes a DON signer configuration from deployed Stellar CRE +// forwarders. Used to rotate out a bad or retired DON config; reports for the +// cleared (donID, configVersion) pair are rejected afterwards. +type ClearConfigs struct{} + +type ClearConfigRequest struct { + DonID uint32 + ConfigVersion uint32 + + // Chains is optional. When set, only those selectors are cleared. + Chains map[uint64]struct{} + Qualifier string + Version string + + // MCMS, when set, builds a governed clear_config timelock proposal instead + // of sending directly with the deployer key. Required once the forwarder + // is owned by the MCMS timelock. Its Qualifier selects the MCMS instance. + MCMS *cldf.MCMSTimelockProposalInput +} + +func (cs ClearConfigs) VerifyPreconditions(env cldf.Environment, req *ClearConfigRequest) error { + if req == nil { + return errors.New("request is required") + } + if req.DonID == 0 { + return errors.New("DON ID is required") + } + if req.ConfigVersion == 0 { + return errors.New("config version is required") + } + if req.Qualifier == "" { + return errors.New("forwarder qualifier is required") + } + if req.Version == "" { + return errors.New("forwarder version is required") + } + version, err := semver.NewVersion(req.Version) + if err != nil { + return fmt.Errorf("invalid forwarder version %q: %w", req.Version, err) + } + if req.MCMS != nil { + if err := req.MCMS.Validate(); err != nil { + return fmt.Errorf("invalid MCMS timelock proposal input: %w", err) + } + } + + chains := req.Chains + if len(chains) == 0 { + chains = make(map[uint64]struct{}) + for sel := range env.BlockChains.StellarChains() { + chains[sel] = struct{}{} + } + } + for sel := range chains { + if _, ok := env.BlockChains.StellarChains()[sel]; !ok { + return fmt.Errorf("stellar chain not found for chain selector %d", sel) + } + refKey := datastore.NewAddressRefKey(sel, ForwarderContract, version, req.Qualifier) + if _, err := env.DataStore.Addresses().Get(refKey); err != nil { + return fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err) + } + } + + return nil +} + +func (cs ClearConfigs) Apply(env cldf.Environment, req *ClearConfigRequest) (cldf.ChangesetOutput, error) { + var out cldf.ChangesetOutput + + version := semver.MustParse(req.Version) + chains := req.Chains + if len(chains) == 0 { + chains = make(map[uint64]struct{}) + for sel := range env.BlockChains.StellarChains() { + chains[sel] = struct{}{} + } + } + + var batchOps []mcmstypes.BatchOperation + + for sel := range chains { + ch, ok := env.BlockChains.StellarChains()[sel] + if !ok { + return out, fmt.Errorf("stellar chain not found for chain selector %d", sel) + } + + refKey := datastore.NewAddressRefKey(sel, ForwarderContract, version, req.Qualifier) + addrRef, err := env.DataStore.Addresses().Get(refKey) + if err != nil { + return out, fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err) + } + + // The governed path only reads (owner check) and encodes; it must not + // require the deployer signing key. + if req.MCMS != nil { + if err := requireTimelockOwnership(env.GetContext(), env, ch, addrRef.Address, *req.MCMS); err != nil { + return out, err + } + + batchOp, err := forwarderClearConfigBatchOp(sel, addrRef.Address, req.DonID, req.ConfigVersion) + if err != nil { + return out, fmt.Errorf("failed to build clear_config batch operation for stellar forwarder %s on chain selector %d: %w", addrRef.Address, sel, err) + } + batchOps = append(batchOps, batchOp) + + env.Logger.Infow("Built governed Stellar forwarder clear_config operation", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DonID, "configVersion", req.ConfigVersion) + + continue + } + + deployer, err := stellardeployment.NewDeployerFromChain(ch) + if err != nil { + return out, fmt.Errorf("failed to build stellar deployer for chain selector %d: %w", sel, err) + } + + client := crebindings.NewForwarderClient(deployer, addrRef.Address) + if err := client.ClearConfig(env.GetContext(), req.DonID, req.ConfigVersion); err != nil { + return out, fmt.Errorf("failed to clear config on stellar forwarder %s (chain selector %d): %w", addrRef.Address, sel, err) + } + + env.Logger.Infow("Cleared Stellar CRE forwarder config", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DonID, "configVersion", req.ConfigVersion) + } + + if req.MCMS != nil { + return cldf.NewOutputBuilder(env, nil). + WithTimelockProposal(*req.MCMS, batchOps). + Build() + } + + return out, nil +} diff --git a/deployment/cre/forwarder/stellar/configure_forwarder.go b/deployment/cre/forwarder/stellar/configure_forwarder.go index 30a14c44065..e69f7475209 100644 --- a/deployment/cre/forwarder/stellar/configure_forwarder.go +++ b/deployment/cre/forwarder/stellar/configure_forwarder.go @@ -8,6 +8,8 @@ import ( "github.com/Masterminds/semver/v3" chainselectors "github.com/smartcontractkit/chain-selectors" + mcmstypes "github.com/smartcontractkit/mcms/types" + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" "github.com/smartcontractkit/chainlink-deployments-framework/offchain" @@ -36,6 +38,11 @@ type ConfigureForwarderRequest struct { Chains map[uint64]struct{} Qualifier string Version string + + // MCMS, when set, builds a governed set_config timelock proposal instead + // of sending directly with the deployer key. Required once the forwarder + // is owned by the MCMS timelock. Its Qualifier selects the MCMS instance. + MCMS *cldf.MCMSTimelockProposalInput } func (cs ConfigureForwarders) VerifyPreconditions(env cldf.Environment, req *ConfigureForwarderRequest) error { @@ -58,6 +65,11 @@ func (cs ConfigureForwarders) VerifyPreconditions(env cldf.Environment, req *Con if err != nil { return fmt.Errorf("invalid forwarder version %q: %w", req.Version, err) } + if req.MCMS != nil { + if err := req.MCMS.Validate(); err != nil { + return fmt.Errorf("invalid MCMS timelock proposal input: %w", err) + } + } chains := req.Chains if len(chains) == 0 { @@ -100,6 +112,8 @@ func (cs ConfigureForwarders) Apply(env cldf.Environment, req *ConfigureForwarde return out, fmt.Errorf("no stellar signers found for DON %q", req.DON.Name) } + var batchOps []mcmstypes.BatchOperation + for sel := range chains { ch, ok := env.BlockChains.StellarChains()[sel] if !ok { @@ -112,6 +126,24 @@ func (cs ConfigureForwarders) Apply(env cldf.Environment, req *ConfigureForwarde return out, fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err) } + // The governed path only reads (owner check) and encodes; it must not + // require the deployer signing key. + if req.MCMS != nil { + if err := requireTimelockOwnership(env.GetContext(), env, ch, addrRef.Address, *req.MCMS); err != nil { + return out, err + } + + batchOp, err := forwarderSetConfigBatchOp(sel, addrRef.Address, req.DON.ID, req.DON.Version, uint32(req.DON.F), signers) + if err != nil { + return out, fmt.Errorf("failed to build set_config batch operation for stellar forwarder %s on chain selector %d: %w", addrRef.Address, sel, err) + } + batchOps = append(batchOps, batchOp) + + env.Logger.Infow("Built governed Stellar forwarder set_config operation", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DON.ID, "f", req.DON.F, "signersLen", len(signers)) + + continue + } + deployer, err := stellardeployment.NewDeployerFromChain(ch) if err != nil { return out, fmt.Errorf("failed to build stellar deployer for chain selector %d: %w", sel, err) @@ -132,6 +164,12 @@ func (cs ConfigureForwarders) Apply(env cldf.Environment, req *ConfigureForwarde env.Logger.Infow("Configured Stellar CRE forwarder", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DON.ID, "f", req.DON.F, "signersLen", len(signers)) } + if req.MCMS != nil { + return cldf.NewOutputBuilder(env, nil). + WithTimelockProposal(*req.MCMS, batchOps). + Build() + } + return out, nil } diff --git a/deployment/cre/forwarder/stellar/mcms.go b/deployment/cre/forwarder/stellar/mcms.go new file mode 100644 index 00000000000..d53a4c0cac9 --- /dev/null +++ b/deployment/cre/forwarder/stellar/mcms.go @@ -0,0 +1,156 @@ +package stellar + +import ( + "context" + "fmt" + + chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/stellar/go-stellar-sdk/keypair" + "github.com/stellar/go-stellar-sdk/xdr" + + mcmsstellar "github.com/smartcontractkit/mcms/sdk/stellar" + mcmstypes "github.com/smartcontractkit/mcms/types" + + cldfstellar "github.com/smartcontractkit/chainlink-deployments-framework/chain/stellar" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + "github.com/smartcontractkit/chainlink-stellar/bindings" + "github.com/smartcontractkit/chainlink-stellar/bindings/scval" +) + +// forwarderSetConfigBatchOp encodes the forwarder set_config call as an MCMS +// batch operation. The argument encoding mirrors ForwarderClient.SetConfig in +// chainlink-stellar/bindings/contracts/cre. +func forwarderSetConfigBatchOp( + chainSelector uint64, + forwarder string, + donID, configVersion, f uint32, + signers [][32]byte, +) (mcmstypes.BatchOperation, error) { + args := []xdr.ScVal{ + scval.Uint32ToScVal(donID), + scval.Uint32ToScVal(configVersion), + scval.Uint32ToScVal(f), + scval.Bytes32SliceToScVal(signers), + } + + return mcmsstellar.NewBatchOperation( + mcmstypes.ChainSelector(chainSelector), + forwarder, + "set_config", + args, + string(ForwarderContract), + nil, + ) +} + +// forwarderClearConfigBatchOp encodes the forwarder clear_config call as an +// MCMS batch operation. The argument encoding mirrors ForwarderClient.ClearConfig. +func forwarderClearConfigBatchOp( + chainSelector uint64, + forwarder string, + donID, configVersion uint32, +) (mcmstypes.BatchOperation, error) { + args := []xdr.ScVal{ + scval.Uint32ToScVal(donID), + scval.Uint32ToScVal(configVersion), + } + + return mcmsstellar.NewBatchOperation( + mcmstypes.ChainSelector(chainSelector), + forwarder, + "clear_config", + args, + string(ForwarderContract), + nil, + ) +} + +// addForwardersBatchOp encodes one add_forwarder call per transmitter as a +// single MCMS batch operation, so the allow-list change applies atomically. +// The argument encoding mirrors ForwarderClient.AddForwarder. +func addForwardersBatchOp( + chainSelector uint64, + forwarder string, + transmitters []string, +) (mcmstypes.BatchOperation, error) { + transactions := make([]mcmstypes.Transaction, 0, len(transmitters)) + for _, transmitter := range transmitters { + tx, err := mcmsstellar.NewTransaction( + forwarder, + "add_forwarder", + []xdr.ScVal{scval.AddressToScVal(transmitter)}, + string(ForwarderContract), + nil, + ) + if err != nil { + return mcmstypes.BatchOperation{}, fmt.Errorf("build add_forwarder transaction for %s: %w", transmitter, err) + } + transactions = append(transactions, tx) + } + + return mcmstypes.BatchOperation{ + ChainSelector: mcmstypes.ChainSelector(chainSelector), + Transactions: transactions, + }, nil +} + +// readOnlyInvoker builds an invoker for simulate-only reads. It signs nothing: +// the owner/pending-owner lookups go through SimulateContract, which only +// needs a source-account address, so an ephemeral keypair suffices. This keeps +// governed proposal building usable without the deployer signing key. +func readOnlyInvoker(chain cldfstellar.Chain) (bindings.Invoker, error) { + kp, err := keypair.Random() + if err != nil { + return nil, fmt.Errorf("generate ephemeral stellar keypair: %w", err) + } + + return mcmsstellar.NewInvokerWithNetworkPassphrase(chain.Client, bindings.NewStellarKeypairSigner(kp), chain.NetworkPassphrase) +} + +// requireTimelockOwnership fails unless the forwarder is owned by the MCMS +// timelock the proposal input resolves to. It guards the governed path against +// building proposals the timelock cannot execute. The Stellar MCMS reader must +// be registered by the consumer (e.g. via a blank import of +// cld-changesets/mcms/stellar/readers in the CLD domain). +func requireTimelockOwnership( + ctx context.Context, + env cldf.Environment, + chain cldfstellar.Chain, + forwarder string, + mcmsInput cldf.MCMSTimelockProposalInput, +) error { + chainSelector := chain.Selector + + reader, ok := cldf.GetMCMSReaderRegistry().Get(chainselectors.FamilyStellar) + if !ok { + return fmt.Errorf("no MCMS reader registered for chain family %q", chainselectors.FamilyStellar) + } + + timelockRef, err := reader.GetTimelockRef(env, chainSelector, mcmsInput) + if err != nil { + return fmt.Errorf("resolve Stellar timelock for chain %d: %w", chainSelector, err) + } + + invoker, err := readOnlyInvoker(chain) + if err != nil { + return err + } + + owner, err := mcmsstellar.NewInspectorFromInvoker(invoker).GetOwner(ctx, forwarder) + if err != nil { + return fmt.Errorf("read owner of stellar forwarder %s: %w", forwarder, err) + } + if owner == nil { + return fmt.Errorf("stellar forwarder %s has no owner", forwarder) + } + if *owner != timelockRef.Address { + return fmt.Errorf( + "stellar forwarder %s is owned by %s, not the MCMS timelock %s; apply directly (without MCMS) while the deployer owns it, or transfer ownership to the timelock first", + forwarder, + *owner, + timelockRef.Address, + ) + } + + return nil +} diff --git a/deployment/cre/forwarder/stellar/mcms_test.go b/deployment/cre/forwarder/stellar/mcms_test.go new file mode 100644 index 00000000000..0a5fe0df75a --- /dev/null +++ b/deployment/cre/forwarder/stellar/mcms_test.go @@ -0,0 +1,139 @@ +package stellar + +import ( + "testing" + + chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/stellar/go-stellar-sdk/strkey" + "github.com/stellar/go-stellar-sdk/xdr" + "github.com/stretchr/testify/require" + + mcmsstellar "github.com/smartcontractkit/mcms/sdk/stellar" + mcmstypes "github.com/smartcontractkit/mcms/types" + + "github.com/smartcontractkit/chainlink-stellar/bindings/scval" +) + +func testContractAddress(t *testing.T, seed byte) string { + t.Helper() + + raw := make([]byte, 32) + for i := range raw { + raw[i] = seed + byte(i) + } + + addr, err := strkey.Encode(strkey.VersionByteContract, raw) + require.NoError(t, err) + + return addr +} + +func testAccountAddress(t *testing.T, seed byte) string { + t.Helper() + + raw := make([]byte, 32) + raw[0] = seed + + addr, err := strkey.Encode(strkey.VersionByteAccountID, raw) + require.NoError(t, err) + + return addr +} + +func TestForwarderSetConfigBatchOp(t *testing.T) { + t.Parallel() + + selector := chainselectors.STELLAR_TESTNET.Selector + forwarderAddr := testContractAddress(t, 1) + signers := [][32]byte{{1}, {2}, {3}} + + batchOp, err := forwarderSetConfigBatchOp(selector, forwarderAddr, 5, 2, 1, signers) + require.NoError(t, err) + + require.Equal(t, mcmstypes.ChainSelector(selector), batchOp.ChainSelector) + require.Len(t, batchOp.Transactions, 1) + + tx := batchOp.Transactions[0] + require.Equal(t, forwarderAddr, tx.To) + require.Equal(t, string(ForwarderContract), tx.ContractType) + require.Contains(t, string(tx.AdditionalFields), chainselectors.FamilyStellar) + + // Decode the payload and assert the exact function name and argument + // order/values the on-chain forwarder expects; a swapped argument would + // otherwise only fail at execution time. + payload, err := mcmsstellar.DecodeSorobanInvokePayload(tx.Data) + require.NoError(t, err) + require.Equal(t, "set_config", payload.Function) + require.Equal(t, []xdr.ScVal{ + scval.Uint32ToScVal(5), + scval.Uint32ToScVal(2), + scval.Uint32ToScVal(1), + scval.Bytes32SliceToScVal(signers), + }, payload.Args) +} + +func TestForwarderSetConfigBatchOp_InvalidTarget(t *testing.T) { + t.Parallel() + + _, err := forwarderSetConfigBatchOp(chainselectors.STELLAR_TESTNET.Selector, "not-an-address", 5, 2, 1, [][32]byte{{1}}) + require.Error(t, err) +} + +func TestAddForwardersBatchOp(t *testing.T) { + t.Parallel() + + selector := chainselectors.STELLAR_TESTNET.Selector + forwarderAddr := testContractAddress(t, 1) + transmitters := []string{ + testAccountAddress(t, 10), + testAccountAddress(t, 20), + } + + batchOp, err := addForwardersBatchOp(selector, forwarderAddr, transmitters) + require.NoError(t, err) + + require.Equal(t, mcmstypes.ChainSelector(selector), batchOp.ChainSelector) + require.Len(t, batchOp.Transactions, len(transmitters)) + + for i, tx := range batchOp.Transactions { + require.Equal(t, forwarderAddr, tx.To) + require.Equal(t, string(ForwarderContract), tx.ContractType) + + payload, err := mcmsstellar.DecodeSorobanInvokePayload(tx.Data) + require.NoError(t, err) + require.Equal(t, "add_forwarder", payload.Function) + require.Equal(t, []xdr.ScVal{scval.AddressToScVal(transmitters[i])}, payload.Args) + } +} + +func TestForwarderClearConfigBatchOp(t *testing.T) { + t.Parallel() + + selector := chainselectors.STELLAR_TESTNET.Selector + forwarderAddr := testContractAddress(t, 1) + + batchOp, err := forwarderClearConfigBatchOp(selector, forwarderAddr, 5, 2) + require.NoError(t, err) + + require.Equal(t, mcmstypes.ChainSelector(selector), batchOp.ChainSelector) + require.Len(t, batchOp.Transactions, 1) + + tx := batchOp.Transactions[0] + require.Equal(t, forwarderAddr, tx.To) + require.Equal(t, string(ForwarderContract), tx.ContractType) + + payload, err := mcmsstellar.DecodeSorobanInvokePayload(tx.Data) + require.NoError(t, err) + require.Equal(t, "clear_config", payload.Function) + require.Equal(t, []xdr.ScVal{ + scval.Uint32ToScVal(5), + scval.Uint32ToScVal(2), + }, payload.Args) +} + +func TestAddForwardersBatchOp_InvalidTarget(t *testing.T) { + t.Parallel() + + _, err := addForwardersBatchOp(chainselectors.STELLAR_TESTNET.Selector, "not-an-address", []string{testAccountAddress(t, 10)}) + require.Error(t, err) +} diff --git a/deployment/cre/stellar/artifacts.go b/deployment/cre/stellar/artifacts.go index 5984ff60e92..13ec545191d 100644 --- a/deployment/cre/stellar/artifacts.go +++ b/deployment/cre/stellar/artifacts.go @@ -2,28 +2,35 @@ package stellar import "github.com/smartcontractkit/chainlink-stellar/deployment/cre" -// Artifact filenames of the CRE contract WASM committed in chainlink-stellar -// (deployment/cre/artifacts/). These are duplicated from -// chainlink-stellar/deployment/cre/artifacts.go so callers in the deployment -// module and downstream system-tests/lib do not need to import chainlink-stellar. +// Artifact filenames for Stellar contracts embedded in chainlink-stellar. +// +// Deployment changesets should use this package instead of importing the +// chainlink-stellar artifact package directly. const ( - // ReadFixtureWasm is the CRE ReadContract test fixture (contracts/cre/test/read_fixture). + // MCMSWasm is the Stellar Many Chain MultiSig contract. + MCMSWasm = cre.MCMSWasm + + // TimelockWasm is the Stellar Timelock contract. + TimelockWasm = cre.TimelockWasm + + // ReadFixtureWasm is the CRE ReadContract test fixture. ReadFixtureWasm = cre.ReadFixtureWasm - // ForwarderWasm is the CRE forwarder (contracts/cre/forwarder). + // ForwarderWasm is the CRE Forwarder contract. ForwarderWasm = cre.ForwarderWasm - // ReceiverWasm is the CRE test receiver (contracts/cre/test/receiver). + // ReceiverWasm is the CRE test receiver. ReceiverWasm = cre.ReceiverWasm - // RejectingReceiverWasm is the CRE test receiver that always rejects on_report. + // RejectingReceiverWasm is the CRE test receiver that always rejects + // on_report calls. RejectingReceiverWasm = cre.RejectingReceiverWasm ) -// Artifact returns the compiled WASM for one of the filename constants above. -// The bytes are those embedded in the pinned chainlink-stellar module, kept in -// sync with the contract sources by that repo's check-generated CI job — -// nothing is compiled, downloaded, or resolved at deploy time. +// Artifact returns the compiled WASM for the requested Stellar contract. +// +// The artifacts are embedded in the pinned chainlink-stellar module. Nothing +// is compiled, downloaded, or resolved at deployment time. func Artifact(name string) ([]byte, error) { return cre.Artifact(name) } diff --git a/deployment/go.mod b/deployment/go.mod index 45144078ad6..392ab33f7a2 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -62,6 +62,7 @@ require ( github.com/smartcontractkit/chainlink-solana v1.3.1-0.20260605202330-b5a89c32fdc1 github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260513123719-d347eaf314e1 github.com/smartcontractkit/chainlink-stellar v0.0.5 + github.com/smartcontractkit/chainlink-stellar/bindings v0.0.0-20260828132741-4eb62ddc67fb github.com/smartcontractkit/chainlink-sui v0.0.0-20260827120130-362c4a408695 github.com/smartcontractkit/chainlink-sui/codec v0.0.0-20260720132736-e99278bfdc96 github.com/smartcontractkit/chainlink-sui/deployment v0.0.0-20260827120130-362c4a408695 @@ -76,6 +77,7 @@ require ( github.com/smartcontractkit/smdkg v0.0.0-20260819115032-4afa3ab56bc4 github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945 github.com/spf13/cobra v1.10.2 + github.com/stellar/go-stellar-sdk v0.7.3 github.com/stretchr/testify v1.12.1 github.com/testcontainers/testcontainers-go v0.44.0 github.com/vmihailenco/msgpack/v5 v5.4.1 @@ -457,7 +459,6 @@ require ( github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 // indirect github.com/smartcontractkit/chainlink-protos/svr v1.3.0 // indirect github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260821155228-fa1d775e2138 // indirect - github.com/smartcontractkit/chainlink-stellar/bindings v0.0.0-20260828132741-4eb62ddc67fb // indirect github.com/smartcontractkit/chainlink-testing-framework/parrot v0.6.2 // indirect github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 // indirect github.com/smartcontractkit/chainlink-ton/cciplib v0.1.1-0.20260716214810-db5ecc877490 // indirect @@ -470,7 +471,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect - github.com/stellar/go-stellar-sdk v0.7.3 // indirect github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a // indirect github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect