-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* build: install flow as submodules build: update PRBMath build: add flow/lockup foundry profiles feat: implement a FlowUtilities library feat: implement flow creator contract refactor: rename v2 to lockup chore: add scripts in package json for the new profiles * feat: add flow batchable * feat: add flow manager contract * fix: calculation * correct contract name * rename constructor var * fix spelling * fix: deposit functions build: install flow repo via package.json test: add tests for FlowStreamCreator test: add tests for FlowBatchable * chore: polish flow examples * ci: fix build and test commands * build: install flow as NPM package * refactor: rename contract to FlowStreamManager * refactor: declare the flow contract as constant polish code use the latest deployment * refactor: calculate first the duration in ratePerSecondForTimestamps * feat: add all functions in FlowStreamManager --------- Co-authored-by: smol-ninja <[email protected]>
- Loading branch information
1 parent
689f633
commit cdee078
Showing
36 changed files
with
515 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// 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 } from "@prb/math/src/UD60x18.sol"; | ||
import { Broker, ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; | ||
|
||
/// @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 { | ||
// Sepolia addresses | ||
IERC20 public constant USDC = IERC20(0xf08A50178dfcDe18524640EA6618a1f965821715); | ||
ISablierFlow public constant FLOW = ISablierFlow(0x5Ae8c13f6Ae094887322012425b34b0919097d8A); | ||
|
||
/// @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 the stream. | ||
USDC.transferFrom(msg.sender, address(this), depositAmount); | ||
|
||
// Approve the Sablier contract to spend USDC. | ||
USDC.approve(address(FLOW), depositAmount); | ||
|
||
// Fetch the stream recipient. | ||
address recipient = FLOW.getRecipient(streamId); | ||
|
||
// The call data declared as bytes. | ||
bytes[] memory calls = new bytes[](2); | ||
calls[0] = abi.encodeCall(FLOW.adjustRatePerSecond, (streamId, newRatePerSecond)); | ||
calls[1] = abi.encodeCall(FLOW.deposit, (streamId, depositAmount, msg.sender, recipient)); | ||
|
||
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 the stream. | ||
USDC.transferFrom(msg.sender, address(this), depositAmount); | ||
|
||
// Approve the Sablier contract to spend USDC. | ||
USDC.approve(address(FLOW), depositAmount); | ||
|
||
streamId = FLOW.nextStreamId(); | ||
|
||
// The call data declared as bytes | ||
bytes[] memory calls = new bytes[](2); | ||
calls[0] = abi.encodeCall(FLOW.create, (sender, recipient, ratePerSecond, USDC, true)); | ||
calls[1] = abi.encodeCall(FLOW.depositViaBroker, (streamId, depositAmount, sender, recipient, broker)); | ||
|
||
// Execute multiple calls in a single transaction using the prepared call data. | ||
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(FLOW.create, (sender, firstRecipient, firstRatePerSecond, USDC, true)); | ||
calls[1] = abi.encodeCall(FLOW.create, (sender, secondRecipient, secondRatePerSecond, USDC, true)); | ||
|
||
// Prepare the `streamIds` array to return them | ||
uint256 nextStreamId = 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. | ||
FLOW.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); | ||
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(FLOW), 2 * depositAmount); | ||
|
||
// The broker struct | ||
Broker memory broker = Broker({ | ||
account: address(0xDEAD), | ||
fee: ud60x18(0.0001e18) // the fee percentage | ||
}); | ||
|
||
uint256 nextStreamId = 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(FLOW.create, (sender, firstRecipient, ratePerSecond, USDC, true)); | ||
calls[1] = abi.encodeCall(FLOW.create, (sender, secondRecipient, ratePerSecond, USDC, true)); | ||
calls[2] = abi.encodeCall(FLOW.depositViaBroker, (streamIds[0], depositAmount, sender, firstRecipient, broker)); | ||
calls[3] = abi.encodeCall(FLOW.depositViaBroker, (streamIds[1], depositAmount, sender, secondRecipient, broker)); | ||
|
||
// Execute multiple calls in a single transaction using the prepared call data. | ||
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(FLOW.pause, (streamId)); | ||
calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); | ||
|
||
// Execute multiple calls in a single transaction using the prepared call data. | ||
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(FLOW.void, (streamId)); | ||
calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); | ||
|
||
// Execute multiple calls in a single transaction using the prepared call data. | ||
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 = FLOW.getRecipient(streamIds[i]); | ||
calls[i] = abi.encodeCall(FLOW.withdrawMax, (streamIds[i], recipient)); | ||
} | ||
|
||
// Execute multiple calls in a single transaction using the prepared call data. | ||
FLOW.batch(calls); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// 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"; | ||
|
||
contract FlowBatchable_Test is Test { | ||
FlowBatchable internal batchable; | ||
address internal user; | ||
|
||
function setUp() external { | ||
// Fork Ethereum Sepolia | ||
vm.createSelectFork({ urlOrAlias: "sepolia", blockNumber: 7_250_564 }); | ||
|
||
// Deploy the batchable contract | ||
batchable = new FlowBatchable(); | ||
|
||
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 = batchable.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 = batchable.FLOW().nextStreamId(); | ||
|
||
uint256[] memory actualStreamIds = batchable.createMultipleAndDepositViaBroker(); | ||
uint256[] memory expectedStreamIds = new uint256[](2); | ||
expectedStreamIds[0] = nextStreamIdBefore; | ||
expectedStreamIds[1] = nextStreamIdBefore + 1; | ||
|
||
assertEq(actualStreamIds, expectedStreamIds); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// 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 { | ||
// Sepolia addresses | ||
IERC20 public constant USDC = IERC20(0xf08A50178dfcDe18524640EA6618a1f965821715); | ||
ISablierFlow public constant FLOW = ISablierFlow(0x5Ae8c13f6Ae094887322012425b34b0919097d8A); | ||
|
||
// 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 }); | ||
|
||
streamId = FLOW.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 = FLOW.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 | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// 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"; | ||
|
||
contract FlowStreamCreator_Test is Test { | ||
FlowStreamCreator internal streamCreator; | ||
address internal user; | ||
|
||
function setUp() external { | ||
// Fork Ethereum Sepolia | ||
vm.createSelectFork({ urlOrAlias: "sepolia", blockNumber: 7_250_564 }); | ||
|
||
// Deploy the FlowStreamCreator contract | ||
streamCreator = new FlowStreamCreator(); | ||
|
||
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_1K_PerMonth() external { | ||
uint256 expectedStreamId = streamCreator.FLOW().nextStreamId(); | ||
|
||
uint256 actualStreamId = streamCreator.createStream_1K_PerMonth(); | ||
assertEq(actualStreamId, expectedStreamId); | ||
|
||
// 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(streamCreator.FLOW().totalDebtOf(actualStreamId), 1000e6); | ||
} | ||
|
||
function test_CreateStream_1M_PerYear() external { | ||
uint256 expectedStreamId = streamCreator.FLOW().nextStreamId(); | ||
|
||
uint256 actualStreamId = streamCreator.createStream_1M_PerYear(); | ||
assertEq(actualStreamId, expectedStreamId); | ||
|
||
// 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(streamCreator.FLOW().totalDebtOf(actualStreamId), 1_000_000e6); | ||
} | ||
} |
Oops, something went wrong.