Repository URL to install this package:
|
Version:
0.0.0 ▾
|
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.10;
import "../interfaces/IFuroStream.sol";
// Use the FuroStreamRouter to create Streams and do not create streams directly.
contract FuroStream is
IFuroStream,
ERC721("Furo Stream", "FUROSTREAM"),
Multicall,
BoringOwnable
{
IBentoBoxMinimal public immutable bentoBox;
address public immutable wETH;
uint256 public streamIds;
address public tokenURIFetcher;
mapping(uint256 => Stream) public streams;
// custom errors
error NotSenderOrRecipient();
error InvalidStartTime();
error InvalidEndTime();
error InvalidWithdrawTooMuch();
error NotSender();
error Overflow();
constructor(IBentoBoxMinimal _bentoBox, address _wETH) {
bentoBox = _bentoBox;
wETH = _wETH;
streamIds = 1000;
_bentoBox.registerProtocol();
}
function setTokenURIFetcher(address _fetcher) external onlyOwner {
tokenURIFetcher = _fetcher;
}
function tokenURI(uint256 id) public view override returns (string memory) {
return ITokenURIFetcher(tokenURIFetcher).fetchTokenURIData(id);
}
function setBentoBoxApproval(
address user,
bool approved,
uint8 v,
bytes32 r,
bytes32 s
) external payable override {
bentoBox.setMasterContractApproval(
user,
address(this),
approved,
v,
r,
s
);
}
function createStream(
address recipient,
address token,
uint64 startTime,
uint64 endTime,
uint256 amount, /// @dev in token amount and not in shares
bool fromBentoBox
)
external
payable
override
returns (uint256 streamId, uint256 depositedShares)
{
if (startTime < block.timestamp) revert InvalidStartTime();
if (endTime <= startTime) revert InvalidEndTime();
depositedShares = _depositToken(
token,
msg.sender,
address(this),
amount,
fromBentoBox
);
streamId = streamIds++;
_mint(recipient, streamId);
streams[streamId] = Stream({
sender: msg.sender,
token: token == address(0) ? wETH : token,
depositedShares: uint128(depositedShares), // @dev safe since we know bento returns u128
withdrawnShares: 0,
startTime: startTime,
endTime: endTime
});
emit CreateStream(
streamId,
msg.sender,
recipient,
token,
depositedShares,
startTime,
endTime,
fromBentoBox
);
}
function withdrawFromStream(
uint256 streamId,
uint256 sharesToWithdraw,
address withdrawTo,
bool toBentoBox,
bytes calldata taskData
) external override returns (uint256 recipientBalance, address to) {
address recipient = ownerOf[streamId];
if (msg.sender != streams[streamId].sender && msg.sender != recipient) {
revert NotSenderOrRecipient();
}
Stream storage stream = streams[streamId];
(, recipientBalance) = _streamBalanceOf(stream);
if (recipientBalance < sharesToWithdraw)
revert InvalidWithdrawTooMuch();
stream.withdrawnShares += uint128(sharesToWithdraw);
if (msg.sender == recipient && withdrawTo != address(0)) {
to = withdrawTo;
} else {
to = recipient;
}
_transferToken(
stream.token,
address(this),
to,
sharesToWithdraw,
toBentoBox
);
if (taskData.length != 0 && msg.sender == recipient)
ITasker(to).onTaskReceived(taskData);
emit Withdraw(
streamId,
sharesToWithdraw,
withdrawTo,
stream.token,
toBentoBox
);
}
function cancelStream(uint256 streamId, bool toBentoBox)
external
override
returns (uint256 senderBalance, uint256 recipientBalance)
{
address recipient = ownerOf[streamId];
if (msg.sender != streams[streamId].sender && msg.sender != recipient) {
revert NotSenderOrRecipient();
}
Stream memory stream = streams[streamId];
(senderBalance, recipientBalance) = _streamBalanceOf(stream);
delete streams[streamId];
_transferToken(
stream.token,
address(this),
recipient,
recipientBalance,
toBentoBox
);
_transferToken(
stream.token,
address(this),
stream.sender,
senderBalance,
toBentoBox
);
emit CancelStream(
streamId,
senderBalance,
recipientBalance,
stream.token,
toBentoBox
);
}
function getStream(uint256 streamId)
external
view
override
returns (Stream memory)
{
return streams[streamId];
}
function streamBalanceOf(uint256 streamId)
external
view
override
returns (uint256 senderBalance, uint256 recipientBalance)
{
return _streamBalanceOf(streams[streamId]);
}
function _streamBalanceOf(Stream memory stream)
internal
view
returns (uint256 senderBalance, uint256 recipientBalance)
{
if (block.timestamp <= stream.startTime) {
senderBalance = stream.depositedShares;
recipientBalance = 0;
} else if (stream.endTime <= block.timestamp) {
recipientBalance = stream.depositedShares - stream.withdrawnShares;
senderBalance = 0;
} else {
uint64 timeDelta = uint64(block.timestamp) - stream.startTime;
uint128 streamed = ((stream.depositedShares * timeDelta) /
(stream.endTime - stream.startTime));
recipientBalance = streamed - stream.withdrawnShares;
senderBalance = stream.depositedShares - streamed;
}
}
function updateSender(uint256 streamId, address sender) external override {
Stream storage stream = streams[streamId];
if (msg.sender != stream.sender) revert NotSender();
stream.sender = sender;
}
function updateStream(
uint256 streamId,
uint128 topUpAmount,
uint64 extendTime,
bool fromBentoBox
) external payable override returns (uint256 depositedShares) {
Stream storage stream = streams[streamId];
if (msg.sender != stream.sender) revert NotSender();
depositedShares = _depositToken(
stream.token,
stream.sender,
address(this),
topUpAmount,
fromBentoBox
);
address recipient = ownerOf[streamId];
(uint256 senderBalance, uint256 recipientBalance) = _streamBalanceOf(
stream
);
stream.startTime = uint64(block.timestamp);
stream.withdrawnShares = 0;
uint256 newDepositedShares = senderBalance + depositedShares;
if (newDepositedShares > type(uint128).max) revert Overflow();
stream.depositedShares = uint128(newDepositedShares);
stream.endTime += extendTime;
_transferToken(
stream.token,
address(this),
recipient,
recipientBalance,
true
);
emit UpdateStream(streamId, topUpAmount, extendTime, fromBentoBox);
}
function _depositToken(
address token,
address from,
address to,
uint256 amount,
bool fromBentoBox
) internal returns (uint256 depositedShares) {
if (fromBentoBox) {
depositedShares = bentoBox.toShare(token, amount, false);
bentoBox.transfer(token, from, to, depositedShares);
} else {
(, depositedShares) = bentoBox.deposit{
value: token == address(0) ? amount : 0
}(token, from, to, amount, 0);
}
}
function _transferToken(
address token,
address from,
address to,
uint256 share,
bool toBentoBox
) internal {
if (toBentoBox) {
bentoBox.transfer(token, from, to, share);
} else {
bentoBox.withdraw(token, from, to, 0, share);
}
}
}