Skip to content

Commit

Permalink
chore: polish flow examples
Browse files Browse the repository at this point in the history
  • Loading branch information
smol-ninja committed Nov 26, 2024
1 parent 8bb3f4c commit 9fedb7a
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 102 deletions.
6 changes: 5 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Binary file modified bun.lockb
Binary file not shown.
133 changes: 68 additions & 65 deletions flow/FlowBatchable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,41 @@ pragma solidity >=0.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.
Expand All @@ -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);
Expand All @@ -100,68 +104,67 @@ 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({
account: address(0xDEAD),
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);
}
}
2 changes: 1 addition & 1 deletion flow/FlowManager.sol
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
6 changes: 3 additions & 3 deletions flow/FlowStreamCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand All @@ -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 });
Expand Down
8 changes: 4 additions & 4 deletions flow/FlowStreamCreator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
31 changes: 15 additions & 16 deletions flow/FlowUtilities.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -61,34 +60,34 @@ library FlowUtilities {
// Calculate the duration.
uint40 duration = end - start;

if (decimals < 18) {
if (decimals == 18) {
return ud21x18(amount / duration);
}

// Calculate the scale factor from the token's decimals.
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) {
Expand Down
2 changes: 1 addition & 1 deletion lockup/core/RecipientHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_) {
Expand Down
Loading

0 comments on commit 9fedb7a

Please sign in to comment.