Skip to content

Commit 808d447

Browse files
committed
feat: decode pending blobs
1 parent 507c27a commit 808d447

File tree

9 files changed

+187
-48
lines changed

9 files changed

+187
-48
lines changed

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extra_output = ["storageLayout"]
88
gas_reports = ["Hoku"]
99
ffi = true
1010
ast = true
11+
via_ir = true
1112

1213
[fmt]
1314
# These are all the `forge fmt` defaults

src/BlobManager.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.26;
33

44
import {IBlobManager} from "./interfaces/IBlobManager.sol";
5-
import {AddBlobParams, Blob, BlobStatus, StorageStats, SubnetStats} from "./types/BlobTypes.sol";
5+
import {AddBlobParams, Blob, BlobStatus, BlobTuple, StorageStats, SubnetStats} from "./types/BlobTypes.sol";
66
import {LibBlob} from "./util/LibBlob.sol";
77

88
contract BlobManager is IBlobManager {
@@ -21,7 +21,7 @@ contract BlobManager is IBlobManager {
2121
}
2222

2323
/// @dev See {ICredit-getPendingBlobs}.
24-
function getPendingBlobs(uint32 size) external view returns (bytes memory) {
24+
function getPendingBlobs(uint32 size) external view returns (BlobTuple[] memory blobs) {
2525
return LibBlob.getPendingBlobs(size);
2626
}
2727

src/interfaces/IBlobManager.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22
pragma solidity ^0.8.26;
33

4-
import {AddBlobParams, Blob, BlobStatus, StorageStats, SubnetStats} from "../types/BlobTypes.sol";
4+
import {AddBlobParams, Blob, BlobStatus, BlobTuple, StorageStats, SubnetStats} from "../types/BlobTypes.sol";
55

66
/// @dev Hoku Blobs actor EVM interface for managing and querying information about blogs/storage.
77
/// See Rust implementation for details:
@@ -30,8 +30,8 @@ interface IBlobManager {
3030

3131
/// @dev Get a list of pending blobs.
3232
/// @param size Maximum number of pending blobs to return.
33-
/// @return List of pending blobs encoded as bytes.
34-
function getPendingBlobs(uint32 size) external view returns (bytes memory);
33+
/// @return blobs List of pending blobs.
34+
function getPendingBlobs(uint32 size) external view returns (BlobTuple[] memory blobs);
3535

3636
/// @dev Get the total count of pending blobs.
3737
/// @return Total number of pending blobs.

src/interfaces/IBucketManager.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ interface IBucketManager {
6363

6464
/// @dev Add an object to a bucket.
6565
/// @param bucket The bucket.
66-
/// @param AddObjectParams The add object params. See {AddObjectParams} for more details.
67-
function addObject(string memory bucket, AddObjectParams memory AddObjectParams) external;
66+
/// @param addObjectParams The add object params. See {AddObjectParams} for more details.
67+
function addObject(string memory bucket, AddObjectParams memory addObjectParams) external;
6868

6969
/// @dev Delete an object from a bucket.
7070
/// @param bucket The bucket.

src/types/BlobTypes.sol

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ struct AddBlobParams {
138138
struct Blob {
139139
uint64 size;
140140
string metadataHash;
141-
// TODO: decode the following from Rust type: HashMap<Address, SubscriptionGroup>
142141
Subscriber[] subscribers;
143142
BlobStatus status;
144143
}
@@ -171,7 +170,7 @@ struct SubscriptionGroup {
171170
// of the encoding/decoding logic works...except you might see odd decoding with a bucket-backed blob, like a
172171
// subscription ID of `��0������䣱p�V�%���?��:\u{8}4T�~��V`.
173172
string subscriptionId;
174-
bytes subscription; // TODO: update type
173+
Subscription subscription;
175174
}
176175

177176
/// @dev A subscription to a blob.
@@ -200,9 +199,19 @@ struct Delegate {
200199
}
201200

202201
/// @dev Pending blob information. Represents a Rust `(Hash, HashSet<(Address, SubscriptionId, PublicKey)>)`
203-
/// @param blobHash (bytes): The blob hash.
204-
/// @param sourceInfo (Subscription[]): The pending subscriptions.
205-
struct PendingBlob {
206-
bytes blobHash;
207-
bytes sourceInfo;
202+
/// @param blobHash (string): The blob hash.
203+
/// @param sourceInfo (BlobSourceInfo[]): The source information for the blob.
204+
struct BlobTuple {
205+
string blobHash;
206+
BlobSourceInfo sourceInfo;
207+
}
208+
209+
/// @dev Source information for a blob.
210+
/// @param subscriber (address): The subscriber address.
211+
/// @param subscriptionId (string): The subscription ID.
212+
/// @param source (string): The source Iroh node ID used for ingestion.
213+
struct BlobSourceInfo {
214+
address subscriber;
215+
string subscriptionId;
216+
string source;
208217
}

src/util/LibBlob.sol

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import {
88
Approvals,
99
Balance,
1010
Blob,
11+
BlobSourceInfo,
1112
BlobStatus,
13+
BlobTuple,
1214
CreditApproval,
1315
CreditStats,
16+
Delegate,
1417
StorageStats,
1518
SubnetStats,
1619
Subscriber,
20+
Subscription,
1721
SubscriptionGroup
1822
} from "../types/BlobTypes.sol";
1923
import {KeyValue} from "../types/CommonTypes.sol";
@@ -122,16 +126,55 @@ library LibBlob {
122126
/// @param data The encoded subscription ID.
123127
/// @return decoded The decoded subscription ID.
124128
function decodeSubscriptionId(bytes memory data) internal view returns (string memory) {
125-
// If the leading indicator is `a1`, it's a mapping with a single key-value pair; else, default
126-
if (data[0] == hex"a1") {
127-
// Decode the mapping with Key and subscription ID bytes (a single key-value pair)
128-
bytes[2][] memory decoded = data.decodeCborMappingToBytes();
129-
// Second value is the subscription ID bytes (ignore the first value `Key` key)
130-
bytes memory subscriptionId = decoded[0][1].decodeCborBytesArrayToBytes();
131-
return string(subscriptionId);
132-
} else {
129+
// If not a mapping with key-value pair, return default
130+
if (data[0] != hex"a1") {
133131
return "Default";
134132
}
133+
134+
// Decode the mapping and return subscription ID bytes
135+
bytes[2][] memory decoded = data.decodeCborMappingToBytes();
136+
return string(decoded[0][1].decodeCborBytesArrayToBytes());
137+
}
138+
139+
/// @dev Decode a delegate from CBOR.
140+
/// @param data The encoded CBOR array of a delegate.
141+
/// @return origin The delegate origin address
142+
/// @return caller The delegate caller address
143+
function decodeDelegate(bytes memory data) internal view returns (address origin, address caller) {
144+
if (data[0] == hex"00" || data[0] == hex"f6") {
145+
return (address(0), address(0));
146+
}
147+
bytes[] memory decoded = data.decodeCborArrayToBytes();
148+
origin = decoded[0].decodeCborAddress();
149+
caller = decoded[1].decodeCborAddress();
150+
}
151+
152+
/// @dev Decode a subscription from CBOR.
153+
/// @param data The encoded CBOR array of a subscription.
154+
/// @return subscription The decoded subscription.
155+
function decodeSubscription(bytes memory data) internal view returns (Subscription memory subscription) {
156+
bytes[] memory decoded = data.decodeCborArrayToBytes();
157+
subscription.added = decoded[0].decodeCborBytesToUint64();
158+
subscription.expiry = decoded[1].decodeCborBytesToUint64();
159+
subscription.autoRenew = decoded[2].decodeCborBool();
160+
subscription.source = string(decoded[3].decodeCborBlobHashOrNodeId());
161+
(subscription.delegate.origin, subscription.delegate.caller) = decodeDelegate(decoded[4]);
162+
subscription.failed = decoded[5].decodeCborBool();
163+
}
164+
165+
/// @dev Decode a subscription group from CBOR.
166+
/// @param subscriptionGroupBytes The encoded subscription group bytes
167+
/// @return group The decoded subscription group
168+
function decodeSubscriptionGroup(bytes[2][] memory subscriptionGroupBytes)
169+
internal
170+
view
171+
returns (SubscriptionGroup[] memory group)
172+
{
173+
group = new SubscriptionGroup[](subscriptionGroupBytes.length);
174+
for (uint256 j = 0; j < subscriptionGroupBytes.length; j++) {
175+
group[j].subscriptionId = decodeSubscriptionId(subscriptionGroupBytes[j][0]);
176+
group[j].subscription = decodeSubscription(subscriptionGroupBytes[j][1]);
177+
}
135178
}
136179

137180
/// @dev Decode subscribers from CBOR.
@@ -142,12 +185,7 @@ library LibBlob {
142185
subscribers = new Subscriber[](decoded.length);
143186
for (uint256 i = 0; i < decoded.length; i++) {
144187
subscribers[i].subscriber = decoded[i][0].decodeCborAddress();
145-
bytes[2][] memory subscriptionGroupBytes = decoded[i][1].decodeCborMappingToBytes();
146-
subscribers[i].subscriptionGroup = new SubscriptionGroup[](subscriptionGroupBytes.length);
147-
for (uint256 j = 0; j < subscriptionGroupBytes.length; j++) {
148-
subscribers[i].subscriptionGroup[j].subscriptionId = decodeSubscriptionId(subscriptionGroupBytes[j][0]);
149-
subscribers[i].subscriptionGroup[j].subscription = subscriptionGroupBytes[j][1];
150-
}
188+
subscribers[i].subscriptionGroup = decodeSubscriptionGroup(decoded[i][1].decodeCborMappingToBytes());
151189
}
152190
}
153191

@@ -157,12 +195,38 @@ library LibBlob {
157195
function decodeBlob(bytes memory data) internal view returns (Blob memory blob) {
158196
bytes[] memory decoded = data.decodeCborArrayToBytes();
159197
if (decoded.length == 0) return blob;
198+
160199
blob.size = decoded[0].decodeCborBytesToUint64();
161-
blob.metadataHash = string(decoded[1].decodeBlobHash());
200+
blob.metadataHash = string(decoded[1].decodeCborBlobHashOrNodeId());
162201
blob.subscribers = decodeSubscribers(decoded[2]);
163202
blob.status = decodeBlobStatus(decoded[3]);
164203
}
165204

205+
/// @dev Decode a blob source info from CBOR.
206+
/// @param data The encoded CBOR array of a blob source info.
207+
/// @return sourceInfo The decoded blob source info.
208+
function decodeBlobSourceInfo(bytes memory data) internal view returns (BlobSourceInfo memory sourceInfo) {
209+
bytes[] memory decodedOuter = data.decodeCborArrayToBytes();
210+
bytes[] memory decodedInner = decodedOuter[0].decodeCborArrayToBytes();
211+
sourceInfo.subscriber = decodedInner[0].decodeCborAddress();
212+
sourceInfo.subscriptionId = decodeSubscriptionId(decodedInner[1]);
213+
sourceInfo.source = string(decodedInner[2].decodeCborBlobHashOrNodeId());
214+
}
215+
216+
/// @dev Decode pending blobs from CBOR.
217+
/// @param data The encoded CBOR array of pending blobs.
218+
/// @return blobs The decoded pending blobs.
219+
function decodePendingBlobs(bytes memory data) internal view returns (BlobTuple[] memory blobs) {
220+
bytes[] memory decoded = data.decodeCborArrayToBytes();
221+
if (decoded.length == 0) return blobs;
222+
blobs = new BlobTuple[](decoded.length);
223+
for (uint256 i = 0; i < decoded.length; i++) {
224+
bytes[] memory blobTuple = decoded[i].decodeCborArrayToBytes();
225+
blobs[i].blobHash = string(blobTuple[0].decodeCborBlobHashOrNodeId());
226+
blobs[i].sourceInfo = decodeBlobSourceInfo(blobTuple[1]);
227+
}
228+
}
229+
166230
/// @dev Helper function to encode approve credit params.
167231
/// @param from (address): Account address that is approving the credit.
168232
/// @param receiver (address): Account address that is receiving the approval.
@@ -328,12 +392,20 @@ library LibBlob {
328392
return accountToBalance(account);
329393
}
330394

395+
/// @dev Get a blob.
396+
/// @param blobHash The hash of the blob.
397+
/// @return blob The blob.
331398
function getBlob(string memory blobHash) external view returns (Blob memory blob) {
332399
bytes memory params = blobHash.encodeCborBlobHashOrNodeId();
333400
bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_BLOB, params);
334401
return decodeBlob(data);
335402
}
336403

404+
/// @dev Get the status of a blob.
405+
/// @param subscriber The address of the subscriber.
406+
/// @param blobHash The hash of the blob.
407+
/// @param subscriptionId The subscription ID.
408+
/// @return status The status of the blob.
337409
function getBlobStatus(address subscriber, string memory blobHash, string memory subscriptionId)
338410
external
339411
view
@@ -348,16 +420,24 @@ library LibBlob {
348420
return decodeBlobStatus(data);
349421
}
350422

351-
function getPendingBlobs(uint32 size) external view returns (bytes memory) {
423+
/// @dev Get pending blobs.
424+
/// @param size The size of the blobs to get.
425+
/// @return blobs The pending blobs.
426+
function getPendingBlobs(uint32 size) external view returns (BlobTuple[] memory blobs) {
352427
bytes memory params = size.encodeCborUint64();
353-
return LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BLOBS, params);
428+
bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BLOBS, params);
429+
return decodePendingBlobs(data);
354430
}
355431

432+
/// @dev Get the number of pending blobs.
433+
/// @return count The number of pending blobs.
356434
function getPendingBlobsCount() external view returns (uint64) {
357435
bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BLOBS_COUNT);
358436
return data.decodeCborBytesToUint64();
359437
}
360438

439+
/// @dev Get the number of pending bytes.
440+
/// @return count The number of pending bytes.
361441
function getPendingBytesCount() external view returns (uint64) {
362442
bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BYTES_COUNT);
363443
return data.decodeCborBytesToUint64();

src/util/LibBucket.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ library LibBucket {
103103
bytes[] memory decoded = data.decodeCborArrayToBytes();
104104
if (decoded.length == 0) return value;
105105
value = Value({
106-
blobHash: string(decoded[0].decodeBlobHash()),
107-
recoveryHash: string(decoded[1].decodeBlobHash()),
106+
blobHash: string(decoded[0].decodeCborBlobHashOrNodeId()),
107+
recoveryHash: string(decoded[1].decodeCborBlobHashOrNodeId()),
108108
size: decoded[2].decodeCborBytesToUint64(),
109109
expiry: decoded[3].decodeCborBytesToUint64(),
110110
metadata: decodeMetadata(decoded[4])

src/util/LibWasm.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ library LibWasm {
113113
return ByteParser.bytesToBigNumber(data);
114114
}
115115

116+
/// @dev Decode CBOR encoded boolean.
117+
/// @param data The encoded CBOR boolean.
118+
/// @return result The decoded boolean.
119+
function decodeCborBool(bytes memory data) internal pure returns (bool) {
120+
// In CBOR, `0xf5` is true, `0xf4` is false; in practice, it may decode to `0x01` for true and `0x00` for false
121+
// This happens when `decodeCborArrayToBytes` is used to unpack a boolean value, so we need to handle both.
122+
return data[0] == 0x01 || data[0] == 0xf5;
123+
}
124+
116125
/// @dev Decode CBOR encoded Filecoin address bytes to an Ethereum address.
117126
/// @param addr The encoded CBOR Filecoin address. Example: 0x040a15d34aaf54267db7d7c367839aaf71a00a2c6a65
118127
/// @return result The decoded Ethereum address. Example: 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
@@ -266,7 +275,7 @@ library LibWasm {
266275
/// @dev Decode a CBOR encoded blob hash to a string (a Rust Iroh hash value `Hash(pub [u8; 32])`).
267276
/// @param value The encoded CBOR blob hash (e.g,. `0x9820188e184c...`)
268277
/// @return result The decoded blob hash as base32 encoded bytes.
269-
function decodeBlobHash(bytes memory value) internal pure returns (bytes memory) {
278+
function decodeCborBlobHashOrNodeId(bytes memory value) internal pure returns (bytes memory) {
270279
bytes memory decoded = decodeCborFixedArrayToBytes(value);
271280
return Base32.encode(decoded);
272281
}

0 commit comments

Comments
 (0)