Skip to content

Commit 3c3f932

Browse files
USDC Transfer Test - basic transfer test case stub (#569)
* Fixing configuration for tokenVerifier * Fixing configuration for tokenVerifier * Fixing configuration for tokenVerifier * Fixing configuration for tokenVerifier * Fixing configuration for tokenVerifier * Fixing configuration for tokenVerifier * USDC tests stub * USDC tests stub * USDC tests stub * Fix * Fix * Fix * Fix
1 parent 2823198 commit 3c3f932

22 files changed

+704
-147
lines changed

build/devenv/fakes/air.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
root = "/app/build/devenv/fakes"
22

33
[build]
4-
cmd = "go build -o ./tmp/fake cmd"
4+
cmd = "go build -o ./tmp/fake cmd/main.go"
55
bin = "/app/build/devenv/fakes/tmp/fake"
66
include_ext = ["go", "mod", "sum"]
77
exclude_dir = ["tmp", "vendor", "testdata", ".git"]

build/devenv/fakes/cmd/main.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package main
33
import (
44
"log"
55

6-
"github.com/smartcontractkit/devenv/ccip17/fakes/pkg/offchainstorage"
7-
86
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake"
7+
8+
"github.com/smartcontractkit/devenv/ccip17/fakes/pkg/cctp"
9+
"github.com/smartcontractkit/devenv/ccip17/fakes/pkg/offchainstorage"
910
)
1011

1112
func main() {
@@ -16,16 +17,18 @@ func main() {
1617
}
1718

1819
// Create and configure the offchain storage API
19-
api := offchainstorage.NewOffChainStorageAPI()
20-
21-
// Register the API endpoints
22-
err = api.Register()
23-
if err != nil {
20+
offchainStorage := offchainstorage.NewOffChainStorageAPI()
21+
if err = offchainStorage.Register(); err != nil {
2422
panic(err)
2523
}
26-
2724
log.Printf("Fake offchain storage API running on port %d", fake.DefaultFakeServicePort)
2825

26+
cctpAttestations := cctp.NewAttestationAPI()
27+
if err = cctpAttestations.Register(); err != nil {
28+
panic(err)
29+
}
30+
log.Printf("Fake CCTP Attestation API running on port %d", fake.DefaultFakeServicePort)
31+
2932
// Keep the server running
3033
select {}
3134
}

build/devenv/fakes/go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ require (
4545
github.com/go-resty/resty/v2 v2.16.5 // indirect
4646
github.com/goccy/go-json v0.10.5 // indirect
4747
github.com/google/uuid v1.6.0 // indirect
48+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
4849
github.com/holiman/uint256 v1.3.2 // indirect
4950
github.com/json-iterator/go v1.1.12 // indirect
5051
github.com/klauspost/compress v1.18.2 // indirect
@@ -64,6 +65,7 @@ require (
6465
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6566
github.com/modern-go/reflect2 v1.0.2 // indirect
6667
github.com/morikuni/aec v1.0.0 // indirect
68+
github.com/mr-tron/base58 v1.2.0 // indirect
6769
github.com/opencontainers/go-digest v1.0.0 // indirect
6870
github.com/opencontainers/image-spec v1.1.1 // indirect
6971
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
@@ -73,7 +75,11 @@ require (
7375
github.com/rs/zerolog v1.34.0 // indirect
7476
github.com/shirou/gopsutil/v4 v4.25.9 // indirect
7577
github.com/sirupsen/logrus v1.9.3 // indirect
78+
github.com/smartcontractkit/chain-selectors v1.0.79 // indirect
79+
github.com/smartcontractkit/chainlink-common v0.9.6-0.20260114190811-74301cd99dc3 // indirect
80+
github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d // indirect
7681
github.com/smartcontractkit/chainlink-testing-framework/framework v0.12.6 // indirect
82+
github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d // indirect
7783
github.com/stretchr/testify v1.11.1 // indirect
7884
github.com/testcontainers/testcontainers-go v0.39.0 // indirect
7985
github.com/tklauser/go-sysconf v0.3.15 // indirect
@@ -87,12 +93,17 @@ require (
8793
go.opentelemetry.io/otel/metric v1.38.0 // indirect
8894
go.opentelemetry.io/otel/trace v1.38.0 // indirect
8995
go.uber.org/multierr v1.11.0 // indirect
96+
go.uber.org/zap v1.27.0 // indirect
9097
golang.org/x/arch v0.21.0 // indirect
9198
golang.org/x/crypto v0.46.0 // indirect
99+
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
92100
golang.org/x/net v0.47.0 // indirect
93101
golang.org/x/sync v0.19.0 // indirect
94102
golang.org/x/sys v0.39.0 // indirect
95103
golang.org/x/text v0.32.0 // indirect
104+
golang.org/x/time v0.12.0 // indirect
105+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
106+
google.golang.org/grpc v1.76.0 // indirect
96107
google.golang.org/protobuf v1.36.11 // indirect
97108
gopkg.in/yaml.v3 v3.0.1 // indirect
98109
)

build/devenv/fakes/go.sum

Lines changed: 97 additions & 1 deletion
Large diffs are not rendered by default.

build/devenv/fakes/pkg/cctp/api.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package cctp
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"sync"
8+
9+
"github.com/gin-gonic/gin"
10+
11+
cctpclient "github.com/smartcontractkit/chainlink-ccv/verifier/token/cctp"
12+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake"
13+
)
14+
15+
type RegisterAttestationRequest struct {
16+
SourceDomain string `json:"sourceDomain" binding:"required"`
17+
MessageID string `json:"messageID" binding:"required"`
18+
Status string `json:"status" binding:"required"`
19+
MessageSender string `json:"messageSender" binding:"required"`
20+
Message string `json:"message"`
21+
Attestation string `json:"attestation"`
22+
}
23+
24+
type AttestationAPI struct {
25+
mu sync.RWMutex
26+
responses map[string]cctpclient.Message
27+
}
28+
29+
func NewAttestationAPI() *AttestationAPI {
30+
return &AttestationAPI{
31+
responses: make(map[string]cctpclient.Message),
32+
}
33+
}
34+
35+
// RegisterAttestation registers a new attestation response for a given sourceDomain.
36+
func (a *AttestationAPI) RegisterAttestation(sourceDomain, messageID, status, message, attestation, messageSender string) cctpclient.Message {
37+
a.mu.Lock()
38+
defer a.mu.Unlock()
39+
40+
// Construct hookData as 0x8e1d1a9d + messageID (without 0x prefix)
41+
// Strip 0x prefix from messageID if present
42+
cleanMessageID := messageID
43+
if len(messageID) > 2 && messageID[:2] == "0x" {
44+
cleanMessageID = messageID[2:]
45+
}
46+
hookData := "0x8e1d1a9d" + cleanMessageID
47+
48+
// Use provided values or defaults
49+
if message == "" {
50+
message = "0xbbbbbbbb"
51+
}
52+
if attestation == "" {
53+
attestation = "0xaaaaaaaa"
54+
}
55+
56+
// Create a response based on the example attestation but with the provided sourceDomain, hookData and status
57+
response := cctpclient.Message{
58+
Message: message,
59+
EventNonce: "9682",
60+
Attestation: attestation,
61+
DecodedMessage: cctpclient.DecodedMessage{
62+
SourceDomain: sourceDomain,
63+
DestinationDomain: "5",
64+
Nonce: "569",
65+
Sender: "0xthis_is_ignored_for_simplicity",
66+
Recipient: "0xthis_is_ignored_for_simplicity",
67+
DestinationCaller: "0xthis_is_ignored_for_simplicity",
68+
MessageBody: "0xthis_is_ignored_for_simplicity",
69+
DecodedMessageBody: cctpclient.DecodedMessageBody{
70+
BurnToken: "0xthis_is_ignored_for_simplicity",
71+
MintRecipient: "0xthis_is_ignored_for_simplicity",
72+
Amount: "1000",
73+
MessageSender: messageSender,
74+
HookData: hookData,
75+
},
76+
},
77+
CCTPVersion: "2",
78+
Status: cctpclient.AttestationStatus(status),
79+
}
80+
81+
a.responses[sourceDomain] = response
82+
return response
83+
}
84+
85+
func (a *AttestationAPI) Register() error {
86+
// POST endpoint to register attestation responses
87+
err := fake.Func("POST", "/cctp/v2/attestations", func(ctx *gin.Context) {
88+
var req RegisterAttestationRequest
89+
if err := ctx.ShouldBindJSON(&req); err != nil {
90+
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
91+
return
92+
}
93+
94+
response := a.RegisterAttestation(req.SourceDomain, req.MessageID, req.Status, req.Message, req.Attestation, req.MessageSender)
95+
ctx.JSON(http.StatusOK, response)
96+
})
97+
if err != nil {
98+
return err
99+
}
100+
101+
// GET endpoint to retrieve attestation by sourceDomain
102+
err = fake.Func("GET", "/cctp/v2/messages/:sourceDomain", func(ctx *gin.Context) {
103+
sourceDomain := ctx.Param("sourceDomain")
104+
105+
a.mu.RLock()
106+
response, exists := a.responses[sourceDomain]
107+
a.mu.RUnlock()
108+
109+
if !exists {
110+
ctx.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("no attestation found for sourceDomain: %s", sourceDomain)})
111+
return
112+
}
113+
114+
// Return wrapped in "messages" array to match the expected format
115+
wrappedResponse := cctpclient.Messages{
116+
Messages: []cctpclient.Message{response},
117+
}
118+
responseBytes, err := json.Marshal(wrappedResponse)
119+
if err != nil {
120+
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
121+
return
122+
}
123+
124+
ctx.Data(http.StatusOK, "application/json", responseBytes)
125+
})
126+
if err != nil {
127+
return err
128+
}
129+
130+
return nil
131+
}

build/devenv/services/indexer.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ func defaults(in *IndexerInput) {
142142
BatchSize: 100,
143143
MaxBatchWaitTime: 50,
144144
},
145+
{
146+
Type: config.ReaderTypeRest,
147+
RestReaderConfig: config.RestReaderConfig{
148+
BaseURL: "token-verifier-1:8700/v1",
149+
RequestTimeout: 10,
150+
},
151+
Name: "Token Verifier (Primary)",
152+
BatchSize: 10,
153+
MaxBatchWaitTime: 100,
154+
},
145155
},
146156
Storage: config.StorageConfig{
147157
Strategy: config.StorageStrategySink,

build/devenv/services/tokenVerifier.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"strconv"
1010
"time"
1111

12+
"github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/cctp_verifier"
13+
"github.com/smartcontractkit/chainlink-ccv/verifier/token/cctp"
14+
1215
"github.com/BurntSushi/toml"
1316
"github.com/Masterminds/semver/v3"
1417

@@ -50,6 +53,8 @@ type TokenVerifierInput struct {
5053
DefaultExecutorOnRampAddresses map[string]string `toml:"default_executor_on_ramp_addresses"`
5154
// Maps to rmn_remote_addresses in the verifier config toml.
5255
RMNRemoteAddresses map[string]string `toml:"rmn_remote_addresses"`
56+
57+
CCTPVerifierAddresses map[string]string `toml:"cctp_verifier_addresses"`
5358
}
5459

5560
type TokenVerifierOutput struct {
@@ -202,6 +207,26 @@ func (v *TokenVerifierInput) buildVerifierConfiguration(config *token.Config) er
202207
config.VerifierID = v.ContainerName
203208
config.OnRampAddresses = v.OnRampAddresses
204209
config.RMNRemoteAddresses = v.RMNRemoteAddresses
210+
if len(config.TokenVerifiers) == 0 {
211+
config.TokenVerifiers = make([]token.VerifierConfig, 0)
212+
}
213+
214+
if len(v.CCTPVerifierAddresses) > 0 {
215+
verifiers := make(map[string]any)
216+
for k, addr := range v.CCTPVerifierAddresses {
217+
verifiers[k] = addr
218+
}
219+
config.TokenVerifiers = append(config.TokenVerifiers, token.VerifierConfig{
220+
Type: "cctp",
221+
Version: "2.0",
222+
CCTPConfig: &cctp.CCTPConfig{
223+
AttestationAPI: "http://fake:9111/cctp",
224+
AttestationAPIInterval: 100 * time.Millisecond,
225+
AttestationAPITimeout: 1 * time.Second,
226+
Verifiers: verifiers,
227+
},
228+
})
229+
}
205230

206231
return nil
207232
}
@@ -210,6 +235,7 @@ func ResolveContractsForTokenVerifier(ds datastore.DataStore, blockchains []*blo
210235
ver.OnRampAddresses = make(map[string]string)
211236
ver.DefaultExecutorOnRampAddresses = make(map[string]string)
212237
ver.RMNRemoteAddresses = make(map[string]string)
238+
ver.CCTPVerifierAddresses = make(map[string]string)
213239

214240
for _, chain := range blockchains {
215241
networkInfo, err := chainsel.GetChainDetailsByChainIDAndFamily(chain.ChainID, chainsel.FamilyEVM)
@@ -218,6 +244,20 @@ func ResolveContractsForTokenVerifier(ds datastore.DataStore, blockchains []*blo
218244
}
219245
selectorStr := strconv.FormatUint(networkInfo.ChainSelector, 10)
220246

247+
cctpTokenVerifierAddressRef, err := ds.Addresses().Get(datastore.NewAddressRefKey(
248+
networkInfo.ChainSelector,
249+
datastore.ContractType(cctp_verifier.ResolverType),
250+
semver.MustParse(cctp_verifier.Deploy.Version()),
251+
"CCTP",
252+
))
253+
if err != nil {
254+
framework.L.Info().
255+
Str("chainID", chain.ChainID).
256+
Msg("Failed to get CCTP Verifier address from datastore")
257+
} else {
258+
ver.CCTPVerifierAddresses[selectorStr] = cctpTokenVerifierAddressRef.Address
259+
}
260+
221261
onRampAddressRef, err := ds.Addresses().Get(datastore.NewAddressRefKey(
222262
networkInfo.ChainSelector,
223263
datastore.ContractType(onrampoperations.ContractType),

build/devenv/services/tokenVerifier.template.toml

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,6 @@ MetricReaderInterval = 5
1313
TraceSampleRatio = 1.0
1414
TraceBatchTimeout = 10
1515

16-
[[token_verifiers]]
17-
type = "lbtc"
18-
version = "1.0"
19-
attestation_api = "https://lbtc-api.example.com"
20-
attestation_api_timeout = "10s"
21-
attestation_api_interval = "100ms"
22-
23-
[token_verifiers.addresses]
24-
3379446385462418246 = "0x9ac06ca36bb3dfd6a70eeb508ea5a33dadecca55"
25-
12922642891491394802 = "0xabb9d2d6f21b2be4d7250de972bd9f778accb3c5"
26-
27-
[[token_verifiers]]
28-
type = "cctp"
29-
version = "2.0"
30-
attestation_api = "https://cctp-api.example.com"
31-
attestation_api_timeout = "10s"
32-
attestation_api_interval = "100ms"
33-
34-
[token_verifiers.addresses]
35-
3379446385462418246 = "0x9ac06ca36bb3dfd6a70eeb508ea5a33dadecca55"
36-
12922642891491394802 = "0xabb9d2d6f21b2be4d7250de972bd9f778accb3c5"
37-
3816

3917
#[blockchain_infos]
4018
# [blockchain_infos.3379446385462418246]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package e2e
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"encoding/json"
7+
"maps"
8+
"net/http"
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/smartcontractkit/chainlink-ccv/protocol"
14+
)
15+
16+
// registerCCTPAttestation registers a CCTP attestation response with the fake service.
17+
func registerCCTPAttestation(
18+
t *testing.T,
19+
httpUrl string,
20+
messageID [32]byte,
21+
messageSender protocol.UnknownAddress,
22+
status string,
23+
optionalFields ...map[string]string,
24+
) {
25+
messageIDHex := "0x" + hex.EncodeToString(messageID[:])
26+
27+
reqBody := map[string]string{
28+
"sourceDomain": "100",
29+
"messageID": messageIDHex,
30+
"status": status,
31+
"messageSender": messageSender.String(),
32+
}
33+
34+
// Add optional fields if provided (message, attestation, messageSender can be overridden)
35+
if len(optionalFields) > 0 {
36+
maps.Copy(reqBody, optionalFields[0])
37+
}
38+
39+
reqJSON, err := json.Marshal(reqBody)
40+
require.NoError(t, err)
41+
42+
resp, err := http.Post(
43+
httpUrl+"/cctp/v2/attestations",
44+
"application/json",
45+
bytes.NewBuffer(reqJSON),
46+
)
47+
require.NoError(t, err)
48+
defer resp.Body.Close()
49+
50+
require.Equal(t, http.StatusOK, resp.StatusCode, "Failed to register CCTP attestation")
51+
}

0 commit comments

Comments
 (0)