Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Functions 1.1 review #11252

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions contracts/GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ ALL_FOUNDRY_PRODUCTS = llo-feeds functions shared
snapshot: ## Make a snapshot for a specific product.
export FOUNDRY_PROFILE=$(FOUNDRY_PROFILE) && forge snapshot --nmt "testFuzz_\w{1,}?" --snap gas-snapshots/$(FOUNDRY_PROFILE).gas-snapshot

.PHONY: snapshot-diff
snapshot-diff: ## Make a snapshot for a specific product.
export FOUNDRY_PROFILE=$(FOUNDRY_PROFILE) && forge snapshot --nmt "testFuzz_\w{1,}?" --diff gas-snapshots/$(FOUNDRY_PROFILE).gas-snapshot


.PHONY: snapshot-all
snapshot-all: ## Make a snapshot for all products.
for foundry_profile in $(ALL_FOUNDRY_PRODUCTS) ; do \
Expand Down
116 changes: 57 additions & 59 deletions contracts/gas-snapshots/functions.gas-snapshot

Large diffs are not rendered by default.

391 changes: 0 additions & 391 deletions contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol

This file was deleted.

426 changes: 386 additions & 40 deletions contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol

Large diffs are not rendered by default.

13 changes: 4 additions & 9 deletions contracts/src/v0.8/functions/dev/v1_X/FunctionsRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable,
address linkToken,
Config memory config
) FunctionsSubscriptions(linkToken) ConfirmedOwner(msg.sender) Pausable() {
// Set the intial configuration
// Set the initial configuration
updateConfig(config);
}

Expand Down Expand Up @@ -377,8 +377,7 @@ contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable,
commitment.estimatedTotalCostJuels,
commitment.client,
commitment.adminFee,
juelsPerGas,
SafeCast.toUint96(result.gasUsed),
juelsPerGas * SafeCast.toUint96(result.gasUsed),
Copy link
Contributor

@justinkaseman justinkaseman Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making a breaking Router change like this would mean maintaining and deploying two different Coordinators, since existing networks won't be receiving a Router update for the next scheduled deployment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah called that out in the doc, probably ok to backlog until you'd be doing a v2

costWithoutCallback
);

