Skip to content

Commit 968ec7b

Browse files
dtbuchholzsanderpick
authored andcommitted
feat: handle account approvals to/from (#56)
- Alter `Account` and `Balance` to include the fields: `approvalsTo` and `approvalsFrom`. - Alter the `Approval` struct's address field to be called `addr`—since `to` no longer makes sense due to the feature above. - Add support for decoding these approvals in `getAccount()` and `getCreditBalance()`. - New cbor decoding method that takes string `f410f...` values and converts them to hex address values.
1 parent af48efb commit 968ec7b

File tree

7 files changed

+100
-74
lines changed

7 files changed

+100
-74
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,13 @@ We can get the credit account info for the address at `EVM_ADDRESS` (the variabl
385385
you could provide any account's EVM public key that exists in the subnet.
386386

387387
```sh
388-
cast abi-decode "getAccount(address)((uint64,uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],uint64,uint256))" $(cast call --rpc-url $ETH_RPC_URL $CREDIT "getAccount(address)" $EVM_ADDRESS)
388+
cast abi-decode "getAccount(address)((uint64,uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],(address,(uint256,uint256,uint64,uint256,uint256))[],uint64,uint256))" $(cast call --rpc-url $ETH_RPC_URL $CREDIT "getAccount(address)" $EVM_ADDRESS)
389389
```
390390

391391
This will return the following values:
392392

393393
```
394-
(6, 4999999999999999454276000000000000000000 [4.999e39], 504150000000000000000000 [5.041e23], 0x0000000000000000000000000000000000000000, 7200, [(0x90F79bf6EB2c4f870365E785982E1f101E93b906, (12345000000000000000000 [1.234e22], 987654321 [9.876e8], 11722 [1.172e4], 0, 0))], 86400 [8.64e4], 4999999984799342175554 [4.999e21])
394+
(6, 4999999999999999454276000000000000000000 [4.999e39], 504150000000000000000000 [5.041e23], 0x0000000000000000000000000000000000000000, 7200, [(0x90F79bf6EB2c4f870365E785982E1f101E93b906, (12345000000000000000000 [1.234e22], 987654321 [9.876e8], 11722 [1.172e4], 0, 0))], [], 86400 [8.64e4], 4999999984799342175554 [4.999e21])
395395
```
396396

397397
Which maps to the `Account` struct:
@@ -403,7 +403,8 @@ struct Account {
403403
uint256 creditCommitted; // 504150000000000000000000
404404
address creditSponsor; // 0x0000000000000000000000000000000000000000 (null)
405405
uint64 lastDebitEpoch; // 7200
406-
Approval[] approvals; // See Approval struct below
406+
Approval[] approvalsTo; // See Approval struct below
407+
Approval[] approvalsFrom; // [] (empty)
407408
uint64 maxTtl; // 86400
408409
uint256 gasAllowance; // 4999999984799342175554
409410
}
@@ -414,7 +415,7 @@ approvals authorized. We can expand this to be interpreted as the following:
414415

415416
```solidity
416417
struct Approval {
417-
string to; // 0x90F79bf6EB2c4f870365E785982E1f101E93b906
418+
address addr; // 0x90F79bf6EB2c4f870365E785982E1f101E93b906
418419
CreditApproval approval; // See CreditApproval struct below
419420
}
420421
@@ -489,7 +490,7 @@ struct CreditApproval {
489490
Fetch the credit balance for the address at `EVM_ADDRESS`:
490491

491492
```sh
492-
cast abi-decode "getCreditBalance(address)((uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],uint256))" $(cast call --rpc-url $ETH_RPC_URL $CREDIT "getCreditBalance(address)" $EVM_ADDRESS)
493+
cast abi-decode "getCreditBalance(address)((uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],(address,(uint256,uint256,uint64,uint256,uint256))[],uint256))" $(cast call --rpc-url $ETH_RPC_URL $CREDIT "getCreditBalance(address)" $EVM_ADDRESS)
493494
```
494495

495496
This will return the following values:
@@ -948,7 +949,6 @@ accepts "optional" arguments. All of the method parameters and return types can
948949
the string that was chosen during `addBlob`).
949950
- `overwriteBlob(string,AddBlobParams memory)`: Overwrite a blob from the network, passing the old
950951
blob hash, and the new blob parameters.
951-
- `getAccountType(address)`: Get the account's max blob TTL.
952952
- `getBlob(string)`: Get information about a specific blob at its blake3 hash.
953953
- `getBlobStatus(address,string,string)`: Get a blob's status, providing its credit sponsor (i.e.,
954954
the account's `address`, or `address(0)` if null), its blake3 blob hash (the first `string`

src/types/BlobTypes.sol

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,8 @@ pragma solidity ^0.8.26;
77
/// @param creditCommitted (uint256): Current committed credit in byte-blocks that will be used for debits.
88
/// @param creditSponsor (address): Optional default sponsor account address.
99
/// @param lastDebitEpoch (uint64): The chain epoch of the last debit.
10-
/// @param approvals (Approvals[]): Credit approvals to other accounts, keyed by receiver, keyed by caller,
11-
/// which could be the receiver or a specific contract, like a bucket.
12-
/// This allows for limiting approvals to interactions from a specific contract.
13-
/// For example, an approval for Alice might be valid for any contract caller, so long as
14-
/// the origin is Alice.
15-
/// An approval for Bob might be valid from only one contract caller, so long as
16-
/// the origin is Bob.
10+
/// @param approvalsTo (Approvals[]): Credit approvals to other accounts from this account, keyed by receiver.
11+
/// @param approvalsFrom (Approvals[]): Credit approvals to this account from other accounts, keyed by sender.
1712
/// @param maxTtl (uint64): The maximum allowed TTL for actor's blobs.
1813
struct Account {
1914
uint64 capacityUsed;
@@ -22,17 +17,18 @@ struct Account {
2217
address creditSponsor;
2318
uint64 lastDebitEpoch;
2419
// Note: this is a nested array that emulates a Rust `HashMap<String, CreditApproval>`
25-
Approval[] approvals;
20+
Approval[] approvalsTo;
21+
Approval[] approvalsFrom;
2622
uint64 maxTtl;
2723
uint256 gasAllowance;
2824
}
2925

3026
/// @dev Credit approval from one account to another.
31-
/// @param to (address): Optional restriction on caller address, e.g., an object store. Use zero address if
27+
/// @param addr (address): Optional restriction on caller address, e.g., an object store. Use zero address if
3228
/// unused, indicating a null value.
3329
/// @param approval (CreditApproval): The credit approval. See {CreditApproval} for more details.
3430
struct Approval {
35-
address to;
31+
address addr;
3632
CreditApproval approval;
3733
}
3834

@@ -41,20 +37,16 @@ struct Approval {
4137
/// @param creditCommitted (uint256): Current committed credit in byte-blocks that will be used for debits.
4238
/// @param creditSponsor (address): Optional default sponsor account address.
4339
/// @param lastDebitEpoch (uint64): The chain epoch of the last debit.
44-
/// @param approvals (Approvals[]): Credit approvals to other accounts, keyed by receiver, keyed by caller,
45-
/// which could be the receiver or a specific contract, like a bucket.
46-
/// This allows for limiting approvals to interactions from a specific contract.
47-
/// For example, an approval for Alice might be valid for any contract caller, so long as
48-
/// the origin is Alice.
49-
/// An approval for Bob might be valid from only one contract caller, so long as
50-
/// the origin is Bob.
40+
/// @param approvalsTo (Approvals[]): Credit approvals to other accounts from this account, keyed by receiver.
41+
/// @param approvalsFrom (Approvals[]): Credit approvals to this account from other accounts, keyed by sender.
5142
/// @param gasAllowance (uint256): The amount of gas allowance for the account.
5243
struct Balance {
5344
uint256 creditFree;
5445
uint256 creditCommitted;
5546
address creditSponsor;
5647
uint64 lastDebitEpoch;
57-
Approval[] approvals;
48+
Approval[] approvalsTo;
49+
Approval[] approvalsFrom;
5850
uint256 gasAllowance;
5951
}
6052

src/wrappers/LibBlob.sol

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,10 @@ library LibBlob {
8383
account.creditCommitted = decoded[2].decodeCborBytesToUint256();
8484
account.creditSponsor = decoded[3][0] == 0x00 ? address(0) : decoded[3].decodeCborAddress();
8585
account.lastDebitEpoch = decoded[4].decodeCborBytesToUint64();
86-
account.approvals = decodeApprovals(decoded[5]);
87-
account.maxTtl = decoded[6].decodeCborBytesToUint64();
88-
account.gasAllowance = decoded[7].decodeCborBytesToUint256();
86+
account.approvalsTo = decodeApprovals(decoded[5]);
87+
account.approvalsFrom = decodeApprovals(decoded[6]);
88+
account.maxTtl = decoded[7].decodeCborBytesToUint64();
89+
account.gasAllowance = decoded[8].decodeCborBytesToUint256();
8990
}
9091

9192
/// @dev Helper function to decode a credit approval from CBOR to solidity.
@@ -111,8 +112,9 @@ library LibBlob {
111112
bytes[2][] memory decoded = data.decodeCborMappingToBytes();
112113
approvals = new Approval[](decoded.length);
113114
for (uint256 i = 0; i < decoded.length; i++) {
114-
// The `to` value is an ID address string, like `f0123`, so we convert it to an address
115-
approvals[i].to = decoded[i][0].decodeCborActorIdStringToAddress();
115+
// The `addr` value is an delegated address string, like `f410fsd3zx5xlfrhyoa3f46czqlq7capjhoighmzagaq`, so
116+
// we need to convert it to an address
117+
approvals[i].addr = decoded[i][0].decodeCborDelegatedAddressStringToAddress();
116118
approvals[i].approval = decodeCreditApproval(decoded[i][1]);
117119
}
118120
}
@@ -337,7 +339,8 @@ library LibBlob {
337339
balance.creditCommitted = account.creditCommitted;
338340
balance.creditSponsor = account.creditSponsor;
339341
balance.lastDebitEpoch = account.lastDebitEpoch;
340-
balance.approvals = account.approvals;
342+
balance.approvalsTo = account.approvalsTo;
343+
balance.approvalsFrom = account.approvalsFrom;
341344
balance.gasAllowance = account.gasAllowance;
342345
}
343346

@@ -443,6 +446,12 @@ library LibBlob {
443446
return decodeBlob(data);
444447
}
445448

449+
function getBlob2(string memory blobHash) external view returns (bytes memory data) {
450+
bytes memory params = blobHash.encodeCborBlobHashOrNodeId();
451+
data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_BLOB, params);
452+
return data;
453+
}
454+
446455
/// @dev Get the status of a blob.
447456
/// @param subscriber The address of the subscriber.
448457
/// @param blobHash The hash of the blob.

src/wrappers/LibWasm.sol

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ library LibWasm {
166166
}
167167

168168
/// @dev Decode CBOR encoded actor ID bytes to an Ethereum address.
169-
/// @param data The encoded CBOR actor ID bytes.
169+
/// @param data The encoded CBOR actor ID bytes. Example: `f0123`
170170
/// @return result The decoded Ethereum address.
171171
function decodeCborActorIdStringToAddress(bytes memory data) internal view returns (address) {
172172
bytes memory actorIdStr = new bytes(data.length - 1);
@@ -185,6 +185,31 @@ library LibWasm {
185185
return getDelegatedOrMaskedAddressFromActorId(actorId);
186186
}
187187

188+
/// @dev Decode CBOR encoded delegated address string to an Ethereum address.
189+
/// @param data The encoded CBOR delegated address string value. Example:
190+
/// `f410feot7hrogmplrcupubsdbbqarkdewmb4vkwc5qqq` (i.e., the bytes are `0x6634313066...`)
191+
/// @return result The decoded Ethereum address.
192+
function decodeCborDelegatedAddressStringToAddress(bytes memory data) internal pure returns (address) {
193+
bytes memory subAddressBytes = new bytes(data.length - 5);
194+
// Remove the first 5 bytes, which are the network prefix, protocol, and `f` prefix (`f410f`)
195+
assembly {
196+
let srcPtr := add(data, 37) // +32 for array length, +5 to skip the first 5 bytes
197+
let destPtr := add(subAddressBytes, 32) // +32 for array length
198+
let len := sub(mload(data), 5) // data.length - 5
199+
for { let i := 0 } lt(i, len) { i := add(i, 32) } { mstore(add(destPtr, i), mload(add(srcPtr, i))) }
200+
}
201+
bytes32 decoded = bytes32(Base32.decode(subAddressBytes));
202+
address result;
203+
// Shift right by 96 bits (12 bytes) to get the last 20 bytes of the address (note: removes the last 4 bytes)
204+
assembly {
205+
result := shr(96, decoded)
206+
}
207+
return result;
208+
}
209+
210+
/// @dev Decode CBOR encoded actor ID bytes to a uint64.
211+
/// @param data The encoded CBOR actor ID bytes.
212+
/// @return result The decoded uint64.
188213
function decodeCborActorIdBytesToUint64(bytes memory data) internal pure returns (uint64) {
189214
// First, decode the LEB128 bytes value to the actor ID
190215
uint64 result = 0;

test/LibBlob.t.sol

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -45,56 +45,50 @@ contract LibBlobTest is Test {
4545
function testDecodeAccount() public view {
4646
// No approvals
4747
bytes memory data =
48-
hex"880652000eb194f8e1ae50e56f45b07f78a0d400004b006dc68533171604000000f6191834a01a000151804b00010f0cf0647ee57f02b9";
48+
hex"890052000eb194f8e1ae525fd5dcfab0800000000040f6191068a0a01a000151804b00010f0cf064dd59200000";
4949
CreditAccount memory account = data.decodeAccount();
50-
assertEq(account.capacityUsed, 6);
51-
assertEq(account.creditFree, 4999999999999998213053000000000000000000);
52-
assertEq(account.creditCommitted, 518400000000000000000000);
50+
assertEq(account.capacityUsed, 0);
51+
assertEq(account.creditFree, 5000000000000000000000000000000000000000);
52+
assertEq(account.creditCommitted, 0);
5353
assertEq(account.creditSponsor, address(0));
54-
assertEq(account.lastDebitEpoch, 6196);
55-
assertEq(account.approvals.length, 0);
54+
assertEq(account.lastDebitEpoch, 4200);
55+
assertEq(account.approvalsTo.length, 0);
56+
assertEq(account.approvalsFrom.length, 0);
5657
assertEq(account.maxTtl, 86400);
57-
assertEq(account.gasAllowance, 4999999999594333143737);
58+
assertEq(account.gasAllowance, 5000000000000000000000);
5859

59-
// With approvals to two different accounts, but no set credit, gas fee, or ttl limits
60+
// With all fields set: approvals to two different accounts, approvals from one account, and all optionals (set
61+
// credit limit, gas fee limit, and ttl)
6062
data =
61-
hex"880652000eb194f8e1ae50e56f45b07f78a0d400004b006dc68533171604000000f6191834a265663031323585f6f6f6404065663031323785f6f6f640401a000151804b00010f0cf0647e8620f2b9";
63+
hex"890652000eb61887b895080136932adf5bcc4800004b006dc6853317160400000056040a15d34aaf54267db7d7c367839aaf71a00a2c6a65193b4ea2782c663431306663786a75766c3275657a3633707636646d36627a766c33727561666379327466766264617a7069854c0052b7d2dcc80cd2e400000045003b9aca001949ba4b000f60a131ed58b468000045003b9aca00782c663431306674667376613769326b77366d65326b346c6335626e367a7833616d33626a673436367667376a6985f6f6f64040a1782c663431306663786a75766c3275657a3633707636646d36627a766c33727561666379327466766264617a706985f6f6f64045002faf08001a000151804b00010f6034abfb0e425a07";
6264
account = data.decodeAccount();
6365
assertEq(account.capacityUsed, 6);
64-
assertEq(account.creditFree, 4999999999999998213053000000000000000000);
66+
assertEq(account.creditFree, 5005999999999999352418000000000000000000);
6567
assertEq(account.creditCommitted, 518400000000000000000000);
66-
assertEq(account.creditSponsor, address(0));
67-
assertEq(account.lastDebitEpoch, 6196);
68-
assertEq(account.approvals.length, 2);
69-
// Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated address
70-
// This is because tests don't access the delegated address lookup precompile via the FVM runtime
71-
assertEq(account.approvals[0].to, 0xFF0000000000000000000000000000000000007D);
72-
assertEq(account.approvals[0].approval.creditLimit, 0);
73-
assertEq(account.approvals[0].approval.gasFeeLimit, 0);
74-
assertEq(account.approvals[0].approval.expiry, 0);
75-
assertEq(account.approvals[0].approval.creditUsed, 0);
76-
assertEq(account.approvals[0].approval.gasFeeUsed, 0);
77-
// Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated address
78-
assertEq(account.approvals[1].to, 0xff0000000000000000000000000000000000007f);
79-
assertEq(account.approvals[1].approval.creditLimit, 0);
80-
assertEq(account.approvals[1].approval.gasFeeLimit, 0);
81-
assertEq(account.approvals[1].approval.expiry, 0);
82-
assertEq(account.approvals[1].approval.creditUsed, 0);
83-
assertEq(account.approvals[1].approval.gasFeeUsed, 0);
68+
assertEq(account.creditSponsor, 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65);
69+
assertEq(account.lastDebitEpoch, 15182);
70+
assertEq(account.approvalsTo.length, 2);
71+
assertEq(account.approvalsFrom.length, 1);
8472
assertEq(account.maxTtl, 86400);
85-
assertEq(account.gasAllowance, 4999999999592733143737);
86-
87-
// With approval to one account with set credit, gas fee, and ttl limits
88-
data =
89-
hex"880652000eb194f8e1ae50e56f45b07f78a0d400004b006dc68533171604000000f6191834a1656630313235854c0052b7d2dcc80cd2e4000000430003e81927844b0005650e85ae5dfb900000401a000151804b00010f0cf0647f152e0ab9";
90-
account = data.decodeAccount();
91-
// Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated address
92-
assertEq(account.approvals[0].to, 0xFF0000000000000000000000000000000000007D);
93-
assertEq(account.approvals[0].approval.creditLimit, 100000000000000000000000000);
94-
assertEq(account.approvals[0].approval.gasFeeLimit, 1000);
95-
assertEq(account.approvals[0].approval.expiry, 10116);
96-
assertEq(account.approvals[0].approval.creditUsed, 25476000000000000000000);
97-
assertEq(account.approvals[0].approval.gasFeeUsed, 0);
73+
assertEq(account.gasAllowance, 5005999998796482894343);
74+
assertEq(account.approvalsTo[0].addr, 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65);
75+
assertEq(account.approvalsTo[0].approval.creditLimit, 100000000000000000000000000);
76+
assertEq(account.approvalsTo[0].approval.gasFeeLimit, 1000000000);
77+
assertEq(account.approvalsTo[0].approval.expiry, 18874);
78+
assertEq(account.approvalsTo[0].approval.creditUsed, 72618000000000000000000);
79+
assertEq(account.approvalsTo[0].approval.gasFeeUsed, 1000000000);
80+
assertEq(account.approvalsTo[1].addr, 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc);
81+
assertEq(account.approvalsTo[1].approval.creditLimit, 0);
82+
assertEq(account.approvalsTo[1].approval.gasFeeLimit, 0);
83+
assertEq(account.approvalsTo[1].approval.expiry, 0);
84+
assertEq(account.approvalsTo[1].approval.creditUsed, 0);
85+
assertEq(account.approvalsTo[1].approval.gasFeeUsed, 0);
86+
assertEq(account.approvalsFrom[0].addr, 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65);
87+
assertEq(account.approvalsFrom[0].approval.creditLimit, 0);
88+
assertEq(account.approvalsFrom[0].approval.gasFeeLimit, 0);
89+
assertEq(account.approvalsFrom[0].approval.expiry, 0);
90+
assertEq(account.approvalsFrom[0].approval.creditUsed, 0);
91+
assertEq(account.approvalsFrom[0].approval.gasFeeUsed, 800000000);
9892
}
9993

10094
function testEncodeApproveCreditParams() public pure {

test/LibWasm.t.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ contract LibWasmTest is Test {
209209
assertEq(result, hex"024722ebdc6ff1cd1c5e01b1484f4ff4cc551f2f19");
210210
}
211211

212+
function testDecodeCborDelegatedAddressStringToAddress() public pure {
213+
bytes memory data = bytes("f410fsd3zx5xlfrhyoa3f46czqlq7capjhoighmzagaq");
214+
address result = LibWasm.decodeCborDelegatedAddressStringToAddress(data);
215+
assertEq(result, 0x90F79bf6EB2c4f870365E785982E1f101E93b906);
216+
}
217+
212218
function testDecodeCborActorAddress() public pure {
213219
bytes memory addr = hex"02770d21925703390a236f68f84ef1d432ca5742c4";
214220
string memory result = LibWasm.decodeCborActorAddress(addr);

test/scripts/wrapper_integration_test.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ if [ "$output" = "0x" ]; then
309309
echo "getAccount failed"
310310
exit 1
311311
fi
312-
DECODED_ACCOUNT=$(cast abi-decode "getAccount(address)((uint64,uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],uint64,uint256))" "$output")
312+
DECODED_ACCOUNT=$(cast abi-decode "getAccount(address)((uint64,uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],(address,(uint256,uint256,uint64,uint256,uint256))[],uint64,uint256))" "$output")
313313
echo "Account info: $DECODED_ACCOUNT"
314314

315315
# Test getCreditStats
@@ -342,7 +342,7 @@ if [ "$output" = "0x" ]; then
342342
echo "getCreditBalance failed"
343343
exit 1
344344
fi
345-
DECODED_BALANCE=$(cast abi-decode "getCreditBalance(address)((uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],uint256))" $output)
345+
DECODED_BALANCE=$(cast abi-decode "getCreditBalance(address)((uint256,uint256,address,uint64,(address,(uint256,uint256,uint64,uint256,uint256))[],(address,(uint256,uint256,uint64,uint256,uint256))[],uint256))" $output)
346346
echo "Credit balance: $DECODED_BALANCE"
347347

348348
# Test buyCredit

0 commit comments

Comments
 (0)