From 3c161fe465b86e66cf9911088ed67a7fcd96786e Mon Sep 17 00:00:00 2001 From: Dan Buchholz Date: Mon, 12 May 2025 11:42:25 -0500 Subject: [PATCH] feat: remove wrappers --- README.md | 1106 +--------------------- script/BlobManager.s.sol | 20 - script/BucketManager.s.sol | 20 - script/CreditManager.s.sol | 20 - src/{wrappers => util}/LibWasm.sol | 0 src/wrappers/BlobManager.sol | 74 -- src/wrappers/BucketManager.sol | 123 --- src/wrappers/CreditManager.sol | 87 -- src/wrappers/LibBlob.sol | 584 ------------ src/wrappers/LibBucket.sol | 330 ------- test/BlobManager.t.sol | 19 - test/BucketManager.t.sol | 19 - test/CreditManager.t.sol | 19 - test/LibBlob.t.sol | 275 ------ test/LibBucket.t.sol | 189 ---- test/LibWasm.t.sol | 2 +- test/scripts/wrapper_integration_test.sh | 0 17 files changed, 30 insertions(+), 2857 deletions(-) delete mode 100644 script/BlobManager.s.sol delete mode 100644 script/BucketManager.s.sol delete mode 100644 script/CreditManager.s.sol rename src/{wrappers => util}/LibWasm.sol (100%) delete mode 100644 src/wrappers/BlobManager.sol delete mode 100644 src/wrappers/BucketManager.sol delete mode 100644 src/wrappers/CreditManager.sol delete mode 100644 src/wrappers/LibBlob.sol delete mode 100644 src/wrappers/LibBucket.sol delete mode 100644 test/BlobManager.t.sol delete mode 100644 test/BucketManager.t.sol delete mode 100644 test/CreditManager.t.sol delete mode 100644 test/LibBlob.t.sol delete mode 100644 test/LibBucket.t.sol mode change 100755 => 100644 test/scripts/wrapper_integration_test.sh diff --git a/README.md b/README.md index da37d92..afe84b4 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,9 @@ - [Mainnet](#mainnet) - [Development](#development) - [Scripts](#scripts) - - [Examples usage](#examples-usage) - - [Credit contract](#credit-contract) - - [Methods](#methods) - - [Examples](#examples) - - [Buckets contract](#buckets-contract) - - [Methods](#methods-1) - - [Examples](#examples-1) - - [Query objects](#query-objects) - - [Update object metadata](#update-object-metadata) - - [Blobs contract](#blobs-contract) - - [Methods](#methods-2) - [Testing](#testing) +- [Contributing](#contributing) +- [License](#license) ## Background @@ -38,16 +29,10 @@ for Recall. It includes the following: - `Recall.sol`: An ERC20 token implementation. - `Faucet.sol`: The accompanying onchain faucet (rate limiter) contract for dripping testnet funds. -- `CreditManager.sol`: Manage subnet credit, including credit purchases, approvals/rejections, and - related read-only operations (uses the `LibCredit` and `LibWasm` libraries). -- `BucketManager.sol`: Manage buckets, including creating buckets, listing buckets, querying - objects, and other object-related operations (uses the `LibBucket` and `LibWasm` libraries). - `ValidatorGater.sol`: A contract for managing validator access. -- `interfaces/ICredit.sol`: The interface for the credit contract. +- `interfaces/facades/`: The interfaces for facades, which handle bucket, blob, and credit + operations. - `types/`: Various method parameters and return types for core contracts. -- `utils/LibCredit.sol`: A library for interacting with credits, wrapped by the `Credit` contract. -- `utils/LibBucket.sol`: A library for interacting with buckets, wrapped by the `BucketManager` - contract. - `utils/LibWasm.sol`: A library for facilitating proxy calls to WASM contracts from Solidity. - `utils/solidity-cbor`: Libraries for encoding and decoding CBOR data, used in proxy WASM calls (forked from [this repo](https://github.com/smartcontractkit/solidity-cborutils)). @@ -56,20 +41,10 @@ for Recall. It includes the following: ### Deployments -The following contracts are deployed in the testnet environment (Filecoin Calibration or the Recall -subnet): +See the Recall docs for more information on the testnet and mainnet deployments: +[https://docs.recall.network/protocol/contracts](https://docs.recall.network/protocol/contracts). -| Contract | Chain | Address | -| -------------- | ----------- | -------------------------------------------- | -| Recall (ERC20) | Calibration | `0x20d8a696091153c4d4816ba1fdefe113f71e0905` | -| Faucet | Subnet | `0x7Aff9112A46D98A455f4d4F93c0e3D2438716A44` | -| BlobManager | Subnet | `0xTODO` | -| BucketManager | Subnet | `0xTODO` | -| CreditManager | Subnet | `0xTODO` | -| ValidatorGater | Subnet | `0x880126f3134EdFBa4f1a65827D5870f021bb7124` | - -To get testnet tokens, visit: -[https://faucet.node-0.testnet.recall.network](https://faucet.node-0.testnet.recall.network). Also, +To get testnet tokens, visit: [https://faucet.recall.network](https://faucet.recall.network). Also, you can check out the `foundry.toml` file to see the RPC URLs for each network (described in more detail below). @@ -90,8 +65,7 @@ directory: ```shell pnpm install -forge install -forge build +pnpm build ``` Also, you can clean the build artifacts with: @@ -107,9 +81,7 @@ The scripts for deploying contracts are in `script/` directory: - `Recall.s.sol`: Deploy the Recall ERC20 contract. - `Faucet.s.sol`: Deploy the faucet contract. - `ValidatorGater.s.sol`: Deploy the validator gater contract. -- `CreditManager.s.sol`: Deploy the credit contract. -- `BlobManager.s.sol`: Deploy the blobs contract. -- `BucketManager.s.sol`: Deploy the Bucket Manager contract. +- `ValidatorRewarder.s.sol`: Deploy the validator rewarder contract. - `Bridge.s.sol`: Deploy the bridge contract—relevant for the Recall ERC20 on live chains. > [!NOTE] If you're deploying _to_ the Recall subnet or Filecoin Calibration, you'll need to @@ -139,10 +111,10 @@ environment used by the `--sig` flag below: - `testnet`: Testnet chain - `ethereum` or `filecoin`: Mainnet chain (note: mainnet is not available yet) -Most scripts use the `--sig` flag with `run(string)` (or `run(string,uint256)` for the faucet) to -execute deployment with the given argument above, and the `--broadcast` flag actually sends the -transaction to the network. Recall that you **must** set `-g 100000` to ensure the gas estimate is -sufficiently high. +Most scripts use the `--sig` flag with `run()` (or `run(uint256)` for the faucet) to execute +deployment with the given argument above, and the `--broadcast` flag actually sends the transaction +to the network. Recall that you **must** set `-g 100000` to ensure the gas estimate is sufficiently +high. Mainnet deployments require the address of the Axelar Interchain Token Service on chain you are deploying to, which is handled in the ERC20's `DeployScript` logic. @@ -155,7 +127,7 @@ Deploy the Recall ERC20 contract to the localnet parent chain (i.e., `http://loc the `-g` flag is not used here since the gas estimate is sufficiently low on Anvil. ```shell -forge script script/Recall.s.sol --tc DeployScript --sig 'run(string)' local --rpc-url localnet_parent --private-key $PRIVATE_KEY --broadcast -vv +forge script script/Recall.s.sol --tc DeployScript --sig 'run()' --rpc-url localnet_parent --private-key $PRIVATE_KEY --broadcast -vv ``` ##### Faucet @@ -168,30 +140,6 @@ Recall tokens, owned by the deployer's account which will be transferred to the PRIVATE_KEY=<0x...> forge script script/Faucet.s.sol --tc DeployScript --sig 'run(uint256)' 5000000000000000000000 --rpc-url localnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv ``` -##### Credit - -Deploy the Credit contract to the localnet subnet: - -```shell -forge script script/CreditManager.s.sol --tc DeployScript --sig 'run()' --rpc-url localnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv -``` - -##### Buckets - -Deploy the Bucket Manager contract to the localnet subnet: - -```shell -forge script script/BucketManager.s.sol --tc DeployScript --sig 'run()' --rpc-url localnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv -``` - -##### Blobs - -Deploy the Blob Manager contract to the localnet subnet: - -```shell -forge script script/BlobManager.s.sol --tc DeployScript --sig 'run()' --rpc-url localnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv -``` - #### Testnet ##### Recall ERC20 @@ -200,7 +148,7 @@ Deploy the Recall ERC20 contract to the testnet parent chain. Note the `-g` flag (this differs from the localnet setup above since we're deploying to Filecoin Calibration); ```shell -forge script script/Recall.s.sol --tc DeployScript --sig 'run(string)' testnet --rpc-url testnet_parent --private-key $PRIVATE_KEY --broadcast -g 100000 -vv +forge script script/Recall.s.sol --tc DeployScript --sig 'run()' --rpc-url testnet_parent --private-key $PRIVATE_KEY --broadcast -g 100000 -vv ``` ##### Faucet @@ -213,30 +161,6 @@ Recall tokens, owned by the deployer's account which will be transferred to the forge script script/Faucet.s.sol --tc DeployScript --sig 'run(uint256)' 5000000000000000000000 --rpc-url testnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv ``` -##### Credit - -Deploy the Credit Manager contract to the testnet subnet: - -```shell -forge script script/CreditManager.s.sol --tc DeployScript --sig 'run()' --rpc-url testnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv -``` - -##### Buckets - -Deploy the Bucket Manager contract to the testnet subnet: - -```shell -forge script script/BucketManager.s.sol --tc DeployScript --sig 'run()' --rpc-url testnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv -``` - -##### Blobs - -Deploy the Blob Manager contract to the testnet subnet: - -```shell -forge script script/BlobManager.s.sol --tc DeployScript --sig 'run()' --rpc-url testnet_subnet --private-key $PRIVATE_KEY --broadcast -g 100000 -vv -``` - #### Devnet The devnet does not have the concept of a "parent" chain, so all RPCs would use `--rpc-url devnet` @@ -246,7 +170,7 @@ If you're trying to simply deploy to an Anvil node (i.e., `http://localhost:8545 same pattern, or just explicitly set the RPC URL: ```shell -forge script script/Recall.s.sol --tc DeployScript --sig 'run(string)' local --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY --broadcast -vv +forge script script/Recall.s.sol --tc DeployScript --sig 'run()' --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY --broadcast -vv ``` #### Mainnet @@ -262,13 +186,13 @@ use the following. Note these will enable behavior for the Axelar Interchain Tok Deploy to Ethereum: ```shell -forge script script/Recall.s.sol:DeployScript --sig 'run(string)' ethereum --rpc-url https://eth.merkle.io --private-key $PRIVATE_KEY --broadcast -vv +forge script script/Recall.s.sol:DeployScript --sig 'run()' --rpc-url https://eth.merkle.io --private-key $PRIVATE_KEY --broadcast -vv ``` And for Filecoin: ```shell -forge script script/Recall.s.sol:DeployScript --sig 'run(string)' filecoin --rpc-url https://api.node.glif.io/rpc/v1 --private-key $PRIVATE_KEY --broadcast -vv +forge script script/Recall.s.sol:DeployScript --sig 'run()' --rpc-url https://api.node.glif.io/rpc/v1 --private-key $PRIVATE_KEY --broadcast -vv ``` ## Development @@ -282,989 +206,6 @@ Deployment scripts are described above. Additional `pnpm` scripts are available - `test`: Run `forge test` to run the tests. - `clean`: Run `forge clean` to clean the build artifacts. -### Examples usage - -Below are examples for interacting with various contracts. We'll set a few environment variables to -demonstrate usage patterns, all of which will assume you've first defined the following. We'll first -set the `PRIVATE_KEY` to the hex-encoded private key (same as with the deployment scripts above), -and an arbitrary `EVM_ADDRESS` that is the public key of this private key. - -```sh -export PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 -export EVM_ADDRESS=0x90f79bf6eb2c4f870365e785982e1f101e93b906 -``` - -The `--rpc-url` flag can use the same RPC URLs as the deployments scripts, as defined in -`foundry.toml`. For simplicity sake, we'll define an environment variable `ETH_RPC_URL` set to one -of these RPC URLs so these examples can be run as-is: - -```sh -export ETH_RPC_URL=testnet_subnet -``` - -The subsequent sections will define other environment variables as needed. - -### Credit contract - -You can interact with the existing credit contract on the testnet via the address above. If you're -working on `localnet`, you'll have to deploy this yourself. Here's a quick one-liner to do so—also -setting the `CREDIT` environment variable to the deployed address: - -``` -CREDIT=$(forge script script/CreditManager.s.sol \ ---tc DeployScript \ ---sig 'run()' \ ---rpc-url localnet_subnet \ ---private-key $PRIVATE_KEY \ ---broadcast \ --g 100000 \ -| grep "0: contract CreditManager" | awk '{print $NF}') -``` - -#### Methods - -The following methods are available on the credit contract, shown with their function signatures. -Note that overloads are available for some methods, primarily, where the underlying WASM contract -accepts "optional" arguments. All of the method parameters and return types can be found in -`util/CreditTypes.sol`. - -- `getAccount(address)`: Get credit account info for an address. -- `getCreditStats()`: Get credit stats. -- `getCreditApproval(address,address)`: Get credit approval `from` one account `to` another, if it - exists. -- `getCreditBalance(address)`: Get credit balance for an address. -- `buyCredit()`: Buy credit for the `msg.sender`. -- `buyCredit(address)`: Buy credit for the given address. -- `approveCredit(address)`: Approve credit for an address (`to`), assuming `msg.sender` is the owner - of the credit (inferred as `from` in underlying logic). -- `approveCredit(address,address)`: Approve credit for the credit owner (`from`) for an address - (`to`). Effectively, the same as `approveCredit(address)` but explicitly sets the `from` address. -- `approveCredit(address,address,address[])`: Approve credit for the credit owner (`from`) for an - address (`to`), with a restriction on the caller address (`caller`) (e.g., enforce `to` can only - use credit at `caller`). -- `approveCredit(address,address,address[],uint256,uint256,uint64)`: Approve credit for the credit - owner (`from`) for an address (`to`), providing all of the optional fields (`caller`, - `creditLimit`, `gasFeeLimit`, and `ttl`). -- `setCreditSponsor(address,address)`: Set the credit sponsor for an address (`from`) to an address - (`sponsor`, use zero address if unused). -- `revokeCredit(address)`: Revoke credit for an address (`to`), assuming `msg.sender` is the owner - of the credit (inferred as `from` in underlying logic). -- `revokeCredit(address,address)`: Revoke credit for the credit owner (`from`) for an address - (`to`). Effectively, the same as `approveCredit(address)` but explicitly sets the `from` address. -- `revokeCredit(address,address,address)`: Revoke credit for the credit owner (`from`) for an - address (`to`), with a restriction on the caller address (`caller`) (e.g., remove permissions for - `to` at `caller`). - -The overloads above have a bit of nuance when it comes to "optional" arguments. See the `ICredit` -interface in `interfaces/ICredit.sol` for more details. For example, zero values are interpreted as -null values when encoded in WASM calls. - -#### Examples - -Make sure you've already set the `PRIVATE_KEY`, `EVM_ADDRESS`, and `ETH_RPC_URL` environment -variables. Then, define a `CREDIT` environment variable, which points to the credit contract -deployment address. For example: - -```sh -export CREDIT=0xAfC2973fbc4213DA7007A6b9459003A89c9C5b0E -``` - -And lastly, we'll define a `RECEIVER_ADDR` environment variable, which points to the `to` address -we'll be approving and revoking credit for. For example: - -```sh -export RECEIVER_ADDR=0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 -``` - -##### Get account info - -We can get the credit account info for the address at `EVM_ADDRESS` (the variable we set above), or -you could provide any account's EVM public key that exists in the subnet. - -```sh -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) -``` - -This will return the following values: - -``` -(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]) -``` - -Which maps to the `Account` struct: - -```solidity -struct Account { - uint256 capacityUsed; // 6 - uint256 creditFree; // 4999999999999999454276000000000000000000 - uint256 creditCommitted; // 504150000000000000000000 - address creditSponsor; // 0x0000000000000000000000000000000000000000 (null) - uint64 lastDebitEpoch; // 7200 - Approval[] approvalsTo; // See Approval struct below - Approval[] approvalsFrom; // [] (empty) - uint64 maxTtl; // 86400 - uint256 gasAllowance; // 4999999984799342175554 -} -``` - -The `approvals` array is empty if no approvals have been made. However, our example _does_ have -approvals authorized. We can expand this to be interpreted as the following: - -```solidity -struct Approval { - address addr; // 0x90F79bf6EB2c4f870365E785982E1f101E93b906 - CreditApproval approval; // See CreditApproval struct below -} - -struct CreditApproval { - uint256 creditLimit; // 12345000000000000000000 - uint256 gasFeeLimit; // 987654321 - uint64 expiry; // 11722 - uint256 creditUsed; // 0 - uint256 gasFeeUsed; // 0 -} -``` - -Due to intricacies with optional arguments in WASM being used in Solidity, you can interpret zero -values as null values in the structs above. That is, the example address has no restrictions on the -`limit` or `expiry` with using the delegated/approved credit from the owner's account. - -##### Get credit stats - -We can fetch the overall credit stats for the subnet with the following command: - -```sh -cast abi-decode "getCreditStats()((uint256,uint256,uint256,uint256,uint64,uint64))" $(cast call --rpc-url $ETH_RPC_URL $CREDIT "getCreditStats()") -``` - -This will return the following values: - -``` -(50000999975762821509530 [5e22], 50001000000000000000000 [5e22], 21600 [2.16e4], 24237178535296 [2.423e13], 1000000000000000000000000000000000000 [1e36], 10) -``` - -Which maps to the `CreditStats` struct: - -```solidity -struct CreditStats { - uint256 balance; // 50000999975762821509530 - uint256 creditSold; // 50001000000000000000000 - uint256 creditCommitted; // 21600 - uint256 creditDebited; // 24237178535296 - uint256 tokenCreditRate; // 1000000000000000000000000000000000000 - uint64 numAccounts; // 10 -} -``` - -##### Get credit approval for an account - -Get the credit approval from the address at `EVM_ADDRESS` to the address at `RECEIVER_ADDR`: - -```sh -cast abi-decode "getCreditApproval(address,address)((uint256,uint256,uint64,uint256,uint256))" $(cast call --rpc-url $ETH_RPC_URL $CREDIT "getCreditApproval(address,address)" $EVM_ADDRESS $RECEIVER_ADDR) -``` - -This will return the following values: - -``` -(100000000000000000000000000 [1e26], 1000, 7275, 0, 0) -``` - -Which maps to the `CreditApproval` struct: - -```solidity -struct CreditApproval { - uint256 creditLimit; // 100000000000000000000000000 - uint256 gasFeeLimit; // 1000 - uint64 expiry; // 7275 - uint256 creditUsed; // 0 - uint256 gasFeeUsed; // 0 -} -``` - -##### Get credit balance for an account - -Fetch the credit balance for the address at `EVM_ADDRESS`: - -```sh -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) -``` - -This will return the following values: - -``` -(5001999999999998208637000000000000000000 [5.001e39], 518400000000000000000000 [5.184e23], 0x0000000000000000000000000000000000000000, 6932, [(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65, (0, 0, 0, 0, 0))], 1) -``` - -Which maps to the `Balance` struct: - -```solidity -struct Balance { - uint256 creditFree; // 5001999999999998208637000000000000000000 - uint256 creditCommitted; // 518400000000000000000000 - address creditSponsor; // 0x0000000000000000000000000000000000000000 (null) - uint64 lastDebitEpoch; // 6932 - Approval[] approvals; // See Approval struct below -} - -struct Approval { - string to; // 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 - CreditApproval approval; // See CreditApproval struct below -} - -struct CreditApproval { - uint256 creditLimit; // 0 - uint256 gasFeeLimit; // 0 - uint64 expiry; // 0 - uint256 creditUsed; // 0 - uint256 gasFeeUsed; // 0 -} -``` - -##### Buy credit for an address - -You can buy credit for your address with the following command, which will buy credit equivalent to -1 native subnet token (via `msg.value`) for the `msg.sender`: - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "buyCredit()" --value 1ether --private-key $PRIVATE_KEY -``` - -Or, you can buy credit for a specific EVM address with the following command: - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "buyCredit(address)" $EVM_ADDRESS --value 1ether --private-key $PRIVATE_KEY -``` - -##### Approve credit for an address - -Approving credit has a few variations. The first variation is approving credit for the address -defined in the call, assuming the `msg.sender` is the owner of the credit. The `RECEIVER_ADDR` -address is the `to` we want to approve credit for (defined as -`0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65` above). - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "approveCredit(address)" $RECEIVER_ADDR --private-key $PRIVATE_KEY -``` - -There also exists `approveCredit(address,address)` and `approveCredit(address,address,address[])`, -which inherently assumes a null value for the `limit` and `ttl` fields, and the order of the -addresses is `from`, `to`, and `caller` (for the latter variation). Here's an example using the -latter variation, effectively the same as the former due to the use of the zero address: - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "approveCredit(address,address,address[])" $EVM_ADDRESS $RECEIVER_ADDR '[]' --private-key $PRIVATE_KEY -``` - -If, instead, we wanted to also restrict how the `to` can use the credit, we would set the `caller` -(e.g., a contract address at `0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc`): - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "approveCredit(address,address,address[])" $EVM_ADDRESS $RECEIVER_ADDR '[0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc]' --private-key $PRIVATE_KEY -``` - -This would restrict the `to` to only be able to use the approved `from` address at the `caller` -address. - -> [!NOTE] The `caller` can, in theory, be an EVM or WASM contract address. However, the logic -> assumes only an EVM address is provided. Namely, it is _generally_ possible to restrict the -> `caller` to a specific WASM contract (e.g., bucket with `t0...` prefix), but the current Solidity -> implementation does not account for this and only assumes an EVM address. - -Lastly, if we want to include all of the optional fields, we can use the following command: - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "approveCredit(address,address,address[],uint256,uint256,uint64)" $EVM_ADDRESS $RECEIVER_ADDR '[]' 100 100 3600 --private-key $PRIVATE_KEY -``` - -This includes the `creditLimit` field set to `100` credit, the `gasFeeLimit` set to `100` gas fee, -and the `ttl` set to `3600` seconds (`1` hour). If either of these should instead be null, just set -them to `0`. - -##### Set credit sponsor for an address - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "setCreditSponsor(address,address)" $EVM_ADDRESS $RECEIVER_ADDR --private-key $PRIVATE_KEY -``` - -This will set the credit sponsor for the `from` address to the `sponsor` address. - -##### Revoke credit for an address - -Revoking credit is the opposite of approving credit and also has a few variations. The simplest form -is revoking credit for the address defining in the call (`to`), which assumes the `msg.sender` is -the owner of the credit. - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "revokeCredit(address)" $RECEIVER_ADDR --private-key $PRIVATE_KEY -``` - -The other variants are `revokeCredit(address,address)` and `revokeCredit(address,address,address)`. -Just like `approveCredit`, the order is: `from`, `to`, and `caller`. Here's an example using the -latter variation: - -```sh -cast send --rpc-url $ETH_RPC_URL $CREDIT "revokeCredit(address,address,address)" $EVM_ADDRESS $RECEIVER_ADDR 0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc --private-key $PRIVATE_KEY -``` - -This would revoke the `to`'s ability to use the `from` address at the `caller` address. - -### Buckets contract - -You can interact with the existing buckets contract on the testnet via the address above. If you're -working on `localnet`, you'll have to deploy this yourself. Here's a quick one-liner to do so—also -setting the `BUCKETS` environment variable to the deployed address: - -``` -BUCKETS=$(forge script script/BucketManager.s.sol \ ---tc DeployScript \ ---sig 'run()' \ ---rpc-url localnet_subnet \ ---private-key $PRIVATE_KEY \ ---broadcast \ --g 100000 \ -| grep "0: contract BucketManager" | awk '{print $NF}') -``` - -#### Methods - -The following methods are available on the credit contract, shown with their function signatures. - -- `createBucket()`: Create a bucket for the sender. -- `createBucket(address)`: Create a bucket for the specified address. -- `createBucket(address,(string,string)[])`: Create a bucket for the specified address with - metadata. -- `listBuckets()`: List all buckets for the sender. -- `listBuckets(address)`: List all buckets for the specified address. -- `addObject(address,string,string,string,uint64,address)`: Add an object to a bucket and associated - object upload parameters. The first value is the bucket address, the subsequent values are all of - the "required" values in `AddObjectParams` (`source` node ID, `key`, `blobHash`, and `size`). -- `addObject(address,(string,string,string,string,uint64,uint64,(string,string)[],bool,address))`: - Add an object to a bucket (first value) and associated object upload parameters (second value) as - the `AddObjectParams` struct, described in more detail below. -- `deleteObject(address,string,address)`: Remove an object from a bucket. -- `getObject(address,string)`: Get an object from a bucket. -- `queryObjects(address)`: Query the bucket (hex address) with no prefix (defaults to `/` delimiter - and the default offset and limit in the underlying WASM layer). -- `queryObjects(address,string)`: Query the bucket with a prefix (e.g., `/` string value), - but no delimiter, offset, or limit. -- `queryObjects(address,string,string)`: Query the bucket with a custom delimiter (e.g., something - besides the default `/` delimeter value), but default offset and limit. -- `queryObjects(address,string,string,uint64)`: Query the bucket with a prefix and delimiter, but no - limit. -- `queryObjects(address,string,string,uint64,uint64)`: Query the bucket with a prefix, delimiter, - offset, and limit. -- `updateObjectMetadata(address,string,(string,string)[],address)`: Update the metadata of an - object. - -#### Examples - -Make sure you've already set the `PRIVATE_KEY`, `EVM_ADDRESS`, and `ETH_RPC_URL` environment -variables. Then, define a `BUCKETS` environment variable, which points to the bucket contract -deployment address. For example: - -```sh -export BUCKETS=0x314512a8692245cf507ac6E9d0eB805EA820d9a8 -``` - -The account you use to create buckets should have the following: - -- A RECALL token balance in the subnet (e.g., from the faucet at: - [https://faucet.node-0.testnet.recall.network](https://faucet.node-0.testnet.recall.network)). You - can verify this with the [Recall CLI](https://github.com/recallnet/rust-recall): - `recall account info` -- A credit balance in the subnet. You can verify this with `recall credit balance`. If you don't - have credits, you can buy them with the Credits contract above, or run the - `recall credit buy ` command. - -Creating a bucket will cost native RECALL tokens, and writing to it will cost credit. - -##### Create a bucket - -To create a bucket, you can use the following command: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "createBucket(address)" $EVM_ADDRESS --private-key $PRIVATE_KEY -``` - -This will execute an onchain transaction to create a bucket for the provided address. Alternatively, -you can create a bucket for the sender with the following command: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "createBucket()" --private-key $PRIVATE_KEY -``` - -To create a bucket with metadata, you can use the following command, where each metadata value is a -`KeyValue` (a pair of strings) within an array—something like `[("alias","foo")]`: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "createBucket(address,(string,string)[])" $EVM_ADDRESS '[("alias","foo")]' --private-key $PRIVATE_KEY -``` - -##### List buckets - -You can list buckets for a specific address with the following command. Note you can use the -overloaded `listBuckets()` to list buckets for the sender. - -```sh -cast abi-decode "listBuckets(address)((uint8,address,(string,string)[])[])" $(cast call --rpc-url $ETH_RPC_URL $BUCKETS "listBuckets(address)" $EVM_ADDRESS) -``` - -This will return the following output: - -``` -(0, 0xff000000000000000000000000000000000000ed, [("foo", "bar")]) -``` - -Which maps to an array of the `Machine` struct: - -```solidity -struct Machine { - Kind kind; // See `Kind` struct below - address addr; // 0xff000000000000000000000000000000000000ed - KeyValue[] metadata; // See `KeyValue` struct below -} - -struct Kind { - Bucket, // 0 == `Bucket` - Timehub, // 1 == `Timehub` -} - -struct KeyValue { - string key; // "foo" - string value; // "bar" -} -``` - -##### Add an object - -Given a bucket address, you can read or mutate the objects in the bucket. First, we'll simply add an -object, setting `BUCKET_ADDR` to an existing bucket from the command above: - -```sh -export BUCKET_ADDR=0xff0000000000000000000000000000000000008f -``` - -Adding an object is a bit involved. You need to stage data offchain to a `source` bucket storage -node ID address, which will return the hashed value (`blobHash`) of the staged data and its -corresponding `size` in bytes. You then pass all of these as parameters when you add an object to -the bucket. - -In the example below, we've already staged this data offchain and are using the following: - -- `source`: The node ID address (e.g., `cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq`). -- `blobHash`: The hash of the staged data (e.g., - `rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq` is the base32 encoded blake3 hashed value - of our file contents, which contains the string `hello`). -- `recoveryHash`: Blake3 hash of the metadata to use for object recovery (note: this is currently - hardcoded, so you can pass an empty string value here). -- `size`: The size of the data in bytes (e.g., `6`, which is the number of bytes in the `hello` - string). - -We also include custom parameters for the bucket key, metadata, TTL, and overwrite flag: - -- `key`: The key to assign to the object in the bucket (`hello/world`). -- `metadata`: The metadata to assign to the object in the bucket (`[("foo","bar")]`). -- `overwrite`: The overwrite flag to assign to the object in the bucket (`false`). - -This all gets passed as a single `AddObjectParams` struct to the `add` method: - -```solidity -struct AddObjectParams { - string source; // cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq - string key; // hello/world - string blobHash; // rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq - string recoveryHash; // (note: this is currently hardcoded to an empty string) - uint64 size; // 6 - uint64 ttl; // 0 (which is interpreted as null) - KeyValue[] metadata; // [("foo","bar")] - bool overwrite; // false -} -``` - -We then pass this as a single parameter to the `add` method: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "addObject(string,(string,string,string,string,uint64,uint64,(string,string)[],bool))" $BUCKET_ADDR '("cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq","hello/world","rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq","",6,0,[("foo","bar")],false)' --private-key $PRIVATE_KEY -``` - -Alternatively, to use the overloaded `add` method that has default values for the `ttl`, `metadata`, -and `overwrite`, you can do the following: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "addObject(string,string,string,string,string,uint64)" $BUCKET_ADDR "cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq" "hello/world" "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" "" 6 --private-key $PRIVATE_KEY -``` - -If you're wondering where to get the `source` storage bucket's node ID (the example's -`cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq`), you can find it with a `curl` request. On -localnet, this looks like the following: - -```sh -curl http://localhost:8001/v1/node | jq '.node_id' -``` - -Or on testnet, you'd replace the URL with public bucket API endpoint -`https://objects.node-0.testnet.recall.network`. - -##### Delete an object - -Similar to [getting an object](#get-an-object), you can delete an object with the following command, -specifying the bucket and key for the mutating transaction: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "deleteObject(address,string)" $BUCKET_ADDR "hello/world" --private-key $PRIVATE_KEY -``` - -##### Get an object - -Getting a single object is similar to the response of `query`, except only a single object is -returned. Thus, the response simply includes a single value. The `BUCKET_ADDR` is the same one from -above. - -```sh -cast abi-decode "getObject(address,string)((string,string,uint64,uint64,(string,string)[]))" $(cast call --rpc-url $ETH_RPC_URL $BUCKETS "getObject(address,string)" $BUCKET_ADDR "hello/world") -``` - -This will return the following response: - -```sh -("rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq", "utiakbxaag7udhsriu6dm64cgr7bk4zahiudaaiwuk6rfv43r3rq", 6, 103381 [1.033e5], [("foo","bar")]) -``` - -Which maps to the `Value` struct: - -```solidity -struct ObjectValue { - string blobHash; // "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" - string recoveryHash; // "utiakbxaag7udhsriu6dm64cgr7bk4zahiudaaiwuk6rfv43r3rq" - uint64 size; // 6 - uint64 expiry; // 103381 - KeyValue[] metadata; // See `KeyValue` struct below -} - -struct KeyValue { - string key; // "foo" - string value; // "bar" -} -``` - -#### Query objects - -We'll continue using the same `BUCKET_ADDR` from the previous examples. - -```sh -cast abi-decode "queryObjects(address)(((string,(string,uint64,uint64,(string,string)[]))[],string[],string))" $(cast call --rpc-url $ETH_RPC_URL $BUCKETS "queryObjects(address)" $BUCKET_ADDR) -``` - -This will return the following `Query` output: - -``` -([], ["hello/"], "") -``` - -Where the first array is an empty set of objects, and the second array is the common prefixes in the -bucket: - -```solidity -struct Query { - Object[] objects; // Empty array if no objects - string[] commonPrefixes; // ["hello/"] - string nextKey; // "" -} -``` - -In the next example, we'll set `PREFIX` to query objects with the prefix `hello/`: - -```sh -export PREFIX="hello/" -``` - -Now, we can query for these objects with the following command: - -```sh -cast abi-decode "queryObjects(address,string)(((string,(string,uint64,uint64,(string,string)[]))[],string[],string))" $(cast call --rpc-url $ETH_RPC_URL $BUCKETS "queryObjects(address,string)" $BUCKET_ADDR $PREFIX) -``` - -This will return the following `Query` output: - -``` -([("hello/world", ("rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq", 6, [("foo", "bar")]))], [], "") -``` - -Which maps to the following structs: - -```solidity -struct Query { - Object[] objects; // See `Object` struct below - string[] commonPrefixes; // Empty array if no common prefixes - string nextKey; // Null value (empty string `""`) -} - -struct Object { - string key; // "hello/world" - ObjectState state; // See `ObjectState` struct below -} - -struct ObjectState { - string blobHash; // "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" - uint64 size; // 6 - KeyValue[] metadata; // See `KeyValue` struct below -} - -struct KeyValue { - string key; // "foo" - string value; // "bar" -} -``` - -#### Update object metadata - -You can update the metadata of an existing object with the following command: - -```sh -cast send --rpc-url $ETH_RPC_URL $BUCKETS "updateObjectMetadata(address,string,(string,string)[],address)" $BUCKET_ADDR "hello/world" '[("alias","foo")]' $EVM_ADDRESS --private-key $PRIVATE_KEY -``` - -### Blobs contract - -You can interact with the existing blobs contract on the testnet via the address above. If you're -working on `localnet`, you'll have to deploy this yourself. Here's a quick one-liner to do so—also -setting the `BLOBS` environment variable to the deployed address: - -``` -BLOBS=$(forge script script/BlobManager.s.sol \ ---tc DeployScript \ ---sig 'run()' \ ---rpc-url localnet_subnet \ ---private-key $PRIVATE_KEY \ ---broadcast \ --g 100000 \ -| grep "0: contract BlobManager" | awk '{print $NF}') -``` - -#### Methods - -The following methods are available on the credit contract, shown with their function signatures. -Note that overloads are available for some methods, primarily, where the underlying WASM contract -accepts "optional" arguments. All of the method parameters and return types can be found in -`util/CreditTypes.sol`. - -- `addBlob(AddBlobParams memory params)`: Store a blob directly on the network. This is described in - more detail below, and it involves a two-step approach with first staging data with the blob - storage node, and then passing related values onchain. -- `deleteBlob(address,string,string,address)`: Delete a blob from the network, passing the sponsor's - address, the blob hash, and the subscription ID (either `""` if none was originally provided, or - the string that was chosen during `addBlob`). -- `overwriteBlob(string,AddBlobParams memory)`: Overwrite a blob from the network, passing the old - blob hash, and the new blob parameters. -- `getBlob(string)`: Get information about a specific blob at its blake3 hash. -- `getBlobStatus(address,string,string)`: Get a blob's status, providing its credit sponsor (i.e., - the account's `address`, or `address(0)` if null), its blake3 blob hash (the first `string` - parameter), and its blob hash key (an empty string `""` to indicate the default, or the key used - upon creating the blob). -- `getPendingBlobs()`: Get the values of pending blobs across the network. -- `getPendingBlobsCount(uint32)`: Get the number of pending blobs across the network, up to a limit. -- `getPendingBytesCount()`: Get the total number of pending bytes across the network. -- `getStorageStats()`: Get storage stats. -- `getStorageUsage(address)`: Get storage usage for an address. -- `getSubnetStats()`: Get subnet stats. - -##### Add a blob - -Adding a blob is a bit involved. You need to stage data offchain to a `source` blob storage node ID -address, which will return the hashed value (`blobHash`) of the staged data and its corresponding -`size` in bytes. You then pass all of these as parameters when you add an object to the bucket. - -In the example below, we've already staged this data offchain and are using the following: - -- `sponsor`: Optional sponsor address. E.g., if you have credits, you don't need to pass this, but - if someone has approve for you to use credits, you can specify the credit sponsor here. -- `source`: The storage node ID address (e.g., - `cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq`). -- `blobHash`: The blake3 hash of the staged data (e.g., - `rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq` is the base32 encoded blake3 hashed value - of our file contents, which contains the string `hello`). -- `metadataHash`: Blake3 hash of the metadata to use for blob recovery (hardcoded, so you can pass - an empty string value here). -- `subscriptionId`: Identifier used to differentiate blob additions for the same subscriber. You can - pass an empty string, indicating the default value (`Default`). Or, passing a string value, which - under the hood, will use this key value (`Key(Vec)`) for the hashmap key. -- `size`: The size of the data in bytes (e.g., `6`, which is the number of bytes in the `hello` - string). -- `ttl`: Blob time-to-live epochs. If specified as `0`, the auto-debitor maintains about one hour of - credits as an ongoing commitment. -- `from`: The address of the account to use for the transaction. - -This all gets passed as a single `AddBlobParams` struct to the `addBlob` method: - -```solidity -struct AddBlobParams { - address sponsor; // `address(0)` for default/null, or the credit sponsor's address - string source; // cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq - string blobHash; // rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq - string metadataHash; // (note: this is currently hardcoded to an empty string) - string subscriptionId; // use `""` for the default, or pass a string value - uint64 size; // 6 - uint64 ttl; // 0 (which is interpreted as null) - address from; // 0x90F79bf6EB2c4f870365E785982E1f101E93b906 -} -``` - -We then pass this as a single parameter to the `add` method: - -```sh -cast send --rpc-url $ETH_RPC_URL $BLOBS "addBlob((address,string,string,string,string,uint64,uint64,address))" "(0x0000000000000000000000000000000000000000,"cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq","rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq",\"\",\"\",6,0,$EVM_ADDRESS)" --private-key $PRIVATE_KEY -``` - -To include a custom subscription ID, you would replace the empty string (which indicates `default`) -in the call above, like so: `(...,"rzgh...","","my_custom_id",6,0,...)`. - -If you're wondering where to get the `source` storage bucket's node ID (the example's -`cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq`), you can find it with a `curl` request. On -localnet, this looks like the following: - -```sh -curl http://localhost:8001/v1/node | jq '.node_id' -``` - -Or on testnet, you'd replace the URL with public bucket API endpoint -`https://objects.node-0.testnet.recall.network`. - -##### Delete a blob - -You can a delete a blob you've created with the following, passing the sponsor's address (zero -address if null), the blob's blake3 hash, and the subscription ID (either the default empty string -`""` or the string you passed during `addBlob`). - -```sh -cast send --rpc-url $ETH_RPC_URL $BLOBS "deleteBlob(address,string,string)" 0x0000000000000000000000000000000000000000 "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" "" --private-key $PRIVATE_KEY -``` - -This will emit a `DeleteBlob` event and delete the blob from the network. - -##### Overwrite a blob - -You can overwrite a blob you've created with the following, passing the old blob's blake3 hash, and -the new blob's parameters. - -```sh -cast send --rpc-url $ETH_RPC_URL $BLOBS "overwriteBlob(string,(address,string,string,string,string,uint64,uint64,adderss))" "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" '(0x0000000000000000000000000000000000000000,"cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq","rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq","","",6,0,0x90F79bf6EB2c4f870365E785982E1f101E93b906)' --private-key $PRIVATE_KEY -``` - -This will emit an `OverwriteBlob` event and overwrite the blob in the network. - -##### Get a blob - -```sh -cast abi-decode "getBlob(string)((uint64,string,(string,uint64)[],uint8))" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getBlob(string)" "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq") -``` - -This will return the following response: - -```sh -6, "utiakbxaag7udhsriu6dm64cgr7bk4zahiudaaiwuk6rfv43r3rq", [("1bce5a746a1134bf082baac80be951906bb406787e14c9fee0d7d8db0fc0b7f4", 98266 [9.826e4])], 2) -``` - -Which maps to the `Blob` struct: - -```solidity -struct Blob { - uint64 size; // 6 - string metadataHash; // "utiakbxaag7udhsriu6dm64cgr7bk4zahiudaaiwuk6rfv43r3rq" - Subscriber[] subscribers; // See `Subscriber` struct below - BlobStatus status; // 2 (Resolved) -} - -struct Subscriber { - string subscriptionId; // "1bce5a746a1134bf082baac80be951906bb406787e14c9fee0d7d8db0fc0b7f4" - uint64 expiry; // 98266 -} -``` - -##### Get blob status - -- Pass an address as the first field to represent the origin address that requested the blob. -- Provide the `blobHash` blake3 value. -- Also give the `subscriptionId`, which uses the default empty value if you provide an empty string - `""`, or it can take the string that matches the blob's `subscriptionId` upon creation. - -```sh -cast abi-decode "getBlobStatus(address,string,string)(uint8)" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getBlobStatus(address,string,string)" $EVM_ADDRESS "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" "") -``` - -This will return the following response (either a `0` for `Added`, `1` for `Pending`, `2` for -`Resolved`, or `3` for `Failed`): - -```sh -2 -``` - -Which maps to the `BlobStatus` enum: - -```solidity -enum BlobStatus { - Added, // 0 - Pending, // 1 - Resolved, // 2 -- the value above - Failed // 3 -} -``` - -##### Get added blobs - -```sh -cast abi-decode "getAddedBlobs(uint32)((string,(address,string,string)[])[])" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getAddedBlobs(uint32)" 1) -``` - -This returns the values of added blobs, up to the `size` passed as the parameter: - -```sh -[("rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq", [(0x90F79bf6EB2c4f870365E785982E1f101E93b906, "default", "cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq")])] -``` - -Which maps to an array of the `BlobTuple` struct: - -```solidity -struct BlobTuple { - string blobHash; // "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" - BlobSourceInfo[] sourceInfo; // See `Subscriber` struct below -} - -struct BlobSourceInfo { - address subscriber; // 0x90F79bf6EB2c4f870365E785982E1f101E93b906 - string subscriptionId; // "default" - string source; // "cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq" -} -``` - -##### Get pending blobs - -```sh -cast abi-decode "getPendingBlobs(uint32)((string,(address,string,string)[])[])" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getPendingBlobs(uint32)" 1) -``` - -This returns the values of pending blobs, up to the `size` passed as the parameter: - -```sh -[("rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq", [(0x90F79bf6EB2c4f870365E785982E1f101E93b906, "default", "cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq")])] -``` - -Which maps to an array of the `BlobTuple` struct: - -```solidity -struct BlobTuple { - string blobHash; // "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq" - BlobSourceInfo[] sourceInfo; // See `Subscriber` struct below -} - -struct BlobSourceInfo { - address subscriber; // 0x90F79bf6EB2c4f870365E785982E1f101E93b906 - string subscriptionId; // "default" - string source; // "cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq" -} -``` - -##### Get pending blobs count - -```sh -cast abi-decode "getPendingBlobsCount()(uint64)" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getPendingBlobsCount()") -``` - -This returns the number of pending blobs: - -```sh -123 -``` - -##### Get pending bytes count - -```sh -cast abi-decode "getPendingBytesCount()(uint64)" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getPendingBytesCount()") -``` - -This returns the total number of bytes that are pending network resolution: - -```sh -987654321 -``` - -##### Get subnet stats - -We can fetch the overall subnet stats with the following command: - -```sh -cast abi-decode "getSubnetStats()((uint256,uint64,uint64,uint256,uint256,uint256,uint256,uint64,uint64,uint64,uint64,uint64,uint64))" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getSubnetStats()") -``` - -This will return the following values: - -``` -(50000999980767329202072 [5e22], 10995116277754 [1.099e13], 6, 50001000000000000000000000000000000000000 [5e40], 1002060000000000000000000 [1.002e24], 62064000000000000000000 [6.206e22], 1000000000000000000000000000000000000 [1e36], 10, 1, 0, 0, 0, 0) -``` - -Which maps to the `SubnetStats` struct: - -```solidity -struct SubnetStats { - uint256 balance; // 50000000000000000000000 - uint64 capacityFree; // 10995116277754 - uint64 capacityUsed; // 6 - uint256 creditSold; // 50001000000000000000000 - uint256 creditCommitted; // 21156 - uint256 creditDebited; // 25349384457000 - uint256 tokenCreditRate; // 1000000000000000000000000000000000000 - uint64 numAccounts; // 10 - uint64 numBlobs; // 1 - uint64 numResolving; // 0 - uint64 bytesResolving; // 0 - uint64 numAdded; // 0 - uint64 bytesAdded; // 0 -} -``` - -##### Get storage stats - -We can fetch the overall storage stats for the subnet with the following command: - -```sh -cast abi-decode "getStorageStats()((uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64))" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getStorageStats()") -``` - -This will return the following values: - -``` -(10995116277754 [1.099e13], 6, 1, 0, 10, 0, 0, 0) -``` - -Which maps to the `StorageStats` struct: - -```solidity -struct StorageStats { - uint64 capacityFree; // 10995116277754 - uint64 capacityUsed; // 6 - uint64 numBlobs; // 1 - uint64 numResolving; // 0 - uint64 numAccounts; // 10 - uint64 bytesResolving; // 0 - uint64 numAdded; // 0 - uint64 bytesAdded; // 0 -} -``` - -##### Get storage usage for an account - -Fetch the storage usage for the address at `EVM_ADDRESS`: - -```sh -cast abi-decode "getStorageUsage(address)(uint256)" $(cast call --rpc-url $ETH_RPC_URL $BLOBS "getStorageUsage(address)" $EVM_ADDRESS) -``` - -This will return the following values: - -``` -123 -``` - ### Testing You can run all of the unit tests with the following command: @@ -1291,3 +232,14 @@ test/scripts/wrapper_integration_test.sh If everything is working, you should see `All tests completed successfully` logged at the end. But, if there's an error (i.e., incompatability with the wrappers relative to the subnet's expectation), the script will exit early. + +## Contributing + +PRs accepted. Please ensure your changes follow our coding standards and include appropriate tests. + +Small note: If editing the README, please conform to the +[standard-readme](https://github.com/RichardLitt/standard-readme) specification. + +## License + +MIT OR Apache-2.0, © 2025 Recall Contributors diff --git a/script/BlobManager.s.sol b/script/BlobManager.s.sol deleted file mode 100644 index 9915f53..0000000 --- a/script/BlobManager.s.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Script} from "forge-std/Script.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {BlobManager} from "../src/wrappers/BlobManager.sol"; - -contract DeployScript is Script { - function run() public returns (BlobManager) { - vm.startBroadcast(); - - BlobManager blobs = new BlobManager(); - - vm.stopBroadcast(); - - return blobs; - } -} diff --git a/script/BucketManager.s.sol b/script/BucketManager.s.sol deleted file mode 100644 index 80f9fd1..0000000 --- a/script/BucketManager.s.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Script} from "forge-std/Script.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {BucketManager} from "../src/wrappers/BucketManager.sol"; - -contract DeployScript is Script { - function run() public returns (BucketManager) { - vm.startBroadcast(); - - BucketManager bucketManager = new BucketManager(); - - vm.stopBroadcast(); - - return bucketManager; - } -} diff --git a/script/CreditManager.s.sol b/script/CreditManager.s.sol deleted file mode 100644 index 9757a88..0000000 --- a/script/CreditManager.s.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Script} from "forge-std/Script.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {CreditManager} from "../src/wrappers/CreditManager.sol"; - -contract DeployScript is Script { - function run() public returns (CreditManager) { - vm.startBroadcast(); - - CreditManager credit = new CreditManager(); - - vm.stopBroadcast(); - - return credit; - } -} diff --git a/src/wrappers/LibWasm.sol b/src/util/LibWasm.sol similarity index 100% rename from src/wrappers/LibWasm.sol rename to src/util/LibWasm.sol diff --git a/src/wrappers/BlobManager.sol b/src/wrappers/BlobManager.sol deleted file mode 100644 index 18b90ed..0000000 --- a/src/wrappers/BlobManager.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {IBlobManager} from "../interfaces/IBlobManager.sol"; -import {AddBlobParams, Blob, BlobStatus, BlobTuple, StorageStats, SubnetStats} from "../types/BlobTypes.sol"; -import {LibBlob} from "./LibBlob.sol"; - -contract BlobManager is IBlobManager { - /// @dev See {IBlobManager-getAddedBlobs}. - function getAddedBlobs(uint32 size) external view returns (BlobTuple[] memory blobs) { - return LibBlob.getPendingBlobs(size); - } - - /// @dev See {IBlobManager-getBlob}. - function getBlob(string memory blobHash) external view returns (Blob memory blob) { - return LibBlob.getBlob(blobHash); - } - - /// @dev See {IBlobManager-getBlobStatus}. - function getBlobStatus(address subscriber, string memory blobHash, string memory subscriptionId) - external - view - returns (BlobStatus status) - { - return LibBlob.getBlobStatus(subscriber, blobHash, subscriptionId); - } - - /// @dev See {IBlobManager-getPendingBlobs}. - function getPendingBlobs(uint32 size) external view returns (BlobTuple[] memory blobs) { - return LibBlob.getPendingBlobs(size); - } - - /// @dev See {IBlobManager-getPendingBlobsCount}. - function getPendingBlobsCount() external view returns (uint64) { - return LibBlob.getPendingBlobsCount(); - } - - /// @dev See {IBlobManager-getPendingBytesCount}. - function getPendingBytesCount() external view returns (uint64) { - return LibBlob.getPendingBytesCount(); - } - - /// @dev See {IBlobManager-getStorageUsage}. - function getStorageUsage(address addr) external view returns (uint256) { - return LibBlob.getStorageUsage(addr); - } - - /// @dev See {IBlobManager-getStorageStats}. - function getStorageStats() external view returns (StorageStats memory stats) { - return LibBlob.getStorageStats(); - } - - /// @dev See {IBlobManager-getSubnetStats}. - function getSubnetStats() external view returns (SubnetStats memory stats) { - return LibBlob.getSubnetStats(); - } - - /// @dev See {IBlobManager-addBlob}. - function addBlob(AddBlobParams memory params) external { - LibBlob.addBlob(params); - } - - /// @dev See {IBlobManager-deleteBlob}. - function deleteBlob(address subscriber, string memory blobHash, string memory subscriptionId, address from) - external - { - LibBlob.deleteBlob(subscriber, blobHash, subscriptionId, from); - } - - /// @dev See {IBlobManager-overwriteBlob}. - function overwriteBlob(string memory oldHash, AddBlobParams memory params) external { - LibBlob.overwriteBlob(oldHash, params); - } -} diff --git a/src/wrappers/BucketManager.sol b/src/wrappers/BucketManager.sol deleted file mode 100644 index bace3f0..0000000 --- a/src/wrappers/BucketManager.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {IBucketManager} from "../interfaces/IBucketManager.sol"; -import {AddObjectParams, KeyValue, Machine, ObjectValue, Query} from "../types/BucketTypes.sol"; -import {LibBucket} from "./LibBucket.sol"; -import {LibWasm} from "./LibWasm.sol"; - -/// @title Bucket Manager Contract -/// @dev Implementation of the Recall Bucket actor EVM interface. See {IBucketManager} for details. -contract BucketManager is IBucketManager { - /// @dev See {IBucketManager-createBucket}. - function createBucket() external { - KeyValue[] memory metadata = new KeyValue[](0); - LibBucket.createBucket(msg.sender, metadata); - } - - /// @dev See {IBucketManager-createBucket}. - function createBucket(address owner) external { - KeyValue[] memory metadata = new KeyValue[](0); - LibBucket.createBucket(owner, metadata); - } - - /// @dev See {IBucketManager-createBucket}. - function createBucket(address owner, KeyValue[] memory metadata) external { - LibBucket.createBucket(owner, metadata); - } - - /// @dev See {IBucketManager-listBuckets}. - function listBuckets() external view returns (Machine[] memory) { - return LibBucket.listBuckets(msg.sender); - } - - /// @dev See {IBucketManager-listBuckets}. - function listBuckets(address owner) external view returns (Machine[] memory) { - return LibBucket.listBuckets(owner); - } - - /// @dev See {IBucketManager-addObject}. - function addObject( - address bucket, - string memory source, - string memory key, - string memory blobHash, - string memory recoveryHash, - uint64 size, - address from - ) external { - AddObjectParams memory params = AddObjectParams({ - source: source, - key: key, - blobHash: blobHash, - recoveryHash: recoveryHash, - size: size, - ttl: 0, // No expiration - metadata: new KeyValue[](0), // No metadata - overwrite: false, // Do not overwrite - from: from - }); - LibBucket.addObject(bucket, params); - } - - /// @dev See {IBucketManager-addObject}. - function addObject(address bucket, AddObjectParams memory params) external { - LibBucket.addObject(bucket, params); - } - - /// @dev See {IBucketManager-deleteObject}. - function deleteObject(address bucket, string memory key, address from) external { - LibBucket.deleteObject(bucket, key, from); - } - - /// @dev See {IBucketManager-updateObjectMetadata}. - function updateObjectMetadata(address bucket, string memory key, KeyValue[] memory metadata, address from) - external - { - LibBucket.updateObjectMetadata(bucket, key, metadata, from); - } - - /// @dev See {IBucketManager-getObject}. - function getObject(address bucket, string memory key) external view returns (ObjectValue memory) { - return LibBucket.getObject(bucket, key); - } - - /// @dev See {IBucketManager-queryObjects}. - function queryObjects(address bucket) external view returns (Query memory) { - return LibBucket.queryObjects(bucket, "", "/", "", 0); - } - - /// @dev See {IBucketManager-queryObjects}. - function queryObjects(address bucket, string memory prefix) external view returns (Query memory) { - return LibBucket.queryObjects(bucket, prefix, "/", "", 0); - } - - /// @dev See {IBucketManager-queryObjects}. - function queryObjects(address bucket, string memory prefix, string memory delimiter) - external - view - returns (Query memory) - { - return LibBucket.queryObjects(bucket, prefix, delimiter, "", 0); - } - - /// @dev See {IBucketManager-queryObjects}. - function queryObjects(address bucket, string memory prefix, string memory delimiter, string memory startKey) - external - view - returns (Query memory) - { - return LibBucket.queryObjects(bucket, prefix, delimiter, startKey, 0); - } - - /// @dev See {IBucketManager-queryObjects}. - function queryObjects( - address bucket, - string memory prefix, - string memory delimiter, - string memory startKey, - uint64 limit - ) external view returns (Query memory) { - return LibBucket.queryObjects(bucket, prefix, delimiter, startKey, limit); - } -} diff --git a/src/wrappers/CreditManager.sol b/src/wrappers/CreditManager.sol deleted file mode 100644 index 3538485..0000000 --- a/src/wrappers/CreditManager.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {ICreditManager} from "../interfaces/ICreditManager.sol"; -import {Account, Balance, CreditApproval, CreditStats} from "../types/BlobTypes.sol"; -import {LibBlob} from "./LibBlob.sol"; - -/// @title Credits Contract -/// @dev Implementation of the Recall Blobs actor EVM interface. See {ICredit} for details. -contract CreditManager is ICreditManager { - /// @dev See {ICreditManager-getAccount}. - function getAccount(address addr) external view returns (Account memory account) { - return LibBlob.getAccount(addr); - } - - /// @dev See {ICreditManager-getCreditStats}. - function getCreditStats() external view returns (CreditStats memory stats) { - return LibBlob.getCreditStats(); - } - - /// @dev See {ICreditManager-getCreditApproval}. - function getCreditApproval(address from, address to) external view returns (CreditApproval memory approval) { - return LibBlob.getCreditApproval(from, to); - } - - /// @dev See {ICreditManager-getCreditBalance}. - function getCreditBalance(address addr) external view returns (Balance memory balance) { - return LibBlob.getCreditBalance(addr); - } - - /// @dev See {ICreditManager-approveCredit}. - function approveCredit(address to) external { - LibBlob.approveCredit(msg.sender, to, new address[](0), 0, 0, 0); - } - - /// @dev See {ICreditManager-approveCredit}. - function approveCredit(address from, address to) external { - LibBlob.approveCredit(from, to, new address[](0), 0, 0, 0); - } - - /// @dev See {ICreditManager-approveCredit}. - function approveCredit(address from, address to, address[] memory caller) external { - LibBlob.approveCredit(from, to, caller, 0, 0, 0); - } - - /// @dev See {ICreditManager-approveCredit}. - function approveCredit( - address from, - address to, - address[] memory caller, - uint256 creditLimit, - uint256 gasFeeLimit, - uint64 ttl - ) external { - LibBlob.approveCredit(from, to, caller, creditLimit, gasFeeLimit, ttl); - } - - /// @dev See {ICreditManager-buyCredit}. - function buyCredit() external payable { - LibBlob.buyCredit(msg.sender); - } - - /// @dev See {ICreditManager-buyCredit}. - function buyCredit(address recipient) external payable { - LibBlob.buyCredit(recipient); - } - - /// @dev See {ICreditManager-revokeCredit}. - function revokeCredit(address to) external { - LibBlob.revokeCredit(msg.sender, to, address(0)); - } - - /// @dev See {ICreditManager-revokeCredit}. - function revokeCredit(address from, address to) external { - LibBlob.revokeCredit(from, to, address(0)); - } - - /// @dev See {ICreditManager-revokeCredit}. - function revokeCredit(address from, address to, address caller) external { - LibBlob.revokeCredit(from, to, caller); - } - - /// @dev See {ICreditManager-setAccountSponsor}. - function setAccountSponsor(address from, address sponsor) external { - LibBlob.setAccountSponsor(from, sponsor); - } -} diff --git a/src/wrappers/LibBlob.sol b/src/wrappers/LibBlob.sol deleted file mode 100644 index 6467085..0000000 --- a/src/wrappers/LibBlob.sol +++ /dev/null @@ -1,584 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import { - Account, - AddBlobParams, - Approval, - Balance, - Blob, - BlobSourceInfo, - BlobStatus, - BlobTuple, - CreditApproval, - CreditStats, - StorageStats, - SubnetStats, - Subscriber, - Subscription, - SubscriptionGroup -} from "../types/BlobTypes.sol"; -import {KeyValue} from "../types/CommonTypes.sol"; -import {InvalidValue, LibWasm} from "./LibWasm.sol"; - -/// @title Blobs Library -/// @dev Utility functions for interacting with the Recall Blobs actor. -library LibBlob { - using LibWasm for *; - - // Constants for the actor and method IDs of the Recall Blobs actor - uint64 internal constant ACTOR_ID = 66; - - // User methods - uint64 internal constant METHOD_ADD_BLOB = 913855558; - uint64 internal constant METHOD_APPROVE_CREDIT = 2276438360; - uint64 internal constant METHOD_BUY_CREDIT = 1035900737; - uint64 internal constant METHOD_GET_ACCOUNT = 3435393067; - uint64 internal constant METHOD_GET_BLOB = 1739171512; - uint64 internal constant METHOD_GET_CREDIT_APPROVAL = 4218154481; - uint64 internal constant METHOD_DELETE_BLOB = 4230608948; - uint64 internal constant METHOD_OVERWRITE_BLOB = 609646161; - uint64 internal constant METHOD_REVOKE_CREDIT = 37550845; - uint64 internal constant METHOD_SET_ACCOUNT_SPONSOR = 228279820; - - // System methods - uint64 internal constant METHOD_GET_ADDED_BLOBS = 2462124090; - uint64 internal constant METHOD_GET_BLOB_STATUS = 3505892271; - uint64 internal constant METHOD_GET_PENDING_BLOBS = 799531123; - uint64 internal constant METHOD_GET_PENDING_BLOBS_COUNT = 1694235671; - uint64 internal constant METHOD_GET_PENDING_BYTES_COUNT = 3795566289; - - // Metrics methods - uint64 internal constant METHOD_GET_STATS = 188400153; - - /// @dev Helper function to decode the subnet stats from CBOR to solidity. - /// @param data The encoded CBOR array of stats. - /// @return stats The decoded stats. - function decodeSubnetStats(bytes memory data) internal view returns (SubnetStats memory stats) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return stats; - stats.balance = decoded[0].decodeCborBytesToUint256(); - stats.capacityFree = decoded[1].decodeCborBytesToUint64(); - stats.capacityUsed = decoded[2].decodeCborBytesToUint64(); - stats.creditSold = decoded[3].decodeCborBytesToUint256(); - stats.creditCommitted = decoded[4].decodeCborBytesToUint256(); - stats.creditDebited = decoded[5].decodeCborBytesToUint256(); - stats.tokenCreditRate = decodeTokenCreditRate(decoded[6]); - stats.numAccounts = decoded[7].decodeCborBytesToUint64(); - stats.numBlobs = decoded[8].decodeCborBytesToUint64(); - stats.numAdded = decoded[9].decodeCborBytesToUint64(); - stats.bytesAdded = decoded[10].decodeCborBytesToUint64(); - stats.numResolving = decoded[11].decodeCborBytesToUint64(); - stats.bytesResolving = decoded[12].decodeCborBytesToUint64(); - } - - /// @dev Helper function to decode an account from CBOR to solidity. - /// @param data The encoded CBOR array of the account. - /// @return account The decoded account. - function decodeAccount(bytes memory data) internal view returns (Account memory account) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return account; - account.capacityUsed = decoded[0].decodeCborBytesToUint64(); - account.creditFree = decoded[1].decodeCborBytesToUint256(); - account.creditCommitted = decoded[2].decodeCborBytesToUint256(); - account.creditSponsor = decoded[3][0] == 0x00 ? address(0) : decoded[3].decodeCborAddress(); - account.lastDebitEpoch = decoded[4].decodeCborBytesToUint64(); - account.approvalsTo = decodeApprovals(decoded[5]); - account.approvalsFrom = decodeApprovals(decoded[6]); - account.maxTtl = decoded[7].decodeCborBytesToUint64(); - account.gasAllowance = decoded[8].decodeCborBytesToUint256(); - } - - /// @dev Helper function to decode a credit approval from CBOR to solidity. - /// @param data The encoded CBOR array of a credit approval. - /// @return approval The decoded approval. - function decodeCreditApproval(bytes memory data) internal view returns (CreditApproval memory approval) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return approval; - // Note: `limit` is encoded as a BigUInt (single array with no sign bit and values) when writing data, but it - // gets encoded as a BigInt (array with sign bit and nested array of values) when reading data. - approval.creditLimit = decoded[0].decodeCborBytesToUint256(); - approval.gasFeeLimit = decoded[1].decodeCborBytesToUint256(); - approval.expiry = decoded[2].decodeCborBytesToUint64(); - approval.creditUsed = decoded[3].decodeCborBytesToUint256(); - approval.gasFeeUsed = decoded[4].decodeCborBytesToUint256(); - } - - /// @dev Helper function to decode approvals from CBOR to solidity. - /// @param data The encoded CBOR mapping of approvals. This is a `HashMap>>` in Rust. - /// @return approvals The decoded approvals, represented as a nested {Approvals} array. - function decodeApprovals(bytes memory data) internal view returns (Approval[] memory approvals) { - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - approvals = new Approval[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - // The `addr` value is an delegated address as bytes like `040A14DC79964DA2C08B23698B3D3CC7CA32193D9955` - approvals[i].addr = decoded[i][0].decodeCborAddress(); - approvals[i].approval = decodeCreditApproval(decoded[i][1]); - } - } - - /// @dev Convert a string to a blob status. - /// @param status The string representation of the blob status, either "Added", "Pending", "Resolved", or "Failed". - /// @return status The blob status. - function decodeBlobStatus(bytes memory status) internal pure returns (BlobStatus) { - bytes32 statusBytes = keccak256(status); - if (statusBytes == keccak256(bytes("Added"))) { - return BlobStatus.Added; - } else if (statusBytes == keccak256(bytes("Pending"))) { - return BlobStatus.Pending; - } else if (statusBytes == keccak256(bytes("Resolved"))) { - return BlobStatus.Resolved; - } else { - return BlobStatus.Failed; - } - } - - /// @dev Decode a subscription ID from CBOR. - /// @param data The encoded subscription ID. - /// @return decoded The decoded subscription ID. - function decodeSubscriptionId(bytes memory data) internal view returns (string memory) { - // Decode the mapping and return subscription ID bytes - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - // An empty string gets decoded into 0x00 via `decodeCborMappingToBytes` - return (decoded[0][1].length == 1 && decoded[0][1][0] == hex"00") ? "" : string(decoded[0][1]); - } - - /// @dev Decode a subscription from CBOR. - /// @param data The encoded CBOR array of a subscription. - /// @return subscription The decoded subscription. - function decodeSubscription(bytes memory data) internal view returns (Subscription memory subscription) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - subscription.added = decoded[0].decodeCborBytesToUint64(); - subscription.expiry = decoded[1].decodeCborBytesToUint64(); - subscription.source = string(decoded[2].decodeCborBlobHashOrNodeId()); - subscription.delegate = decoded[3].isCborNull() ? address(0) : decoded[3].decodeCborAddress(); - subscription.failed = decoded[4].decodeCborBool(); - } - - /// @dev Decode a subscription group from CBOR. - /// @param data The encoded subscription group bytes - /// @return group The decoded subscription group - function decodeSubscriptionGroup(bytes memory data) internal view returns (SubscriptionGroup[] memory group) { - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - group = new SubscriptionGroup[](decoded.length); - for (uint256 j = 0; j < decoded.length; j++) { - // String encoded in `SubscriptionGroup` (`HashMap`), not as `SubscriptionId` mapping - group[j].subscriptionId = string(decoded[j][0]); - group[j].subscription = decodeSubscription(decoded[j][1]); - } - } - - /// @dev Decode subscribers from CBOR. - /// @param data The encoded CBOR mapping of subscribers. - /// @return subscribers The decoded subscribers. - function decodeSubscribers(bytes memory data) internal view returns (Subscriber[] memory subscribers) { - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - subscribers = new Subscriber[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - subscribers[i].subscriptionId = decodeSubscriptionId(decoded[i][0]); - subscribers[i].expiry = decoded[i][1].decodeCborBytesToUint64(); - } - } - - /// @dev Decode a blob from CBOR. - /// @param data The encoded CBOR array of a blob. - /// @return blob The decoded blob. - function decodeBlob(bytes memory data) internal view returns (Blob memory blob) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return blob; - - blob.size = decoded[0].decodeCborBytesToUint64(); - blob.metadataHash = string(decoded[1].decodeCborBlobHashOrNodeId()); - blob.subscribers = decodeSubscribers(decoded[2]); - blob.status = decodeBlobStatus(decoded[3]); - } - - /// @dev Decode a blob source info from CBOR. - /// @param data The encoded CBOR array of a blob source info. - /// @return sourceInfo The decoded blob source info. - function decodeBlobSourceInfo(bytes memory data) internal view returns (BlobSourceInfo[] memory sourceInfo) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return sourceInfo; - sourceInfo = new BlobSourceInfo[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - bytes[] memory decodedInner = decoded[i].decodeCborArrayToBytes(); - sourceInfo[i].subscriber = decodedInner[0].decodeCborAddress(); - sourceInfo[i].subscriptionId = decodeSubscriptionId(decodedInner[1]); - sourceInfo[i].source = string(decodedInner[2].decodeCborBlobHashOrNodeId()); - } - } - - /// @dev Decode pending blobs from CBOR. - /// @param data The encoded CBOR array of pending blobs. - /// @return blobs The decoded pending blobs. - function decodeAddedOrPendingBlobs(bytes memory data) internal view returns (BlobTuple[] memory blobs) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return blobs; - blobs = new BlobTuple[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - bytes[] memory blobTuple = decoded[i].decodeCborArrayToBytes(); - blobs[i].blobHash = string(blobTuple[0].decodeCborBlobHashOrNodeId()); - blobs[i].sourceInfo = decodeBlobSourceInfo(blobTuple[1]); - } - } - - /// @dev Decode a token credit rate from CBOR. - /// @param data The encoded CBOR array of a token credit rate. - /// @return rate The decoded token credit rate. - function decodeTokenCreditRate(bytes memory data) internal view returns (uint256) { - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - // The TokenCreditRate mapping is `{rate: }`, so we grab the value - return decoded[0][1].decodeCborBigUintToUint256(); - } - - /// @dev Helper function to encode approve credit params. - /// @param from (address): Account address that is approving the credit. - /// @param to (address): Account address that is receiving the approval. - /// @param caller (address): Optional restriction on caller address, e.g., an object store. Use zero address - /// if unused, indicating a null value. - /// @param creditLimit (uint256): Optional credit approval limit. Use zero if unused, indicating a null value. - /// @param gasFeeLimit (uint256): Optional gas fee approval limit. Use zero if unused, indicating a null value. - /// @param ttl (uint64): Optional credit approval time-to-live epochs. Minimum value is 3600 (1 hour). Use zero if - /// unused, indicating a null value. - /// @return encoded The encoded params. - function encodeApproveCreditParams( - address from, - address to, - address[] memory caller, - uint256 creditLimit, - uint256 gasFeeLimit, - uint64 ttl - ) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](6); - encoded[0] = from.encodeCborAddress(); - encoded[1] = to.encodeCborAddress(); - encoded[2] = encodeCallerAllowlist(caller); - encoded[3] = creditLimit == 0 ? LibWasm.encodeCborNull() : creditLimit.encodeCborUint256AsBytes(true); - encoded[4] = gasFeeLimit == 0 ? LibWasm.encodeCborNull() : gasFeeLimit.encodeCborUint256AsBytes(false); - encoded[5] = ttl == 0 ? LibWasm.encodeCborNull() : ttl.encodeCborUint64(); - - return encoded.encodeCborArray(); - } - - /// @dev Helper function to encode caller allowlist. - /// @param caller The caller allowlist. - /// @return encoded The encoded caller allowlist. - function encodeCallerAllowlist(address[] memory caller) internal pure returns (bytes memory) { - if (caller.length == 0) return LibWasm.encodeCborNull(); - bytes[] memory encoded = new bytes[](caller.length); - for (uint256 i = 0; i < caller.length; i++) { - encoded[i] = caller[i].encodeCborAddress(); - } - return encoded.encodeCborArray(); - } - - /// @dev Helper function to encode set credit sponsor params. - /// @param from The address of the account. - /// @param sponsor The address of the sponsor. Use zero address if unused. - /// @return encoded The encoded params. - function encodeSetAccountSponsorParams(address from, address sponsor) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](2); - encoded[0] = from.encodeCborAddress(); - encoded[1] = sponsor == address(0) ? LibWasm.encodeCborNull() : sponsor.encodeCborAddress(); - return encoded.encodeCborArray(); - } - - /// @dev Helper function to encode revoke credit params. - /// @param from The address of the account that is revoking the credit. - /// @param to The address of the account that is receiving the credit. - /// @param caller The address of the account that is required to call this method. Use zero address - /// if unused, indicating a null value. - /// @return encoded The encoded params. - function encodeRevokeCreditParams(address from, address to, address caller) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](3); - encoded[0] = from.encodeCborAddress(); - encoded[1] = to.encodeCborAddress(); - encoded[2] = caller == address(0) ? LibWasm.encodeCborNull() : caller.encodeCborAddress(); - - return encoded.encodeCborArray(); - } - - /// @dev Encode a subscription ID. - /// @param subscriptionId The subscription ID. - /// @return encoded The encoded subscription ID. - function encodeSubscriptionId(string memory subscriptionId) internal pure returns (bytes memory) { - // Encoded as a 1 value mapping with `inner` key and value of the subscription ID as bytes - bytes[] memory encoded = new bytes[](3); - encoded[0] = hex"a1"; - encoded[1] = "inner".encodeCborString(); - encoded[2] = subscriptionId.encodeCborString(); - return encoded.concatBytes(); - } - - /// @dev Encode add blob params. - /// @param params The parameters for adding a blob. - /// @return encoded The encoded parameters. - function encodeAddBlobParams(AddBlobParams memory params) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](8); - encoded[0] = params.sponsor == address(0) ? LibWasm.encodeCborNull() : params.sponsor.encodeCborAddress(); - encoded[1] = params.source.encodeCborBlobHashOrNodeId(); - encoded[2] = params.blobHash.encodeCborBlobHashOrNodeId(); - // TODO: this value is currently hardcoded, but it'll eventually use the same method above - encoded[3] = bytes(params.metadataHash).length == 0 - ? hex"0000000000000000000000000000000000000000000000000000000000000000".encodeCborFixedArray() - : params.metadataHash.encodeCborBlobHashOrNodeId(); - encoded[4] = encodeSubscriptionId(params.subscriptionId); - encoded[5] = params.size.encodeCborUint64(); - encoded[6] = params.ttl == 0 ? LibWasm.encodeCborNull() : params.ttl.encodeCborUint64(); - encoded[7] = params.from.encodeCborAddress(); - return encoded.encodeCborArray(); - } - - /// @dev Helper function to convert a credit account to a balance. - /// @param account The account to convert. - /// @return balance The balance of the account. - function accountToBalance(Account memory account) internal pure returns (Balance memory balance) { - balance.creditFree = account.creditFree; - balance.creditCommitted = account.creditCommitted; - balance.creditSponsor = account.creditSponsor; - balance.lastDebitEpoch = account.lastDebitEpoch; - balance.approvalsTo = account.approvalsTo; - balance.approvalsFrom = account.approvalsFrom; - balance.gasAllowance = account.gasAllowance; - } - - /// @dev Helper function to convert subnet stats to storage stats. - /// @param subnetStats The subnet stats to convert. - /// @return stats The storage stats. - function subnetStatsToStorageStats(SubnetStats memory subnetStats) - internal - pure - returns (StorageStats memory stats) - { - stats.capacityFree = subnetStats.capacityFree; - stats.capacityUsed = subnetStats.capacityUsed; - stats.numBlobs = subnetStats.numBlobs; - stats.numResolving = subnetStats.numResolving; - stats.numAccounts = subnetStats.numAccounts; - stats.bytesResolving = subnetStats.bytesResolving; - stats.numAdded = subnetStats.numAdded; - stats.bytesAdded = subnetStats.bytesAdded; - } - - /// @dev Helper function to convert subnet stats to credit stats. - /// @param subnetStats The subnet stats to convert. - /// @return stats The credit stats. - function subnetStatsToCreditStats(SubnetStats memory subnetStats) - internal - pure - returns (CreditStats memory stats) - { - stats.balance = subnetStats.balance; - stats.creditSold = subnetStats.creditSold; - stats.creditCommitted = subnetStats.creditCommitted; - stats.creditDebited = subnetStats.creditDebited; - stats.tokenCreditRate = subnetStats.tokenCreditRate; - stats.numAccounts = subnetStats.numAccounts; - } - - /// @dev Get the subnet stats. - /// @return stats The subnet stats. - function getSubnetStats() public view returns (SubnetStats memory stats) { - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_STATS); - return decodeSubnetStats(data); - } - - /// @dev Get the credit account for an address. - /// @param addr The address of the account. - /// @return account The credit account for the address. - function getAccount(address addr) public view returns (Account memory account) { - bytes memory params = addr.encodeCborAddress(); - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_ACCOUNT, params); - return decodeAccount(data); - } - - /// @dev Get the storage usage for an account. - /// @param addr The address of the account. - /// @return usage The storage usage for the account. - function getStorageUsage(address addr) external view returns (uint256) { - Account memory account = getAccount(addr); - return account.capacityUsed; - } - - /// @dev Get the storage stats for the subnet. - /// @return stats The storage stats for the subnet. - function getStorageStats() external view returns (StorageStats memory stats) { - SubnetStats memory subnetStats = getSubnetStats(); - return subnetStatsToStorageStats(subnetStats); - } - - /// @dev Get the subnet-wide credit statistics. - /// @return stats The subnet-wide credit statistics. - function getCreditStats() external view returns (CreditStats memory stats) { - SubnetStats memory subnetStats = getSubnetStats(); - return subnetStatsToCreditStats(subnetStats); - } - - /// @dev Get the credit approval from one account to another, if it exists. - /// @param from The address of the account. - /// @param to The address of the account to check the approval for. - /// @return approval The credit approval for the account. - function getCreditApproval(address from, address to) external view returns (CreditApproval memory approval) { - bytes[] memory encoded = new bytes[](2); - encoded[0] = from.encodeCborAddress(); - encoded[1] = to.encodeCborAddress(); - bytes memory params = encoded.encodeCborArray(); - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_CREDIT_APPROVAL, params); - return decodeCreditApproval(data); - } - - /// @dev Get the credit balance of an account. - /// @param addr The address of the account. - /// @return balance The credit balance of the account. - function getCreditBalance(address addr) external view returns (Balance memory balance) { - Account memory account = getAccount(addr); - return accountToBalance(account); - } - - /// @dev Get a blob. - /// @param blobHash The hash of the blob. - /// @return blob The blob. - function getBlob(string memory blobHash) external view returns (Blob memory blob) { - bytes memory params = blobHash.encodeCborBlobHashOrNodeId(); - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_BLOB, params); - return decodeBlob(data); - } - - /// @dev Get the status of a blob. - /// @param subscriber The address of the subscriber. - /// @param blobHash The hash of the blob. - /// @param subscriptionId The subscription ID. - /// @return status The status of the blob. - function getBlobStatus(address subscriber, string memory blobHash, string memory subscriptionId) - external - view - returns (BlobStatus status) - { - bytes[] memory encoded = new bytes[](3); - encoded[0] = subscriber.encodeCborAddress(); - encoded[1] = blobHash.encodeCborBlobHashOrNodeId(); - encoded[2] = encodeSubscriptionId(subscriptionId); - bytes memory params = encoded.encodeCborArray(); - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_BLOB_STATUS, params); - bytes memory decoded = data.decodeCborStringToBytes(); - return decodeBlobStatus(decoded); - } - - /// @dev Get added blobs. - /// @param size Maximum number of added blobs to return. - /// @return blobs The added blobs. - function getAddedBlobs(uint32 size) external view returns (BlobTuple[] memory blobs) { - bytes memory params = size.encodeCborUint64(); - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_ADDED_BLOBS, params); - return decodeAddedOrPendingBlobs(data); - } - - /// @dev Get pending blobs. - /// @param size Maximum number of added blobs to return. - /// @return blobs The pending blobs. - function getPendingBlobs(uint32 size) external view returns (BlobTuple[] memory blobs) { - bytes memory params = size.encodeCborUint64(); - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BLOBS, params); - return decodeAddedOrPendingBlobs(data); - } - - /// @dev Get the number of pending blobs. - /// @return count The number of pending blobs. - function getPendingBlobsCount() external view returns (uint64) { - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BLOBS_COUNT); - return data.decodeCborBytesToUint64(); - } - - /// @dev Get the number of pending bytes. - /// @return count The number of pending bytes. - function getPendingBytesCount() external view returns (uint64) { - bytes memory data = LibWasm.readFromWasmActor(ACTOR_ID, METHOD_GET_PENDING_BYTES_COUNT); - return data.decodeCborBytesToUint64(); - } - - /// @dev Buy credits for a specified account with a `msg.value` for number of native currency to spend on credits. - /// @param recipient The address of the account. - /// @return data The balance of the account after buying credits. - function buyCredit(address recipient) external returns (bytes memory data) { - if (msg.value == 0) revert InvalidValue("Amount must be greater than zero"); - bytes memory params = recipient.encodeCborAddress(); - return LibWasm.writeToWasmActor(ACTOR_ID, METHOD_BUY_CREDIT, params); - } - - /// @dev Approve credits for an account. This is a simplified variant when no optional fields are needed. - /// @param from The address of the account that owns the credits. - /// @param to The address of the account to approve credits for. - /// @param caller Optional restriction on caller address, e.g., an object store. Use zero address if unused. - /// @param creditLimit Optional credit approval limit. Use zero if unused, indicating a null value. - /// @param gasFeeLimit Optional gas fee limit. Use zero if unused, indicating a null value. - /// @param ttl Optional credit approval time-to-live epochs. Minimum value is 3600 (1 hour). Use zero if - /// unused, indicating a null value. - /// @return data The credit approval (`CreditApproval`) response as bytes. - function approveCredit( - address from, - address to, - address[] memory caller, - uint256 creditLimit, - uint256 gasFeeLimit, - uint64 ttl - ) external returns (bytes memory data) { - bytes memory params = encodeApproveCreditParams(from, to, caller, creditLimit, gasFeeLimit, ttl); - return LibWasm.writeToWasmActor(ACTOR_ID, METHOD_APPROVE_CREDIT, params); - } - - /// @dev Revoke credits for an account. Includes optional fields, which if set to zero, will be encoded as null. - /// @param from The address of the account that owns the credits. - /// @param to The address of the account to revoke credits for. - /// @param caller Optional restriction on caller address, e.g., an object store. - function revokeCredit(address from, address to, address caller) external { - bytes memory params = encodeRevokeCreditParams(from, to, caller); - // Note: response bytes are always empty - LibWasm.writeToWasmActor(ACTOR_ID, METHOD_REVOKE_CREDIT, params); - } - - /// @dev Set the credit sponsor for an account. - /// @param from The address of the account. - /// @param sponsor The address of the sponsor. Use zero address if unused. - function setAccountSponsor(address from, address sponsor) external returns (bytes memory data) { - bytes memory params = encodeSetAccountSponsorParams(from, sponsor); - return LibWasm.writeToWasmActor(ACTOR_ID, METHOD_SET_ACCOUNT_SPONSOR, params); - } - - /// @dev Add a blob to the subnet. - /// @param params The parameters for adding a blob. - /// @return data The response from the actor. - function addBlob(AddBlobParams memory params) external returns (bytes memory data) { - bytes memory _params = encodeAddBlobParams(params); - return LibWasm.writeToWasmActor(ACTOR_ID, METHOD_ADD_BLOB, _params); - } - - /// @dev Delete a blob from the subnet. - /// @param subscriber The address of the subscriber. - /// @param blobHash The hash of the blob. - /// @param subscriptionId The subscription ID. - /// @param from Account address that initiated the deletion. - function deleteBlob(address subscriber, string memory blobHash, string memory subscriptionId, address from) - external - { - bytes[] memory encoded = new bytes[](4); - encoded[0] = subscriber == address(0) ? LibWasm.encodeCborNull() : subscriber.encodeCborAddress(); - encoded[1] = blobHash.encodeCborBlobHashOrNodeId(); - encoded[2] = encodeSubscriptionId(subscriptionId); - encoded[3] = from.encodeCborAddress(); - bytes memory params = encoded.encodeCborArray(); - // Note: response bytes are always empty - LibWasm.writeToWasmActor(ACTOR_ID, METHOD_DELETE_BLOB, params); - } - - /// @dev Overwrite a blob in the subnet by deleting and adding within a single transaction. - /// @param oldHash The blake3 hash of the blob to be deleted. - /// @param params The parameters for adding a blob. - function overwriteBlob(string memory oldHash, AddBlobParams memory params) external returns (bytes memory data) { - bytes[] memory encoded = new bytes[](2); - encoded[0] = oldHash.encodeCborBlobHashOrNodeId(); - encoded[1] = encodeAddBlobParams(params); - bytes memory _params = encoded.encodeCborArray(); - return LibWasm.writeToWasmActor(ACTOR_ID, METHOD_OVERWRITE_BLOB, _params); - } -} diff --git a/src/wrappers/LibBucket.sol b/src/wrappers/LibBucket.sol deleted file mode 100644 index 3e72771..0000000 --- a/src/wrappers/LibBucket.sol +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import { - AddObjectParams, - CreateBucketParams, - KeyValue, - Kind, - Machine, - Object, - ObjectState, - ObjectValue, - Query -} from "../types/BucketTypes.sol"; -import {InvalidValue, LibWasm} from "./LibWasm.sol"; - -/// @title Bucket Library -/// @dev Utility functions for interacting with the Recall Bucket actor. -library LibBucket { - using LibWasm for *; - - // Constants for the actor and method IDs of the Recall ADM actor - uint64 internal constant ADM_ACTOR_ID = 17; - // Methods that don't interact with an instance of a Bucket contract - uint64 internal constant METHOD_CREATE_EXTERNAL = 1214262202; - uint64 internal constant METHOD_UPDATE_DEPLOYERS = 1768606754; - uint64 internal constant METHOD_LIST_METADATA = 2283215593; - // Methods for instances of Bucket contracts - uint64 internal constant METHOD_GET_METADATA = 4024736952; - uint64 internal constant METHOD_ADD_OBJECT = 3518119203; - uint64 internal constant METHOD_DELETE_OBJECT = 4237275016; - uint64 internal constant METHOD_GET_OBJECT = 1894890866; - uint64 internal constant METHOD_LIST_OBJECTS = 572676265; - uint64 internal constant METHOD_UPDATE_OBJECT_METADATA = 540229491; - - /// @dev Decode a CBOR encoded list. - /// @param data The CBOR encoded list. - /// @return decodedList The decoded list. - function decodeList(bytes memory data) internal view returns (Machine[] memory decodedList) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return decodedList; - decodedList = new Machine[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - decodedList[i] = decodeMachine(decoded[i]); - } - } - - /// @dev Decode a CBOR encoded machine metadata. - /// @param data The CBOR encoded machine metadata. - /// @return machine The decoded machine metadata. - function decodeMachine(bytes memory data) internal view returns (Machine memory machine) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return machine; - machine = Machine({ - kind: stringToKind(string(decoded[0])), // Decoded array automatically removes leading byte (string length) - addr: decoded[1].decodeCborAddress(), - metadata: decodeMetadata(decoded[2]) - }); - } - - /// @dev Decode a CBOR encoded array of metadata. - /// @param data The CBOR encoded array of metadata. - /// @return metadata The decoded metadata. - function decodeMetadata(bytes memory data) internal view returns (KeyValue[] memory metadata) { - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - if (decoded.length == 0) return metadata; - metadata = new KeyValue[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - metadata[i] = KeyValue({key: string(decoded[i][0]), value: string(decoded[i][1])}); - } - return metadata; - } - - /// @dev Decode a CBOR encoded query. - /// @param data The CBOR encoded query. - /// @return decodedQuery The decoded query. - function decodeQuery(bytes memory data) internal view returns (Query memory decodedQuery) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return decodedQuery; - decodedQuery.objects = decodeObjects(decoded[0]); - decodedQuery.commonPrefixes = decodeCommonPrefixes(decoded[1]); - decodedQuery.nextKey = decoded[2].isCborNull() ? "" : string(decoded[2].decodeCborBytesArrayToBytes()); - } - - /// @dev Decode a CBOR encoded array of objects. - /// @param data The CBOR encoded array of objects. - /// @return objects The decoded objects. - function decodeObjects(bytes memory data) internal view returns (Object[] memory objects) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return objects; - objects = new Object[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - bytes[] memory object = decoded[i].decodeCborArrayToBytes(); - string memory key = string(object[0].decodeCborBytesArrayToBytes()); - ObjectState memory state = decodeObjectState(object[1]); - objects[i] = Object({key: key, state: state}); - } - return objects; - } - - /// @dev Decode a CBOR encoded object state for `queryObjects`, which is represented as a mapping. - /// @param data The CBOR encoded value. - /// @return value The decoded value. - function decodeObjectState(bytes memory data) internal view returns (ObjectState memory value) { - bytes[2][] memory decoded = data.decodeCborMappingToBytes(); - if (decoded.length == 0) return value; - value = ObjectState({ - blobHash: string(decoded[0][1].decodeCborBlobHashOrNodeId()), - size: decoded[1][1].decodeCborBytesToUint64(), - expiry: decoded[2][1].decodeCborBytesToUint64(), - metadata: decodeMetadata(decoded[3][1]) - }); - } - - /// @dev Decode a CBOR encoded object value for `getObject`, which is represented as an array. - /// @param data The CBOR encoded value. - /// @return value The decoded value. - function decodeObjectValue(bytes memory data) internal view returns (ObjectValue memory value) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return value; - value = ObjectValue({ - blobHash: string(decoded[0].decodeCborBlobHashOrNodeId()), - recoveryHash: string(decoded[1].decodeCborBlobHashOrNodeId()), - size: decoded[2].decodeCborBytesToUint64(), - expiry: decoded[3].decodeCborBytesToUint64(), - metadata: decodeMetadata(decoded[4]) - }); - } - - /// @dev Decode a CBOR encoded array of common prefixes. - /// @param data The CBOR encoded array of common prefixes. - /// @return commonPrefixes The decoded common prefixes. - function decodeCommonPrefixes(bytes memory data) internal view returns (string[] memory commonPrefixes) { - bytes[] memory decoded = data.decodeCborArrayToBytes(); - if (decoded.length == 0) return commonPrefixes; - commonPrefixes = new string[](decoded.length); - for (uint256 i = 0; i < decoded.length; i++) { - commonPrefixes[i] = string(decoded[i].decodeCborBytesArrayToBytes()); - } - } - - /// @dev Encode a CBOR encoded create bucket params. - /// @param params The create bucket params. - /// @return encoded The CBOR encoded create bucket params. - function encodeCreateBucketParams(CreateBucketParams memory params) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](3); - encoded[0] = params.owner.encodeCborAddress(); - encoded[1] = kindToString(params.kind).encodeCborString(); - encoded[2] = params.metadata.encodeCborKeyValueMap(); - return encoded.encodeCborArray(); - } - - /// @dev Encode a CBOR encoded add params. - /// @param params The add params. - /// @return encoded The CBOR encoded add params. - function encodeAddObjectParams(AddObjectParams memory params) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](9); - encoded[0] = params.source.encodeCborBlobHashOrNodeId(); - encoded[1] = params.key.encodeCborBytes(); - encoded[2] = params.blobHash.encodeCborBlobHashOrNodeId(); - // TODO: this currently is hardcoded to a 32 byte array of all zeros, but should use the method above - // Once https://github.com/recallnet/ipc/issues/300 is merged, this'll need to change - encoded[3] = bytes(params.recoveryHash).length == 0 - ? hex"0000000000000000000000000000000000000000000000000000000000000000".encodeCborFixedArray() - : params.recoveryHash.encodeCborBlobHashOrNodeId(); - encoded[4] = params.size.encodeCborUint64(); - encoded[5] = params.ttl == 0 ? LibWasm.encodeCborNull() : params.ttl.encodeCborUint64(); - encoded[6] = params.metadata.encodeCborKeyValueMap(); - encoded[7] = params.overwrite.encodeCborBool(); - encoded[8] = params.from.encodeCborAddress(); - return encoded.encodeCborArray(); - } - - /// @dev Encode a CBOR encoded delete object params. - /// @param key The key of the object to delete. - /// @param from The address of the account that is deleting the object. - /// @return encoded The CBOR encoded delete object params. - function encodeDeleteObjectParams(string memory key, address from) internal pure returns (bytes memory) { - bytes[] memory encoded = new bytes[](2); - encoded[0] = key.encodeCborBytes(); - encoded[1] = from.encodeCborAddress(); - return encoded.encodeCborArray(); - } - - /// @dev Encode a CBOR encoded query params. - /// @param prefix The prefix. - /// @param delimiter The delimiter. - /// @param startKey The key to start listing objects from. - /// @param limit The limit. - /// @return encoded The CBOR encoded query params. - function encodeQueryParams(string memory prefix, string memory delimiter, string memory startKey, uint64 limit) - internal - pure - returns (bytes memory) - { - bytes[] memory encoded = new bytes[](4); - bytes memory _startKey = bytes(startKey); - encoded[0] = prefix.encodeCborBytes(); - encoded[1] = delimiter.encodeCborBytes(); - encoded[2] = _startKey.length == 0 ? LibWasm.encodeCborNull() : _startKey.encodeCborBytesArray(); - encoded[3] = limit.encodeCborUint64(); - return encoded.encodeCborArray(); - } - - /// @dev Encode a CBOR encoded update object metadata params. - /// @param key The key of the object to update. - /// @param metadata The metadata. - /// @param from The address of the account that is updating the metadata. - /// @return encoded The CBOR encoded update object metadata params. - function encodeUpdateObjectMetadataParams(string memory key, KeyValue[] memory metadata, address from) - internal - pure - returns (bytes memory) - { - bytes[] memory encoded = new bytes[](3); - encoded[0] = key.encodeCborBytes(); - encoded[1] = metadata.encodeCborKeyValueMap(); - encoded[2] = from.encodeCborAddress(); - return encoded.encodeCborArray(); - } - - /// @dev Convert a kind to a string. - /// @param kind The kind. - /// @return string The string representation of the kind. - function kindToString(Kind kind) internal pure returns (string memory) { - if (kind == Kind.Bucket) { - return "Bucket"; - } else if (kind == Kind.Timehub) { - return "Timehub"; - } - revert InvalidValue("Invalid machine kind"); - } - - /// @dev Convert a string to a kind. - /// @param kind The string representation of the kind. - /// @return kind The kind. - function stringToKind(string memory kind) internal pure returns (Kind) { - if (keccak256(bytes(kind)) == keccak256(bytes("Bucket"))) { - return Kind.Bucket; - } else if (keccak256(bytes(kind)) == keccak256(bytes("Timehub"))) { - return Kind.Timehub; - } - revert InvalidValue("Invalid machine kind"); - } - - /// @dev Create a bucket. - /// @param owner The owner. - /// @param metadata The metadata. - function createBucket(address owner, KeyValue[] memory metadata) external returns (bytes memory) { - CreateBucketParams memory createParams = - CreateBucketParams({owner: owner, kind: Kind.Bucket, metadata: metadata}); - bytes memory params = encodeCreateBucketParams(createParams); - return LibWasm.writeToWasmActor(ADM_ACTOR_ID, METHOD_CREATE_EXTERNAL, params); - } - - /// @dev List all buckets owned by an address. - /// @param owner The owner of the buckets. - /// @return The list of buckets. - function listBuckets(address owner) external view returns (Machine[] memory) { - bytes memory addrEncoded = owner.encodeCborAddress(); - bytes[] memory encoded = new bytes[](1); - encoded[0] = addrEncoded; - bytes memory params = encoded.encodeCborArray(); - bytes memory data = LibWasm.readFromWasmActor(ADM_ACTOR_ID, METHOD_LIST_METADATA, params); - return decodeList(data); - } - - /// @dev Add an object to the bucket. - /// @param bucket The bucket. - /// @param params The add object params. See {AddObjectParams} for more details. - function addObject(address bucket, AddObjectParams memory params) external { - uint64 bucketAddr = bucket.addressToActorId(); - bytes memory _params = encodeAddObjectParams(params); - LibWasm.writeToWasmActor(bucketAddr, METHOD_ADD_OBJECT, _params); - } - - /// @dev Delete an object from the bucket. - /// @param bucket The bucket. - /// @param key The object key. - /// @param from The address of the account that is deleting the object. - function deleteObject(address bucket, string memory key, address from) external { - uint64 bucketAddr = bucket.addressToActorId(); - bytes memory params = encodeDeleteObjectParams(key, from); - LibWasm.writeToWasmActor(bucketAddr, METHOD_DELETE_OBJECT, params); - } - - /// @dev Update the metadata of an object. - /// @param bucket The bucket. - /// @param key The object key. - /// @param metadata The metadata. - /// @param from The address of the account that is updating the metadata. - function updateObjectMetadata(address bucket, string memory key, KeyValue[] memory metadata, address from) - external - { - uint64 bucketAddr = bucket.addressToActorId(); - bytes memory params = encodeUpdateObjectMetadataParams(key, metadata, from); - LibWasm.writeToWasmActor(bucketAddr, METHOD_UPDATE_OBJECT_METADATA, params); - } - - /// @dev Get an object from the bucket. - /// @param bucket The bucket. - /// @param key The object key. - /// @return Object's value. See {Value} for more details. - function getObject(address bucket, string memory key) external view returns (ObjectValue memory) { - uint64 bucketAddr = bucket.addressToActorId(); - bytes memory params = key.encodeCborBytes(); - bytes memory data = LibWasm.readFromWasmActor(bucketAddr, METHOD_GET_OBJECT, params); - return decodeObjectValue(data); - } - - /// @dev Query the bucket. - /// @param bucket The bucket. - /// @param prefix The prefix. - /// @param delimiter The delimiter. - /// @param startKey The key to start listing objects from. - /// @param limit The limit. - /// @return All objects matching the query. - function queryObjects( - address bucket, - string memory prefix, - string memory delimiter, - string memory startKey, - uint64 limit - ) external view returns (Query memory) { - uint64 bucketAddr = bucket.addressToActorId(); - bytes memory params = encodeQueryParams(prefix, delimiter, startKey, limit); - bytes memory data = LibWasm.readFromWasmActor(bucketAddr, METHOD_LIST_OBJECTS, params); - return decodeQuery(data); - } -} diff --git a/test/BlobManager.t.sol b/test/BlobManager.t.sol deleted file mode 100644 index d9c3eb7..0000000 --- a/test/BlobManager.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {StdUtils} from "forge-std/StdUtils.sol"; -import {Test, Vm} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {DeployScript as BlobDeployer} from "../script/BlobManager.s.sol"; -import {BlobManager} from "../src/wrappers/BlobManager.sol"; - -// TODO: add integration tests once it's possible in CI -contract BlobManagerTest is Test, BlobManager { - BlobManager internal blobs; - - function setUp() public virtual { - BlobDeployer blobsDeployer = new BlobDeployer(); - blobs = blobsDeployer.run(); - } -} diff --git a/test/BucketManager.t.sol b/test/BucketManager.t.sol deleted file mode 100644 index fe59410..0000000 --- a/test/BucketManager.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {StdUtils} from "forge-std/StdUtils.sol"; -import {Test, Vm} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {DeployScript as BucketManagerDeployer} from "../script/BucketManager.s.sol"; -import {BucketManager} from "../src/wrappers/BucketManager.sol"; - -// TODO: add integration tests once it's possible in CI -contract BucketManagerTest is Test, BucketManager { - BucketManager internal bucketManager; - - function setUp() public virtual { - BucketManagerDeployer bucketManagerDeployer = new BucketManagerDeployer(); - bucketManager = bucketManagerDeployer.run(); - } -} diff --git a/test/CreditManager.t.sol b/test/CreditManager.t.sol deleted file mode 100644 index 5df4282..0000000 --- a/test/CreditManager.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {StdUtils} from "forge-std/StdUtils.sol"; -import {Test, Vm} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {DeployScript as CreditDeployer} from "../script/CreditManager.s.sol"; -import {CreditManager} from "../src/wrappers/CreditManager.sol"; - -// TODO: add integration tests once it's possible in CI -contract CreditTest is Test, CreditManager { - CreditManager internal credit; - - function setUp() public virtual { - CreditDeployer creditDeployer = new CreditDeployer(); - credit = creditDeployer.run(); - } -} diff --git a/test/LibBlob.t.sol b/test/LibBlob.t.sol deleted file mode 100644 index 9777a02..0000000 --- a/test/LibBlob.t.sol +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {Test, Vm} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import { - Account as CreditAccount, - AddBlobParams, - Approval, - Blob, - BlobStatus, - BlobTuple, - CreditApproval, - SubnetStats, - Subscriber, - Subscription, - SubscriptionGroup -} from "../src/types/BlobTypes.sol"; -import {LibBlob} from "../src/wrappers/LibBlob.sol"; -import {LibWasm} from "../src/wrappers/LibWasm.sol"; - -contract LibBlobTest is Test { - using LibBlob for bytes; - - function testDecodeSubnetStats() public view { - bytes memory data = - hex"8d4b000a96b8e6cac7bd69c1f71b000009fffffffff50b520092f2d4180abe5bdab16ef96140000000004c00021142cb3f19fe361000004b0001ece0d8c484fb900000a1647261746584001ab34b9f101a7bc907151a00c097ce0a0600000000"; - SubnetStats memory stats = data.decodeSubnetStats(); - assertEq(stats.balance, 50003999999259732197879); - assertEq(stats.capacityFree, 10995116277749); - assertEq(stats.capacityUsed, 11); - assertEq(stats.creditSold, 50004000000000000000000000000000000000000); - assertEq(stats.creditCommitted, 2499364000000000000000000); - assertEq(stats.creditDebited, 9092000000000000000000); - assertEq(stats.tokenCreditRate, 1000000000000000000000000000000000000); - assertEq(stats.numAccounts, 10); - assertEq(stats.numBlobs, 6); - assertEq(stats.numAdded, 0); - assertEq(stats.bytesAdded, 0); - assertEq(stats.numResolving, 0); - assertEq(stats.bytesResolving, 0); - } - - function testDecodeAccount() public view { - // No approvals - bytes memory data = - hex"890052000eb194f8e1ae525fd5dcfab0800000000040f6190258a0a01a000151804b00010f0cf064dd59200000"; - CreditAccount memory account = data.decodeAccount(); - assertEq(account.capacityUsed, 0); - assertEq(account.creditFree, 5000000000000000000000000000000000000000); - assertEq(account.creditCommitted, 0); - assertEq(account.creditSponsor, address(0)); - assertEq(account.lastDebitEpoch, 600); - assertEq(account.approvalsTo.length, 0); - assertEq(account.approvalsFrom.length, 0); - assertEq(account.maxTtl, 86400); - assertEq(account.gasAllowance, 5000000000000000000000); - - // With all fields set: approvals to two different accounts, approvals from one account, and all optionals (set - // credit limit, gas fee limit, and ttl) - data = - hex"891152000eb316287ea5e336f1a66fbd7e21c000004c000135afae88fd38f250000056040a15d34aaf54267db7d7c367839aaf71a00a2c6a65190518a256040a15d34aaf54267db7d7c367839aaf71a00a2c6a65855400047bf19673df52e37f2410011d10000000000045003b9aca001915764b00c941498854fdb20000004056040a9965507d1a55bcc2695c58ba16fb37d819b0a4dc85f6f6f64040a156040a14dc79964da2c08b23698b3d3cc7ca32193d995585f6f6f640401a000151804b00010f28b1d2070cc7ee37"; - account = data.decodeAccount(); - assertEq(account.capacityUsed, 17); - assertEq(account.creditFree, 5001999999999998531056000000000000000000); - assertEq(account.creditCommitted, 1462452000000000000000000); - assertEq(account.creditSponsor, 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65); - assertEq(account.lastDebitEpoch, 1304); - assertEq(account.approvalsTo.length, 2); - assertEq(account.approvalsFrom.length, 1); - assertEq(account.maxTtl, 86400); - assertEq(account.gasAllowance, 5001999999735404424759); - assertEq(account.approvalsTo[0].addr, 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65); - assertEq(account.approvalsTo[0].approval.creditLimit, 100000000000000000000000000000000000000000000); - assertEq(account.approvalsTo[0].approval.gasFeeLimit, 1000000000); - assertEq(account.approvalsTo[0].approval.expiry, 5494); - assertEq(account.approvalsTo[0].approval.creditUsed, 950400000000000000000000); - assertEq(account.approvalsTo[0].approval.gasFeeUsed, 0); - assertEq(account.approvalsTo[1].addr, 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc); - assertEq(account.approvalsTo[1].approval.creditLimit, 0); - assertEq(account.approvalsTo[1].approval.gasFeeLimit, 0); - assertEq(account.approvalsTo[1].approval.expiry, 0); - assertEq(account.approvalsTo[1].approval.creditUsed, 0); - assertEq(account.approvalsTo[1].approval.gasFeeUsed, 0); - assertEq(account.approvalsFrom[0].addr, 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955); - assertEq(account.approvalsFrom[0].approval.creditLimit, 0); - assertEq(account.approvalsFrom[0].approval.gasFeeLimit, 0); - assertEq(account.approvalsFrom[0].approval.expiry, 0); - assertEq(account.approvalsFrom[0].approval.creditUsed, 0); - assertEq(account.approvalsFrom[0].approval.gasFeeUsed, 0); - } - - function testEncodeApproveCreditParams() public pure { - address from = 0x90F79bf6EB2c4f870365E785982E1f101E93b906; - address to = 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65; - address[] memory caller = new address[](2); - caller[0] = 0x976EA74026E726554dB657fA54763abd0C3a0aa9; - caller[1] = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955; - uint256 creditLimit = 12345; - uint256 gasFeeLimit = 987654321; - uint64 ttl = 4000; - bytes memory params = LibBlob.encodeApproveCreditParams(from, to, caller, creditLimit, gasFeeLimit, ttl); - assertEq( - params, - hex"8656040a90f79bf6eb2c4f870365e785982e1f101e93b90656040a15d34aaf54267db7d7c367839aaf71a00a2c6a658256040a976ea74026e726554db657fa54763abd0c3a0aa956040a14dc79964da2c08b23698b3d3cc7ca32193d99554b00029d394a5d630544000045003ade68b1190fa0" - ); - } - - function testEncodeRevokeCreditParams() public pure { - address from = 0x90F79bf6EB2c4f870365E785982E1f101E93b906; - address to = 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65; - address caller = 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65; - bytes memory params = LibBlob.encodeRevokeCreditParams(from, to, caller); - assertEq( - params, - hex"8356040a90f79bf6eb2c4f870365e785982e1f101e93b90656040a15d34aaf54267db7d7c367839aaf71a00a2c6a6556040a15d34aaf54267db7d7c367839aaf71a00a2c6a65" - ); - } - - function testEncodeAddBlobParams() public pure { - AddBlobParams memory params = AddBlobParams({ - sponsor: address(0), - source: "iw3iqgwajbfxlbgovlqdqhbtola3g4nevdvosqqvtovjvzwub3sa", - blobHash: "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq", - // TODO: update this once the dummy value is replaced with a blake3 hash; it's hardcoded as `[32; 0]` - metadataHash: "", - subscriptionId: "", - size: 6, - ttl: 0, // Null value - from: address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045) // vitalik.eth - }); - bytes memory encoded = LibBlob.encodeAddBlobParams(params); - assertEq( - encoded, - hex"88f69820184518b61888181a18c01848184b1875188418ce18aa18e01838181c1833187218c118b3187118a418a818ea18e9184215189b18aa189a18e618d40e18e49820188e184c187c181b189918db18fd185018e718a91851188518fe18ad185e18e11844188f18a90418a218fd18d7187818ea18f518f218db18fd1862189a189998200000000000000000000000000000000000000000000000000000000000000000a165696e6e65726006f656040ad8da6bf26964af9d7eed9e03e53415d37aa96045" - ); - } - - function testEncodeDecodeSubscriptionId() public view { - // A small key string - string memory key = "foo"; - bytes memory data = LibBlob.encodeSubscriptionId(key); - assertEq(data, hex"a165696e6e657263666f6f"); - string memory decoded = LibBlob.decodeSubscriptionId(data); - assertEq(decoded, key); - key = "f"; - data = LibBlob.encodeSubscriptionId(key); - assertEq(data, hex"a165696e6e65726166"); - decoded = LibBlob.decodeSubscriptionId(data); - assertEq(decoded, key); - - // Max 64 bytes - key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - data = LibBlob.encodeSubscriptionId(key); - assertEq( - data, - hex"a165696e6e6572784061616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161" - ); - decoded = LibBlob.decodeSubscriptionId(data); - assertEq(decoded, key); - - // Default case - key = ""; - data = LibBlob.encodeSubscriptionId(key); - assertEq(data, hex"a165696e6e657260"); - decoded = LibBlob.decodeSubscriptionId(data); - assertEq(decoded, ""); - } - - function testDecodeSubscribers() public view { - // One subscriber - bytes memory data = - hex"a1a165696e6e65727840393763323930343836383633336164376232353337646534623035336338356333396262306165306263313631383965316261633738373165613065656633371a00016fb4"; - Subscriber[] memory subscribers = LibBlob.decodeSubscribers(data); - // Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated address - assertEq(subscribers[0].subscriptionId, "97c2904868633ad7b2537de4b053c85c39bb0ae0bc16189e1bac7871ea0eef37"); - assertEq(subscribers[0].expiry, 94132); - - // Two subscribers (i.e., same blob, different buckets) - data = - hex"a2a165696e6e65727840353764303161653032386136636131303937653630346137633065363863303433353135663437386162636334626165663763386638353137343133343736651a00017f0fa165696e6e65727840613465343463326138303638313061613737353163376632373834633030376131623636326666333130383936363863613362346634653862326330633963631a00017f42"; - subscribers = LibBlob.decodeSubscribers(data); - // Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated address - assertEq(subscribers[0].subscriptionId, "57d01ae028a6ca1097e604a7c0e68c043515f478abcc4baef7c8f8517413476e"); - assertEq(subscribers[0].expiry, 98063); - assertEq(subscribers[1].subscriptionId, "a4e44c2a806810aa7751c7f2784c007a1b662ff31089668ca3b4f4e8b2c0c9cc"); - assertEq(subscribers[1].expiry, 98114); - - // Three different subscribers (different account and buckets), same blob - data = - hex"a3a165696e6e65727840316263653561373436613131333462663038326261616338306265393531393036626234303637383765313463396665653064376438646230666330623766341a00017fdaa165696e6e65727840353764303161653032386136636131303937653630346137633065363863303433353135663437386162636334626165663763386638353137343133343736651a00017f0fa165696e6e65727840613465343463326138303638313061613737353163376632373834633030376131623636326666333130383936363863613362346634653862326330633963631a00017f42"; - subscribers = LibBlob.decodeSubscribers(data); - // Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated - assertEq(subscribers[0].subscriptionId, "1bce5a746a1134bf082baac80be951906bb406787e14c9fee0d7d8db0fc0b7f4"); - assertEq(subscribers[0].expiry, 98266); - assertEq(subscribers[1].subscriptionId, "57d01ae028a6ca1097e604a7c0e68c043515f478abcc4baef7c8f8517413476e"); - assertEq(subscribers[1].expiry, 98063); - assertEq(subscribers[2].subscriptionId, "a4e44c2a806810aa7751c7f2784c007a1b662ff31089668ca3b4f4e8b2c0c9cc"); - assertEq(subscribers[2].expiry, 98114); - } - - function testDecodeSubscriptionGroup() public view { - bytes memory data = - hex"a17840343564616464393261326161373431383133323638376639613432313439306261643963356161643332656134653937623135356533373739316630306435308519021f1a0001539f982018fd18bb1871185b08188508184418a1183e182b18b518980118cb18a60218bf183b18351118720418fb18f41836188101181b18ef18941839f6f4"; - SubscriptionGroup[] memory subscriptionGroup = LibBlob.decodeSubscriptionGroup(data); - assertEq( - subscriptionGroup[0].subscriptionId, "45dadd92a2aa7418132687f9a421490bad9c5aad32ea4e97b155e37791f00d50" - ); - assertEq(subscriptionGroup[0].subscription.added, 543); - assertEq(subscriptionGroup[0].subscription.expiry, 86943); - assertEq(subscriptionGroup[0].subscription.source, "7w5xcwyiqueejij6fo2zqaoluybl6ozvcfzaj67ug2aqcg7psq4q"); - assertEq(subscriptionGroup[0].subscription.delegate, address(0)); - assertEq(subscriptionGroup[0].subscription.failed, false); - } - - function testDecodeSubscription() public view { - // No delegate - bytes memory data = - hex"8519021f1a0001539f982018fd18bb1871185b08188508184418a1183e182b18b518980118cb18a60218bf183b18351118720418fb18f41836188101181b18ef18941839f6f4"; - Subscription memory subscription = LibBlob.decodeSubscription(data); - assertEq(subscription.added, 543); - assertEq(subscription.expiry, 86943); - assertEq(subscription.source, "7w5xcwyiqueejij6fo2zqaoluybl6ozvcfzaj67ug2aqcg7psq4q"); - assertEq(subscription.delegate, address(0)); - assertEq(subscription.failed, false); - - // With delegate - data = - hex"85190b911a00015d11982018fd18bb1871185b08188508184418a1183e182b18b518980118cb18a60218bf183b18351118720418fb18f41836188101181b18ef1894183942007df4"; - subscription = LibBlob.decodeSubscription(data); - assertEq(subscription.added, 2961); - assertEq(subscription.expiry, 89361); - assertEq(subscription.source, "7w5xcwyiqueejij6fo2zqaoluybl6ozvcfzaj67ug2aqcg7psq4q"); - // Note: tests will always show this as a masked ID address, but in reality, it's a looked up delegated address - assertEq(subscription.delegate, 0xFF0000000000000000000000000000000000007D); - assertEq(subscription.failed, false); - } - - function testDecodeAddedOrPendingBlobs() public view { - // Single blob - bytes memory data = - hex"81829820182718c118b318c218610518aa0f183a18bc188f0a182c182b18b418af18f8184c18c111188218960618cf187f18c9181818fa18881822188218c9818356040acafeb0ba00000000000000000000000000000000a165696e6e657260982001184518fe18d118ba18fb1518ae184406189318e2186b18e1186518b1189018ee182f18901518250418c4181d187c18b318b3185c18191318ae"; - BlobTuple[] memory blobs = LibBlob.decodeAddedOrPendingBlobs(data); - assertEq(blobs[0].blobHash, "e7a3hqtbawva6ov4r4fcyk5uv74ezqirqklant37zempvcbcqleq"); - assertEq(blobs[0].sourceInfo[0].subscriber, 0xCafeB0ba00000000000000000000000000000000); - assertEq(blobs[0].sourceInfo[0].subscriptionId, ""); - assertEq(blobs[0].sourceInfo[0].source, "afc75un27mk24ragsprgxylfwgio4l4qcusqjra5psz3gxazcoxa"); - } - - function testDecodeBlob() public view { - bytes memory data = - hex"84069820182617188e181e18451847188b18b918f1184a181918e318b4188b051849182e18de188918ca18ea1874189f18e3185618a9185b18c9188c171884182fa3a165696e6e65727840316263653561373436613131333462663038326261616338306265393531393036626234303637383765313463396665653064376438646230666330623766341a00017fdaa165696e6e65727840353764303161653032386136636131303937653630346137633065363863303433353135663437386162636334626165663763386638353137343133343736651a00017f0fa165696e6e65727840613465343463326138303638313061613737353163376632373834633030376131623636326666333130383936363863613362346634653862326330633963631a00017f42685265736f6c766564"; - Blob memory blob = LibBlob.decodeBlob(data); - assertEq(blob.size, 6); - assertEq(blob.metadataHash, "eyly4hsfi6f3t4kkdhr3jcyfjexn5cok5j2j7y2wvfn4tdaxqqxq"); - assertEq(blob.subscribers.length, 3); - assertEq(blob.subscribers[0].subscriptionId, "1bce5a746a1134bf082baac80be951906bb406787e14c9fee0d7d8db0fc0b7f4"); - assertEq(blob.subscribers[0].expiry, 98266); - assertEq(uint8(blob.status), uint8(BlobStatus.Resolved)); - } - - function testDecodeBlobStatus() public view { - bytes memory status = hex"685265736F6C766564"; // Resolved - bytes memory decoded = LibWasm.decodeCborStringToBytes(status); - BlobStatus decodedStatus = LibBlob.decodeBlobStatus(decoded); - assertEq(uint8(decodedStatus), uint8(BlobStatus.Resolved)); - } - - function testDecodeTokenCreditRate() public view { - bytes memory data = hex"a1647261746584001ab34b9f101a7bc907151a00c097ce"; - uint256 rate = LibBlob.decodeTokenCreditRate(data); - assertEq(rate, 1000000000000000000000000000000000000); - } -} diff --git a/test/LibBucket.t.sol b/test/LibBucket.t.sol deleted file mode 100644 index d5dca66..0000000 --- a/test/LibBucket.t.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.26; - -import {Test, Vm} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {CreateBucketParams, Kind, Machine, ObjectState, ObjectValue, Query} from "../src/types/BucketTypes.sol"; -import {AddObjectParams, KeyValue, LibBucket} from "../src/wrappers/LibBucket.sol"; - -contract LibBucketTest is Test { - function testEncodeQueryParams() public pure { - // All default values - string memory prefix = ""; - string memory delimiter = "/"; - string memory startKey = ""; - uint64 limit = 0; - bytes memory encoded = LibBucket.encodeQueryParams(prefix, delimiter, startKey, limit); - assertEq(encoded, hex"8440412ff600"); - - // With a prefix - prefix = "hello/"; - encoded = LibBucket.encodeQueryParams(prefix, delimiter, startKey, limit); - assertEq(encoded, hex"844668656c6c6f2f412ff600"); - - // With a start key - prefix = ""; - startKey = "hello/world"; - encoded = LibBucket.encodeQueryParams(prefix, delimiter, startKey, limit); - assertEq(encoded, hex"8440412f8b18681865186c186c186f182f1877186f1872186c186400"); - } - - // Note: this is the underlying decoding in `queryObjects` - function testDecodeQuery() public view { - // Empty objects, empty common prefixes, null next key - Query memory query = LibBucket.decodeQuery(hex"838080f6"); - assertEq(query.objects.length, 0); - assertEq(query.commonPrefixes.length, 0); - assertEq(query.nextKey, ""); - - // Empty objects, 1 common prefixes, null next key - query = LibBucket.decodeQuery(hex"8380818618681865186c186c186f182ff6"); - assertEq(query.objects.length, 0); - assertEq(query.commonPrefixes[0], "hello/"); - assertEq(query.nextKey, ""); - - // Empty objects, 2 common prefixes, null next key - query = LibBucket.decodeQuery(hex"8380828618681865186c186c186f182f8718681865186c186c186f1832182ff6"); - assertEq(query.objects.length, 0); - assertEq(query.commonPrefixes[0], "hello/"); - assertEq(query.commonPrefixes[1], "hello2/"); - assertEq(query.nextKey, ""); - - // 1 object (with custom metadata), no common prefixes nor next key - query = LibBucket.decodeQuery( - hex"8381828b18681865186c186c186f182f1877186f1872186c1864a46468617368982018cf185d188418a1182f189818ec18fd18a518b71882183618cf18b51884187e1872183418c0185d18c518de18f2184618e51857182e18d7181d18b60118a96473697a6506666578706972791a00015e8d686d65746164617461a263666f6f636261726c636f6e74656e742d7479706578186170706c69636174696f6e2f6f637465742d73747265616d80f6" - ); - assertEq(query.objects[0].key, "hello/world"); - assertEq(query.objects[0].state.blobHash, "z5oyjijptdwp3jnxqi3m7nmepzzdjqc5yxpperxfk4xnohnwaguq"); - assertEq(query.objects[0].state.size, 6); - assertEq(query.objects[0].state.expiry, 89741); - assertEq(query.objects[0].state.metadata[0].key, "foo"); - assertEq(query.objects[0].state.metadata[0].value, "bar"); - assertEq(query.objects[0].state.metadata[1].key, "content-type"); - assertEq(query.objects[0].state.metadata[1].value, "application/octet-stream"); - assertEq(query.commonPrefixes.length, 0); - assertEq(query.nextKey, ""); - - // 2 objects, 1 common prefixes, null next key - query = LibBucket.decodeQuery( - hex"8382828b18681865186c186c186f182f1877186f1872186c1864a46468617368982018cf185d188418a1182f189818ec18fd18a518b71882183618cf18b51884187e1872183418c0185d18c518de18f2184618e51857182e18d7181d18b60118a96473697a6506666578706972791a00015e8d686d65746164617461a263666f6f636261726c636f6e74656e742d7479706578186170706c69636174696f6e2f6f637465742d73747265616d828a18681865186c186c186f182f1874186518731874a46468617368982018ed1830189318bf183318b318bc187601187a0e0818a618fa189318201820101881183918d718d9185418a5184f18b208183918d71873186818ae6473697a6506666578706972791a00015ef2686d65746164617461a16c636f6e74656e742d7479706578186170706c69636174696f6e2f6f637465742d73747265616d818c18681865186c186c186f182f1877186f1872186c1864182ff6" - ); - assertEq(query.objects[0].key, "hello/world"); - assertEq(query.objects[0].state.blobHash, "z5oyjijptdwp3jnxqi3m7nmepzzdjqc5yxpperxfk4xnohnwaguq"); - assertEq(query.objects[0].state.size, 6); - assertEq(query.objects[0].state.expiry, 89741); - assertEq(query.objects[0].state.metadata.length, 2); - assertEq(query.objects[1].key, "hello/test"); - assertEq(query.objects[1].state.blobHash, "5uyjhpztwo6hmal2byekn6uteaqbbajz27mvjjkpwiedtv3tncxa"); - assertEq(query.objects[1].state.size, 6); - assertEq(query.objects[1].state.expiry, 89842); - assertEq(query.objects[1].state.metadata.length, 1); // Always has `content-type` metadata - assertEq(query.commonPrefixes[0], "hello/world/"); - assertEq(query.nextKey, ""); - - // Query with `hello/test/` as start key and prefix `hello/`; 1 object, 1 common prefix, null next key - query = LibBucket.decodeQuery( - hex"8381828a18681865186c186c186f182f1874186518731874a46468617368982018ed1830189318bf183318b318bc187601187a0e0818a618fa189318201820101881183918d718d9185418a5184f18b208183918d71873186818ae6473697a6506666578706972791a00015ef2686d65746164617461a16c636f6e74656e742d7479706578186170706c69636174696f6e2f6f637465742d73747265616d818c18681865186c186c186f182f1877186f1872186c1864182ff6" - ); - assertEq(query.objects[0].key, "hello/test"); - assertEq(query.objects[0].state.blobHash, "5uyjhpztwo6hmal2byekn6uteaqbbajz27mvjjkpwiedtv3tncxa"); - assertEq(query.objects[0].state.size, 6); - assertEq(query.objects[0].state.metadata.length, 1); - assertEq(query.commonPrefixes[0], "hello/world/"); - assertEq(query.nextKey, ""); - } - - // Note: this is the underlying decoding in `getObject` - function testDecodeObjectValue() public view { - bytes memory data = - hex"859820188e184c187c181b189918db18fd185018e718a91851188518fe18ad185e18e11844188f18a90418a218fd18d7187818ea18f518f218db18fd1862189a18999820184a18d5189d1883189718f91849185318ce18ab1618d2182d18c618e718b1187018ae03187a185b188518c6189a186c18af18fc18ff18c918e318ac0306197260a263666f6f636261726c636f6e74656e742d7479706578186170706c69636174696f6e2f6f637465742d73747265616d"; - ObjectValue memory value = LibBucket.decodeObjectValue(data); - assertEq(value.blobHash, "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq"); - assertEq(value.recoveryHash, "jlkz3a4x7fevhtvlc3jc3rxhwfyk4a32loc4ngtmv76p7spdvqbq"); - assertEq(value.size, 6); - assertEq(value.expiry, 29280); - assertEq(value.metadata[0].key, "foo"); - assertEq(value.metadata[0].value, "bar"); - assertEq(value.metadata[1].key, "content-type"); - assertEq(value.metadata[1].value, "application/octet-stream"); - } - - function testDecodeList() public view { - // Empty list (no buckets) - Machine[] memory machines = LibBucket.decodeList(hex"80"); - assertEq(machines.length, 0); - - // 1 bucket - machines = LibBucket.decodeList(hex"8183664275636b65744300ed01a0"); - assertEq(machines.length, 1); - assertEq(uint8(machines[0].kind), uint8(Kind.Bucket)); - assertEq(machines[0].addr, 0xFf000000000000000000000000000000000000ed); - assertEq(machines[0].metadata.length, 0); - - // Multiple buckets (with metadata) - machines = LibBucket.decodeList( - hex"8383664275636b65744300ed01a083664275636b65744300ee01a083664275636b65744300ef01a165616c69617363666f6f" - ); - assertEq(machines.length, 3); - assertEq(uint8(machines[0].kind), uint8(Kind.Bucket)); - assertEq(machines[0].addr, 0xFf000000000000000000000000000000000000ed); - assertEq(machines[0].metadata.length, 0); - assertEq(uint8(machines[1].kind), uint8(Kind.Bucket)); - assertEq(machines[1].addr, 0xFF000000000000000000000000000000000000EE); - assertEq(machines[1].metadata.length, 0); - assertEq(uint8(machines[2].kind), uint8(Kind.Bucket)); - assertEq(machines[2].addr, 0xFf000000000000000000000000000000000000Ef); - assertEq(machines[2].metadata[0].key, "alias"); - assertEq(machines[2].metadata[0].value, "foo"); - } - - function testEncodeAddObjectParams() public pure { - KeyValue[] memory metadata = new KeyValue[](1); - metadata[0] = KeyValue({key: "content-type", value: "application/octet-stream"}); - AddObjectParams memory params = AddObjectParams({ - source: "cydkrslhbj4soqppzc66u6lzwxgjwgbhdlxmyeahytzqrh65qtjq", - key: "hello/world", - blobHash: "rzghyg4z3p6vbz5jkgc75lk64fci7kieul65o6hk6xznx7lctkmq", - // TODO: update this once the dummy value is replaced with a blake3 hash; it's hardcoded as `[32; 0]` - recoveryHash: "", - size: 6, - ttl: 0, // Null value - metadata: metadata, - overwrite: false, - from: 0x90F79bf6EB2c4f870365E785982E1f101E93b906 - }); - bytes memory encoded = LibBucket.encodeAddObjectParams(params); - assertEq( - encoded, - hex"899820160618a818c918670a18791827184118ef18c818bd18ea1879187918b518cc189b18181827181a18ee18cc100718c418f308189f18dd188418d34b68656c6c6f2f776f726c649820188e184c187c181b189918db18fd185018e718a91851188518fe18ad185e18e11844188f18a90418a218fd18d7187818ea18f518f218db18fd1862189a18999820000000000000000000000000000000000000000000000000000000000000000006f6a16c636f6e74656e742d7479706578186170706c69636174696f6e2f6f637465742d73747265616df456040a90f79bf6eb2c4f870365e785982e1f101e93b906" - ); - } - - function testEncodeCreateBucketParams() public pure { - CreateBucketParams memory params = CreateBucketParams({ - owner: 0x90F79bf6EB2c4f870365E785982E1f101E93b906, - kind: Kind.Bucket, - metadata: new KeyValue[](0) - }); - bytes memory encoded = LibBucket.encodeCreateBucketParams(params); - assertEq(encoded, hex"8356040a90f79bf6eb2c4f870365e785982e1f101e93b906664275636b6574a0"); - } - - function testEncodeDeleteObjectParams() public pure { - bytes memory encoded = - LibBucket.encodeDeleteObjectParams("hello/world", 0x90F79bf6EB2c4f870365E785982E1f101E93b906); - assertEq(encoded, hex"824b68656c6c6f2f776f726c6456040a90f79bf6eb2c4f870365e785982e1f101e93b906"); - } - - function testEncodeUpdateObjectMetadataParams() public pure { - KeyValue[] memory metadata = new KeyValue[](1); - metadata[0] = KeyValue("alias", "foo"); - bytes memory encoded = LibBucket.encodeUpdateObjectMetadataParams( - "hello/world", metadata, 0x90F79bf6EB2c4f870365E785982E1f101E93b906 - ); - assertEq( - encoded, hex"834b68656c6c6f2f776f726c64a165616c69617363666f6f56040a90f79bf6eb2c4f870365e785982e1f101e93b906" - ); - } -} diff --git a/test/LibWasm.t.sol b/test/LibWasm.t.sol index 5056a88..0129907 100644 --- a/test/LibWasm.t.sol +++ b/test/LibWasm.t.sol @@ -5,7 +5,7 @@ import {Test, Vm} from "forge-std/Test.sol"; import {console2 as console} from "forge-std/console2.sol"; import {Base32} from "../src/util/Base32.sol"; -import {InvalidValue, KeyValue, LibWasm} from "../src/wrappers/LibWasm.sol"; +import {InvalidValue, KeyValue, LibWasm} from "../src/util/LibWasm.sol"; contract LibWasmTest is Test { function testDecodeCborArray() public view { diff --git a/test/scripts/wrapper_integration_test.sh b/test/scripts/wrapper_integration_test.sh old mode 100755 new mode 100644