Expand All @@ -403,12 +402,8 @@ contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable,
uint32 callbackGasLimit,
address client
) private returns (CallbackResult memory) {
bool destinationNoLongerExists;
assembly {
// solidity calls check that a contract actually exists at the destination, so we do the same
destinationNoLongerExists := iszero(extcodesize(client))
}
if (destinationNoLongerExists) {
// solidity calls check that a contract actually exists at the destination, so we do the same
if (client.code.length == 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduced stack size so forge coverage could run

// Return without attempting callback
// The subscription will still be charged to reimburse transmitter's gas overhead
return CallbackResult({success: false, gasUsed: 0, returnData: new bytes(0)});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,9 @@ abstract contract FunctionsSubscriptions is IFunctionsSubscriptions, IERC677Rece
uint96 estimatedTotalCostJuels,
address client,
uint96 adminFee,
uint96 juelsPerGas,
uint96 gasUsed,
uint96 callbackGasCostJuels,
uint96 costWithoutCallbackJuels
) internal returns (Receipt memory) {
uint96 callbackGasCostJuels = juelsPerGas * gasUsed;
uint96 totalCostJuels = costWithoutCallbackJuels + adminFee + callbackGasCostJuels;

if (
Expand Down
45 changes: 0 additions & 45 deletions contracts/src/v0.8/functions/dev/v1_X/Routable.sol

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ArbGasInfo} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";
import {OVM_GasPriceOracle} from "../../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol";
import {ArbGasInfo} from "../../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";
import {OVM_GasPriceOracle} from "../../../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol";

/// @dev A library that abstracts out opcodes that behave differently across chains.
/// @dev The methods below return values that are pertinent to the given chain.
Expand Down
20 changes: 9 additions & 11 deletions contracts/src/v0.8/functions/tests/v1_X/FunctionsBilling.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
pragma solidity ^0.8.19;

import {FunctionsCoordinator} from "../../dev/v1_X/FunctionsCoordinator.sol";
import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol";
import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol";
import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol";
import {FunctionsSubscriptions} from "../../dev/v1_X/FunctionsSubscriptions.sol";
import {Routable} from "../../dev/v1_X/Routable.sol";

import {FunctionsRouterSetup, FunctionsSubscriptionSetup, FunctionsClientRequestSetup, FunctionsMultipleFulfillmentsSetup} from "./Setup.t.sol";

Expand All @@ -25,7 +23,7 @@ contract FunctionsBilling_GetConfig is FunctionsRouterSetup {
vm.stopPrank();
vm.startPrank(STRANGER_ADDRESS);

FunctionsBilling.Config memory config = s_functionsCoordinator.getConfig();
FunctionsCoordinator.Config memory config = s_functionsCoordinator.getConfig();
assertEq(config.feedStalenessSeconds, getCoordinatorConfig().feedStalenessSeconds);
assertEq(config.gasOverheadBeforeCallback, getCoordinatorConfig().gasOverheadBeforeCallback);
assertEq(config.gasOverheadAfterCallback, getCoordinatorConfig().gasOverheadAfterCallback);
Expand All @@ -39,12 +37,12 @@ contract FunctionsBilling_GetConfig is FunctionsRouterSetup {

/// @notice #updateConfig
contract FunctionsBilling_UpdateConfig is FunctionsRouterSetup {
FunctionsBilling.Config internal configToSet;
FunctionsCoordinator.Config internal configToSet;

function setUp() public virtual override {
FunctionsRouterSetup.setUp();

configToSet = FunctionsBilling.Config({
configToSet = FunctionsCoordinator.Config({
feedStalenessSeconds: getCoordinatorConfig().feedStalenessSeconds * 2,
gasOverheadAfterCallback: getCoordinatorConfig().gasOverheadAfterCallback * 2,
gasOverheadBeforeCallback: getCoordinatorConfig().gasOverheadBeforeCallback * 2,
Expand All @@ -66,7 +64,7 @@ contract FunctionsBilling_UpdateConfig is FunctionsRouterSetup {
s_functionsCoordinator.updateConfig(configToSet);
}

event ConfigUpdated(FunctionsBilling.Config config);
event ConfigUpdated(FunctionsCoordinator.Config config);

function test_UpdateConfig_Success() public {
// topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true).
Expand All @@ -79,7 +77,7 @@ contract FunctionsBilling_UpdateConfig is FunctionsRouterSetup {

s_functionsCoordinator.updateConfig(configToSet);

FunctionsBilling.Config memory config = s_functionsCoordinator.getConfig();
FunctionsCoordinator.Config memory config = s_functionsCoordinator.getConfig();
assertEq(config.feedStalenessSeconds, configToSet.feedStalenessSeconds);
assertEq(config.gasOverheadAfterCallback, configToSet.gasOverheadAfterCallback);
assertEq(config.gasOverheadBeforeCallback, configToSet.gasOverheadBeforeCallback);
Expand Down Expand Up @@ -155,7 +153,7 @@ contract FunctionsBilling_EstimateCost is FunctionsSubscriptionSetup {
uint32 callbackGasLimit = 5_500;
uint256 gasPriceWei = REASONABLE_GAS_PRICE_CEILING + 1;

vm.expectRevert(FunctionsBilling.InvalidCalldata.selector);
vm.expectRevert(FunctionsCoordinator.InvalidCalldata.selector);

s_functionsCoordinator.estimateCost(s_subscriptionId, requestData, callbackGasLimit, gasPriceWei);
}
Expand Down Expand Up @@ -280,7 +278,7 @@ contract FunctionsBilling_DeleteCommitment is FunctionsClientRequestSetup {
vm.stopPrank();
vm.startPrank(STRANGER_ADDRESS);

vm.expectRevert(Routable.OnlyCallableByRouter.selector);
vm.expectRevert(FunctionsCoordinator.OnlyCallableByRouter.selector);
s_functionsCoordinator.deleteCommitment(s_requests[1].requestId);
}

Expand Down Expand Up @@ -327,7 +325,7 @@ contract FunctionsBilling_OracleWithdraw is FunctionsMultipleFulfillmentsSetup {
vm.stopPrank();
vm.startPrank(NOP_TRANSMITTER_ADDRESS_1);

vm.expectRevert(FunctionsBilling.InsufficientBalance.selector);
vm.expectRevert(FunctionsCoordinator.InsufficientBalance.selector);

// Attempt to withdraw more than the Coordinator has assigned
s_functionsCoordinator.oracleWithdraw(NOP_TRANSMITTER_ADDRESS_1, s_fulfillmentCoordinatorBalance + 1);
Expand Down Expand Up @@ -420,7 +418,7 @@ contract FunctionsBilling__DisperseFeePool is FunctionsRouterSetup {
// Manually set s_feePool (at slot 11) to 1 to get past first check in _disperseFeePool
vm.store(address(s_functionsCoordinator), bytes32(uint256(11)), bytes32(uint256(1)));

vm.expectRevert(FunctionsBilling.NoTransmittersSet.selector);
vm.expectRevert(FunctionsCoordinator.NoTransmittersSet.selector);
s_functionsCoordinator.disperseFeePool_HARNESS();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
pragma solidity ^0.8.19;

import {FunctionsCoordinator} from "../../dev/v1_X/FunctionsCoordinator.sol";
import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol";
import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol";
import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol";
import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol";
import {Routable} from "../../dev/v1_X/Routable.sol";

import {BaseTest} from "./BaseTest.t.sol";
import {FunctionsRouterSetup, FunctionsDONSetup, FunctionsSubscriptionSetup} from "./Setup.t.sol";
Expand Down Expand Up @@ -107,27 +105,14 @@ contract FunctionsCoordinator_SetDONPublicKey is FunctionsDONSetup {
}
}

/// @notice #_isTransmitter
contract FunctionsCoordinator__IsTransmitter is FunctionsDONSetup {
function test__IsTransmitter_SuccessFound() public {
bool isTransmitter = s_functionsCoordinator.isTransmitter_HARNESS(NOP_TRANSMITTER_ADDRESS_1);
assertEq(isTransmitter, true);
}

function test__IsTransmitter_SuccessNotFound() public {
bool isTransmitter = s_functionsCoordinator.isTransmitter_HARNESS(STRANGER_ADDRESS);
assertEq(isTransmitter, false);
}
}

/// @notice #startRequest
contract FunctionsCoordinator_StartRequest is FunctionsSubscriptionSetup {
function test_StartRequest_RevertIfNotRouter() public {
// Send as stranger
vm.stopPrank();
vm.startPrank(STRANGER_ADDRESS);

vm.expectRevert(Routable.OnlyCallableByRouter.selector);
vm.expectRevert(FunctionsCoordinator.OnlyCallableByRouter.selector);

s_functionsCoordinator.startRequest(
FunctionsResponse.RequestMeta({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.19;
import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol";
import {FunctionsSubscriptions} from "../../dev/v1_X/FunctionsSubscriptions.sol";
import {FunctionsCoordinator} from "../../dev/v1_X/FunctionsCoordinator.sol";
import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol";
import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol";
import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol";
import {FunctionsCoordinatorTestHelper} from "./testhelpers/FunctionsCoordinatorTestHelper.sol";
Expand Down Expand Up @@ -373,7 +372,7 @@ contract FunctionsRouter_SendRequest is FunctionsSubscriptionSetup {
bytes memory requestData = FunctionsRequest._encodeCBOR(request);

uint32 callbackGasLimit = 5000;
vm.expectRevert(FunctionsBilling.InsufficientBalance.selector);
vm.expectRevert(FunctionsCoordinator.InsufficientBalance.selector);

s_functionsRouter.sendRequest(
subscriptionId,
Expand Down Expand Up @@ -713,7 +712,7 @@ contract FunctionsRouter_SendRequestToProposed is FunctionsSubscriptionSetup {
bytes memory requestData = FunctionsRequest._encodeCBOR(request);

uint32 callbackGasLimit = 5000;
vm.expectRevert(FunctionsBilling.InsufficientBalance.selector);
vm.expectRevert(FunctionsCoordinator.InsufficientBalance.selector);

s_functionsRouter.sendRequestToProposed(
subscriptionId,
Expand Down
6 changes: 3 additions & 3 deletions contracts/src/v0.8/functions/tests/v1_X/Setup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {BaseTest} from "./BaseTest.t.sol";
import {FunctionsClientHarness} from "./testhelpers/FunctionsClientHarness.sol";
import {FunctionsRouterHarness, FunctionsRouter} from "./testhelpers/FunctionsRouterHarness.sol";
import {FunctionsCoordinatorHarness} from "./testhelpers/FunctionsCoordinatorHarness.sol";
import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol";
import {FunctionsCoordinator} from "../../dev/v1_X/FunctionsCoordinator.sol";
import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol";
import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol";
import {TermsOfServiceAllowList} from "../../dev/v1_X/accessControl/TermsOfServiceAllowList.sol";
Expand Down Expand Up @@ -64,9 +64,9 @@ contract FunctionsRouterSetup is BaseTest {
});
}

function getCoordinatorConfig() public view returns (FunctionsBilling.Config memory) {
function getCoordinatorConfig() public view returns (FunctionsCoordinator.Config memory) {
return
FunctionsBilling.Config({
FunctionsCoordinator.Config({
feedStalenessSeconds: 24 * 60 * 60, // 1 day
gasOverheadAfterCallback: 93_942,
gasOverheadBeforeCallback: 105_000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.19;

import {FunctionsCoordinator} from "../../../dev/v1_X/FunctionsCoordinator.sol";
import {FunctionsBilling} from "../../../dev/v1_X/FunctionsBilling.sol";
import {FunctionsResponse} from "../../../dev/v1_X/libraries/FunctionsResponse.sol";

/// @title Functions Coordinator Test Harness
Expand All @@ -13,26 +12,17 @@ contract FunctionsCoordinatorHarness is FunctionsCoordinator {

constructor(
address router,
FunctionsBilling.Config memory config,
FunctionsCoordinator.Config memory config,
address linkToNativeFeed
) FunctionsCoordinator(router, config, linkToNativeFeed) {
s_linkToNativeFeed_HARNESS = linkToNativeFeed;
s_router_HARNESS = router;
}

function isTransmitter_HARNESS(address node) external view returns (bool) {
return super._isTransmitter(node);
}

function beforeSetConfig_HARNESS(uint8 _f, bytes memory _onchainConfig) external {
return super._beforeSetConfig(_f, _onchainConfig);
}

/// @dev Used by FunctionsBilling.sol
function getTransmitters_HARNESS() external view returns (address[] memory) {
return super._getTransmitters();
}

function report_HARNESS(
uint256 initialGas,
address transmitter,
Expand All @@ -43,10 +33,6 @@ contract FunctionsCoordinatorHarness is FunctionsCoordinator {
return super._report(initialGas, transmitter, signerCount, signers, report);
}

function onlyOwner_HARNESS() external view {
return super._onlyOwner();
}

// ================================================================
// | Functions Billing |
// ================================================================
Expand All @@ -65,7 +51,7 @@ contract FunctionsCoordinatorHarness is FunctionsCoordinator {
uint72 donFee,
uint72 adminFee
) external view returns (uint96) {
return super._calculateCostEstimate(callbackGasLimit, gasPriceWei, donFee, adminFee);
return super._calculateCostEstimate(callbackGasLimit, gasPriceWei) + uint96(donFee) + uint96(adminFee);
}

function startBilling_HARNESS(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
pragma solidity ^0.8.19;

import {FunctionsCoordinator} from "../../../dev/v1_X/FunctionsCoordinator.sol";
import {FunctionsBilling} from "../../../dev/v1_X/FunctionsBilling.sol";

contract FunctionsCoordinatorTestHelper is FunctionsCoordinator {
constructor(
address router,
FunctionsBilling.Config memory config,
FunctionsCoordinator.Config memory config,
address linkToNativeFeed
) FunctionsCoordinator(router, config, linkToNativeFeed) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ contract FunctionsSubscriptionsHarness is FunctionsSubscriptions {
estimatedTotalCostJuels,
client,
adminFee,
juelsPerGas,
gasUsed,
juelsPerGas * gasUsed,
costWithoutCallbackJuels
);
}
Expand Down
3 changes: 1 addition & 2 deletions contracts/src/v0.8/functions/v1_0_0/FunctionsRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,7 @@ contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable,
commitment.estimatedTotalCostJuels,
commitment.client,
commitment.adminFee,
juelsPerGas,
SafeCast.toUint96(result.gasUsed),
juelsPerGas * SafeCast.toUint96(result.gasUsed),
costWithoutCallback
);

Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ functions_allow_list: ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServ
functions_billing_registry_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.bin 50deeb883bd9c3729702be335c0388f9d8553bab4be5e26ecacac496a89e2b77
functions_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.bin 2368f537a04489c720a46733f8596c4fc88a31062ecfa966d05f25dd98608aca
functions_client_example: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.bin abf32e69f268f40e8530eb8d8e96bf310b798a4c0049a58022d9d2fb527b601b
functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 4e05ca5e624b7a1e604b81b84bc088818b376d533f556ba1c2ee586b7eb38b68
functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 25277767ae63e87459dcbe8507b8e0e63be53814156253ba9f79c43ae4d9c71d
functions_load_test_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.bin c8dbbd5ebb34435800d6674700068837c3a252db60046a14b0e61e829db517de
functions_oracle_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.bin 3ca70f966f8fe751987f0ccb50bebb6aa5be77e4a9f835d1ae99e0e9bfb7d52c
functions_router: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.bin 9dedd3a36043605fd9bedf821e7ec5b4281a5c7ae2e4a1955f37aff8ba13519f
functions_router: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.bin cb732d04c66a3fdda6bd72b180ad03b543a20235eb7c8cc41d56311ca5cb57ee
functions_v1_events_mock: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsV1EventsMock.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsV1EventsMock.bin 0f0ba42e0cc33c7abc8b8fd4fdfce903748a169886dd5f16cfdd56e75bcf708d
ocr2dr: ../../../contracts/solc/v0.8.6/functions/v0_0_0/Functions.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/Functions.bin d9a794b33f47cc57563d216f7cf3a612309fc3062356a27e30005cf1d59e449d
ocr2dr_client: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsClient.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsClient.bin 84aa63f9dbc5c7eac240db699b09e613ca4c6cd56dab10bdc25b02461b717e21
Expand Down
Loading