Skip to content

Commit 4cbb302

Browse files
authored
feat: Permitted Payment Assets (#45)
* feat: Permitted Payment Assets * feat: add tests
1 parent 7ce2100 commit 4cbb302

File tree

4 files changed

+1036
-2
lines changed

4 files changed

+1036
-2
lines changed

src/BlueprintServiceManagerBase.sol

Lines changed: 249 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.20;
33

4+
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
5+
46
import "src/Permissions.sol";
57
import "src/IBlueprintServiceManager.sol";
68

@@ -13,12 +15,22 @@ import "src/IBlueprintServiceManager.sol";
1315
/// Each function serves as a hook for different lifecycle events, and reverting any
1416
/// of these functions interrupts the process flow.
1517
contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabled {
18+
using EnumerableSet for EnumerableSet.AddressSet;
19+
1620
/// @dev The Current Blueprint Id
1721
uint256 public currentBlueprintId;
1822

1923
/// @dev The address of the owner of the blueprint
2024
address public blueprintOwner;
2125

26+
/// @dev a mapping between service id and permitted payment assets.
27+
/// @dev serviceId => EnumerableSet of permitted payment assets.
28+
/// @notice This mapping is used to store the permitted payment assets for each service.
29+
mapping(uint64 => EnumerableSet.AddressSet) private _permittedPaymentAssets;
30+
31+
/// @dev The supplied address is not a valid asset address, it does not start with 0xFFFFFFFF.
32+
error InvalidAssetId(address assetAddress);
33+
2234
/// @inheritdoc IBlueprintServiceManager
2335
function onBlueprintCreated(uint64 blueprintId, address owner, address mbsm) external virtual onlyFromRootChain {
2436
currentBlueprintId = blueprintId;
@@ -152,7 +164,243 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
152164
}
153165

154166
/// @inheritdoc IBlueprintServiceManager
155-
function queryDeveloperPaymentAddress(uint64) external view virtual returns (address developerPaymentAddress) {
167+
function queryDeveloperPaymentAddress(uint64)
168+
external
169+
view
170+
virtual
171+
returns (address payable developerPaymentAddress)
172+
{
156173
return payable(blueprintOwner);
157174
}
175+
176+
/// @inheritdoc IBlueprintServiceManager
177+
function queryIsPaymentAssetAllowed(
178+
uint64 serviceId,
179+
ServiceOperators.Asset calldata asset
180+
)
181+
external
182+
view
183+
virtual
184+
returns (bool isAllowed)
185+
{
186+
return _isAssetPermitted(serviceId, asset);
187+
}
188+
189+
/**
190+
* @notice Permits a specific asset for a given service.
191+
* @dev Adds the asset to the set of permitted payment assets based on its kind.
192+
* @param serviceId The ID of the service for which the asset is being permitted.
193+
* @param asset The asset to be permitted, defined by its kind and data.
194+
*/
195+
function _permitAsset(
196+
uint64 serviceId,
197+
ServiceOperators.Asset calldata asset
198+
)
199+
internal
200+
virtual
201+
returns (bool added)
202+
{
203+
if (asset.kind == ServiceOperators.AssetKind.Erc20) {
204+
address assetAddress = address(uint160(uint256(asset.data)));
205+
bool _added = _permittedPaymentAssets[serviceId].add(assetAddress);
206+
return _added;
207+
} else if (asset.kind == ServiceOperators.AssetKind.Custom) {
208+
address assetAddress = _assetIdToAddress(asset.data);
209+
bool _added = _permittedPaymentAssets[serviceId].add(assetAddress);
210+
return _added;
211+
} else {
212+
return false;
213+
}
214+
}
215+
216+
/**
217+
* @notice Revokes a previously permitted asset for a given service.
218+
* @dev Removes the asset from the set of permitted payment assets based on its kind.
219+
* @param serviceId The ID of the service for which the asset is being revoked.
220+
* @param asset The asset to be revoked, defined by its kind and data.
221+
*/
222+
function _revokeAsset(
223+
uint64 serviceId,
224+
ServiceOperators.Asset calldata asset
225+
)
226+
internal
227+
virtual
228+
returns (bool removed)
229+
{
230+
if (asset.kind == ServiceOperators.AssetKind.Erc20) {
231+
address assetAddress = address(uint160(uint256(asset.data)));
232+
bool _removed = _permittedPaymentAssets[serviceId].remove(assetAddress);
233+
return _removed;
234+
} else if (asset.kind == ServiceOperators.AssetKind.Custom) {
235+
address assetAddress = _assetIdToAddress(asset.data);
236+
bool _removed = _permittedPaymentAssets[serviceId].remove(assetAddress);
237+
return _removed;
238+
} else {
239+
return false;
240+
}
241+
}
242+
243+
/**
244+
* @notice Clears all permitted assets for a given service.
245+
* @dev Iterates through the set of permitted assets and removes each one.
246+
* @param serviceId The ID of the service for which permitted assets are being cleared.
247+
*/
248+
function _clearPermittedAssets(uint64 serviceId) internal virtual returns (bool cleared) {
249+
EnumerableSet.AddressSet storage permittedAssets = _permittedPaymentAssets[serviceId];
250+
uint256 length = permittedAssets.length();
251+
while (length > 0) {
252+
address assetAddress = permittedAssets.at(0);
253+
permittedAssets.remove(assetAddress);
254+
length = permittedAssets.length();
255+
}
256+
257+
// The set should be empty after clearing all permitted assets.
258+
return permittedAssets.length() == 0;
259+
}
260+
261+
/**
262+
* @notice Retrieves all permitted assets for a given service as an array of addresses.
263+
* @dev Converts the EnumerableSet of permitted assets to a dynamic array of addresses.
264+
* @param serviceId The ID of the service for which permitted assets are being retrieved.
265+
* @return assets An array of addresses representing the permitted assets.
266+
*/
267+
function _getPermittedAssetsAsAddresses(uint64 serviceId) internal view virtual returns (address[] memory) {
268+
EnumerableSet.AddressSet storage permittedAssets = _permittedPaymentAssets[serviceId];
269+
address[] memory assets = new address[](permittedAssets.length());
270+
for (uint256 i = 0; i < permittedAssets.length(); i++) {
271+
assets[i] = permittedAssets.at(i);
272+
}
273+
return assets;
274+
}
275+
276+
/**
277+
* @notice Retrieves all permitted assets for a given service as an array of Asset structs.
278+
* @dev Converts the EnumerableSet of permitted assets to a dynamic array of ServiceOperators.Asset.
279+
* @param serviceId The ID of the service for which permitted assets are being retrieved.
280+
* @return assets An array of ServiceOperators.Asset structs representing the permitted assets.
281+
*/
282+
function _getPermittedAssets(uint64 serviceId) internal view virtual returns (ServiceOperators.Asset[] memory) {
283+
EnumerableSet.AddressSet storage permittedAssets = _permittedPaymentAssets[serviceId];
284+
ServiceOperators.Asset[] memory assets = new ServiceOperators.Asset[](permittedAssets.length());
285+
for (uint256 i = 0; i < permittedAssets.length(); i++) {
286+
address assetAddress = permittedAssets.at(i);
287+
if (assetAddress == address(0)) {
288+
continue;
289+
}
290+
ServiceOperators.AssetKind kind;
291+
bytes32 data;
292+
if (_checkAddressIsAssetIdCompatible(assetAddress)) {
293+
kind = ServiceOperators.AssetKind.Custom;
294+
data = _addressToAssetId(assetAddress);
295+
} else {
296+
kind = ServiceOperators.AssetKind.Erc20;
297+
data = bytes32(uint256(uint160(assetAddress)));
298+
}
299+
assets[i] = ServiceOperators.Asset(kind, data);
300+
}
301+
return assets;
302+
}
303+
304+
/**
305+
* @notice Checks if a specific asset is permitted for a given service.
306+
* @dev Determines if the asset is contained within the set of permitted payment assets based on its kind.
307+
* @param serviceId The ID of the service to check.
308+
* @param asset The asset to check, defined by its kind and data.
309+
* @return isAllowed Boolean indicating whether the asset is permitted.
310+
*/
311+
function _isAssetPermitted(
312+
uint64 serviceId,
313+
ServiceOperators.Asset calldata asset
314+
)
315+
internal
316+
view
317+
virtual
318+
returns (bool)
319+
{
320+
// Native assets are always permitted.
321+
if (_isNativeAsset(asset)) {
322+
return true;
323+
} else if (asset.kind == ServiceOperators.AssetKind.Erc20) {
324+
address assetAddress = address(uint160(uint256(asset.data)));
325+
return _permittedPaymentAssets[serviceId].contains(assetAddress);
326+
} else if (asset.kind == ServiceOperators.AssetKind.Custom) {
327+
address assetAddress = _assetIdToAddress(asset.data);
328+
return _permittedPaymentAssets[serviceId].contains(assetAddress);
329+
} else {
330+
return false;
331+
}
332+
}
333+
334+
/**
335+
* @notice Converts a given asset ID to its corresponding address representation.
336+
* @dev The conversion follows the pattern: 0xFFFFFFFF followed by the 16-byte asset ID.
337+
*
338+
* @param assetId The bytes32 asset ID to be converted.
339+
* @return The address representation of the asset ID.
340+
*/
341+
function _assetIdToAddress(bytes32 assetId) internal pure returns (address) {
342+
// Construct the address by combining the prefix 0xFFFFFFFF00000000000000000000000000000000
343+
// with the lower 16 bytes of the assetId.
344+
// This ensures the address follows the designated asset address format.
345+
return address(uint160(uint256(0xFFFFFFFF << 128) | uint256(assetId)));
346+
}
347+
348+
/**
349+
* @notice Converts an asset address back to its original asset ID.
350+
* @dev Validates that the address starts with the prefix 0xFFFFFFFF and extracts the 16-byte asset ID.
351+
*
352+
* @param assetAddress The address to be converted back to an asset ID.
353+
* @return The bytes32 representation of the original asset ID.
354+
*/
355+
function _addressToAssetId(address assetAddress) internal pure returns (bytes32) {
356+
// Convert the address to a uint256 for bit manipulation.
357+
uint256 addr = uint256(uint160(assetAddress));
358+
359+
// Ensure the upper 128 bits match the expected prefix 0xFFFFFFFF.
360+
if (!_checkAddressIsAssetIdCompatible(assetAddress)) {
361+
revert InvalidAssetId(assetAddress);
362+
}
363+
364+
// Extract the lower 128 bits which represent the original asset ID.
365+
uint128 assetIdUint = uint128(addr);
366+
367+
// Convert the uint128 asset ID back to bytes32 format.
368+
return bytes32(uint256(assetIdUint));
369+
}
370+
371+
/**
372+
* @notice Checks if the given asset address is compatible by verifying it starts with the prefix 0xFFFFFFFF.
373+
* @dev This function converts the asset address to a uint256 and ensures the upper 128 bits match 0xFFFFFFFF.
374+
* @param assetAddress The address of the asset to check for compatibility.
375+
* @return bool Returns true if the asset address is compatible, false otherwise.
376+
*/
377+
function _checkAddressIsAssetIdCompatible(address assetAddress) internal pure returns (bool) {
378+
// Convert the address to a uint256 for bit manipulation.
379+
uint256 addr = uint256(uint160(assetAddress));
380+
381+
// Ensure the upper 128 bits match the expected prefix 0xFFFFFFFF.
382+
if ((addr >> 128) != 0xFFFFFFFF) {
383+
return false;
384+
}
385+
386+
return true;
387+
}
388+
389+
/**
390+
* @notice Determines if the provided asset is a native asset.
391+
* @dev This function checks the asset kind and verifies if the asset address or ID corresponds to a native asset.
392+
* @param asset The asset to be checked, defined by its kind and data.
393+
* @return bool Returns true if the asset is native, false otherwise.
394+
*/
395+
function _isNativeAsset(ServiceOperators.Asset calldata asset) internal pure returns (bool) {
396+
if (asset.kind == ServiceOperators.AssetKind.Erc20) {
397+
address assetAddress = address(uint160(uint256(asset.data)));
398+
return (assetAddress == address(0));
399+
} else if (asset.kind == ServiceOperators.AssetKind.Custom) {
400+
uint256 assetId = uint256(asset.data);
401+
return (assetId == 0);
402+
} else {
403+
return false;
404+
}
405+
}
158406
}

src/IBlueprintServiceManager.sol

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,20 @@ interface IBlueprintServiceManager {
220220
/// @notice This function should be implemented by the Blueprint Service Manager contract.
221221
/// @param serviceId The ID of the service.
222222
/// @return developerPaymentAddress The address of the developer payment address for that service
223-
function queryDeveloperPaymentAddress(uint64 serviceId) external view returns (address developerPaymentAddress);
223+
function queryDeveloperPaymentAddress(uint64 serviceId)
224+
external
225+
view
226+
returns (address payable developerPaymentAddress);
227+
228+
/// @dev Determines if a specified payment asset is permitted for a given service.
229+
/// @param serviceId The ID of the service to check against.
230+
/// @param asset The asset to verify for allowance.
231+
/// @return isAllowed Returns true if the asset is allowed, false otherwise.
232+
function queryIsPaymentAssetAllowed(
233+
uint64 serviceId,
234+
ServiceOperators.Asset calldata asset
235+
)
236+
external
237+
view
238+
returns (bool isAllowed);
224239
}

0 commit comments

Comments
 (0)