Skip to content

Commit 3d42886

Browse files
feat(ServiceProviderRegistry): add getProvidersByIds batch lookup function (#251)
## Summary Implements `getProvidersByIds` function to enable batch retrieval of provider information by an array of IDs, addressing the need to efficiently query multiple providers after getting approved provider IDs from WASM storage. ## Changes - **New function**: `getProvidersByIds(uint256[] calldata providerIds)` - **Returns**: Array of `ServiceProviderInfoView` structs and corresponding validity flags - **Handles invalid IDs**: Returns empty structs with `validIds[i] = false` for non-existent/inactive providers - **Gas optimized**: Caches storage reads and uses efficient storage references - **Helper function**: `_getEmptyProviderInfoView()` for consistent empty struct creation ## Use Case Enables the workflow described in [issue #232](#232): 1. Call WASM storage `getApprovedProviders` to get list of provider IDs 2. Use same list to batch query service registry for provider info 3. Process results with validity flags to handle any invalid IDs ## Testing - ✅ Comprehensive test coverage for success cases, invalid IDs, and edge cases - ✅ All existing tests pass ```solidity function getProvidersByIds(uint256[] calldata providerIds) external view returns (ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) ``` Closes #232
1 parent 18ea826 commit 3d42886

File tree

3 files changed

+320
-10
lines changed

3 files changed

+320
-10
lines changed

service_contracts/abi/ServiceProviderRegistry.abi.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,69 @@
843843
],
844844
"stateMutability": "view"
845845
},
846+
{
847+
"type": "function",
848+
"name": "getProvidersByIds",
849+
"inputs": [
850+
{
851+
"name": "providerIds",
852+
"type": "uint256[]",
853+
"internalType": "uint256[]"
854+
}
855+
],
856+
"outputs": [
857+
{
858+
"name": "providerInfos",
859+
"type": "tuple[]",
860+
"internalType": "struct ServiceProviderRegistry.ServiceProviderInfoView[]",
861+
"components": [
862+
{
863+
"name": "providerId",
864+
"type": "uint256",
865+
"internalType": "uint256"
866+
},
867+
{
868+
"name": "info",
869+
"type": "tuple",
870+
"internalType": "struct ServiceProviderRegistryStorage.ServiceProviderInfo",
871+
"components": [
872+
{
873+
"name": "serviceProvider",
874+
"type": "address",
875+
"internalType": "address"
876+
},
877+
{
878+
"name": "payee",
879+
"type": "address",
880+
"internalType": "address"
881+
},
882+
{
883+
"name": "name",
884+
"type": "string",
885+
"internalType": "string"
886+
},
887+
{
888+
"name": "description",
889+
"type": "string",
890+
"internalType": "string"
891+
},
892+
{
893+
"name": "isActive",
894+
"type": "bool",
895+
"internalType": "bool"
896+
}
897+
]
898+
}
899+
]
900+
},
901+
{
902+
"name": "validIds",
903+
"type": "bool[]",
904+
"internalType": "bool[]"
905+
}
906+
],
907+
"stateMutability": "view"
908+
},
846909
{
847910
"type": "function",
848911
"name": "getProvidersByProductType",

service_contracts/src/ServiceProviderRegistry.sol

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -647,16 +647,7 @@ contract ServiceProviderRegistry is
647647
{
648648
uint256 providerId = addressToProviderId[providerAddress];
649649
if (providerId == 0) {
650-
return ServiceProviderInfoView({
651-
providerId: 0,
652-
info: ServiceProviderInfo({
653-
serviceProvider: address(0),
654-
payee: address(0),
655-
name: "",
656-
description: "",
657-
isActive: false
658-
})
659-
});
650+
return _getEmptyProviderInfoView();
660651
}
661652

662653
ServiceProviderInfo storage provider = providers[providerId];
@@ -715,6 +706,56 @@ contract ServiceProviderRegistry is
715706
}
716707
}
717708

