From cd02c44c6c5323ef4fa0750eec72a86856c750b8 Mon Sep 17 00:00:00 2001 From: akawalsky Date: Tue, 6 Jan 2026 17:34:25 -0500 Subject: [PATCH 1/4] update fuel contract --- chains/fuel/contracts/stork/src/main.sw | 17 ++++++++++++++ chains/fuel/contracts/stork/tests/harness.rs | 22 +++++++++++++++++++ .../fuel/sdks/stork_sway_sdk/src/interface.sw | 3 +++ 3 files changed, 42 insertions(+) diff --git a/chains/fuel/contracts/stork/src/main.sw b/chains/fuel/contracts/stork/src/main.sw index 460bb7ae..38b35cd8 100644 --- a/chains/fuel/contracts/stork/src/main.sw +++ b/chains/fuel/contracts/stork/src/main.sw @@ -241,6 +241,23 @@ impl Stork for Contract { latest_value } + #[storage(read)] + fn get_temporal_numeric_values_unchecked_v1(ids: Vec) -> Vec { + let mut values = Vec::new(); + let mut i = 0; + while i < ids.len() { + let id = ids.get(i).unwrap(); + values.push(match latest_canonical_temporal_numeric_value(id) { + Ok(value) => value, + Err(error) => { + panic error; + } + }); + i += 1; + } + values + } + fn version() -> String { return String::from_ascii_str("1.0.0"); } diff --git a/chains/fuel/contracts/stork/tests/harness.rs b/chains/fuel/contracts/stork/tests/harness.rs index 62fda773..c4a90a84 100644 --- a/chains/fuel/contracts/stork/tests/harness.rs +++ b/chains/fuel/contracts/stork/tests/harness.rs @@ -526,6 +526,17 @@ async fn test_update_temporal_numeric_value_v1_valid() { .value; assert_eq!(temporal_numeric_value.timestamp_ns, recv_time); assert_eq!(temporal_numeric_value.quantized_value, quantized_value); + + // get the temporal numeric values + let temporal_numeric_values = stork_instance + .methods() + .get_temporal_numeric_values_unchecked_v1(vec![id]) + .call() + .await + .unwrap() + .value; + assert_eq!(temporal_numeric_values[0].timestamp_ns, recv_time); + assert_eq!(temporal_numeric_values[0].quantized_value, quantized_value); } #[tokio::test] @@ -624,6 +635,17 @@ async fn test_update_temporal_numeric_value_v1_negative() { .value; assert_eq!(temporal_numeric_value.timestamp_ns, recv_time); assert_eq!(temporal_numeric_value.quantized_value, quantized_value); + + // get the temporal numeric values + let temporal_numeric_values = stork_instance + .methods() + .get_temporal_numeric_values_unchecked_v1(vec![id]) + .call() + .await + .unwrap() + .value; + assert_eq!(temporal_numeric_values[0].timestamp_ns, recv_time); + assert_eq!(temporal_numeric_values[0].quantized_value, quantized_value); } #[tokio::test] diff --git a/chains/fuel/sdks/stork_sway_sdk/src/interface.sw b/chains/fuel/sdks/stork_sway_sdk/src/interface.sw index 888a3f73..40c4deee 100644 --- a/chains/fuel/sdks/stork_sway_sdk/src/interface.sw +++ b/chains/fuel/sdks/stork_sway_sdk/src/interface.sw @@ -50,6 +50,9 @@ abi Stork { #[storage(read)] fn get_temporal_numeric_value_unchecked_v1(id: b256) -> TemporalNumericValue; + #[storage(read)] + fn get_temporal_numeric_values_unchecked_v1(ids: Vec) -> Vec; + fn version() -> String; #[storage(read, write)] From 10943e64915a9a0ad35982bf42de94823eb6ac5e Mon Sep 17 00:00:00 2001 From: akawalsky Date: Tue, 6 Jan 2026 18:11:48 -0500 Subject: [PATCH 2/4] use new batch get method in pusher --- .../fuel/bindings/fuel_ffi/src/fuel_client.rs | 33 ++++++++++ .../pkg/fuel/bindings/fuel_ffi/src/lib.rs | 61 +++++++++++++++++++ .../pkg/fuel/bindings/fuel_ffi/stork-abi.json | 52 ++++++++++++++-- .../pkg/fuel/bindings/stork_fuel_contract.go | 39 ++++++++++++ apps/chain_pusher/pkg/fuel/interactor.go | 56 ++++++++--------- .../contracts/stork/artifacts/stork-abi.json | 50 ++++++++++++++- 6 files changed, 256 insertions(+), 35 deletions(-) diff --git a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs index f31a0d92..35b8bf68 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs +++ b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs @@ -127,6 +127,39 @@ impl FuelClient { Ok(Some(tnv)) } + pub async fn get_latest_temporal_numeric_values( + &self, + ids: Vec<[u8; 32]>, + ) -> Result, FuelClientError> { + let ids_bits256: Vec = ids.into_iter().map(Bits256).collect(); + + let response = self + .proxy_contract + .methods() + .get_temporal_numeric_values_unchecked_v1(ids_bits256) + .determine_missing_contracts() + .await + .map_err(|e| { + FuelClientError::ContractCallFailed(format!( + "Failed to determine missing contracts: {e}" + )) + })? + .simulate(Execution::state_read_only()) + .await + .map_err(|e| process_contract_error(e, &self.proxy_contract.log_decoder()))?; + + let values = response + .value + .into_iter() + .map(|contract_tnv| FuelTemporalNumericValue { + timestamp_ns: contract_tnv.timestamp_ns, + quantized_value: contract_tnv.quantized_value.into(), + }) + .collect(); + + Ok(values) + } + pub async fn update_temporal_numeric_values( &self, inputs: Vec, diff --git a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs index 2a52b331..c90631c9 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs +++ b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs @@ -142,6 +142,67 @@ pub unsafe extern "C" fn fuel_get_latest_value( ) } +/// # Safety +/// +/// - `client` must be a valid pointer to a FuelClient created by `fuel_client_new` +/// - `ids_ptr` must be a valid pointer to an array of 32-byte arrays +/// - `ids_len` must be the correct length of the ids array +/// - `out_values_json` must be a valid, writable pointer +/// - `out_error` must be a valid, writable pointer +/// - The caller is responsible for ensuring the pointers remain valid for the duration of the call +#[no_mangle] +pub unsafe extern "C" fn fuel_get_latest_values( + client: *mut FuelClient, + ids_ptr: *const [u8; 32], + ids_len: usize, + out_values_json: *mut *mut c_char, + out_error: *mut *mut c_char, +) -> FuelClientStatus { + if out_values_json.is_null() { + return FuelClientError::NullPointer("out_values_json is null".to_string()).into(); + } + if client.is_null() { + return FuelClientError::NullPointer("client is null".to_string()).into(); + } + if ids_ptr.is_null() { + return FuelClientError::NullPointer("ids_ptr is null".to_string()).into(); + } + + // Initialize output to null + unsafe { + *out_values_json = std::ptr::null_mut(); + } + + let result = (|| -> Result { + let client = unsafe { &*client }; + + // Convert pointer to slice of ids + let ids_slice = unsafe { std::slice::from_raw_parts(ids_ptr, ids_len) }; + let ids: Vec<[u8; 32]> = ids_slice.to_vec(); + + let values = client.rt.block_on(async { + tokio::time::timeout(TIMEOUT, client.get_latest_temporal_numeric_values(ids)) + .await + .map_err(|_| FuelClientError::Timeout(format!("Timed out after {TIMEOUT:?}")))? + })?; + + let json_str = serde_json::to_string(&values)?; + Ok(json_str) + })(); + + handle_ffi_result( + result, + |json_str| { + let c_str = string_to_c_char(json_str)?; + unsafe { + *out_values_json = c_str; + } + Ok(()) + }, + out_error, + ) +} + /// # Safety /// /// - `client` must be a valid pointer to a FuelClient created by `fuel_client_new` diff --git a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/stork-abi.json b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/stork-abi.json index 787d466c..48373eee 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/stork-abi.json +++ b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/stork-abi.json @@ -54,6 +54,14 @@ "concreteTypeId": "9a7f1d3e963c10e0a4ea70a8e20a4813d1dc5682e28f74cb102ae50d32f7f98c", "metadataTypeId": 13 }, + { + "type": "struct std::vec::Vec", + "concreteTypeId": "32559685d0c9845f059bf9d472a0a38cf77d36c23dfcffe5489e86a65cdd9198", + "metadataTypeId": 16, + "typeArguments": [ + "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + ] + }, { "type": "struct std::vec::Vec", "concreteTypeId": "e67278f564f3da524afebc87950681dff66e11946370df7f4c68b5f01329590b", @@ -62,6 +70,14 @@ "672654baba0e998dd82f818c92c2b544c9275ee09007b0f65f59195a94a916d6" ] }, + { + "type": "struct std::vec::Vec", + "concreteTypeId": "872137d0d4d329e1a5711b34dd5a31a31277c08d54727a431ddc614863a15eda", + "metadataTypeId": 16, + "typeArguments": [ + "6972e006137b782c482ffc099e21cc55fce9151a2096dd6582df22c9dc81bd9c" + ] + }, { "type": "struct std::vm::evm::evm_address::EvmAddress", "concreteTypeId": "05a44d8c3e00faf7ed545823b7a2b32723545d8715d87a0ab3cf65904948e8d2", @@ -406,6 +422,24 @@ } ] }, + { + "name": "get_temporal_numeric_values_unchecked_v1", + "inputs": [ + { + "name": "ids", + "concreteTypeId": "32559685d0c9845f059bf9d472a0a38cf77d36c23dfcffe5489e86a65cdd9198" + } + ], + "output": "872137d0d4d329e1a5711b34dd5a31a31277c08d54727a431ddc614863a15eda", + "attributes": [ + { + "name": "storage", + "arguments": [ + "read" + ] + } + ] + }, { "name": "get_update_fee_v1", "inputs": [ @@ -648,6 +682,16 @@ "msg": null }, "18446744069414584321": { + "pos": { + "pkg": "stork", + "file": "src/main.sw", + "line": 253, + "column": 21 + }, + "logId": "5142315946124958513", + "msg": null + }, + "18446744069414584322": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -657,7 +701,7 @@ "logId": "5142315946124958513", "msg": null }, - "18446744069414584322": { + "18446744069414584323": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -667,7 +711,7 @@ "logId": "5142315946124958513", "msg": null }, - "18446744069414584323": { + "18446744069414584324": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -677,7 +721,7 @@ "logId": "5142315946124958513", "msg": null }, - "18446744069414584324": { + "18446744069414584325": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -688,4 +732,4 @@ "msg": null } } -} +} \ No newline at end of file diff --git a/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go b/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go index 8364b804..91ba9db2 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go +++ b/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go @@ -11,6 +11,7 @@ package bindings #include "fuel_ffi.h" #include #include +#include */ import "C" @@ -223,6 +224,44 @@ func (s *StorkContract) GetTemporalNumericValueUncheckedV1(id [32]byte) (*Tempor return &result, nil } +func (s *StorkContract) GetTemporalNumericValuesUncheckedV1(ids [][32]byte) ([]TemporalNumericValue, error) { + if len(ids) == 0 { + return []TemporalNumericValue{}, nil + } + + // Convert [][32]byte to *C.uint8_t + idsPtr := (*[32]C.uint8_t)(unsafe.Pointer(&ids[0])) + idsLen := C.size_t(len(ids)) + + var outValuesJSONPtr *C.char + var outErrorPtr *C.char + //nolint:gocritic,nlreturn // linters seemingly trip at some C calls.. + status := C.fuel_get_latest_values(s.Client, idsPtr, idsLen, &outValuesJSONPtr, &outErrorPtr) + + err := handleFuelClientStatus(status) + if err != nil { + if outErrorPtr != nil { + errorStr := C.GoString(outErrorPtr) + C.fuel_free_string(outErrorPtr) + + return nil, fmt.Errorf("failed to get latest values: %w: %s", err, errorStr) + } + + return nil, fmt.Errorf("failed to get latest values: %w", err) + } + + valuesJSON := C.GoString(outValuesJSONPtr) + C.fuel_free_string(outValuesJSONPtr) + + var results []TemporalNumericValue + err = json.Unmarshal([]byte(valuesJSON), &results) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal values JSON: %w", err) + } + + return results, nil +} + func (s *StorkContract) GetWalletBalance() (uint64, error) { var balance C.uint64_t var outErrorPtr *C.char diff --git a/apps/chain_pusher/pkg/fuel/interactor.go b/apps/chain_pusher/pkg/fuel/interactor.go index e50fbb3a..75d3d651 100644 --- a/apps/chain_pusher/pkg/fuel/interactor.go +++ b/apps/chain_pusher/pkg/fuel/interactor.go @@ -7,7 +7,6 @@ import ( "fmt" "math/big" "strconv" - "strings" "github.com/Stork-Oracle/stork-external/apps/chain_pusher/pkg/fuel/bindings" "github.com/Stork-Oracle/stork-external/apps/chain_pusher/pkg/pusher" @@ -79,7 +78,13 @@ func (fci *ContractInteractor) PullValues( ) (map[types.InternalEncodedAssetID]types.InternalTemporalNumericValue, error) { result := make(map[types.InternalEncodedAssetID]types.InternalTemporalNumericValue) - var failedToGetLatestValueErr error + if len(encodedAssetIDs) == 0 { + return result, nil + } + + // Convert all asset IDs to [32]byte format + ids := make([][32]byte, 0, len(encodedAssetIDs)) + validAssetIDs := make([]types.InternalEncodedAssetID, 0, len(encodedAssetIDs)) for _, assetID := range encodedAssetIDs { // Convert asset ID to hex string @@ -89,7 +94,6 @@ func (fci *ContractInteractor) PullValues( idBytes, err := hex.DecodeString(idHex) if err != nil { fci.logger.Error().Err(err).Str("asset_id", idHex).Msg("Failed to decode asset ID") - continue } @@ -97,40 +101,36 @@ func (fci *ContractInteractor) PullValues( //nolint:mnd // 32 bytes is the expected length for a Fuel asset ID. if len(idBytes) != 32 { fci.logger.Error().Str("asset_id", idHex).Msg("Asset ID must be 32 bytes") - continue } - // Call FFI function - valueJSON, err := fci.contract.GetTemporalNumericValueUncheckedV1([32]byte(idBytes)) - if err != nil { - if strings.Contains(err.Error(), "feed not found") { - fci.logger.Warn().Err(err).Str("asset_id", idHex).Msg("No value found") - } else { - fci.logger.Warn().Err(err).Str("asset_id", idHex).Msg("Failed to get temporal numeric value") - failedToGetLatestValueErr = err - } + ids = append(ids, [32]byte(idBytes)) + validAssetIDs = append(validAssetIDs, assetID) + } - continue - } + if len(ids) == 0 { + return result, nil + } - // Convert to internal format + // Call batch FFI function + values, err := fci.contract.GetTemporalNumericValuesUncheckedV1(ids) + if err != nil { + return result, fmt.Errorf("failed to get temporal numeric values: %w", err) + } - internalValue := types.InternalTemporalNumericValue{ - TimestampNs: valueJSON.TimestampNs, - QuantizedValue: valueJSON.QuantizedValue, + // Map results back to asset IDs + for i, value := range values { + if i >= len(validAssetIDs) { + fci.logger.Warn().Int("index", i).Msg("Received more values than requested") + break } - result[assetID] = internalValue - } - - if failedToGetLatestValueErr != nil { - err := fmt.Errorf( - "failed to pull at least one value from the contract. Last error: %w", - failedToGetLatestValueErr, - ) + internalValue := types.InternalTemporalNumericValue{ + TimestampNs: value.TimestampNs, + QuantizedValue: value.QuantizedValue, + } - return result, err + result[validAssetIDs[i]] = internalValue } return result, nil diff --git a/chains/fuel/contracts/stork/artifacts/stork-abi.json b/chains/fuel/contracts/stork/artifacts/stork-abi.json index 1cf527cd..48373eee 100644 --- a/chains/fuel/contracts/stork/artifacts/stork-abi.json +++ b/chains/fuel/contracts/stork/artifacts/stork-abi.json @@ -54,6 +54,14 @@ "concreteTypeId": "9a7f1d3e963c10e0a4ea70a8e20a4813d1dc5682e28f74cb102ae50d32f7f98c", "metadataTypeId": 13 }, + { + "type": "struct std::vec::Vec", + "concreteTypeId": "32559685d0c9845f059bf9d472a0a38cf77d36c23dfcffe5489e86a65cdd9198", + "metadataTypeId": 16, + "typeArguments": [ + "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + ] + }, { "type": "struct std::vec::Vec", "concreteTypeId": "e67278f564f3da524afebc87950681dff66e11946370df7f4c68b5f01329590b", @@ -62,6 +70,14 @@ "672654baba0e998dd82f818c92c2b544c9275ee09007b0f65f59195a94a916d6" ] }, + { + "type": "struct std::vec::Vec", + "concreteTypeId": "872137d0d4d329e1a5711b34dd5a31a31277c08d54727a431ddc614863a15eda", + "metadataTypeId": 16, + "typeArguments": [ + "6972e006137b782c482ffc099e21cc55fce9151a2096dd6582df22c9dc81bd9c" + ] + }, { "type": "struct std::vm::evm::evm_address::EvmAddress", "concreteTypeId": "05a44d8c3e00faf7ed545823b7a2b32723545d8715d87a0ab3cf65904948e8d2", @@ -406,6 +422,24 @@ } ] }, + { + "name": "get_temporal_numeric_values_unchecked_v1", + "inputs": [ + { + "name": "ids", + "concreteTypeId": "32559685d0c9845f059bf9d472a0a38cf77d36c23dfcffe5489e86a65cdd9198" + } + ], + "output": "872137d0d4d329e1a5711b34dd5a31a31277c08d54727a431ddc614863a15eda", + "attributes": [ + { + "name": "storage", + "arguments": [ + "read" + ] + } + ] + }, { "name": "get_update_fee_v1", "inputs": [ @@ -648,6 +682,16 @@ "msg": null }, "18446744069414584321": { + "pos": { + "pkg": "stork", + "file": "src/main.sw", + "line": 253, + "column": 21 + }, + "logId": "5142315946124958513", + "msg": null + }, + "18446744069414584322": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -657,7 +701,7 @@ "logId": "5142315946124958513", "msg": null }, - "18446744069414584322": { + "18446744069414584323": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -667,7 +711,7 @@ "logId": "5142315946124958513", "msg": null }, - "18446744069414584323": { + "18446744069414584324": { "pos": { "pkg": "stork", "file": "src/main.sw", @@ -677,7 +721,7 @@ "logId": "5142315946124958513", "msg": null }, - "18446744069414584324": { + "18446744069414584325": { "pos": { "pkg": "stork", "file": "src/main.sw", From 1cd695d68fdd57d67a23a7423a84cc328726f1a6 Mon Sep 17 00:00:00 2001 From: akawalsky Date: Tue, 6 Jan 2026 18:11:57 -0500 Subject: [PATCH 3/4] fix run-local --- mk/go.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mk/go.mk b/mk/go.mk index 8d0a498f..4cf926b9 100644 --- a/mk/go.mk +++ b/mk/go.mk @@ -80,7 +80,7 @@ clean: clean-rust clean-misc # pass in a target to run-local to run a specific binary run-local: signer_ffi fuel_ffi wasmvm - @$(GO) run ./apps/$(target)/cmd $(args) + @$(GO) run ./apps/$(target) $(args) # Lint Go code using golangci-lint .PHONY: lint-go From efb4d23695cc8a6d69a2f1d55e75e55900cd120f Mon Sep 17 00:00:00 2001 From: akawalsky Date: Mon, 2 Mar 2026 16:14:40 -0500 Subject: [PATCH 4/4] remove bindings changes --- .../fuel/bindings/fuel_ffi/src/fuel_client.rs | 33 ---------- .../pkg/fuel/bindings/fuel_ffi/src/lib.rs | 61 ------------------- .../pkg/fuel/bindings/stork_fuel_contract.go | 39 ------------ apps/chain_pusher/pkg/fuel/interactor.go | 58 +++++++++--------- 4 files changed, 29 insertions(+), 162 deletions(-) diff --git a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs index 35b8bf68..f31a0d92 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs +++ b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/fuel_client.rs @@ -127,39 +127,6 @@ impl FuelClient { Ok(Some(tnv)) } - pub async fn get_latest_temporal_numeric_values( - &self, - ids: Vec<[u8; 32]>, - ) -> Result, FuelClientError> { - let ids_bits256: Vec = ids.into_iter().map(Bits256).collect(); - - let response = self - .proxy_contract - .methods() - .get_temporal_numeric_values_unchecked_v1(ids_bits256) - .determine_missing_contracts() - .await - .map_err(|e| { - FuelClientError::ContractCallFailed(format!( - "Failed to determine missing contracts: {e}" - )) - })? - .simulate(Execution::state_read_only()) - .await - .map_err(|e| process_contract_error(e, &self.proxy_contract.log_decoder()))?; - - let values = response - .value - .into_iter() - .map(|contract_tnv| FuelTemporalNumericValue { - timestamp_ns: contract_tnv.timestamp_ns, - quantized_value: contract_tnv.quantized_value.into(), - }) - .collect(); - - Ok(values) - } - pub async fn update_temporal_numeric_values( &self, inputs: Vec, diff --git a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs index c90631c9..2a52b331 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs +++ b/apps/chain_pusher/pkg/fuel/bindings/fuel_ffi/src/lib.rs @@ -142,67 +142,6 @@ pub unsafe extern "C" fn fuel_get_latest_value( ) } -/// # Safety -/// -/// - `client` must be a valid pointer to a FuelClient created by `fuel_client_new` -/// - `ids_ptr` must be a valid pointer to an array of 32-byte arrays -/// - `ids_len` must be the correct length of the ids array -/// - `out_values_json` must be a valid, writable pointer -/// - `out_error` must be a valid, writable pointer -/// - The caller is responsible for ensuring the pointers remain valid for the duration of the call -#[no_mangle] -pub unsafe extern "C" fn fuel_get_latest_values( - client: *mut FuelClient, - ids_ptr: *const [u8; 32], - ids_len: usize, - out_values_json: *mut *mut c_char, - out_error: *mut *mut c_char, -) -> FuelClientStatus { - if out_values_json.is_null() { - return FuelClientError::NullPointer("out_values_json is null".to_string()).into(); - } - if client.is_null() { - return FuelClientError::NullPointer("client is null".to_string()).into(); - } - if ids_ptr.is_null() { - return FuelClientError::NullPointer("ids_ptr is null".to_string()).into(); - } - - // Initialize output to null - unsafe { - *out_values_json = std::ptr::null_mut(); - } - - let result = (|| -> Result { - let client = unsafe { &*client }; - - // Convert pointer to slice of ids - let ids_slice = unsafe { std::slice::from_raw_parts(ids_ptr, ids_len) }; - let ids: Vec<[u8; 32]> = ids_slice.to_vec(); - - let values = client.rt.block_on(async { - tokio::time::timeout(TIMEOUT, client.get_latest_temporal_numeric_values(ids)) - .await - .map_err(|_| FuelClientError::Timeout(format!("Timed out after {TIMEOUT:?}")))? - })?; - - let json_str = serde_json::to_string(&values)?; - Ok(json_str) - })(); - - handle_ffi_result( - result, - |json_str| { - let c_str = string_to_c_char(json_str)?; - unsafe { - *out_values_json = c_str; - } - Ok(()) - }, - out_error, - ) -} - /// # Safety /// /// - `client` must be a valid pointer to a FuelClient created by `fuel_client_new` diff --git a/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go b/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go index 91ba9db2..8364b804 100644 --- a/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go +++ b/apps/chain_pusher/pkg/fuel/bindings/stork_fuel_contract.go @@ -11,7 +11,6 @@ package bindings #include "fuel_ffi.h" #include #include -#include */ import "C" @@ -224,44 +223,6 @@ func (s *StorkContract) GetTemporalNumericValueUncheckedV1(id [32]byte) (*Tempor return &result, nil } -func (s *StorkContract) GetTemporalNumericValuesUncheckedV1(ids [][32]byte) ([]TemporalNumericValue, error) { - if len(ids) == 0 { - return []TemporalNumericValue{}, nil - } - - // Convert [][32]byte to *C.uint8_t - idsPtr := (*[32]C.uint8_t)(unsafe.Pointer(&ids[0])) - idsLen := C.size_t(len(ids)) - - var outValuesJSONPtr *C.char - var outErrorPtr *C.char - //nolint:gocritic,nlreturn // linters seemingly trip at some C calls.. - status := C.fuel_get_latest_values(s.Client, idsPtr, idsLen, &outValuesJSONPtr, &outErrorPtr) - - err := handleFuelClientStatus(status) - if err != nil { - if outErrorPtr != nil { - errorStr := C.GoString(outErrorPtr) - C.fuel_free_string(outErrorPtr) - - return nil, fmt.Errorf("failed to get latest values: %w: %s", err, errorStr) - } - - return nil, fmt.Errorf("failed to get latest values: %w", err) - } - - valuesJSON := C.GoString(outValuesJSONPtr) - C.fuel_free_string(outValuesJSONPtr) - - var results []TemporalNumericValue - err = json.Unmarshal([]byte(valuesJSON), &results) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal values JSON: %w", err) - } - - return results, nil -} - func (s *StorkContract) GetWalletBalance() (uint64, error) { var balance C.uint64_t var outErrorPtr *C.char diff --git a/apps/chain_pusher/pkg/fuel/interactor.go b/apps/chain_pusher/pkg/fuel/interactor.go index 75d3d651..d7944a1c 100644 --- a/apps/chain_pusher/pkg/fuel/interactor.go +++ b/apps/chain_pusher/pkg/fuel/interactor.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" "strconv" + "strings" "github.com/Stork-Oracle/stork-external/apps/chain_pusher/pkg/fuel/bindings" "github.com/Stork-Oracle/stork-external/apps/chain_pusher/pkg/pusher" @@ -78,13 +79,7 @@ func (fci *ContractInteractor) PullValues( ) (map[types.InternalEncodedAssetID]types.InternalTemporalNumericValue, error) { result := make(map[types.InternalEncodedAssetID]types.InternalTemporalNumericValue) - if len(encodedAssetIDs) == 0 { - return result, nil - } - - // Convert all asset IDs to [32]byte format - ids := make([][32]byte, 0, len(encodedAssetIDs)) - validAssetIDs := make([]types.InternalEncodedAssetID, 0, len(encodedAssetIDs)) + var failedToGetLatestValueErr error for _, assetID := range encodedAssetIDs { // Convert asset ID to hex string @@ -94,6 +89,7 @@ func (fci *ContractInteractor) PullValues( idBytes, err := hex.DecodeString(idHex) if err != nil { fci.logger.Error().Err(err).Str("asset_id", idHex).Msg("Failed to decode asset ID") + continue } @@ -101,36 +97,40 @@ func (fci *ContractInteractor) PullValues( //nolint:mnd // 32 bytes is the expected length for a Fuel asset ID. if len(idBytes) != 32 { fci.logger.Error().Str("asset_id", idHex).Msg("Asset ID must be 32 bytes") + continue } - ids = append(ids, [32]byte(idBytes)) - validAssetIDs = append(validAssetIDs, assetID) - } - - if len(ids) == 0 { - return result, nil - } - - // Call batch FFI function - values, err := fci.contract.GetTemporalNumericValuesUncheckedV1(ids) - if err != nil { - return result, fmt.Errorf("failed to get temporal numeric values: %w", err) - } + // Call FFI function + valueJSON, err := fci.contract.GetTemporalNumericValueUncheckedV1([32]byte(idBytes)) + if err != nil { + if strings.Contains(err.Error(), "feed not found") { + fci.logger.Warn().Err(err).Str("asset_id", idHex).Msg("No value found") + } else { + fci.logger.Warn().Err(err).Str("asset_id", idHex).Msg("Failed to get temporal numeric value") + failedToGetLatestValueErr = err + } - // Map results back to asset IDs - for i, value := range values { - if i >= len(validAssetIDs) { - fci.logger.Warn().Int("index", i).Msg("Received more values than requested") - break + continue } + // Convert to internal format + internalValue := types.InternalTemporalNumericValue{ - TimestampNs: value.TimestampNs, - QuantizedValue: value.QuantizedValue, + TimestampNs: valueJSON.TimestampNs, + QuantizedValue: valueJSON.QuantizedValue, } - result[validAssetIDs[i]] = internalValue + result[assetID] = internalValue + } + + if failedToGetLatestValueErr != nil { + err := fmt.Errorf( + "failed to pull at least one value from the contract. Last error: %w", + failedToGetLatestValueErr, + ) + + return result, err } return result, nil @@ -169,7 +169,7 @@ func (fci *ContractInteractor) BatchPushToContract( return fmt.Errorf("failed to update values on fuel contract: %w", err) } - fci.logger.Info(). + fci.logger.Debug(). Str("tx_hash", txHash). Int("num_updates", len(priceUpdates)). Msg("Successfully pushed updates to Fuel contract")