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

Implement ERC721 standard in both Linear and Pro contracts #131

Merged
merged 10 commits into from
Oct 24, 2022
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