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

feat: flow examples #37

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
Binary file modified bun.lockb
Binary file not shown.
167 changes: 167 additions & 0 deletions flow/FlowBatchable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-3.0-or-later
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 { 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.
contract FlowBatchable {
IERC20 public constant USDC = IERC20(0xf08A50178dfcDe18524640EA6618a1f965821715);
SablierFlow public immutable SABLIER_FLOW;

constructor(SablierFlow sablierFlow_) {
SABLIER_FLOW = sablierFlow_;
}

/// @dev A function to adjust the rate per second and deposits into a stream.
function adjustRatePerSecondAndDeposit(uint256 streamId) external {
UD21x18 newRatePerSecond = ud21x18(0.0001e18);
uint128 depositAmount = 1000e6;

// Transfer to this contract the amount to deposit in both streams.
USDC.transferFrom(msg.sender, address(this), depositAmount);

// Approve the Sablier contract to spend USDC
USDC.approve(address(SABLIER_FLOW), 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);

// 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;

// Execute multiple calls in a single transaction using the prepared call data.
SABLIER_FLOW.batch(calls);
}

/// @dev A function to create a stream and deposit via a broker in a single transaction.
function createAndDepositViaBroker() external returns (uint256 streamId) {
address sender = msg.sender;
address recipient = address(0xCAFE);
UD21x18 ratePerSecond = ud21x18(0.0001e18);
uint128 depositAmount = 1000e6;

// 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.
USDC.transferFrom(msg.sender, address(this), depositAmount);

// Approve the Sablier contract to spend USDC
USDC.approve(address(SABLIER_FLOW), depositAmount);

streamId = SABLIER_FLOW.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));

// Execute multiple calls in a single transaction using the prepared call data.
SABLIER_FLOW.batch(calls);
}

/// @dev A function to create multiple streams and deposit via a broker in a single transaction.
function createMultipleAndDepositViaBroker() external returns (uint256[] memory streamIds) {
address sender = msg.sender;
address firstRecipient = address(0xCAFE);
address secondRecipient = address(0xBEEF);
UD21x18 ratePerSecond = ud21x18(0.0001e18);
uint128 depositAmount = 1000e6;

// 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);

// The broker struct
Broker memory broker = Broker({
account: address(0xDEAD),
fee: ud60x18(0.0001e18) // the fee percentage
});

uint256 nextStreamId = SABLIER_FLOW.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[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)
);

// Execute multiple calls in a single transaction using the prepared call data.
SABLIER_FLOW.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
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeCall(SABLIER_FLOW.pause, (streamId));
calls[1] = abi.encodeCall(SABLIER_FLOW.withdrawMax, (streamId, address(0xCAFE)));

// Execute multiple calls in a single transaction using the prepared call data.
SABLIER_FLOW.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)));

// Execute multiple calls in a single transaction using the prepared call data.
SABLIER_FLOW.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
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));
}

// Execute multiple calls in a single transaction using the prepared call data.
SABLIER_FLOW.batch(calls);
}
}
58 changes: 58 additions & 0 deletions flow/FlowBatchable.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

import { Test } from "forge-std/src/Test.sol";

import { FlowBatchable } from "./FlowBatchable.sol";

import { IFlowNFTDescriptor, SablierFlow } from "@sablier/flow/src/SablierFlow.sol";

contract FlowBatchable_Test is Test {
FlowBatchable internal batchable;
SablierFlow internal flow;
address internal user;

function setUp() external {
// Fork Ethereum Sepolia
vm.createSelectFork({ urlOrAlias: "sepolia", blockNumber: 6_240_816 });

// Deploy a SablierFlow contract
flow = new SablierFlow({ initialAdmin: address(this), initialNFTDescriptor: IFlowNFTDescriptor(address(this)) });

// Deploy the batchable contract
batchable = new FlowBatchable(flow);

user = makeAddr("User");

// Mint some DAI tokens to the test user, which will be pulled by the creator contract
deal({ token: address(batchable.USDC()), to: user, give: 1_000_000e6 });

// Make the test user the `msg.sender` in all following calls
vm.startPrank({ msgSender: user });

// Approve the batchable contract to pull USDC tokens from the test user
batchable.USDC().approve({ spender: address(batchable), value: 1_000_000e6 });
}

function test_CreateMultiple() external {
uint256 nextStreamIdBefore = flow.nextStreamId();

uint256[] memory actualStreamIds = batchable.createMultiple();
uint256[] memory expectedStreamIds = new uint256[](2);
expectedStreamIds[0] = nextStreamIdBefore;
expectedStreamIds[1] = nextStreamIdBefore + 1;

assertEq(actualStreamIds, expectedStreamIds);
}

function test_CreateAndDepositViaBroker() external {
uint256 nextStreamIdBefore = flow.nextStreamId();

uint256[] memory actualStreamIds = batchable.createMultipleAndDepositViaBroker();
uint256[] memory expectedStreamIds = new uint256[](2);
expectedStreamIds[0] = nextStreamIdBefore;
expectedStreamIds[1] = nextStreamIdBefore + 1;

assertEq(actualStreamIds, expectedStreamIds);
}
}
58 changes: 58 additions & 0 deletions flow/FlowManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol";

import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol";

