From 9fedb7ad0f2ce57842e8b6e3da3999c39b935e7d Mon Sep 17 00:00:00 2001 From: smol-ninja Date: Tue, 26 Nov 2024 00:16:16 +0000 Subject: [PATCH] chore: polish flow examples --- .solhint.json | 6 +- bun.lockb | Bin 43019 -> 43014 bytes flow/FlowBatchable.sol | 133 +++++++++++---------- flow/FlowManager.sol | 2 +- flow/FlowStreamCreator.sol | 6 +- flow/FlowStreamCreator.t.sol | 8 +- flow/FlowUtilities.sol | 31 +++-- lockup/core/RecipientHooks.sol | 2 +- lockup/core/StreamManagementWithHook.t.sol | 12 +- package.json | 11 +- 10 files changed, 109 insertions(+), 102 deletions(-) diff --git a/.solhint.json b/.solhint.json index 0b9101b..7620360 100644 --- a/.solhint.json +++ b/.solhint.json @@ -6,9 +6,13 @@ "contract-name-camelcase": "off", "func-name-mixedcase": "off", "func-visibility": ["error", { "ignoreConstructors": true }], + "gas-custom-errors": "off", + "immutable-vars-naming": "off", "max-line-length": ["error", 124], "named-parameters-mapping": "warn", "no-console": "off", - "not-rely-on-time": "off" + "no-empty-blocks": "off", + "not-rely-on-time": "off", + "one-contract-per-file": "off" } } diff --git a/bun.lockb b/bun.lockb index f145dcdd909d7147736c840000e6f61ea0fc7c05..20396e907af08d288f5a4dc1031e3a791e23d2f6 100755 GIT binary patch delta 4151 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.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; -import { ud60x18, UD60x18 } from "@prb/math/src/UD60x18.sol"; +import { ud60x18 } from "@prb/math/src/UD60x18.sol"; import { Broker, SablierFlow } from "@sablier/flow/src/SablierFlow.sol"; -/// @dev The `Batch` contract, inherited in SablierFlow, allows multiple function calls to be batched together. -/// This enables any possible combination of functions to be executed within a single transaction. +/// @notice The `Batch` contract, inherited in SablierFlow, allows multiple function calls to be batched together. This +/// enables any possible combination of functions to be executed within a single transaction. +/// @dev For some functions to work, `msg.sender` must have approved this contract to spend USDC. contract FlowBatchable { IERC20 public constant USDC = IERC20(0xf08A50178dfcDe18524640EA6618a1f965821715); - SablierFlow public immutable SABLIER_FLOW; + SablierFlow public immutable sablierFlow; constructor(SablierFlow sablierFlow_) { - SABLIER_FLOW = sablierFlow_; + sablierFlow = sablierFlow_; } - /// @dev A function to adjust the rate per second and deposits into a stream. + /// @dev A function to adjust the rate per second and deposit into a stream in a single transaction. function adjustRatePerSecondAndDeposit(uint256 streamId) external { UD21x18 newRatePerSecond = ud21x18(0.0001e18); uint128 depositAmount = 1000e6; - // Transfer to this contract the amount to deposit in both streams. + // Transfer to this contract the amount to deposit in the stream. USDC.transferFrom(msg.sender, address(this), depositAmount); - // Approve the Sablier contract to spend USDC - USDC.approve(address(SABLIER_FLOW), depositAmount); + // Approve the Sablier contract to spend USDC. + USDC.approve(address(sablierFlow), depositAmount); - // The call data declared as bytes - bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeCall(SABLIER_FLOW.adjustRatePerSecond, (streamId, newRatePerSecond)); - calls[1] = abi.encodeCall(SABLIER_FLOW.deposit, (streamId, depositAmount, msg.sender, address(0xCAFE))); - - SABLIER_FLOW.batch(calls); - } - - /// @dev A function to create multiple streams in a single transaction. - function createMultiple() external returns (uint256[] memory streamIds) { - address sender = msg.sender; - address firstRecipient = address(0xCAFE); - address secondRecipient = address(0xBEEF); - UD21x18 firstRatePerSecond = ud21x18(0.0001e18); - UD21x18 secondRatePerSecond = ud21x18(0.0002e18); + // Fetch the stream recipient. + address recipient = sablierFlow.getRecipient(streamId); - // The call data declared as bytes + // The call data declared as bytes. bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeCall(SABLIER_FLOW.create, (sender, firstRecipient, firstRatePerSecond, USDC, true)); - calls[1] = abi.encodeCall(SABLIER_FLOW.create, (sender, secondRecipient, secondRatePerSecond, USDC, true)); - - // Prepare the `streamIds` array to return them - uint256 nextStreamId = SABLIER_FLOW.nextStreamId(); - streamIds = new uint256[](2); - streamIds[0] = nextStreamId; - streamIds[1] = nextStreamId + 1; + calls[0] = abi.encodeCall(sablierFlow.adjustRatePerSecond, (streamId, newRatePerSecond)); + calls[1] = abi.encodeCall(sablierFlow.deposit, (streamId, depositAmount, msg.sender, recipient)); - // Execute multiple calls in a single transaction using the prepared call data. - SABLIER_FLOW.batch(calls); + sablierFlow.batch(calls); } /// @dev A function to create a stream and deposit via a broker in a single transaction. @@ -66,30 +47,53 @@ contract FlowBatchable { UD21x18 ratePerSecond = ud21x18(0.0001e18); uint128 depositAmount = 1000e6; - // The broker struct + // The broker struct. Broker memory broker = Broker({ account: address(0xDEAD), fee: ud60x18(0.0001e18) // the fee percentage }); - // Transfer to this contract the amount to deposit in both streams. + // Transfer to this contract the amount to deposit in the stream. USDC.transferFrom(msg.sender, address(this), depositAmount); - // Approve the Sablier contract to spend USDC - USDC.approve(address(SABLIER_FLOW), depositAmount); + // Approve the Sablier contract to spend USDC. + USDC.approve(address(sablierFlow), depositAmount); - streamId = SABLIER_FLOW.nextStreamId(); + streamId = sablierFlow.nextStreamId(); // The call data declared as bytes bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeCall(SABLIER_FLOW.create, (sender, recipient, ratePerSecond, USDC, true)); - calls[1] = abi.encodeCall(SABLIER_FLOW.depositViaBroker, (streamId, depositAmount, sender, recipient, broker)); + calls[0] = abi.encodeCall(sablierFlow.create, (sender, recipient, ratePerSecond, USDC, true)); + calls[1] = abi.encodeCall(sablierFlow.depositViaBroker, (streamId, depositAmount, sender, recipient, broker)); // Execute multiple calls in a single transaction using the prepared call data. - SABLIER_FLOW.batch(calls); + sablierFlow.batch(calls); } - /// @dev A function to create multiple streams and deposit via a broker in a single transaction. + /// @dev A function to create multiple streams in a single transaction. + function createMultiple() external returns (uint256[] memory streamIds) { + address sender = msg.sender; + address firstRecipient = address(0xCAFE); + address secondRecipient = address(0xBEEF); + UD21x18 firstRatePerSecond = ud21x18(0.0001e18); + UD21x18 secondRatePerSecond = ud21x18(0.0002e18); + + // The call data declared as bytes + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeCall(sablierFlow.create, (sender, firstRecipient, firstRatePerSecond, USDC, true)); + calls[1] = abi.encodeCall(sablierFlow.create, (sender, secondRecipient, secondRatePerSecond, USDC, true)); + + // Prepare the `streamIds` array to return them + uint256 nextStreamId = sablierFlow.nextStreamId(); + streamIds = new uint256[](2); + streamIds[0] = nextStreamId; + streamIds[1] = nextStreamId + 1; + + // Execute multiple calls in a single transaction using the prepared call data. + sablierFlow.batch(calls); + } + + /// @dev A function to create multiple streams and deposit via a broker into all the stream in a single transaction. function createMultipleAndDepositViaBroker() external returns (uint256[] memory streamIds) { address sender = msg.sender; address firstRecipient = address(0xCAFE); @@ -100,8 +104,8 @@ contract FlowBatchable { // Transfer the deposit amount of USDC tokens to this contract for both streams USDC.transferFrom(msg.sender, address(this), 2 * depositAmount); - // Approve the Sablier contract to spend USDC - USDC.approve(address(SABLIER_FLOW), 2 * depositAmount); + // Approve the Sablier contract to spend USDC. + USDC.approve(address(sablierFlow), 2 * depositAmount); // The broker struct Broker memory broker = Broker({ @@ -109,59 +113,58 @@ contract FlowBatchable { fee: ud60x18(0.0001e18) // the fee percentage }); - uint256 nextStreamId = SABLIER_FLOW.nextStreamId(); + uint256 nextStreamId = sablierFlow.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; // We need to have 4 different function calls, 2 for creating streams and 2 for depositing via broker bytes[] memory calls = new bytes[](4); - calls[0] = abi.encodeCall(SABLIER_FLOW.create, (sender, firstRecipient, ratePerSecond, USDC, true)); - calls[1] = abi.encodeCall(SABLIER_FLOW.create, (sender, secondRecipient, ratePerSecond, USDC, true)); + calls[0] = abi.encodeCall(sablierFlow.create, (sender, firstRecipient, ratePerSecond, USDC, true)); + calls[1] = abi.encodeCall(sablierFlow.create, (sender, secondRecipient, ratePerSecond, USDC, true)); calls[2] = - abi.encodeCall(SABLIER_FLOW.depositViaBroker, (streamIds[0], depositAmount, sender, firstRecipient, broker)); - calls[3] = abi.encodeCall( - SABLIER_FLOW.depositViaBroker, (streamIds[1], depositAmount, sender, secondRecipient, broker) - ); + abi.encodeCall(sablierFlow.depositViaBroker, (streamIds[0], depositAmount, sender, firstRecipient, broker)); + calls[3] = + abi.encodeCall(sablierFlow.depositViaBroker, (streamIds[1], depositAmount, sender, secondRecipient, broker)); // Execute multiple calls in a single transaction using the prepared call data. - SABLIER_FLOW.batch(calls); + sablierFlow.batch(calls); } /// @dev A function to pause a stream and withdraw the maximum available funds. function pauseAndWithdrawMax(uint256 streamId) external { - // The call data declared as bytes + // The call data declared as bytes. bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeCall(SABLIER_FLOW.pause, (streamId)); - calls[1] = abi.encodeCall(SABLIER_FLOW.withdrawMax, (streamId, address(0xCAFE))); + calls[0] = abi.encodeCall(sablierFlow.pause, (streamId)); + calls[1] = abi.encodeCall(sablierFlow.withdrawMax, (streamId, address(0xCAFE))); // Execute multiple calls in a single transaction using the prepared call data. - SABLIER_FLOW.batch(calls); + sablierFlow.batch(calls); } /// @dev A function to void a stream and withdraw what is left. function voidAndWithdrawMax(uint256 streamId) external { // The call data declared as bytes bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeCall(SABLIER_FLOW.void, (streamId)); - calls[1] = abi.encodeCall(SABLIER_FLOW.withdrawMax, (streamId, address(0xCAFE))); + calls[0] = abi.encodeCall(sablierFlow.void, (streamId)); + calls[1] = abi.encodeCall(sablierFlow.withdrawMax, (streamId, address(0xCAFE))); // Execute multiple calls in a single transaction using the prepared call data. - SABLIER_FLOW.batch(calls); + sablierFlow.batch(calls); } /// @dev A function to withdraw maximum available funds from multiple streams in a single transaction. function withdrawMaxMultiple(uint256[] calldata streamIds) external { uint256 count = streamIds.length; - // Iterate over the streamIds and prepare the call data for each stream + // Iterate over the streamIds and prepare the call data for each stream. bytes[] memory calls = new bytes[](count); for (uint256 i = 0; i < count; ++i) { - address recipient = SABLIER_FLOW.getRecipient(streamIds[i]); - calls[i] = abi.encodeCall(SABLIER_FLOW.withdrawMax, (streamIds[i], recipient)); + address recipient = sablierFlow.getRecipient(streamIds[i]); + calls[i] = abi.encodeCall(sablierFlow.withdrawMax, (streamIds[i], recipient)); } // Execute multiple calls in a single transaction using the prepared call data. - SABLIER_FLOW.batch(calls); + sablierFlow.batch(calls); } } diff --git a/flow/FlowManager.sol b/flow/FlowManager.sol index 9aa2418..11d6af4 100644 --- a/flow/FlowManager.sol +++ b/flow/FlowManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; -import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; +import { ud21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; diff --git a/flow/FlowStreamCreator.sol b/flow/FlowStreamCreator.sol index e62ca8e..977f411 100644 --- a/flow/FlowStreamCreator.sol +++ b/flow/FlowStreamCreator.sol @@ -17,8 +17,8 @@ contract FlowStreamCreator { sablierFlow = sablierFlow_; } - // Create a stream that sends 1000 USDC per month - function createStream_1T_PerMonth() external returns (uint256 streamId) { + // Create a stream that sends 1000 USDC per month. + function createStream_1K_PerMonth() external returns (uint256 streamId) { UD21x18 ratePerSecond = FlowUtilities.ratePerSecondWithDuration({ token: address(USDC), amount: 1000e6, duration: 30 days }); @@ -31,7 +31,7 @@ contract FlowStreamCreator { }); } - // Create a stream that sends 1,000,000 USDC per year + // Create a stream that sends 1,000,000 USDC per year. function createStream_1M_PerYear() external returns (uint256 streamId) { UD21x18 ratePerSecond = FlowUtilities.ratePerSecondWithDuration({ token: address(USDC), amount: 1_000_000e6, duration: 365 days }); diff --git a/flow/FlowStreamCreator.t.sol b/flow/FlowStreamCreator.t.sol index bdc3e62..2d11669 100644 --- a/flow/FlowStreamCreator.t.sol +++ b/flow/FlowStreamCreator.t.sol @@ -34,13 +34,13 @@ contract FlowStreamCreator_Test is Test { streamCreator.USDC().approve({ spender: address(streamCreator), value: 1_000_000e6 }); } - function test_CreateStream_1T_PerMonth() external { + function test_CreateStream_1K_PerMonth() external { uint256 expectedStreamId = flow.nextStreamId(); - uint256 actualStreamId = streamCreator.createStream_1T_PerMonth(); + uint256 actualStreamId = streamCreator.createStream_1K_PerMonth(); assertEq(actualStreamId, expectedStreamId); - // Warp more than 30 days into the future to see if the debt accumulated is more than 1 thousand + // Warp slightly over 30 days so that the debt accumulated is slightly over 1000 USDC. vm.warp({ newTimestamp: block.timestamp + 30 days + 1 seconds }); assertGe(flow.totalDebtOf(actualStreamId), 1000e6); @@ -52,7 +52,7 @@ contract FlowStreamCreator_Test is Test { uint256 actualStreamId = streamCreator.createStream_1M_PerYear(); assertEq(actualStreamId, expectedStreamId); - // Warp more than 30 days into the future to see if the debt accumulated is more than 1 thousand + // Warp slightly over 365 days so that the debt accumulated is slightly over 1M USDC. vm.warp({ newTimestamp: block.timestamp + 365 days + 1 seconds }); assertGe(flow.totalDebtOf(actualStreamId), 1_000_000e6); diff --git a/flow/FlowUtilities.sol b/flow/FlowUtilities.sol index f9bcccf..caf1cde 100644 --- a/flow/FlowUtilities.sol +++ b/flow/FlowUtilities.sol @@ -4,15 +4,14 @@ pragma solidity >=0.8.22; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; -/// @dev A utility library to calculate the rate per second for a given amount of tokens over a specific duration, and -/// the amounts streamed over various periods of time. +/// @dev A utility library to calculate rate per second and streamed amount based on a given time frame. library FlowUtilities { - /// @notice This function calculates the rate per second for a given amount of tokens for a specific duration. - /// @dev The rate per second is a 18 decimal fixed-point number and it is calculated as `amount / duration`. + /// @notice This function calculates the rate per second based on a given amount of tokens and a specified duration. + /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / duration`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. - /// @param duration The duration in seconds wished to stream. - /// @return ratePerSecond The rate per second as a fixed-point number. + /// @param duration The duration in seconds user wishes to stream. + /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondWithDuration( address token, uint128 amount, @@ -35,16 +34,16 @@ library FlowUtilities { uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. - ratePerSecond = ud21x18(scaleFactor * amount / duration); + ratePerSecond = ud21x18((scaleFactor * amount) / duration); } - /// @notice This function calculates the rate per second for a given amount of tokens for a specific range of time. - /// @dev The rate per second is a 18 decimal fixed-point number and it is calculated as `amount / (end - start)`. + /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. + /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. - /// @return ratePerSecond The rate per second as a fixed-point number. + /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, @@ -61,7 +60,7 @@ library FlowUtilities { // Calculate the duration. uint40 duration = end - start; - if (decimals < 18) { + if (decimals == 18) { return ud21x18(amount / duration); } @@ -69,26 +68,26 @@ library FlowUtilities { uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. - ratePerSecond = ud21x18(scaleFactor * amount / duration); + ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. - /// @param ratePerSecond The rate per second as a fixed-point number. + /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. - /// @dev We use 30 days as the number of days in a month. - /// @param ratePerSecond The rate per second as a fixed-point number. + /// @dev For simplicity, we have assumed that there are 30 days in a month. + /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. - /// @dev We use 365 days as the number of days in a year. + /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { diff --git a/lockup/core/RecipientHooks.sol b/lockup/core/RecipientHooks.sol index 52f038d..bbfdd02 100644 --- a/lockup/core/RecipientHooks.sol +++ b/lockup/core/RecipientHooks.sol @@ -11,7 +11,7 @@ contract RecipientHooks is ISablierLockupRecipient { /// depending on which type of streams are supported in this hook. address public immutable SABLIER_LOCKUP; - mapping(address => uint256) internal _balances; + mapping(address account => uint256 amount) internal _balances; /// @dev Constructor will set the address of the lockup contract. constructor(address sablierLockup_) { diff --git a/lockup/core/StreamManagementWithHook.t.sol b/lockup/core/StreamManagementWithHook.t.sol index c3a9bdc..25cf8c5 100644 --- a/lockup/core/StreamManagementWithHook.t.sol +++ b/lockup/core/StreamManagementWithHook.t.sol @@ -20,7 +20,7 @@ contract StreamManagementWithHookTest is Test { ERC20 internal token; uint128 internal amount = 10e18; - uint256 internal DEFAULT_STREAM_ID; + uint256 internal defaultStreamId; address internal alice; address internal bob; @@ -80,8 +80,8 @@ contract StreamManagementWithHookTest is Test { modifier givenStreamsCreated() { // Create a stream with Alice as the beneficiary - DEFAULT_STREAM_ID = streamManager.create({ beneficiary: alice, totalAmount: amount }); - require(DEFAULT_STREAM_ID == 1, "Stream creation failed"); + defaultStreamId = streamManager.create({ beneficiary: alice, totalAmount: amount }); + require(defaultStreamId == 1, "Stream creation failed"); _; } @@ -95,7 +95,7 @@ contract StreamManagementWithHookTest is Test { // Since Alice is the `msg.sender`, `withdraw` to Sablier stream should revert due to hook restriction vm.expectRevert(abi.encodeWithSelector(StreamManagementWithHook.CallerNotThisContract.selector)); - sablierLockup.withdraw(DEFAULT_STREAM_ID, address(streamManager), 1e18); + sablierLockup.withdraw(defaultStreamId, address(streamManager), 1e18); } // Test that withdraw from Sablier stream succeeds if it is called through the `streamManager` contract @@ -107,12 +107,12 @@ contract StreamManagementWithHookTest is Test { vm.startPrank(alice); // Alice can withdraw from the streamManager contract - streamManager.withdraw(DEFAULT_STREAM_ID, 1e18); + streamManager.withdraw(defaultStreamId, 1e18); assertEq(token.balanceOf(alice), 1e18); // Withdraw max tokens from the stream - streamManager.withdrawMax(DEFAULT_STREAM_ID); + streamManager.withdrawMax(defaultStreamId); assertEq(token.balanceOf(alice), 10e18); } diff --git a/package.json b/package.json index 657b66e..d12f5a8 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "devDependencies": { "forge-std": "github:foundry-rs/forge-std#v1.8.1", "prettier": "^2.8.8", - "solhint": "^4.0.0" + "solhint": "^5.0.3" }, "dependencies": { "@openzeppelin/contracts": "5.0.2", "@prb/math": "4.1.0", - "@sablier/flow": "github:sablier-labs/flow#main", + "@sablier/flow": "github:sablier-labs/flow", "@sablier/v2-core": "1.2.0", "@sablier/v2-periphery": "1.2.0" }, @@ -45,9 +45,10 @@ "build:lockup": "FOUNDRY_PROFILE=lockup forge build", "clean": "rm -rf cache out", "lint": "bun run lint:sol && bun run prettier:check", - "lint:sol": "forge fmt --check && bun solhint \"{script,src,test}/**/*.sol\"", - "prettier:check": "prettier --check \"**/*.{json,md,yml}\"", - "prettier:write": "prettier --write \"**/*.{json,md,yml}\"", + "lint:sol": "forge fmt . && bun solhint \"{flow,lockup}/**/*.sol\"", + "prettier:check": "prettier --check \"**/*.{md,yml}\"", + "prettier:write": "prettier --write \"**/*.{md,yml}\"", + "test": "bun run test:flow && bun run test:lockup", "test:flow": "FOUNDRY_PROFILE=flow forge test", "test:lockup": "FOUNDRY_PROFILE=lockup forge test" }