Skip to content

Commit

Permalink
Implement ERC721 standard in both Linear and Pro contracts (#131)
Browse files Browse the repository at this point in the history
* feat: ERC721

build: install "openzeppelin-contracts" lib
feat: inherit "ERC721" in "SablierV2Linear"
feat: inherit "ERC721" in "SablierV2Pro"
feat: mint NFT in the "_create" function
feat: add "isApprovedOrOwner" function
feat: change the modifier to "isAuthorizedForStream"
feat: add "tokenURI" function
perf: remove the recipient address from the "Stream" struct
refactor: swap the order of the checks in the "withdrawTo" function
test: change the struct recipient with the "users.recipient"
test: make recipient the default caller in the "cancel" function
test: order correctly the branches in the "cancel" tree
test: order correctly the branches in the "cancelAll" tree
test: order correctly the branches in the "withdraw" tree
test: when the caller is an approved third party in the "cancel" function
test: when the caller is an approved third party in the "cancelAll" funtion
test: when the caller is an approved third party in the "withdraw" function
test: when the caller is an approved third party in the "withdrawAll" function
test: when the caller is an approved third party in the "withdrawAllTo" function
test: when the caller is an approved third party in the "withdtawTo" function
test: when the recipient is no longer the owner of the stream in the "cancel" function
test: when the recipient is no longer the owner of the stream in the "withdraw" function
test: when the recipient is no longer the owner of the streams in the "cancelAll" function
test: when the recipient is no longer the owner of the streams in the "withdrawAll" function
test: when the recipient is no longer the owner of the streams in the "withdrawAllTo" function
test: when the recipient is no longer the owner of the stream in the "withrawTo" function
test: remove the local "withdrawAmount" variable from the test functions
test: change the new owner of the streams to "eve" when using "safeTransferFrom" function
test: "isApprovedOrOwner" function in the "SablierV2Linear" contract
test: "isApprovedOrOwner" function in the "SablierV2Pro" contract
test: correct comment about return value for the "isCancelable" function

* refactor: inherit from "IERC721" in "ISablierV2"

build: upgrade to latest "prb-math" lib
chore: add ".solhintignore" file
chore: improve wording in comments
docs: improve wording in NatSpec
refactor: change the ERC-721 name
refactor: name return args in "isApprovedOrOwner" and "tokenURI"
refactor: refer to "to" as "recipient"
refactor: use OpenZeppelin's ERC721 implementation
refactor: use "_isApprovedOrOwner" from OpenZeppelin
test: add dummy function implementations in "AbstractSablierV2"
test: delete "isApprovedOrOwner" tests

* fix: use "_ownerOf" ERC721 function

build: upgrade to "release-v4.8" of "openzeppelin-contracts"

* test: improve wording in testing trees

test: add new "operator" user
test: add new testing branches for un/authorized
test: delete superfluous warps in "withdraw" tests
test: move assertions for recipients at the bottom
test: refer to approved "third-party" as "operator"
test: rename "withdrawAmountZero" to "withdrawAmount"
test: refactor "CallerRecipient" to "OriginalRecipient"
test: use the new operator user in the "approved operator" tests

* refactor: isApprovedOrOwner

chore: add visibility keyword in function separators
refactor: inherit from ERC721 last
test: simplify AbstractSablierV2 contract

* chore: remove unnecessary imports

* style: order imports

chore: remove unnecessary imports

* refactor: burn NFT when stream is ended

test: update tests to check for burned NFT

* refactor: use "safeMint" in the "create" function

* test: correct explanatory comment

Co-authored-by: Paul Razvan Berg <hello@paulrberg.com>
  • Loading branch information
andreivladbrg and PaulRBerg committed Oct 24, 2022
1 parent 3d8e153 commit 36680bb
Show file tree
Hide file tree
Showing 39 changed files with 1,185 additions and 420 deletions.
6 changes: 5 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
[submodule "lib/forge-std"]
path = "lib/forge-std"
url = "https://github.com/foundry-rs/forge-std"
[submodule "lib/openzeppelin-contracts"]
branch = "release-v4.8"
path = "lib/openzeppelin-contracts"
url = "https://github.com/OpenZeppelin/openzeppelin-contracts"
[submodule "lib/prb-math"]
branch = "staging"
path = "lib/prb-math"
url = "https://github.com/paulrberg/prb-math"
[submodule "lib/prb-contracts"]
branch = "refactor/foundry"
branch = "main"
path = "lib/prb-contracts"
url = "https://github.com/paulrberg/prb-contracts"
3 changes: 3 additions & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# directories
**/lib
**/node_modules
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at 014ce9
2 changes: 1 addition & 1 deletion lib/prb-contracts
Submodule prb-contracts updated 71 files
+4 −1 .editorconfig
+2 −2 .github/workflows/ci.yml
+9 −4 .gitmodules
+2 −0 .prettierignore
+0 −2 .prettierrc.yml
+1 −1 README.md
+3 −0 foundry.toml
+1 −1 lib/forge-std
+1 −1 lib/prb-math
+1 −0 lib/prb-test
+28 −1 package.json
+1 −0 remappings.txt
+0 −39 src/package.json
+35 −29 src/token/erc20/ERC20.sol
+2 −2 src/token/erc20/ERC20GodMode.sol
+1 −1 src/token/erc20/ERC20Permit.sol
+22 −22 src/token/erc20/NonCompliantERC20.sol
+11 −17 test/PRBContractBaseTest.t.sol
+15 −15 test/shared/SymbollessERC20.t.sol
+4 −4 test/token/erc20-normalizer/ERC20NormalizerTest.t.sol
+10 −25 test/token/erc20-normalizer/compute-scalar/computeScalar.t.sol
+0 −0 test/token/erc20-normalizer/compute-scalar/computeScalar.tree
+19 −33 test/token/erc20-normalizer/denormalize/denormalize.t.sol
+0 −0 test/token/erc20-normalizer/denormalize/denormalize.tree
+19 −25 test/token/erc20-normalizer/normalize/normalize.t.sol
+0 −0 test/token/erc20-normalizer/normalize/normalize.tree
+3 −3 test/token/erc20-permit/ERC20PermitTest.t.sol
+2 −2 test/token/erc20-permit/domain-separator/domainSeparator.t.sol
+0 −0 test/token/erc20-permit/domain-separator/domainSeparator.tree
+2 −2 test/token/erc20-permit/permit-typehash/permitTypehash.t.sol
+0 −0 test/token/erc20-permit/permit-typehash/permitTypehash.tree
+57 −43 test/token/erc20-permit/permit/permit.t.sol
+0 −0 test/token/erc20-permit/permit/permit.tree
+2 −2 test/token/erc20-permit/version/version.t.sol
+0 −0 test/token/erc20-permit/version/version.tree
+5 −5 test/token/erc20-recover/ERC20RecoverTest.t.sol
+7 −20 test/token/erc20-recover/get-token-denylist/getTokenDenylist.t.sol
+0 −0 test/token/erc20-recover/get-token-denylist/getTokenDenylist.tree
+19 −0 test/token/erc20-recover/is-token-denylist-set/isTokenDenylistSet.t.sol
+0 −0 test/token/erc20-recover/is-token-denylist-set/isTokenDenylistSet.tree
+19 −24 test/token/erc20-recover/recover/recover.t.sol
+0 −0 test/token/erc20-recover/recover/recover.tree
+16 −25 test/token/erc20-recover/set-token-denylist/setTokenDenylist.t.sol
+0 −0 test/token/erc20-recover/set-token-denylist/setTokenDenylist.tree
+3 −3 test/token/erc20/ERC20Test.t.sol
+12 −12 test/token/erc20/approve/approve.t.sol
+0 −0 test/token/erc20/approve/approve.tree
+4 −8 test/token/erc20/balance-of/balanceOf.t.sol
+0 −0 test/token/erc20/balance-of/balanceOf.tree
+16 −13 test/token/erc20/burn/burn.t.sol
+0 −0 test/token/erc20/burn/burn.tree
+2 −2 test/token/erc20/decimals/decimals.t.sol
+0 −0 test/token/erc20/decimals/decimals.tree
+8 −8 test/token/erc20/decrease-allowance/decreaseAllowance.t.sol
+0 −0 test/token/erc20/decrease-allowance/decreaseAllowance.tree
+9 −9 test/token/erc20/increase-allowance/increaseAllowance.t.sol
+0 −0 test/token/erc20/increase-allowance/increaseAllowance.tree
+36 −30 test/token/erc20/mint/mint.t.sol
+0 −0 test/token/erc20/mint/mint.tree
+2 −2 test/token/erc20/name/name.t.sol
+0 −0 test/token/erc20/name/name.tree
+2 −2 test/token/erc20/symbol/symbol.t.sol
+0 −0 test/token/erc20/symbol/symbol.tree
+8 −10 test/token/erc20/transfer-from/transferFrom.t.sol
+0 −0 test/token/erc20/transfer-from/transferFrom.tree
+114 −0 test/token/erc20/transfer/transfer.t.sol
+15 −0 test/token/erc20/transfer/transfer.tree
+0 −28 test/unit/token/erc20-recover/is-token-denylist-set/isTokenDenylistSet.t.sol
+0 −78 test/unit/token/erc20/transfer/transfer.t.sol
+0 −12 test/unit/token/erc20/transfer/transfer.tree
+180 −19 yarn.lock
2 changes: 1 addition & 1 deletion lib/prb-math
Submodule prb-math updated 96 files
+8 −11 .gitignore
+8 −0 .gitmodules
+1 −1 .prettierrc.yml
+3 −1 .solhint.json
+0 −591 contracts/PRBMath.sol
+0 −82 contracts/test/PRBMathSD59x18Mock.sol
+0 −78 contracts/test/PRBMathUD60x18Mock.sol
+18 −0 foundry.toml
+1 −1 hardhat.config.ts
+1 −0 lib/forge-std
+1 −0 lib/prb-test
+2 −0 remappings.txt
+0 −0 src/.npmignore
+595 −0 src/Helpers.sol
+18 −18 src/SD59x18.sol
+20 −14 src/UD60x18.sol
+0 −22 src/constants.ts
+0 −38 src/errors.ts
+0 −120 src/functions.ts
+0 −26 src/helpers.ts
+0 −22 src/index.ts
+0 −38 src/math.ts
+17 −0 src/test/Assertions.sol
+61 −0 test/PRBMathBaseTest.t.sol
+0 −8 test/contracts/index.test.ts
+0 −105 test/contracts/prbMathSd59x18/PRBMathSD59x18.test.ts
+0 −71 test/contracts/prbMathSd59x18/pure/abs.test.ts
+0 −125 test/contracts/prbMathSd59x18/pure/avg.test.ts
+0 −70 test/contracts/prbMathSd59x18/pure/ceil.test.ts
+0 −176 test/contracts/prbMathSd59x18/pure/div.test.ts
+0 −89 test/contracts/prbMathSd59x18/pure/exp.test.ts
+0 −95 test/contracts/prbMathSd59x18/pure/exp2.test.ts
+0 −71 test/contracts/prbMathSd59x18/pure/floor.test.ts
+0 −58 test/contracts/prbMathSd59x18/pure/frac.test.ts
+0 −61 test/contracts/prbMathSd59x18/pure/fromSD59x18.test.ts
+0 −94 test/contracts/prbMathSd59x18/pure/gm.test.ts
+0 −72 test/contracts/prbMathSd59x18/pure/inv.test.ts
+0 −58 test/contracts/prbMathSd59x18/pure/ln.test.ts
+0 −86 test/contracts/prbMathSd59x18/pure/log10.test.ts
+0 −79 test/contracts/prbMathSd59x18/pure/log2.test.ts
+0 −179 test/contracts/prbMathSd59x18/pure/mul.test.ts
+0 −125 test/contracts/prbMathSd59x18/pure/pow.test.ts
+0 −135 test/contracts/prbMathSd59x18/pure/powu.test.ts
+0 −75 test/contracts/prbMathSd59x18/pure/sqrt.test.ts
+0 −60 test/contracts/prbMathSd59x18/pure/toSD59x18.test.ts
+0 −100 test/contracts/prbMathUd60x18/PRBMathUD60x18.test.ts
+0 −89 test/contracts/prbMathUd60x18/pure/avg.test.ts
+0 −48 test/contracts/prbMathUd60x18/pure/ceil.test.ts
+0 −75 test/contracts/prbMathUd60x18/pure/div.test.ts
+0 −55 test/contracts/prbMathUd60x18/pure/exp.test.ts
+0 −61 test/contracts/prbMathUd60x18/pure/exp2.test.ts
+0 −38 test/contracts/prbMathUd60x18/pure/floor.test.ts
+0 −38 test/contracts/prbMathUd60x18/pure/frac.test.ts
+0 −38 test/contracts/prbMathUd60x18/pure/fromUD60x18.test.ts
+0 −63 test/contracts/prbMathUd60x18/pure/gm.test.ts
+0 −44 test/contracts/prbMathUd60x18/pure/inv.test.ts
+0 −40 test/contracts/prbMathUd60x18/pure/ln.test.ts
+0 −55 test/contracts/prbMathUd60x18/pure/log10.test.ts
+0 −42 test/contracts/prbMathUd60x18/pure/log2.test.ts
+0 −73 test/contracts/prbMathUd60x18/pure/mul.test.ts
+0 −103 test/contracts/prbMathUd60x18/pure/pow.test.ts
+0 −92 test/contracts/prbMathUd60x18/pure/powu.test.ts
+0 −62 test/contracts/prbMathUd60x18/pure/sqrt.test.ts
+0 −38 test/contracts/prbMathUd60x18/pure/toUD60x18.test.ts
+94 −0 test/sd59x18/SD59x18BaseTest.t.sol
+81 −0 test/sd59x18/inv/inv.t.sol
+8 −0 test/sd59x18/inv/inv.tree
+0 −25 test/shared/assertions.ts
+0 −1 test/shared/constants.ts
+0 −29 test/shared/contexts.ts
+0 −4 test/shared/errors.ts
+0 −36 test/shared/fixtures.ts
+0 −7 test/shared/setup.ts
+0 −31 test/shared/types.ts
+0 −108 test/ts/functions/avg.test.ts
+0 −51 test/ts/functions/ceil.test.ts
+0 −83 test/ts/functions/div.test.ts
+0 −38 test/ts/functions/exp.test.ts
+0 −38 test/ts/functions/exp2.test.ts
+0 −49 test/ts/functions/floor.test.ts
+0 −51 test/ts/functions/frac.test.ts
+0 −59 test/ts/functions/gm.test.ts
+0 −39 test/ts/functions/inv.test.ts
+0 −53 test/ts/functions/ln.test.ts
+0 −53 test/ts/functions/log10.test.ts
+0 −53 test/ts/functions/log2.test.ts
+0 −71 test/ts/functions/mul.test.ts
+0 −89 test/ts/functions/pow.test.ts
+0 −37 test/ts/functions/sqrt.test.ts
+0 −77 test/ts/index.test.ts
+98 −0 test/ud60x18/UD60x18BaseTest.t.sol
+85 −0 test/ud60x18/avg/avg.t.sol
+12 −0 test/ud60x18/avg/avg.tree
+45 −0 test/ud60x18/inv/inv.t.sol
+5 −0 test/ud60x18/inv/inv.tree
+1 −1 tsconfig.json
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/
@prb/contracts/=lib/prb-contracts/src/
@prb/math/=lib/prb-math/contracts/
@prb/math/=lib/prb-math/src/
@sablier/v2-core/=src/
forge-std/=lib/forge-std/src/
47 changes: 27 additions & 20 deletions src/SablierV2.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0
pragma solidity >=0.8.13;

import { IERC20 } from "@prb/contracts/token/erc20/IERC20.sol";

import { ISablierV2 } from "./interfaces/ISablierV2.sol";

/// @title SablierV2
Expand All @@ -20,9 +18,10 @@ abstract contract SablierV2 is ISablierV2 {
MODIFIERS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Checks that `msg.sender` is either the sender or the recipient of the stream.
modifier onlySenderOrRecipient(uint256 streamId) {
if (msg.sender != getSender(streamId) && msg.sender != getRecipient(streamId)) {
/// @notice Checks that `msg.sender` is the sender of the stream, an approved operator, or the owner of the
/// NFT (also known as the recipient of the stream).
modifier isAuthorizedForStream(uint256 streamId) {
if (msg.sender != getSender(streamId) && !_isApprovedOrOwner(msg.sender, streamId)) {
revert SablierV2__Unauthorized(streamId, msg.sender);
}
_;
Expand All @@ -45,7 +44,7 @@ abstract contract SablierV2 is ISablierV2 {
}

/*//////////////////////////////////////////////////////////////////////////
CONSTANT FUNCTIONS
PUBLIC CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ISablierV2
Expand All @@ -58,7 +57,7 @@ abstract contract SablierV2 is ISablierV2 {
function isCancelable(uint256 streamId) public view virtual override returns (bool cancelable);

/*//////////////////////////////////////////////////////////////////////////
NON-CONSTANT FUNCTIONS
PUBLIC NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ISablierV2
Expand Down Expand Up @@ -110,10 +109,10 @@ abstract contract SablierV2 is ISablierV2 {
function withdraw(uint256 streamId, uint256 amount)
external
streamExists(streamId)
onlySenderOrRecipient(streamId)
isAuthorizedForStream(streamId)
{
address to = getRecipient(streamId);
_withdraw(streamId, to, amount);
address recipient = getRecipient(streamId);
_withdraw(streamId, recipient, amount);
}

/// @inheritdoc ISablierV2
Expand All @@ -136,8 +135,9 @@ abstract contract SablierV2 is ISablierV2 {
recipient = getRecipient(streamId);
sender = getSender(streamId);
if (sender != address(0)) {
// Checks: the `msg.sender` is either the sender or the recipient of the stream.
if (msg.sender != sender && msg.sender != recipient) {
// Checks: the `msg.sender` is the sender or the stream, an approved operator, or the owner of the NFT
// (a.k.a. the recipient of the stream).
if (msg.sender != sender && !_isApprovedOrOwner(msg.sender, streamId)) {
revert SablierV2__Unauthorized(streamId, msg.sender);
}

Expand Down Expand Up @@ -177,8 +177,9 @@ abstract contract SablierV2 is ISablierV2 {

// If the `streamId` points to a stream that does not exist, skip it.
if (getSender(streamId) != address(0)) {
// Checks: the `msg.sender` is the recipient of the stream.
if (msg.sender != getRecipient(streamId)) {
// Checks: the `msg.sender` is either an approved operator or the owner of the NFT (a.k.a. the recipient
// of the stream).
if (!_isApprovedOrOwner(msg.sender, streamId)) {
revert SablierV2__Unauthorized(streamId, msg.sender);
}

Expand All @@ -199,23 +200,24 @@ abstract contract SablierV2 is ISablierV2 {
address to,
uint256 amount
) external streamExists(streamId) {
// Checks: the `msg.sender` is the recipient of the stream.
if (msg.sender != getRecipient(streamId)) {
revert SablierV2__Unauthorized(streamId, msg.sender);
}

// Checks: the provided address to withdraw to is not zero.
if (to == address(0)) {
revert SablierV2__WithdrawZeroAddress();
}

// Checks: the `msg.sender` is either an approved operator or the owner of the NFT (a.k.a. the recipient
// of the stream).
if (!_isApprovedOrOwner(msg.sender, streamId)) {
revert SablierV2__Unauthorized(streamId, msg.sender);
}
_withdraw(streamId, to, amount);
}

/*//////////////////////////////////////////////////////////////////////////
INTERNAL CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @dev Checks the basic requiremenets for the `create` function.
/// @dev Checks the basic requirements for the `create` function.
function _checkCreateArguments(
address sender,
address recipient,
Expand Down Expand Up @@ -244,6 +246,11 @@ abstract contract SablierV2 is ISablierV2 {
}
}

/// @dev Checks whether the spender is authorized to interact with the stream.
/// @param spender The spender to make the query for.
/// @param streamId The id of the stream to make the query for.
function _isApprovedOrOwner(address spender, uint256 streamId) internal view virtual returns (bool approvedOrOwner);

/*//////////////////////////////////////////////////////////////////////////
INTERNAL NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
Expand Down
49 changes: 39 additions & 10 deletions src/SablierV2Linear.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0
pragma solidity >=0.8.13;

import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { IERC20 } from "@prb/contracts/token/erc20/IERC20.sol";
import { SafeERC20 } from "@prb/contracts/token/erc20/SafeERC20.sol";
import { UD60x18, toUD60x18 } from "@prb/math/UD60x18.sol";
Expand All @@ -13,7 +14,8 @@ import { SablierV2 } from "./SablierV2.sol";
/// @author Sablier Labs Ltd.
contract SablierV2Linear is
ISablierV2Linear, // one dependency
SablierV2 // one dependency
SablierV2, // two dependencies
ERC721("Sablier V2 Linear NFT", "SAB-V2-LIN") // six dependencies
{
using SafeERC20 for IERC20;

Expand All @@ -25,7 +27,7 @@ contract SablierV2Linear is
mapping(uint256 => Stream) internal _streams;

/*//////////////////////////////////////////////////////////////////////////
CONSTANT FUNCTIONS
PUBLIC CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ISablierV2Linear
Expand All @@ -40,7 +42,7 @@ contract SablierV2Linear is

/// @inheritdoc ISablierV2
function getRecipient(uint256 streamId) public view override(ISablierV2, SablierV2) returns (address recipient) {
recipient = _streams[streamId].recipient;
recipient = _ownerOf(streamId);
}

/// @inheritdoc ISablierV2
Expand Down Expand Up @@ -122,8 +124,13 @@ contract SablierV2Linear is
cancelable = _streams[streamId].cancelable;
}

/// @inheritdoc ERC721
function tokenURI(uint256 streamId) public view override streamExists(streamId) returns (string memory uri) {
uri = "";
}

/*//////////////////////////////////////////////////////////////////////////
NON-CONSTANT FUNCTIONS
PUBLIC NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ISablierV2Linear
Expand Down Expand Up @@ -166,12 +173,26 @@ contract SablierV2Linear is
streamId = _create(sender, recipient, depositAmount, token, startTime, cliffTime, stopTime, cancelable);
}

/*//////////////////////////////////////////////////////////////////////////
INTERNAL CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc SablierV2
function _isApprovedOrOwner(address spender, uint256 streamId)
internal
view
override(ERC721, SablierV2)
returns (bool approvedOrOwner)
{
approvedOrOwner = ERC721._isApprovedOrOwner(spender, streamId);
}

/*//////////////////////////////////////////////////////////////////////////
INTERNAL NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @dev See the documentation for the public functions that call this internal function.
function _cancel(uint256 streamId) internal override onlySenderOrRecipient(streamId) {
function _cancel(uint256 streamId) internal override isAuthorizedForStream(streamId) {
Stream memory stream = _streams[streamId];

// Calculate the withdraw and the return amounts.
Expand All @@ -181,12 +202,17 @@ contract SablierV2Linear is
returnAmount = stream.depositAmount - stream.withdrawnAmount - withdrawAmount;
}

address recipient = getRecipient(streamId);

// Effects: delete the stream from storage.
delete _streams[streamId];

// Effects: burn the NFT.
_burn(streamId);

// Interactions: withdraw the tokens to the recipient, if any.
if (withdrawAmount > 0) {
stream.token.safeTransfer(stream.recipient, withdrawAmount);
stream.token.safeTransfer(recipient, withdrawAmount);
}

// Interactions: return the tokens to the sender, if any.
Expand All @@ -195,7 +221,7 @@ contract SablierV2Linear is
}

// Emit an event.
emit Cancel(streamId, stream.recipient, withdrawAmount, returnAmount);
emit Cancel(streamId, recipient, withdrawAmount, returnAmount);
}

/// @dev See the documentation for the public functions that call this internal function.
Expand All @@ -222,20 +248,22 @@ contract SablierV2Linear is
revert SablierV2Linear__CliffTimeGreaterThanStopTime(cliffTime, stopTime);
}

// Effects: create and store the stream.
// Effects: create the stream.
streamId = nextStreamId;
_streams[streamId] = Stream({
cancelable: cancelable,
cliffTime: cliffTime,
depositAmount: depositAmount,
recipient: recipient,
sender: sender,
startTime: startTime,
stopTime: stopTime,
token: token,
withdrawnAmount: 0
});

// Effects: mint the NFT for the recipient.
_safeMint(recipient, streamId);

// Effects: bump the next stream id.
// We're using unchecked arithmetic here because this cannot realistically overflow, ever.
unchecked {
Expand Down Expand Up @@ -294,9 +322,10 @@ contract SablierV2Linear is
// Load the stream in memory, we will need it below.
Stream memory stream = _streams[streamId];

// Effects: if this stream is done, save gas by deleting it from storage.
// Effects: if this stream is done, delete it from storage and burn the NFT.
if (stream.depositAmount == stream.withdrawnAmount) {
delete _streams[streamId];
_burn(streamId);
}

// Interactions: perform the ERC-20 transfer.
Expand Down
Loading

0 comments on commit 36680bb

Please sign in to comment.