709+
/// @notice Get multiple providers by their IDs
710+
/// @param providerIds Array of provider IDs to retrieve
711+
/// @return providerInfos Array of provider information corresponding to the input IDs
712+
/// @return validIds Array of booleans indicating whether each ID is valid (exists and is active)
713+
/// @dev Returns empty ServiceProviderInfoView structs for invalid IDs, with corresponding validIds[i] = false
714+
function getProvidersByIds(uint256[] calldata providerIds)
715+
external
716+
view
717+
returns (ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds)
718+
{
719+
uint256 length = providerIds.length;
720+
providerInfos = new ServiceProviderInfoView[](length);
721+
validIds = new bool[](length);
722+
723+
uint256 _numProviders = numProviders;
724+
725+
for (uint256 i = 0; i < length; i++) {
726+
uint256 providerId = providerIds[i];
727+
728+
if (providerId > 0 && providerId <= _numProviders) {
729+
ServiceProviderInfo storage provider = providers[providerId];
730+
if (provider.serviceProvider != address(0) && provider.isActive) {
731+
providerInfos[i] = ServiceProviderInfoView({providerId: providerId, info: provider});
732+
validIds[i] = true;
733+
} else {
734+
providerInfos[i] = _getEmptyProviderInfoView();
735+
validIds[i] = false;
736+
}
737+
} else {
738+
providerInfos[i] = _getEmptyProviderInfoView();
739+
validIds[i] = false;
740+
}
741+
}
742+
}
743+
744+
/// @notice Internal helper to create an empty ServiceProviderInfoView
745+
/// @return Empty ServiceProviderInfoView struct
746+
function _getEmptyProviderInfoView() internal pure returns (ServiceProviderInfoView memory) {
747+
return ServiceProviderInfoView({
748+
providerId: 0,
749+
info: ServiceProviderInfo({
750+
serviceProvider: address(0),
751+
payee: address(0),
752+
name: "",
753+
description: "",
754+
isActive: false
755+
})
756+
});
757+
}
758+
718759
/// @notice Get total number of registered providers (including inactive)
719760
/// @return The total count of providers
720761
function getProviderCount() external view returns (uint256) {

service_contracts/test/ServiceProviderRegistry.t.sol

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,210 @@ contract ServiceProviderRegistryTest is Test {
359359
vm.expectRevert("Provider does not exist");
360360
registry.getProviderPayee(1);
361361
}
362+
363+
// ========== Tests for getProvidersByIds ==========
364+
365+
function testGetProvidersByIdsEmptyArray() public {
366+
uint256[] memory emptyIds = new uint256[](0);
367+
368+
(ServiceProviderRegistry.ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) =
369+
registry.getProvidersByIds(emptyIds);
370+
371+
assertEq(providerInfos.length, 0, "Should return empty array for empty input");
372+
assertEq(validIds.length, 0, "Should return empty validIds array for empty input");
373+
}
374+
375+
function testGetProvidersByIdsSingleValidProvider() public {
376+
// Register a provider first
377+
vm.deal(user1, 10 ether);
378+
vm.prank(user1);
379+
uint256 providerId = registry.registerProvider{value: 5 ether}(
380+
user1,
381+
"Test Provider",
382+
"Test Description",
383+
ServiceProviderRegistryStorage.ProductType.PDP,
384+
_createValidPDPOffering(),
385+
new string[](0),
386+
new string[](0)
387+
);
388+
389+
uint256[] memory ids = new uint256[](1);
390+
ids[0] = providerId;
391+
392+
(ServiceProviderRegistry.ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) =
393+
registry.getProvidersByIds(ids);
394+
395+
assertEq(providerInfos.length, 1, "Should return one provider");
396+
assertEq(validIds.length, 1, "Should return one validity flag");
397+
assertTrue(validIds[0], "Provider should be valid");
398+
assertEq(providerInfos[0].providerId, providerId, "Provider ID should match");
399+
assertEq(providerInfos[0].info.serviceProvider, user1, "Service provider address should match");
400+
assertEq(providerInfos[0].info.name, "Test Provider", "Provider name should match");
401+
assertEq(providerInfos[0].info.description, "Test Description", "Provider description should match");
402+
assertTrue(providerInfos[0].info.isActive, "Provider should be active");
403+
}
404+
405+
function testGetProvidersByIdsMultipleValidProviders() public {
406+
// Register multiple providers
407+
vm.deal(user1, 10 ether);
408+
vm.deal(user2, 10 ether);
409+
410+
vm.prank(user1);
411+
uint256 providerId1 = registry.registerProvider{value: 5 ether}(
412+
user1,
413+
"Provider 1",
414+
"Description 1",
415+
ServiceProviderRegistryStorage.ProductType.PDP,
416+
_createValidPDPOffering(),
417+
new string[](0),
418+
new string[](0)
419+
);
420+
421+
vm.prank(user2);
422+
uint256 providerId2 = registry.registerProvider{value: 5 ether}(
423+
user2,
424+
"Provider 2",
425+
"Description 2",
426+
ServiceProviderRegistryStorage.ProductType.PDP,
427+
_createValidPDPOffering(),
428+
new string[](0),
429+
new string[](0)
430+
);
431+
432+
uint256[] memory ids = new uint256[](2);
433+
ids[0] = providerId1;
434+
ids[1] = providerId2;
435+
436+
(ServiceProviderRegistry.ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) =
437+
registry.getProvidersByIds(ids);
438+
439+
assertEq(providerInfos.length, 2, "Should return two providers");
440+
assertEq(validIds.length, 2, "Should return two validity flags");
441+
442+
// Check first provider
443+
assertTrue(validIds[0], "First provider should be valid");
444+
assertEq(providerInfos[0].providerId, providerId1, "First provider ID should match");
445+
assertEq(providerInfos[0].info.serviceProvider, user1, "First provider address should match");
446+
assertEq(providerInfos[0].info.name, "Provider 1", "First provider name should match");
447+
448+
// Check second provider
449+
assertTrue(validIds[1], "Second provider should be valid");
450+
assertEq(providerInfos[1].providerId, providerId2, "Second provider ID should match");
451+
assertEq(providerInfos[1].info.serviceProvider, user2, "Second provider address should match");
452+
assertEq(providerInfos[1].info.name, "Provider 2", "Second provider name should match");
453+
}
454+
455+
function testGetProvidersByIdsInvalidIds() public {
456+
uint256[] memory ids = new uint256[](3);
457+
ids[0] = 0; // Invalid ID (0)
458+
ids[1] = 999; // Non-existent ID
459+
ids[2] = 1; // Valid ID but no provider registered yet
460+
461+
(ServiceProviderRegistry.ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) =
462+
registry.getProvidersByIds(ids);
463+
464+
assertEq(providerInfos.length, 3, "Should return three results");
465+
assertEq(validIds.length, 3, "Should return three validity flags");
466+
467+
// All should be invalid
468+
assertFalse(validIds[0], "ID 0 should be invalid");
469+
assertFalse(validIds[1], "Non-existent ID should be invalid");
470+
assertFalse(validIds[2], "Unregistered ID should be invalid");
471+
472+
// All should have empty structs
473+
for (uint256 i = 0; i < 3; i++) {
474+
assertEq(providerInfos[i].info.serviceProvider, address(0), "Invalid provider should have zero address");
475+
assertEq(providerInfos[i].providerId, 0, "Invalid provider should have zero ID");
476+
assertFalse(providerInfos[i].info.isActive, "Invalid provider should be inactive");
477+
}
478+
}
479+
480+
function testGetProvidersByIdsMixedValidAndInvalid() public {
481+
// Register one provider
482+
vm.deal(user1, 10 ether);
483+
vm.prank(user1);
484+
uint256 validProviderId = registry.registerProvider{value: 5 ether}(
485+
user1,
486+
"Valid Provider",
487+
"Valid Description",
488+
ServiceProviderRegistryStorage.ProductType.PDP,
489+
_createValidPDPOffering(),
490+
new string[](0),
491+
new string[](0)
492+
);
493+
494+
uint256[] memory ids = new uint256[](4);
495+
ids[0] = validProviderId; // Valid
496+
ids[1] = 0; // Invalid
497+
ids[2] = 999; // Invalid
498+
ids[3] = validProviderId; // Valid (duplicate)
499+
500+
(ServiceProviderRegistry.ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) =
501+
registry.getProvidersByIds(ids);
502+
503+
assertEq(providerInfos.length, 4, "Should return four results");
504+
assertEq(validIds.length, 4, "Should return four validity flags");
505+
506+
// Check valid providers
507+
assertTrue(validIds[0], "First provider should be valid");
508+
assertEq(providerInfos[0].providerId, validProviderId, "First provider ID should match");
509+
assertEq(providerInfos[0].info.serviceProvider, user1, "First provider address should match");
510+
511+
// Check invalid providers
512+
assertFalse(validIds[1], "Second provider should be invalid");
513+
assertFalse(validIds[2], "Third provider should be invalid");
514+
515+
// Check duplicate valid provider
516+
assertTrue(validIds[3], "Fourth provider should be valid");
517+
assertEq(providerInfos[3].providerId, validProviderId, "Fourth provider ID should match");
518+
assertEq(providerInfos[3].info.serviceProvider, user1, "Fourth provider address should match");
519+
}
520+
521+
function testGetProvidersByIdsInactiveProvider() public {
522+
// Register a provider
523+
vm.deal(user1, 10 ether);
524+
vm.prank(user1);
525+
uint256 providerId = registry.registerProvider{value: 5 ether}(
526+
user1,
527+
"Test Provider",
528+
"Test Description",
529+
ServiceProviderRegistryStorage.ProductType.PDP,
530+
_createValidPDPOffering(),
531+
new string[](0),
532+
new string[](0)
533+
);
534+
535+
// Remove the provider (make it inactive)
536+
vm.prank(user1);
537+
registry.removeProvider();
538+
539+
uint256[] memory ids = new uint256[](1);
540+
ids[0] = providerId;
541+
542+
(ServiceProviderRegistry.ServiceProviderInfoView[] memory providerInfos, bool[] memory validIds) =
543+
registry.getProvidersByIds(ids);
544+
545+
assertEq(providerInfos.length, 1, "Should return one result");
546+
assertEq(validIds.length, 1, "Should return one validity flag");
547+
assertFalse(validIds[0], "Inactive provider should be invalid");
548+
assertEq(providerInfos[0].info.serviceProvider, address(0), "Inactive provider should have zero address");
549+
assertEq(providerInfos[0].providerId, 0, "Inactive provider should have zero ID");
550+
assertFalse(providerInfos[0].info.isActive, "Inactive provider should be inactive");
551+
}
552+
553+
// Helper function to create a valid PDP offering for tests
554+
function _createValidPDPOffering() internal pure returns (bytes memory) {
555+
ServiceProviderRegistryStorage.PDPOffering memory pdpOffering = ServiceProviderRegistryStorage.PDPOffering({
556+
serviceURL: "https://example.com/api",
557+
minPieceSizeInBytes: 1024,
558+
maxPieceSizeInBytes: 1024 * 1024,
559+
ipniPiece: true,
560+
ipniIpfs: true,
561+
storagePricePerTibPerMonth: 1000,
562+
minProvingPeriodInEpochs: 1,
563+
location: "US",
564+
paymentTokenAddress: IERC20(address(0))
565+
});
566+
return abi.encode(pdpOffering);
567+
}
362568
}

0 commit comments

Comments
 (0)