Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 10 additions & 2 deletions relayer/chainreader/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@ type ChainReaderFunction struct {
ResultTupleToStruct []string
// Defines a mapping for renaming response fields
ResultFieldRenames map[string]aptosCRConfig.RenamedField
// Static response
// Static response, returned as-is to mimic a response from the contract
// The contract can be entirely virtual as long as .Bind() is called with it.
StaticResponse []any
// Response from inputs
// Response from inputs, can reference parameters, package ID (with or without module name, same package), or pointer objects
// Example: ["package_id", "package_id.ANOTHER_MODULE", "params.SOME_PARAM_NAME"]
// If the `package_id` is used without a module name, the latest package ID of the module for this function is returned.
// If the `package_id` is used with a module name, the latest package ID of the module is returned.
// * This might look the same as just `package_id`, however, getting the latest package ID is only possible within a specific module.
// * This is helpful for virtual dependencies between modules (e.g. RMNRemote -> CCIP latest package ID from `state_object` module)
// If the `params.counter_id` is used, the value of the counter object id is returned.
// * The `counter_id` parameter must be specified in the function config.
ResponseFromInputs []string
}

Expand Down
47 changes: 45 additions & 2 deletions relayer/chainreader/reader/chainreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -935,14 +935,57 @@ func (s *suiChainReader) executeFunction(ctx context.Context, parsed *readIdenti
if len(functionConfig.StaticResponse) > 0 {
return functionConfig.StaticResponse, nil
} else if len(functionConfig.ResponseFromInputs) > 0 {
response := make([]any, 0)

for _, pluckFromInput := range functionConfig.ResponseFromInputs {
switch pluckFromInput {
pluckParts := strings.Split(pluckFromInput, ".")

// if the pluckFromInput is empty, skip
if len(pluckParts) == 0 {
continue
}

switch pluckParts[0] {
case "package_id":
return []any{latestPackageId}, nil
// if there are no more parts, return the package ID of the module for this function
if len(pluckParts) == 1 {
response = append(response, latestPackageId)
continue
}

// if there are more parts, return the package ID of the module (must be within the same package)
// this is useful in cases where getting the latest package ID is only possible within a single module
// that is different from the current module (e.g. RMNRemote -> CCIP latest package ID from `state_object` module)
moduleName := pluckParts[1]
modulePackageId, err := s.client.GetLatestPackageId(ctx, parsed.address, moduleName)
if err != nil {
s.logger.Debugw("Failed to get latest package ID for module", "moduleName", moduleName, "error", err)
// fallback to the latest package ID of the current module
response = append(response, latestPackageId)
continue
}
response = append(response, modulePackageId)
continue
case "params":
if len(pluckParts) != 2 {
continue
}

// match the param name to the arg index
for i, param := range functionConfig.Params {
if param.Name == pluckParts[1] {
response = append(response, args[i])
}
}

// Not found
continue
default:
return nil, fmt.Errorf("unknown response from inputs selection: %s", pluckFromInput)
}
}

return response, nil
}

values, err := s.client.ReadFunction(ctx, functionConfig.SignerAddress, parsed.address, parsed.contractName, parsed.readName, args, argTypes, typeArgs)
Expand Down
63 changes: 62 additions & 1 deletion relayer/chainreader/reader/chainreader_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,20 @@ func runChainReaderCounterTest(t *testing.T, log logger.Logger, rpcUrl string) {
Params: []codec.SuiFunctionParam{},
ResponseFromInputs: []string{"package_id"},
},
"response_from_inputs_with_params": {
Name: "response_from_inputs_with_params",
SignerAddress: accountAddress,
Params: []codec.SuiFunctionParam{
{
Type: "object_id",
Name: "counter_id",
PointerTag: pointerTag,
Required: true,
},
},
ResponseFromInputs: []string{"params.counter_id", "package_id"},
ResultTupleToStruct: []string{"counter_id", "package_id"},
},
},
Events: map[string]*config.ChainReaderEvent{
"counter_incremented": {
Expand Down Expand Up @@ -421,6 +435,16 @@ func runChainReaderCounterTest(t *testing.T, log logger.Logger, rpcUrl string) {
},
Events: map[string]*config.ChainReaderEvent{},
},
"RMNProxy": {
Name: "rmn_proxy",
Functions: map[string]*config.ChainReaderFunction{
"get_arm": {
Name: "get_arm",
SignerAddress: accountAddress,
ResponseFromInputs: []string{"package_id.state_object"},
},
},
},
"FeeQuoter": {
Name: "fee_quoter",
Functions: map[string]*config.ChainReaderFunction{
Expand Down Expand Up @@ -487,6 +511,11 @@ func runChainReaderCounterTest(t *testing.T, log logger.Logger, rpcUrl string) {
Address: packageId, // Package ID of the deployed fee_quoter contract
}

rmnProxyBinding := types.BoundContract{
Name: "RMNProxy",
Address: secondaryPackageId, // Package ID of the deployed rmn_proxy contract
}

datastoreUrl := os.Getenv("TEST_DB_URL")
if datastoreUrl == "" {
t.Skip("Skipping persistent tests as TEST_DB_URL is not set in CI")
Expand Down Expand Up @@ -527,7 +556,7 @@ func runChainReaderCounterTest(t *testing.T, log logger.Logger, rpcUrl string) {
chainReader, err := NewChainReader(ctx, log, relayerClient, chainReaderConfig, db, indexerInstance)
require.NoError(t, err)

err = chainReader.Bind(context.Background(), []types.BoundContract{counterBinding, offRampBinding, onRampBinding, routerBinding, feeQuoterBinding})
err = chainReader.Bind(context.Background(), []types.BoundContract{counterBinding, offRampBinding, onRampBinding, routerBinding, feeQuoterBinding, rmnProxyBinding})
require.NoError(t, err)

go func() {
Expand Down Expand Up @@ -1514,4 +1543,36 @@ func runChainReaderCounterTest(t *testing.T, log logger.Logger, rpcUrl string) {
testutils.PrettyPrintDebug(log, retStaticResponse, "retStaticResponse")
require.Equal(t, map[string]any{"a": 1, "b": 2, "c": 3}, retStaticResponse, "Expected static response to be map[string]any with keys a, b, and c")
})

t.Run("GetLatestValue_ResponseFromInputsWithParams", func(t *testing.T) {
var retResponseFromInputs any
params := map[string]any{}

err = chainReader.GetLatestValue(
context.Background(),
strings.Join([]string{packageId, "Counter", "response_from_inputs_with_params"}, "-"),
primitives.Finalized,
&params, // no parameters needed
&retResponseFromInputs,
)
require.NoError(t, err)
testutils.PrettyPrintDebug(log, retResponseFromInputs, "retResponseFromInputs")
require.Equal(t, map[string]any{"counter_id": counterObjectId, "package_id": packageId}, retResponseFromInputs, "Expected response to be the counter object id and package id")
})

t.Run("GetLatestValue_ResponseFromInputsWithModulePackageId", func(t *testing.T) {
var retResponseFromInputs any
params := map[string]any{}

err = chainReader.GetLatestValue(
context.Background(),
strings.Join([]string{secondaryPackageId, "RMNProxy", "get_arm"}, "-"),
primitives.Finalized,
&params, // no parameters needed
&retResponseFromInputs,
)
require.NoError(t, err)
testutils.PrettyPrintDebug(log, retResponseFromInputs, "retResponseFromInputs")
require.Equal(t, secondaryPackageId, retResponseFromInputs, "Expected response to be the package id")
})
}
Loading