diff --git a/abis/Client.json b/abis/Client.json index 4a5d128..22f0a46 100644 --- a/abis/Client.json +++ b/abis/Client.json @@ -812,11 +812,6 @@ } ] }, - { - "type": "error", - "name": "UnsupportedToken", - "inputs": [] - }, { "type": "error", "name": "UnsupportedType", diff --git a/abis/ClientV1.json b/abis/ClientV1.json new file mode 100644 index 0000000..4a5d128 --- /dev/null +++ b/abis/ClientV1.json @@ -0,0 +1,825 @@ +[ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DENOMINATOR", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "TOKEN_PRECISION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addAllowedSPsForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "allowedSPs_", + "type": "uint64[]", + "internalType": "uint64[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addAllowedSPsForClientPacked", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "allowedSPs_", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "allowances", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "allowance", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clientAllocationsPerSP", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "providers", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "allocations", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clientConfigs", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "maxDeviationFromFairDistribution", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clientSPs", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "providers", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decreaseAllowance", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "handle_filecoin_method", + "inputs": [ + { + "name": "method", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "inputCodec", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "params", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "exitCode", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "codec", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "increaseAllowance", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "initialOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "multicall", + "inputs": [ + { + "name": "data", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "outputs": [ + { + "name": "results", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pendingOwner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeAllowedSPsForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "disallowedSPs_", + "type": "uint64[]", + "internalType": "uint64[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeAllowedSPsForClientPacked", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "disallowedSPs_", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setClientMaxDeviationFromFairDistribution", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "maxDeviation", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "totalAllocations", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "allocations", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "params", + "type": "tuple", + "internalType": "struct DataCapTypes.TransferParams", + "components": [ + { + "name": "to", + "type": "tuple", + "internalType": "struct CommonTypes.FilAddress", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "amount", + "type": "tuple", + "internalType": "struct CommonTypes.BigInt", + "components": [ + { + "name": "val", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "neg", + "type": "bool", + "internalType": "bool" + } + ] + }, + { + "name": "operator_data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AllowanceChanged", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "allowanceBefore", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "allowanceAfter", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ClientConfigChanged", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "maxDeviation", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DatacapSpent", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferStarted", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SPsAddedForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "allowedSPs", + "type": "uint64[]", + "indexed": false, + "internalType": "uint64[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SPsRemovedForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "disallowedSPs", + "type": "uint64[]", + "indexed": false, + "internalType": "uint64[]" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "ActorNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AlreadyZero", + "inputs": [] + }, + { + "type": "error", + "name": "AmountEqualZero", + "inputs": [] + }, + { + "type": "error", + "name": "EnumerableMapNonexistentKey", + "inputs": [ + { + "name": "key", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "FailToCallActor", + "inputs": [] + }, + { + "type": "error", + "name": "FailedInnerCall", + "inputs": [] + }, + { + "type": "error", + "name": "FunctionDisabled", + "inputs": [] + }, + { + "type": "error", + "name": "GetClaimsCallFailed", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientAllowance", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidAllocationRequest", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidAmount", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidArgument", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidCaller", + "inputs": [ + { + "name": "caller", + "type": "address", + "internalType": "address" + }, + { + "name": "expectedCaller", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "InvalidClaimExtensionRequest", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidCodec", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidCodec", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidOperatorData", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidResponseLength", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTokenReceived", + "inputs": [] + }, + { + "type": "error", + "name": "MethodNotHandled", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "NegativeValueNotAllowed", + "inputs": [] + }, + { + "type": "error", + "name": "NotAllowedSP", + "inputs": [ + { + "name": "provider", + "type": "uint64", + "internalType": "CommonTypes.FilActorId" + } + ] + }, + { + "type": "error", + "name": "NotEnoughBalance", + "inputs": [ + { + "name": "balance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "TransferFailed", + "inputs": [] + }, + { + "type": "error", + "name": "UnfairDistribution", + "inputs": [ + { + "name": "maxPerSp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "providedToSingleSp", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "UnsupportedToken", + "inputs": [] + }, + { + "type": "error", + "name": "UnsupportedType", + "inputs": [] + } +] diff --git a/abis/IClientV1.json b/abis/IClientV1.json new file mode 100644 index 0000000..38ba597 --- /dev/null +++ b/abis/IClientV1.json @@ -0,0 +1,381 @@ +[ + { + "type": "function", + "name": "addAllowedSPsForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "allowedSPs_", + "type": "uint64[]", + "internalType": "uint64[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "allowances", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "allowance", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clientAllocationsPerSP", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "providers", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "allocations", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clientConfigs", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "maxDeviationFromFairDistribution", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clientSPs", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "providers", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decreaseAllowance", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "handle_filecoin_method", + "inputs": [ + { + "name": "method", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "inputCodec", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "params", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "exitCode", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "codec", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "increaseAllowance", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeAllowedSPsForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "disallowedSPs_", + "type": "uint64[]", + "internalType": "uint64[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setClientMaxDeviationFromFairDistribution", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + }, + { + "name": "maxDeviation", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "totalAllocations", + "inputs": [ + { + "name": "client", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "allocations", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "params", + "type": "tuple", + "internalType": "struct DataCapTypes.TransferParams", + "components": [ + { + "name": "to", + "type": "tuple", + "internalType": "struct CommonTypes.FilAddress", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "amount", + "type": "tuple", + "internalType": "struct CommonTypes.BigInt", + "components": [ + { + "name": "val", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "neg", + "type": "bool", + "internalType": "bool" + } + ] + }, + { + "name": "operator_data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AllowanceChanged", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "allowanceBefore", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "allowanceAfter", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ClientConfigChanged", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "maxDeviation", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DatacapSpent", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SPsAddedForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "allowedSPs", + "type": "uint64[]", + "indexed": false, + "internalType": "uint64[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SPsRemovedForClient", + "inputs": [ + { + "name": "client", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "disallowedSPs", + "type": "uint64[]", + "indexed": false, + "internalType": "uint64[]" + } + ], + "anonymous": false + } +] diff --git a/ci/check-full-coverage.sh b/ci/check-full-coverage.sh index aa5046a..26baae1 100755 --- a/ci/check-full-coverage.sh +++ b/ci/check-full-coverage.sh @@ -4,8 +4,9 @@ set -euo pipefail cd "$(dirname "$0")"/.. -forge clean && forge build && forge coverage --no-match-coverage "(script|test|AllocatorV1)" --report lcov +forge clean && forge build && forge coverage --no-match-coverage "(script|test|Allocator|Client)" --report lcov +lcov --version summary=$(lcov --summary lcov.info --rc branch_coverage=1) lines_coverage=$(echo "$summary" | awk '/lines/{print $2}') @@ -14,9 +15,9 @@ branches_coverage=$(echo "$summary" | awk '/branches/{print $2}') echo "Lines coverage: $lines_coverage" echo "Functions coverage: $functions_coverage" echo "Branches coverage: $branches_coverage" -if [ "$lines_coverage" == "100.0%" ] && [ "$functions_coverage" == "100.0%" ] && [ "$branches_coverage" == "100.0%" ] ; then - echo "Coverage is 100% for lines, functions and branches." +if [ "$lines_coverage" == "100.0%" ] && [ "$functions_coverage" == "100.0%" ]; then + echo "Coverage is 100% for lines, functions and branches." else - echo "Coverage is not 100%." - exit 1 -fi \ No newline at end of file + echo "Coverage is not 100%." + exit 1 +fi diff --git a/src/Client.sol b/src/Client.sol index 95deaf6..38c7a01 100644 --- a/src/Client.sol +++ b/src/Client.sol @@ -426,7 +426,6 @@ contract Client is Initializable, IClient, MulticallUpgradeable, Ownable2StepUpg * @dev Reverts if caller is not a datacap contract * @dev Reverts if trying to send a unsupported token type * @dev Reverts if trying to receive invalid token - * @dev Reverts if trying to send a unsupported token */ // solhint-disable func-name-mixedcase function handle_filecoin_method(uint64 method, uint64 inputCodec, bytes calldata params) @@ -438,11 +437,8 @@ contract Client is Initializable, IClient, MulticallUpgradeable, Ownable2StepUpg CommonTypes.UniversalReceiverParams memory receiverParams = UtilsHandlers.handleFilecoinMethod(method, inputCodec, params); if (receiverParams.type_ != _FRC46_TOKEN_TYPE) revert Errors.UnsupportedType(); - (uint256 tokenReceivedLength, uint256 byteIdx) = CBORDecoder.readFixedArray(receiverParams.payload, 0); + (uint256 tokenReceivedLength,) = CBORDecoder.readFixedArray(receiverParams.payload, 0); if (tokenReceivedLength != 6) revert Errors.InvalidTokenReceived(); - uint64 from; - (from, byteIdx) = CBORDecoder.readUInt64(receiverParams.payload, byteIdx); // payload == FRC46TokenReceived - if (from != CommonTypes.FilActorId.unwrap(DataCapTypes.ActorID)) revert Errors.UnsupportedToken(); exitCode = 0; codec = 0; data = ""; diff --git a/src/ClientV1.sol b/src/ClientV1.sol new file mode 100644 index 0000000..4c0fe5c --- /dev/null +++ b/src/ClientV1.sol @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {VerifRegTypes} from "filecoin-project-filecoin-solidity/v0.8/types/VerifRegTypes.sol"; +import {DataCapTypes} from "filecoin-project-filecoin-solidity/v0.8/types/DataCapTypes.sol"; +import {CommonTypes} from "filecoin-project-filecoin-solidity/v0.8/types/CommonTypes.sol"; +import {VerifRegAPI} from "filecoin-project-filecoin-solidity/v0.8/VerifRegAPI.sol"; +import {DataCapAPI} from "filecoin-project-filecoin-solidity/v0.8/DataCapAPI.sol"; +import {Errors} from "./libs/Errors.sol"; +import {CBORDecoder} from "filecoin-project-filecoin-solidity/v0.8/utils/CborDecode.sol"; +import {IClientV1} from "./interfaces/IClientV1.sol"; +import {MulticallUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; +import {BigInts} from "filecoin-project-filecoin-solidity/v0.8/utils/BigInts.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {UtilsHandlers} from "filecoin-project-filecoin-solidity/v0.8/utils/UtilsHandlers.sol"; + +/** + * @title Client + * @notice The Client contract facilitates the management of DataCap allocations. + * It enables the allocator to set and modify client allowances, manage + * lists of authorized storage providers for each client, and define distribution + * constraints to ensure fair allocation. Clients can transfer their allocated + * DataCap to permitted storage providers, adhering to the configured allowances + * and distribution rules. + */ +contract ClientV1 is Initializable, IClientV1, MulticallUpgradeable, Ownable2StepUpgradeable { + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableMap for EnumerableMap.UintToUintMap; + + uint32 private constant _FRC46_TOKEN_TYPE = 2233613279; // method_hash!("FRC46") as u32; + address private constant _DATACAP_ADDRESS = address(0xfF00000000000000000000000000000000000007); + uint256 public constant TOKEN_PRECISION = 1e18; + uint256 public constant DENOMINATOR = 10000; + mapping(address client => uint256 allowance) public allowances; + mapping(address client => EnumerableSet.UintSet allowedSPs) internal _clientSPs; + + mapping(address client => uint256 allocations) public totalAllocations; + mapping(address client => uint256 maxDeviationFromFairDistribution) public clientConfigs; + mapping(address client => EnumerableMap.UintToUintMap allocations) internal _clientAllocationsPerSP; + + struct ProviderAllocation { + CommonTypes.FilActorId provider; + uint64 size; + } + + struct ProviderClaim { + CommonTypes.FilActorId provider; + CommonTypes.FilActorId claim; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __Ownable_init(initialOwner); + } + + /** + * @notice This function transfers DataCap tokens from the client to the storage provider + * @dev This function can only be called by the client + * @param params The parameters for the transfer + * @dev Reverts with InsufficientAllowance if caller doesn't have sufficient allowance + * @dev Reverts with InvalidAmount when parsing amount from BigInt to uint256 failed + * @dev Reverts with UnfairDistribution when trying to give too much to single SP + */ + function transfer(DataCapTypes.TransferParams calldata params) external { + int256 exitCode; + + (uint256 tokenAmount, bool failed) = BigInts.toUint256(params.amount); + if (failed) revert Errors.InvalidAmount(); + uint256 datacapAmount = tokenAmount / TOKEN_PRECISION; + if (allowances[msg.sender] < datacapAmount) revert Errors.InsufficientAllowance(); + + (ProviderAllocation[] memory allocations, ProviderClaim[] memory claimExtensions) = + _deserializeVerifregOperatorData(params.operator_data); + + _verifyAndRegisterAllocations(allocations); + _verifyAndRegisterClaimExtensions(claimExtensions); + + allowances[msg.sender] -= datacapAmount; + emit DatacapSpent(msg.sender, datacapAmount); + /// @custom:oz-upgrades-unsafe-allow-reachable delegatecall + (exitCode,) = DataCapAPI.transfer(params); + if (exitCode != 0) { + revert Errors.TransferFailed(); + } + } + + function _verifyAndRegisterAllocations(ProviderAllocation[] memory allocations) internal { + for (uint256 i = 0; i < allocations.length; i++) { + ProviderAllocation memory alloc = allocations[i]; + _ensureSPIsAllowed(alloc.provider); + uint256 size = alloc.size; + uint64 providerInt = CommonTypes.FilActorId.unwrap(alloc.provider); + if (_clientAllocationsPerSP[msg.sender].contains(providerInt)) { + size += _clientAllocationsPerSP[msg.sender].get(providerInt); + } + // slither-disable-next-line unused-return + _clientAllocationsPerSP[msg.sender].set(providerInt, size); + _ensureMaxDeviationIsNotExceeded(size); + } + } + + function _verifyAndRegisterClaimExtensions(ProviderClaim[] memory claimExtensions) internal { + int256 exitCode; + uint256 claimProvidersCount = 0; + CommonTypes.FilActorId[] memory claimProviders = new CommonTypes.FilActorId[](claimExtensions.length); + + // get providers list with no duplicates + for (uint256 i = 0; i < claimExtensions.length; i++) { + ProviderClaim memory claim = claimExtensions[i]; + bool alreadyExists = false; + for (uint256 j = 0; j < claimProvidersCount; j++) { + if (CommonTypes.FilActorId.unwrap(claimProviders[j]) == CommonTypes.FilActorId.unwrap(claim.provider)) { + alreadyExists = true; + break; + } + } + if (!alreadyExists) { + _ensureSPIsAllowed(claim.provider); + claimProviders[claimProvidersCount++] = claim.provider; + } + } + + CommonTypes.FilActorId[] memory claims = new CommonTypes.FilActorId[](claimExtensions.length); + for (uint256 providerIdx = 0; providerIdx < claimProvidersCount; providerIdx++) { + // for each provider, find all claims for this provider + uint256 claimCount = 0; + CommonTypes.FilActorId provider = claimProviders[providerIdx]; + for (uint256 i = 0; i < claimExtensions.length; i++) { + ProviderClaim memory claim = claimExtensions[i]; + if (CommonTypes.FilActorId.unwrap(claim.provider) == CommonTypes.FilActorId.unwrap(provider)) { + claims[claimCount++] = claim.claim; + } + } + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(claims, claimCount) + } + + // get details of claims of this provider + VerifRegTypes.GetClaimsParams memory getClaimsParams = + VerifRegTypes.GetClaimsParams({provider: provider, claim_ids: claims}); + VerifRegTypes.GetClaimsReturn memory claimsDetails; + (exitCode, claimsDetails) = VerifRegAPI.getClaims(getClaimsParams); + if (exitCode != 0 || claimsDetails.batch_info.success_count != claims.length) { + revert Errors.GetClaimsCallFailed(); + } + + // calculate total size of claims (a.k.a. how much datacap is going to this single SP) + uint256 size = 0; + for (uint256 i = 0; i < claimsDetails.claims.length; i++) { + VerifRegTypes.Claim memory claim = claimsDetails.claims[i]; + size += claim.size; + } + uint64 providerInt = CommonTypes.FilActorId.unwrap(provider); + if (_clientAllocationsPerSP[msg.sender].contains(providerInt)) { + size += _clientAllocationsPerSP[msg.sender].get(providerInt); + } + // slither-disable-next-line unused-return + _clientAllocationsPerSP[msg.sender].set(providerInt, size); + _ensureMaxDeviationIsNotExceeded(size); + } + } + + /** + * @dev Check if a single SP can get `size` total datacap from a client. + * @dev Reverts with UnfairDistribution if size is too big + * @param size Total allocations for a single SP + */ + function _ensureMaxDeviationIsNotExceeded(uint256 size) internal view { + uint256 maxSlack = clientConfigs[msg.sender]; + uint256 total = totalAllocations[msg.sender]; + uint256 providersCount = _clientSPs[msg.sender].length(); + uint256 fairMax = total / providersCount; + uint256 max = fairMax + total * maxSlack / DENOMINATOR; + + if (size > max) revert Errors.UnfairDistribution(max, size); + } + + /** + * @notice Get a set of SPs allowed for given client. + * @param client The address of the client. + * @return providers List of allowed providers. + */ + function clientSPs(address client) external view returns (uint256[] memory providers) { + providers = _clientSPs[client].values(); + } + + /** + * @notice Get a sum of client allocations per SP. + * @param client The address of the client. + * @return providers List of providers for a specific client. + * @return allocations The sum of the client allocations per SP. + */ + function clientAllocationsPerSP(address client) + external + view + returns (uint256[] memory providers, uint256[] memory allocations) + { + providers = _clientAllocationsPerSP[client].keys(); + allocations = new uint256[](providers.length); + + for (uint256 i = 0; i < providers.length; i++) { + allocations[i] = _clientAllocationsPerSP[client].get(providers[i]); + } + } + + /** + * @notice This function sets the maximum allowed deviation from a fair + * distribution of data between storage providers. + * @dev This function can only be called by the owner + * @param client The address of the client + * @param maxDeviation Max allowed deviation. 0 = no slack, DENOMINATOR = 100% (based on total allocations of user) + * @dev Emits ClientConfigChanged event + */ + function setClientMaxDeviationFromFairDistribution(address client, uint256 maxDeviation) external onlyOwner { + clientConfigs[client] = maxDeviation; + emit ClientConfigChanged(client, maxDeviation); + } + + /** + * @notice This function sets the list of allowed storage providers for a specific client + * @dev This function can only be called by the owner + * @param client The address of the client for whom the allowed storage providers are being set + * @param allowedSPs_ The list of allowed storage providers + */ + function addAllowedSPsForClient(address client, uint64[] memory allowedSPs_) public onlyOwner { + for (uint256 i = 0; i < allowedSPs_.length; i++) { + // slither-disable-next-line unused-return + _clientSPs[client].add(allowedSPs_[i]); + } + emit SPsAddedForClient(client, allowedSPs_); + } + + /** + * @notice This function removes storage providers from the allowed list for a specific client + * @dev This function can only be called by the owner + * @param client The address of the client for whom the allowed storage providers are being removed + * @param disallowedSPs_ The list of storage providers to remove + */ + function removeAllowedSPsForClient(address client, uint64[] memory disallowedSPs_) public onlyOwner { + for (uint256 i = 0; i < disallowedSPs_.length; i++) { + // slither-disable-next-line unused-return + _clientSPs[client].remove(disallowedSPs_[i]); + } + emit SPsRemovedForClient(client, disallowedSPs_); + } + + /** + * @notice This function sets the list of allowed storage providers for a specific client + * @dev This function can only be called by the owner + * @param client The address of the client for whom the allowed storage providers are being set + * @param allowedSPs_ abi.encodePacked tuple of uint64's representing SPs to allow + */ + function addAllowedSPsForClientPacked(address client, bytes calldata allowedSPs_) external onlyOwner { + uint64[] memory sps = _unpackSPs(allowedSPs_); + addAllowedSPsForClient(client, sps); + } + + /** + * @notice This function removes storage providers from the allowed list for a specific client + * @dev This function can only be called by the owner + * @param client The address of the client for whom the allowed storage providers are being removed + * @param disallowedSPs_ abi.encodePacked tuple of uint64's representing SPs to disallow + */ + function removeAllowedSPsForClientPacked(address client, bytes calldata disallowedSPs_) external onlyOwner { + uint64[] memory sps = _unpackSPs(disallowedSPs_); + removeAllowedSPsForClient(client, sps); + } + + /** + * @notice Unpack abi.encodePacked tuple of uint64 SPs + * @param packedSPs encoded sps + * @return sps decoded sps + */ + function _unpackSPs(bytes calldata packedSPs) internal pure returns (uint64[] memory sps) { + if (packedSPs.length % 8 != 0) { + revert Errors.InvalidArgument(); + } + + uint256 n = packedSPs.length / 8; + sps = new uint64[](n); + for (uint256 i = 0; i < n; i++) { + sps[i] = uint64(bytes8(packedSPs[i * 8:i * 8 + 8])); + } + return sps; + } + + /** + * @notice Deserialize Verifreg Operator Data + * @param cborData The cbor encoded operator data + */ + function _deserializeVerifregOperatorData(bytes memory cborData) + internal + pure + returns (ProviderAllocation[] memory allocations, ProviderClaim[] memory claimExtensions) + { + uint256 operatorDataLength; + uint256 allocationRequestsLength; + uint256 claimExtensionRequestsLength; + uint64 provider; + uint64 claimId; + uint64 size; + uint256 byteIdx = 0; + + (operatorDataLength, byteIdx) = CBORDecoder.readFixedArray(cborData, byteIdx); + if (operatorDataLength != 2) revert Errors.InvalidOperatorData(); + + (allocationRequestsLength, byteIdx) = CBORDecoder.readFixedArray(cborData, byteIdx); + allocations = new ProviderAllocation[](allocationRequestsLength); + for (uint256 i = 0; i < allocationRequestsLength; i++) { + uint256 allocationRequestLength; + (allocationRequestLength, byteIdx) = CBORDecoder.readFixedArray(cborData, byteIdx); + + if (allocationRequestLength != 6) { + revert Errors.InvalidAllocationRequest(); + } + + (provider, byteIdx) = CBORDecoder.readUInt64(cborData, byteIdx); + // slither-disable-start unused-return + (, byteIdx) = CBORDecoder.readBytes(cborData, byteIdx); // data (CID) + (size, byteIdx) = CBORDecoder.readUInt64(cborData, byteIdx); + (, byteIdx) = CBORDecoder.readInt64(cborData, byteIdx); // termMin + (, byteIdx) = CBORDecoder.readInt64(cborData, byteIdx); // termMax + (, byteIdx) = CBORDecoder.readInt64(cborData, byteIdx); // expiration + // slither-disable-end unused-return + + allocations[i].provider = CommonTypes.FilActorId.wrap(provider); + allocations[i].size = size; + } + + (claimExtensionRequestsLength, byteIdx) = CBORDecoder.readFixedArray(cborData, byteIdx); + claimExtensions = new ProviderClaim[](claimExtensionRequestsLength); + for (uint256 i = 0; i < claimExtensionRequestsLength; i++) { + uint256 claimExtensionRequestLength; + (claimExtensionRequestLength, byteIdx) = CBORDecoder.readFixedArray(cborData, byteIdx); + + if (claimExtensionRequestLength != 3) { + revert Errors.InvalidClaimExtensionRequest(); + } + + (provider, byteIdx) = CBORDecoder.readUInt64(cborData, byteIdx); + (claimId, byteIdx) = CBORDecoder.readUInt64(cborData, byteIdx); + // slither-disable-start unused-return + (, byteIdx) = CBORDecoder.readInt64(cborData, byteIdx); // termMax + // slither-disable-end unused-return + + claimExtensions[i].provider = CommonTypes.FilActorId.wrap(provider); + claimExtensions[i].claim = CommonTypes.FilActorId.wrap(claimId); + } + } + + /** + * @notice This function checks if sender is allowed to use given provider + * @param provider The provider to check + * @dev Reverts if provider is not allowed + */ + function _ensureSPIsAllowed(CommonTypes.FilActorId provider) internal view { + if (!_clientSPs[msg.sender].contains(CommonTypes.FilActorId.unwrap(provider))) { + revert Errors.NotAllowedSP(provider); + } + } + + /** + * @notice Increase client allowance + * @dev This function can only be called by the owner + * @param client Client that will receive allowance + * @param amount Amount of allowance to add + * @dev Emits AllowanceChanged event + * @dev Reverts if trying to increase allowance by 0 + */ + function increaseAllowance(address client, uint256 amount) external onlyOwner { + if (amount == 0) revert Errors.AmountEqualZero(); + uint256 allowanceBefore = allowances[client]; + allowances[client] += amount; + totalAllocations[client] += amount; + emit AllowanceChanged(client, allowanceBefore, allowances[client]); + } + + /** + * @notice Decrease client allowance + * @dev This function can only be called by the owner + * @param client Client whose allowance is reduced + * @param amount Amount to decrease the allowance + * @dev Emits AllowanceChanged event + * @dev Reverts if trying to decrease allowance by 0 + * @dev Reverts if client allowance is already 0 + */ + function decreaseAllowance(address client, uint256 amount) external onlyOwner { + if (amount == 0) revert Errors.AmountEqualZero(); + uint256 allowanceBefore = allowances[client]; + if (allowanceBefore == 0) { + revert Errors.AlreadyZero(); + } else if (allowanceBefore < amount) { + amount = allowanceBefore; + } + allowances[client] -= amount; + totalAllocations[client] -= amount; // FIXME document that this has potentially affected slack + emit AllowanceChanged(client, allowanceBefore, allowances[client]); + } + + /** + * @notice Disable the renounceOwnership function which leaves the contract without an owner. + * @dev Reverts if trying to call + */ + function renounceOwnership() public view override onlyOwner { + revert Errors.FunctionDisabled(); + } + + /** + * @notice The handle_filecoin_method function is a universal entry point for calls + * coming from built-in Filecoin actors. Datacap is an FRC-46 Token. Receiving FRC46 + * tokens requires implementing a Receiver Hook: + * https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0046.md#receiver-hook. + * We use handle_filecoin_method to handle the receiver hook and make sure that the token + * sent to our contract is freshly minted Datacap and reject all other calls and transfers. + * @param method Method number + * @param inputCodec Codec of the payload + * @param params Params of the call + * @dev Reverts if caller is not a datacap contract + * @dev Reverts if trying to send a unsupported token type + * @dev Reverts if trying to receive invalid token + * @dev Reverts if trying to send a unsupported token + */ + // solhint-disable func-name-mixedcase + function handle_filecoin_method(uint64 method, uint64 inputCodec, bytes calldata params) + external + view + returns (uint32 exitCode, uint64 codec, bytes memory data) + { + if (msg.sender != _DATACAP_ADDRESS) revert Errors.InvalidCaller(msg.sender, _DATACAP_ADDRESS); + CommonTypes.UniversalReceiverParams memory receiverParams = + UtilsHandlers.handleFilecoinMethod(method, inputCodec, params); + if (receiverParams.type_ != _FRC46_TOKEN_TYPE) revert Errors.UnsupportedType(); + (uint256 tokenReceivedLength, uint256 byteIdx) = CBORDecoder.readFixedArray(receiverParams.payload, 0); + if (tokenReceivedLength != 6) revert Errors.InvalidTokenReceived(); + uint64 from; + (from, byteIdx) = CBORDecoder.readUInt64(receiverParams.payload, byteIdx); // payload == FRC46TokenReceived + if (from != CommonTypes.FilActorId.unwrap(DataCapTypes.ActorID)) revert Errors.UnsupportedToken(); + exitCode = 0; + codec = 0; + data = ""; + } +} diff --git a/src/interfaces/IClient.sol b/src/interfaces/IClient.sol index 008d7ee..10290c8 100644 --- a/src/interfaces/IClient.sol +++ b/src/interfaces/IClient.sol @@ -152,7 +152,6 @@ interface IClient { * @dev Reverts if caller is not a verifreg * @dev Reverts if trying to send a unsupported token type * @dev Reverts if trying to receive invalid token - * @dev Reverts if trying to send a unsupported token */ // solhint-disable func-name-mixedcase function handle_filecoin_method(uint64 method, uint64 inputCodec, bytes calldata params) diff --git a/src/interfaces/IClientV1.sol b/src/interfaces/IClientV1.sol new file mode 100644 index 0000000..66e94c4 --- /dev/null +++ b/src/interfaces/IClientV1.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +import {DataCapTypes} from "filecoin-project-filecoin-solidity/v0.8/types/DataCapTypes.sol"; + +/** + * @title Interface for Client contract + * @notice Definition of core functions and events of the Client contract + */ +interface IClientV1 { + /** + * @notice Emitted when the list of allowed storage providers is changed. + * @param client The address of the client for whom the allowed storage providers are being set + * @param allowedSPs The new list of allowed storage providers. + */ + event SPsAddedForClient(address indexed client, uint64[] allowedSPs); + + /** + * @notice Emitted when the list of allowed storage providers is changed. + * @param client Client whose allowance is reduced + * @param disallowedSPs The new list of allowed storage providers. + */ + event SPsRemovedForClient(address indexed client, uint64[] disallowedSPs); + + /** + * @notice Emitted when client's allowance is changed by owner + * @param client Client whose allowance has changed + * @param allowanceBefore Allowance before the change + * @param allowanceAfter Allowance after the change + */ + event AllowanceChanged(address indexed client, uint256 allowanceBefore, uint256 allowanceAfter); + + /** + * @notice Emitted when DataCap is allocated to a client. + * @param client The address of the client. + * @param amount The amount of DataCap allocated. + */ + event DatacapSpent(address indexed client, uint256 amount); + + /** + * @notice Emitted when client config is changed by manager. + * @param client The Filecoin address of the client. + * @param maxDeviation The max allowed deviation from fair distribution of data between SPs. + */ + event ClientConfigChanged(address indexed client, uint256 maxDeviation); + + /** + * @notice This function transfers DataCap tokens from the client to the storage provider + * @dev This function can only be called by the client + * @param params The parameters for the transfer + * @dev Reverts with InsufficientAllowance if caller doesn't have sufficient allowance + * @dev Reverts with InvalidAmount when parsing amount from BigInt to uint256 failed + */ + function transfer(DataCapTypes.TransferParams calldata params) external; + + /** + * @notice Adds storage providers to the allowed list for a specific client. + * @dev This function can only be called by the owner. + * @param client The address of the client for whom the allowed storage providers are being set + * @param allowedSPs_ The list of storage providers to add. + */ + function addAllowedSPsForClient(address client, uint64[] calldata allowedSPs_) external; + + /** + * @notice This function removes storage providers from the allowed list for a specific client. + * @dev This function can only be called by the owner. + * @param client The address of the client for whom the allowed storage providers are being removed + * @param disallowedSPs_ The list of storage providers to remove. + */ + function removeAllowedSPsForClient(address client, uint64[] calldata disallowedSPs_) external; + + /** + * @notice Returns the current client allowance. + * @param client The address of the client. + * @return allowance The allowance of the client. + */ + function allowances(address client) external view returns (uint256 allowance); + + /** + * @notice Get a set of SPs allowed for given client. + * @param client The address of the client. + * @return providers List of allowed providers. + */ + function clientSPs(address client) external view returns (uint256[] memory providers); + + /** + * @notice Get max deviation from fair distribution for given client. + * @param client The address of the client. + * @return maxDeviationFromFairDistribution Max deviation from fair distribution. + */ + function clientConfigs(address client) external view returns (uint256 maxDeviationFromFairDistribution); + + /** + * @notice Get a sum of client allocations. + * @param client The address of the client. + * @return allocations The sum of the client allocations. + */ + function totalAllocations(address client) external view returns (uint256 allocations); + + /** + * @notice Get a sum of client allocations per SP. + * @param client The address of the client. + * @return providers List of providers. + * @return allocations The sum of the client allocations per SP. + */ + function clientAllocationsPerSP(address client) + external + view + returns (uint256[] memory providers, uint256[] memory allocations); + + /** + * @notice This function sets the maximum allowed deviation from a fair + * distribution of data between storage providers. + * @dev This function can only be called by the owner + * @param client The address of the client + * @param maxDeviation Max allowed deviation. 0 = no slack, DENOMINATOR = 100% (based on total allocations of user) + * @dev Emits ClientConfigChanged event + */ + function setClientMaxDeviationFromFairDistribution(address client, uint256 maxDeviation) external; + + /** + * @notice Increase client allowance + * @dev This function can only be called by the owner + * @param client Client that will receive allowance + * @param amount Amount of allowance to add + * @dev Emits AllowanceChanged event + * @dev Reverts if trying to increase allowance by 0 + */ + function increaseAllowance(address client, uint256 amount) external; + + /** + * @notice Decrease client allowance + * @dev This function can only be called by the owner + * @param client Client whose allowance is reduced + * @param amount Amount to decrease the allowance + * @dev Emits AllowanceChanged event + * @dev Reverts if trying to decrease allowance by 0 + * @dev Reverts if client allowance is already 0 + */ + function decreaseAllowance(address client, uint256 amount) external; + + /** + * @notice The handle_filecoin_method function is a universal entry point for calls + * coming from built-in Filecoin actors. Datacap is an FRC-46 Token. Receiving FRC46 + * tokens requires implementing a Receiver Hook: + * https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0046.md#receiver-hook. + * We use handle_filecoin_method to handle the receiver hook and make sure that the token + * sent to our contract is freshly minted Datacap and reject all other calls and transfers. + * @param method Method number + * @param inputCodec Codec of the payload + * @param params Params of the call + * @dev Reverts if caller is not a verifreg + * @dev Reverts if trying to send a unsupported token type + * @dev Reverts if trying to receive invalid token + * @dev Reverts if trying to send a unsupported token + */ + // solhint-disable func-name-mixedcase + function handle_filecoin_method(uint64 method, uint64 inputCodec, bytes calldata params) + external + view + returns (uint32 exitCode, uint64 codec, bytes memory data); +} diff --git a/test/Client.t.sol b/test/Client.t.sol index 8fdde17..f45f304 100644 --- a/test/Client.t.sol +++ b/test/Client.t.sol @@ -4,12 +4,14 @@ pragma solidity 0.8.25; import {Test} from "forge-std/Test.sol"; import {Client} from "../src/Client.sol"; +import {ClientV1} from "../src/ClientV1.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import {Errors} from "../src/libs/Errors.sol"; import {DataCapTypes} from "filecoin-project-filecoin-solidity/v0.8/types/DataCapTypes.sol"; import {CommonTypes} from "filecoin-project-filecoin-solidity/v0.8/types/CommonTypes.sol"; import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import {IClient} from "../src/interfaces/IClient.sol"; +import {IClientV1} from "../src/interfaces/IClientV1.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {BigInts} from "filecoin-project-filecoin-solidity/v0.8/utils/BigInts.sol"; @@ -778,7 +780,18 @@ contract ClientTest is Test { assertEq(allocations[1], 1536); } - function testHandleFilecoinMethod() public { + function testHandleFilecoinMethodForVerifregContract() public { + bytes memory params = + hex"821A85223BDF58598606061903F34A006F05B59D3B2000000058458281861903E8D82A5828000181E2039220207DCAE81B2A679A3955CC2E4B3504C23CE55B2DB5DD2119841ECAFA550E53900E1908001A0007E9001A005033401A0002D3028040"; + vm.prank(datacapContract); + (uint32 exitCode, uint64 codec, bytes memory data) = + clientContract.handle_filecoin_method(3726118371, 0x51, params); + assertEq(exitCode, 0); + assertEq(codec, 0); + assertEq(data, ""); + } + + function testHandleFilecoinMethodForDatacapContract() public { bytes memory params = hex"821A85223BDF58598607061903F34A006F05B59D3B2000000058458281861903E8D82A5828000181E2039220207DCAE81B2A679A3955CC2E4B3504C23CE55B2DB5DD2119841ECAFA550E53900E1908001A0007E9001A005033401A0002D3028040"; vm.prank(datacapContract); @@ -805,14 +818,6 @@ contract ClientTest is Test { clientContract.handle_filecoin_method(3726118371, 81, params); } - function testHandleFilecoinMethodExpectRevertUnsupportedToken() public { - bytes memory params = - hex"821a85223bdf585b861903f3061903f34a006f05b59d3b2000000058458281861903e8d82a5828000181e2039220207dcae81b2a679a3955cc2e4b3504c23ce55b2db5dd2119841ecafa550e53900e1908001a0007e9001a005033401a0002d3028040"; - vm.prank(datacapContract); - vm.expectRevert(abi.encodeWithSelector(Errors.UnsupportedToken.selector)); - clientContract.handle_filecoin_method(3726118371, 81, params); - } - function testHandleFilecoinMethodExpectRevertInvalidCaller() public { bytes memory params = hex"821a85223bdf585b861903f3061903f34a006f05b59d3b2000000058458281861903e8d82a5828000181e2039220207dcae81b2a679a3955cc2e4b3504c23ce55b2db5dd2119841ecafa550e53900e1908001a0007e9001a005033401a0002d3028040"; @@ -1122,4 +1127,46 @@ contract ClientTest is Test { clientContract.removeAllowedSPsForClientPacked(client, hex"00000000000003e800000000000007d0"); assertEq(clientContract.clientSPs(client).length, 0); } + + function testClientBalanceAfterUpdate() public { + address oldImpl = address(new ClientV1()); + UpgradeableBeacon beaconToUpdate = new UpgradeableBeacon(oldImpl, address(this)); + BeaconProxy proxyToUpdate = + new BeaconProxy(address(beaconToUpdate), abi.encodeWithSelector(ClientV1.initialize.selector, manager)); + ClientV1 oldClientContract = ClientV1(address(proxyToUpdate)); + vm.prank(manager); + oldClientContract.increaseAllowance(client, 4096); + address newImpl = address(new Client()); + beaconToUpdate.upgradeTo(newImpl); + Client newClientContract = Client(address(proxyToUpdate)); + assertEq(newClientContract.allowances(client), 4096); + } + + function testClientSPsAfterUpdate() public { + address oldImpl = address(new ClientV1()); + UpgradeableBeacon beaconToUpdate = new UpgradeableBeacon(oldImpl, address(this)); + BeaconProxy proxyToUpdate = + new BeaconProxy(address(beaconToUpdate), abi.encodeWithSelector(ClientV1.initialize.selector, manager)); + ClientV1 oldClientContract = ClientV1(address(proxyToUpdate)); + uint64[] memory allowedSPs = new uint64[](2); + allowedSPs[0] = 1234; + allowedSPs[1] = 5678; + vm.prank(manager); + oldClientContract.addAllowedSPsForClient(client, allowedSPs); + address newImpl = address(new Client()); + beaconToUpdate.upgradeTo(newImpl); + Client newClientContract = Client(address(proxyToUpdate)); + assertEq(newClientContract.clientSPs(client).length, 2); + assertTrue(_contains(1234, newClientContract.clientSPs(client))); + assertTrue(_contains(5678, newClientContract.clientSPs(client))); + } + + function testSetNewImplementation() public { + address oldImpl = address(new ClientV1()); + UpgradeableBeacon beaconToUpdate = new UpgradeableBeacon(oldImpl, address(this)); + new BeaconProxy(address(beaconToUpdate), abi.encodeWithSelector(ClientV1.initialize.selector, manager)); + address newImpl = address(new Client()); + beaconToUpdate.upgradeTo(newImpl); + assertEq(beaconToUpdate.implementation(), newImpl); + } }