Skip to content

Commit 1072c28

Browse files
authored
feat: Payment Split (#46)
1 parent 4cbb302 commit 1072c28

10 files changed

+1774
-317
lines changed

src/AssetsLib.sol

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
/// @title Assets Library
5+
/// @notice A library for handling asset types and conversions in a blockchain system
6+
/// @dev Provides utilities for managing different asset types and converting between asset IDs and addresses
7+
library Assets {
8+
/// @dev Represents different types of assets that can be used in the system
9+
enum Kind {
10+
/// @notice Custom Asset Id
11+
Custom,
12+
/// @notice Standard ERC20 token asset type
13+
Erc20
14+
}
15+
16+
/// @dev Represents an asset with its type and associated data
17+
struct Asset {
18+
/// @notice The kind/type of the asset (Custom or ERC20)
19+
Kind kind;
20+
/// @notice The data associated with the asset, encoded as bytes32
21+
/// @dev The data is encoded as follows:
22+
/// - For Custom assets: The asset ID is stored as uint256
23+
/// - For ERC20 assets: The token address is stored as address
24+
bytes32 data;
25+
}
26+
27+
/// @dev The asset kind is not supported by the library.
28+
error UnsupportedAssetKind(uint256 kind);
29+
30+
/// @dev The supplied address is not a valid asset address, it does not start with 0xFFFFFFFF.
31+
error InvalidAssetId(address assetAddress);
32+
33+
/// @notice Converts a given asset to its corresponding address representation.
34+
/// @dev The conversion follows the pattern: 0xFFFFFFFF followed by the 16-byte asset ID or if the asset is an ERC20
35+
/// token,
36+
/// the address of the token contract.
37+
///
38+
/// @param asset The asset be converted.
39+
/// @return The address representation of the asset.
40+
function toAddress(Asset memory asset) internal pure returns (address) {
41+
if (isErc20(asset)) {
42+
return address(uint160(uint256(asset.data)));
43+
} else if (isCustom(asset)) {
44+
return toAddress(asset.data);
45+
} else {
46+
revert UnsupportedAssetKind(uint256(asset.kind));
47+
}
48+
}
49+
50+
/// @notice Converts a given asset ID to its corresponding address representation.
51+
/// @dev The conversion follows the pattern: 0xFFFFFFFF followed by the 16-byte asset ID.
52+
///
53+
/// @param assetId The bytes32 asset ID to be converted.
54+
/// @return The address representation of the asset ID.
55+
function toAddress(bytes32 assetId) internal pure returns (address) {
56+
// Construct the address by combining the prefix 0xFFFFFFFF00000000000000000000000000000000
57+
// with the lower 16 bytes of the assetId.
58+
// This ensures the address follows the designated asset address format.
59+
return address(uint160(uint256(0xFFFFFFFF << 128) | uint256(assetId)));
60+
}
61+
62+
/// @notice Converts an asset address back to its original asset ID.
63+
/// @dev Validates that the address starts with the prefix 0xFFFFFFFF and extracts the 16-byte asset ID.
64+
///
65+
/// @param assetAddress The address to be converted back to an asset ID.
66+
/// @return The bytes32 representation of the original asset ID.
67+
function toAssetId(address assetAddress) internal pure returns (bytes32) {
68+
// Convert the address to a uint256 for bit manipulation.
69+
uint256 addr = uint256(uint160(assetAddress));
70+
71+
// Ensure the upper 128 bits match the expected prefix 0xFFFFFFFF.
72+
if (!isAssetIdCompatible(assetAddress)) {
73+
revert InvalidAssetId(assetAddress);
74+
}
75+
76+
// Extract the lower 128 bits which represent the original asset ID.
77+
uint128 assetIdUint = uint128(addr);
78+
79+
// Convert the uint128 asset ID back to bytes32 format.
80+
return bytes32(uint256(assetIdUint));
81+
}
82+
83+
/// @notice Converts an asset address to an asset representation.
84+
/// @dev Converts the asset address to an asset ID and constructs an asset object.
85+
/// @param assetAddress The address of the asset to convert.
86+
/// @return Asset Returns the asset representation of the provided address.
87+
function toAsset(address assetAddress) internal pure returns (Asset memory) {
88+
if (isAssetIdCompatible(assetAddress)) {
89+
return Asset(Kind.Custom, toAssetId(assetAddress));
90+
} else {
91+
return Asset(Kind.Erc20, bytes32(uint256(uint160(assetAddress))));
92+
}
93+
}
94+
95+
/// @notice Determines if the provided asset is an ERC20 token.
96+
/// @dev Checks if the asset kind matches the ERC20 enum value.
97+
/// @param asset The asset to check.
98+
/// @return bool Returns true if the asset is an ERC20 token, false otherwise.
99+
function isErc20(Asset memory asset) internal pure returns (bool) {
100+
return asset.kind == Kind.Erc20;
101+
}
102+
103+
/// @notice Checks i the given asset is a Cstom type.
104+
/// @dev Verifies if the asset's kid property mathes he Custom type.
105+
/// @param asset The asset to check, defned by its kind and data.
106+
/// @return bool Returns true if the asset is Custom, false otherwise.
107+
function isCustom(Asset memory asset) internal pure returns (bool) {
108+
return asset.kind == Kind.Custom;
109+
}
110+
111+
/// @notice Checks if the given asset address is compatible by verifying it starts with the prefix 0xFFFFFFFF.
112+
/// @dev This function converts the asset address to a uint256 and ensures the upper 128 bits match 0xFFFFFFFF.
113+
/// @param assetAddress The address of the asset to check for compatibility.
114+
/// @return bool Returns true if the asset address is compatible, false otherwise.
115+
function isAssetIdCompatible(address assetAddress) internal pure returns (bool) {
116+
// Convert the address to a uint256 for bit manipulation.
117+
uint256 addr = uint256(uint160(assetAddress));
118+
119+
// Ensure the upper 128 bits match the expected prefix 0xFFFFFFFF.
120+
if ((addr >> 128) != 0xFFFFFFFF) {
121+
return false;
122+
}
123+
124+
return true;
125+
}
126+
127+
/// @notice Determines if the provided asset is a native asset.
128+
/// @dev This function checks the asset kind and verifies if the asset address or ID corresponds to a native asset.
129+
/// @param asset The asset to be checked, defined by its kind and data.
130+
/// @return bool Returns true if the asset is native, false otherwise.
131+
function isNative(Asset memory asset) internal pure returns (bool) {
132+
if (isErc20(asset)) {
133+
address assetAddress = address(uint160(uint256(asset.data)));
134+
return (assetAddress == address(0));
135+
} else if (isCustom(asset)) {
136+
uint256 assetId = uint256(asset.data);
137+
return (assetId == 0);
138+
} else {
139+
return false;
140+
}
141+
}
142+
}

src/BlueprintServiceManagerBase.sol

Lines changed: 19 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import "src/IBlueprintServiceManager.sol";
1616
/// of these functions interrupts the process flow.
1717
contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabled {
1818
using EnumerableSet for EnumerableSet.AddressSet;
19+
using Assets for Assets.Asset;
20+
using Assets for address;
21+
using Assets for bytes32;
1922

2023
/// @dev The Current Blueprint Id
2124
uint256 public currentBlueprintId;
@@ -28,9 +31,6 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
2831
/// @notice This mapping is used to store the permitted payment assets for each service.
2932
mapping(uint64 => EnumerableSet.AddressSet) private _permittedPaymentAssets;
3033

31-
/// @dev The supplied address is not a valid asset address, it does not start with 0xFFFFFFFF.
32-
error InvalidAssetId(address assetAddress);
33-
3434
/// @inheritdoc IBlueprintServiceManager
3535
function onBlueprintCreated(uint64 blueprintId, address owner, address mbsm) external virtual onlyFromRootChain {
3636
currentBlueprintId = blueprintId;
@@ -176,7 +176,7 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
176176
/// @inheritdoc IBlueprintServiceManager
177177
function queryIsPaymentAssetAllowed(
178178
uint64 serviceId,
179-
ServiceOperators.Asset calldata asset
179+
Assets.Asset calldata asset
180180
)
181181
external
182182
view
@@ -192,25 +192,10 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
192192
* @param serviceId The ID of the service for which the asset is being permitted.
193193
* @param asset The asset to be permitted, defined by its kind and data.
194194
*/
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-
}
195+
function _permitAsset(uint64 serviceId, Assets.Asset calldata asset) internal virtual returns (bool added) {
196+
address assetAddress = asset.toAddress();
197+
bool _added = _permittedPaymentAssets[serviceId].add(assetAddress);
198+
return _added;
214199
}
215200

216201
/**
@@ -219,25 +204,10 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
219204
* @param serviceId The ID of the service for which the asset is being revoked.
220205
* @param asset The asset to be revoked, defined by its kind and data.
221206
*/
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-
}
207+
function _revokeAsset(uint64 serviceId, Assets.Asset calldata asset) internal virtual returns (bool removed) {
208+
address assetAddress = asset.toAddress();
209+
bool _removed = _permittedPaymentAssets[serviceId].remove(assetAddress);
210+
return _removed;
241211
}
242212

243213
/**
@@ -279,24 +249,15 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
279249
* @param serviceId The ID of the service for which permitted assets are being retrieved.
280250
* @return assets An array of ServiceOperators.Asset structs representing the permitted assets.
281251
*/
282-
function _getPermittedAssets(uint64 serviceId) internal view virtual returns (ServiceOperators.Asset[] memory) {
252+
function _getPermittedAssets(uint64 serviceId) internal view virtual returns (Assets.Asset[] memory) {
283253
EnumerableSet.AddressSet storage permittedAssets = _permittedPaymentAssets[serviceId];
284-
ServiceOperators.Asset[] memory assets = new ServiceOperators.Asset[](permittedAssets.length());
254+
Assets.Asset[] memory assets = new Assets.Asset[](permittedAssets.length());
285255
for (uint256 i = 0; i < permittedAssets.length(); i++) {
286256
address assetAddress = permittedAssets.at(i);
287257
if (assetAddress == address(0)) {
288258
continue;
289259
}
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);
260+
assets[i] = assetAddress.toAsset();
300261
}
301262
return assets;
302263
}
@@ -308,99 +269,13 @@ contract BlueprintServiceManagerBase is IBlueprintServiceManager, RootChainEnabl
308269
* @param asset The asset to check, defined by its kind and data.
309270
* @return isAllowed Boolean indicating whether the asset is permitted.
310271
*/
311-
function _isAssetPermitted(
312-
uint64 serviceId,
313-
ServiceOperators.Asset calldata asset
314-
)
315-
internal
316-
view
317-
virtual
318-
returns (bool)
319-
{
272+
function _isAssetPermitted(uint64 serviceId, Assets.Asset calldata asset) internal view virtual returns (bool) {
320273
// Native assets are always permitted.
321-
if (_isNativeAsset(asset)) {
274+
if (asset.isNative()) {
322275
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);
329276
} 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;
277+
address assetAddress = asset.toAddress();
278+
return _permittedPaymentAssets[serviceId].contains(assetAddress);
404279
}
405280
}
406281
}

0 commit comments

Comments
 (0)