Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@sushiswap/furo / contracts / base / FuroStream.sol
Size: Mime:
// 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);
        }
    }
}