contract FlowManager {
ISablierFlow public immutable sablierFlow;

constructor(ISablierFlow sablierFlow_) {
sablierFlow = sablierFlow_;
}

function adjustRatePerSecond(uint256 streamId) external {
sablierFlow.adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0.0001e18) });
}

function deposit(uint256 streamId) external {
sablierFlow.deposit(streamId, 3.14159e18, msg.sender, address(0xCAFE));
}

function depositAndPause(uint256 streamId) external {
sablierFlow.depositAndPause(streamId, 3.14159e18);
}

function pause(uint256 streamId) external {
sablierFlow.pause(streamId);
}

function refund(uint256 streamId) external {
sablierFlow.refund({ streamId: streamId, amount: 1.61803e18 });
}

function refundAndPause(uint256 streamId) external {
sablierFlow.refundAndPause({ streamId: streamId, amount: 1.61803e18 });
}

function restart(uint256 streamId) external {
sablierFlow.restart({ streamId: streamId, ratePerSecond: ud21x18(0.0001e18) });
}

function restartAndDeposit(uint256 streamId) external {
sablierFlow.restartAndDeposit({ streamId: streamId, ratePerSecond: ud21x18(0.0001e18), amount: 2.71828e18 });
}

function void(uint256 streamId) external {
sablierFlow.void(streamId);
}

function withdraw(uint256 streamId) external {
sablierFlow.withdraw({ streamId: streamId, to: address(0xCAFE), amount: 2.71828e18 });
}

function withdrawMax(uint256 streamId) external {
sablierFlow.withdrawMax({ streamId: streamId, to: address(0xCAFE) });
}
}
47 changes: 47 additions & 0 deletions flow/FlowStreamCreator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { UD21x18 } from "@prb/math/src/UD21x18.sol";

import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol";

import { FlowUtilities } from "./FlowUtilities.sol";

contract FlowStreamCreator {
// Mainnet USDC
IERC20 public constant USDC = IERC20(0xf08A50178dfcDe18524640EA6618a1f965821715);
ISablierFlow public immutable sablierFlow;

constructor(ISablierFlow sablierFlow_) {
sablierFlow = sablierFlow_;
}

// Create a stream that sends 1000 USDC per month
function createStream_1T_PerMonth() external returns (uint256 streamId) {
UD21x18 ratePerSecond =
FlowUtilities.ratePerSecondWithDuration({ token: address(USDC), amount: 1000e6, duration: 30 days });

streamId = sablierFlow.create({
sender: msg.sender, // The sender will be able to manage the stream
recipient: address(0xCAFE), // The recipient of the streamed assets
ratePerSecond: ratePerSecond, // The rate per second equivalent to 1000 USDC per month
token: USDC, // The token to be streamed
transferable: true // Whether the stream will be transferable or not
});
}

// 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 });

streamId = sablierFlow.create({
sender: msg.sender, // The sender will be able to manage the stream
recipient: address(0xCAFE), // The recipient of the streamed assets
ratePerSecond: ratePerSecond, // The rate per second equivalent to 1,000,00 USDC per year
token: USDC, // The token to be streamed
transferable: true // Whether the stream will be transferable or not
});
}
}
60 changes: 60 additions & 0 deletions flow/FlowStreamCreator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

import { Test } from "forge-std/src/Test.sol";

import { FlowStreamCreator } from "./FlowStreamCreator.sol";

import { IFlowNFTDescriptor, SablierFlow } from "@sablier/flow/src/SablierFlow.sol";

contract FlowStreamCreator_Test is Test {
FlowStreamCreator internal streamCreator;
SablierFlow internal flow;
address internal user;

function setUp() external {
// Fork Ethereum Sepolia
vm.createSelectFork({ urlOrAlias: "sepolia", blockNumber: 6_240_816 });

// Deploy a SablierFlow contract
flow = new SablierFlow({ initialAdmin: address(this), initialNFTDescriptor: IFlowNFTDescriptor(address(this)) });

// Deploy the FlowStreamCreator contract
streamCreator = new FlowStreamCreator(flow);

user = makeAddr("User");

// Mint some DAI tokens to the test user, which will be pulled by the creator contract
deal({ token: address(streamCreator.USDC()), to: user, give: 1_000_000e6 });

// Make the test user the `msg.sender` in all following calls
vm.startPrank({ msgSender: user });

// Approve the streamCreator contract to pull USDC tokens from the test user
streamCreator.USDC().approve({ spender: address(streamCreator), value: 1_000_000e6 });
}

function test_CreateStream_1T_PerMonth() external {
uint256 expectedStreamId = flow.nextStreamId();

uint256 actualStreamId = streamCreator.createStream_1T_PerMonth();
assertEq(actualStreamId, expectedStreamId);

// Warp more than 30 days into the future to see if the debt accumulated is more than 1 thousand
vm.warp({ newTimestamp: block.timestamp + 30 days + 1 seconds });

assertGe(flow.totalDebtOf(actualStreamId), 1000e6);
}

function test_CreateStream_1M_PerYear() external {
uint256 expectedStreamId = flow.nextStreamId();

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
vm.warp({ newTimestamp: block.timestamp + 365 days + 1 seconds });

assertGe(flow.totalDebtOf(actualStreamId), 1_000_000e6);
}
}
Loading