Skip to content

Commit b2711c3

Browse files
authored
[ETHEREUM-CONTRACTS] Flow NFT Revisions (#1351)
1 parent 534493f commit b2711c3

37 files changed

+1670
-1251
lines changed

packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,100 @@ contract ConstantFlowAgreementV1 is
445445
if (flowParams.flowRate <= 0) revert CFA_INVALID_FLOW_RATE();
446446
}
447447

448+
449+
/**
450+
* @notice Checks whether or not the NFT hook can be called.
451+
* @dev A staticcall, so `CONSTANT_OUTFLOW_NFT` must be a view otherwise the assumption is that it reverts
452+
* @param token the super token that is being streamed
453+
* @return constantOutflowNFTAddress the address returned by low level call
454+
*/
455+
function _canCallNFTHook(
456+
ISuperfluidToken token
457+
) internal view returns (address constantOutflowNFTAddress) {
458+
// solhint-disable-next-line avoid-low-level-calls
459+
(bool success, bytes memory data) = address(token).staticcall(
460+
abi.encodeWithSelector(ISuperToken.CONSTANT_OUTFLOW_NFT.selector)
461+
);
462+
463+
if (success) {
464+
// @note We are aware this may revert if a Custom SuperToken's
465+
// CONSTANT_OUTFLOW_NFT does not return data that can be
466+
// decoded to an address. This would mean it was intentionally
467+
// done by the creator of the Custom SuperToken logic and is
468+
// fully expected to revert in that case as the author desired.
469+
constantOutflowNFTAddress = abi.decode(data, (address));
470+
}
471+
}
472+
473+
function _handleOnCreateHook(
474+
_StackVars_createOrUpdateFlow memory flowVars
475+
) internal {
476+
uint256 gasLeftBefore = gasleft();
477+
478+
address constantOutflowNFTAddress = _canCallNFTHook(flowVars.token);
479+
480+
if (constantOutflowNFTAddress != address(0)) {
481+
try
482+
IConstantOutflowNFT(constantOutflowNFTAddress).onCreate(
483+
flowVars.token,
484+
flowVars.sender,
485+
flowVars.receiver
486+
)
487+
// solhint-disable-next-line no-empty-blocks
488+
{
489+
490+
} catch {
491+
SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
492+
}
493+
}
494+
}
495+
496+
function _handleOnUpdateHook(
497+
_StackVars_createOrUpdateFlow memory flowVars
498+
) internal {
499+
uint256 gasLeftBefore = gasleft();
500+
501+
address constantOutflowNFTAddress = _canCallNFTHook(flowVars.token);
502+
503+
if (constantOutflowNFTAddress != address(0)) {
504+
try
505+
IConstantOutflowNFT(constantOutflowNFTAddress).onUpdate(
506+
flowVars.token,
507+
flowVars.sender,
508+
flowVars.receiver
509+
)
510+
// solhint-disable-next-line no-empty-blocks
511+
{
512+
513+
} catch {
514+
SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
515+
}
516+
}
517+
}
518+
519+
function _handleOnDeleteHook(
520+
_StackVars_createOrUpdateFlow memory flowVars
521+
) internal {
522+
uint256 gasLeftBefore = gasleft();
523+
524+
address constantOutflowNFTAddress = _canCallNFTHook(flowVars.token);
525+
526+
if (constantOutflowNFTAddress != address(0)) {
527+
try
528+
IConstantOutflowNFT(constantOutflowNFTAddress).onDelete(
529+
flowVars.token,
530+
flowVars.sender,
531+
flowVars.receiver
532+
)
533+
// solhint-disable-next-line no-empty-blocks
534+
{
535+
536+
} catch {
537+
SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
538+
}
539+
}
540+
}
541+
448542
function _createFlow(
449543
_StackVars_createOrUpdateFlow memory flowVars,
450544
bytes calldata ctx,
@@ -471,16 +565,7 @@ contract ConstantFlowAgreementV1 is
471565

472566
_requireAvailableBalance(flowVars.token, flowVars.sender, currentContext);
473567

474-
uint256 gasLeftBefore = gasleft();
475-
476-
try
477-
ISuperToken(address(flowVars.token)).constantOutflowNFT().onCreate{
478-
gas: CFA_HOOK_GAS_LIMIT
479-
}(flowVars.sender, flowVars.receiver)
480-
// solhint-disable-next-line no-empty-blocks
481-
{} catch {
482-
SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
483-
}
568+
_handleOnCreateHook(flowVars);
484569
}
485570

486571
function _updateFlow(
@@ -510,16 +595,7 @@ contract ConstantFlowAgreementV1 is
510595

511596
_requireAvailableBalance(flowVars.token, flowVars.sender, currentContext);
512597

513-
uint256 gasLeftBefore = gasleft();
514-
515-
try
516-
ISuperToken(address(flowVars.token)).constantOutflowNFT().onUpdate{
517-
gas: CFA_HOOK_GAS_LIMIT
518-
}(flowVars.sender, flowVars.receiver)
519-
// solhint-disable-next-line no-empty-blocks
520-
{} catch {
521-
SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
522-
}
598+
_handleOnUpdateHook(flowVars);
523599
}
524600

525601
function _deleteFlow(
@@ -631,16 +707,7 @@ contract ConstantFlowAgreementV1 is
631707
}
632708
}
633709

634-
uint256 gasLeftBefore = gasleft();
635-
636-
try
637-
ISuperToken(address(flowVars.token)).constantOutflowNFT().onDelete{
638-
gas: CFA_HOOK_GAS_LIMIT
639-
}(flowVars.sender, flowVars.receiver)
640-
// solhint-disable-next-line no-empty-blocks
641-
{} catch {
642-
SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
643-
}
710+
_handleOnDeleteHook(flowVars);
644711
}
645712

646713
/**************************************************************************

packages/ethereum-contracts/contracts/interfaces/superfluid/IConstantInflowNFT.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import { ISuperToken } from "./ISuperToken.sol";
55
import { IFlowNFTBase } from "./IFlowNFTBase.sol";
66

77
interface IConstantInflowNFT is IFlowNFTBase {
8+
/**************************************************************************
9+
* Custom Errors
10+
*************************************************************************/
11+
error CIF_NFT_ONLY_CONSTANT_OUTFLOW(); // 0xe81ef57a
12+
813
/**************************************************************************
914
* Write Functions
1015
*************************************************************************/
Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,41 @@
11
// SPDX-License-Identifier: AGPLv3
22
pragma solidity >=0.8.4;
33

4-
import { ISuperToken } from "./ISuperToken.sol";
4+
import { ISuperfluidToken } from "./ISuperfluidToken.sol";
55
import { IFlowNFTBase } from "./IFlowNFTBase.sol";
66

77
interface IConstantOutflowNFT is IFlowNFTBase {
8+
/**************************************************************************
9+
* Custom Errors
10+
*************************************************************************/
11+
12+
error COF_NFT_INVALID_SUPER_TOKEN(); // 0x6de98774
13+
error COF_NFT_MINT_TO_AND_FLOW_RECEIVER_SAME(); // 0x0d1d1161
14+
error COF_NFT_MINT_TO_ZERO_ADDRESS(); // 0x43d05e51
15+
error COF_NFT_ONLY_CONSTANT_INFLOW(); // 0xa495a718
16+
error COF_NFT_ONLY_FLOW_AGREEMENTS(); // 0xd367b64f
17+
error COF_NFT_TOKEN_ALREADY_EXISTS(); // 0xe2480183
18+
19+
820
/**************************************************************************
921
* Write Functions
1022
*************************************************************************/
1123

1224
/// @notice The onCreate function is called when a new flow is created.
25+
/// @param token the super token passed from the CFA (flowVars)
1326
/// @param flowSender the flow sender
1427
/// @param flowReceiver the flow receiver
15-
function onCreate(address flowSender, address flowReceiver) external;
28+
function onCreate(ISuperfluidToken token, address flowSender, address flowReceiver) external;
1629

1730
/// @notice The onUpdate function is called when a flow is updated.
31+
/// @param token the super token passed from the CFA (flowVars)
1832
/// @param flowSender the flow sender
1933
/// @param flowReceiver the flow receiver
20-
function onUpdate(address flowSender, address flowReceiver) external;
34+
function onUpdate(ISuperfluidToken token, address flowSender, address flowReceiver) external;
2135

2236
/// @notice The onDelete function is called when a flow is deleted.
37+
/// @param token the super token passed from the CFA (flowVars)
2338
/// @param flowSender the flow sender
2439
/// @param flowReceiver the flow receiver
25-
function onDelete(address flowSender, address flowReceiver) external;
40+
function onDelete(ISuperfluidToken token, address flowSender, address flowReceiver) external;
2641
}

packages/ethereum-contracts/contracts/interfaces/superfluid/IFlowNFTBase.sol

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,19 @@ import { ISuperToken } from "./ISuperToken.sol";
99
interface IFlowNFTBase is IERC721Metadata {
1010
// FlowNFTData struct storage packing:
1111
// b = bits
12-
// WORD 1: | flowSender | flowStartDate | FREE
13-
// | 160b | 32b | 64b
14-
// WORD 2: | flowReceiver | FREE
15-
// | 160b | 96b
16-
// @note Using 32 bits for flowStartDate is future proof "enough":
17-
// 2 ** 32 - 1 = 4294967295 seconds
18-
// Will overflow after: Sun Feb 07 2106 08:28:15
12+
// WORD 1: | superToken | FREE
13+
// | 160b | 96b
14+
// WORD 2: | flowSender | FREE
15+
// | 160b | 96b
16+
// WORD 3: | flowReceiver | flowStartDate | FREE
17+
// | 160b | 32b | 64b
1918
struct FlowNFTData {
19+
address superToken;
2020
address flowSender;
21-
uint32 flowStartDate;
2221
address flowReceiver;
22+
uint32 flowStartDate;
2323
}
2424

25-
/// @notice An external function for querying flow data by `tokenId``
26-
/// @param tokenId the token id
27-
/// @return flowData the flow data associated with `tokenId`
28-
function flowDataByTokenId(
29-
uint256 tokenId
30-
) external view returns (FlowNFTData memory flowData);
31-
32-
function initialize(
33-
ISuperToken superToken,
34-
string memory nftName,
35-
string memory nftSymbol
36-
) external; // initializer;
37-
38-
/// @notice An external function for computing the deterministic tokenId
39-
/// @dev tokenId = uint256(keccak256(abi.encode(flowSender, flowReceiver)))
40-
/// @param flowSender the flow sender
41-
/// @param flowReceiver the flow receiver
42-
/// @return tokenId the tokenId
43-
function getTokenId(
44-
address flowSender,
45-
address flowReceiver
46-
) external view returns (uint256);
47-
48-
function triggerMetadataUpdate(uint256 tokenId) external;
49-
5025
/**************************************************************************
5126
* Custom Errors
5227
*************************************************************************/
@@ -55,7 +30,7 @@ interface IFlowNFTBase is IERC721Metadata {
5530
error CFA_NFT_APPROVE_TO_CALLER(); // 0xd3c77329
5631
error CFA_NFT_APPROVE_TO_CURRENT_OWNER(); // 0xe4790b25
5732
error CFA_NFT_INVALID_TOKEN_ID(); // 0xeab95e3b
58-
error CFA_NFT_ONLY_HOST(); // 0x2d5a6dfa
33+
error CFA_NFT_ONLY_SUPER_TOKEN_FACTORY(); // 0xebb7505b
5934
error CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL(); // 0x2551d606
6035
error CFA_NFT_TRANSFER_FROM_INCORRECT_OWNER(); // 0x5a26c744
6136
error CFA_NFT_TRANSFER_IS_NOT_ALLOWED(); // 0xaa747eca
@@ -69,4 +44,38 @@ interface IFlowNFTBase is IERC721Metadata {
6944
/// @dev This event comes from https://eips.ethereum.org/EIPS/eip-4906
7045
/// @param tokenId the id of the token that should have its metadata updated
7146
event MetadataUpdate(uint256 tokenId);
47+
48+
/**************************************************************************
49+
* View
50+
*************************************************************************/
51+
52+
/// @notice An external function for querying flow data by `tokenId``
53+
/// @param tokenId the token id
54+
/// @return flowData the flow data associated with `tokenId`
55+
function flowDataByTokenId(
56+
uint256 tokenId
57+
) external view returns (FlowNFTData memory flowData);
58+
59+
/// @notice An external function for computing the deterministic tokenId
60+
/// @dev tokenId = uint256(keccak256(abi.encode(block.chainId, superToken, flowSender, flowReceiver)))
61+
/// @param superToken the super token
62+
/// @param flowSender the flow sender
63+
/// @param flowReceiver the flow receiver
64+
/// @return tokenId the tokenId
65+
function getTokenId(
66+
address superToken,
67+
address flowSender,
68+
address flowReceiver
69+
) external view returns (uint256);
70+
71+
/**************************************************************************
72+
* Write
73+
*************************************************************************/
74+
75+
function initialize(
76+
string memory nftName,
77+
string memory nftSymbol
78+
) external; // initializer;
79+
80+
function triggerMetadataUpdate(uint256 tokenId) external;
7281
}

packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ interface ISuperToken is ISuperfluidToken, TokenInfo, IERC20, IERC777 {
3333
error SUPER_TOKEN_MINT_TO_ZERO_ADDRESS(); // 0x0d243157
3434
error SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS(); // 0xeecd6c9b
3535
error SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS(); // 0xe219bd39
36-
error SUPER_TOKEN_NFT_PROXY_ALREADY_SET(); // 0x6bef249d
36+
error SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED(); // 0x6bef249d
3737

3838
/**
3939
* @dev Initialize the contract
@@ -48,8 +48,8 @@ interface ISuperToken is ISuperfluidToken, TokenInfo, IERC20, IERC777 {
4848
/**************************************************************************
4949
* Immutable variables
5050
*************************************************************************/
51-
function CONSTANT_OUTFLOW_NFT_LOGIC() external view returns (IConstantOutflowNFT);
52-
function CONSTANT_INFLOW_NFT_LOGIC() external view returns (IConstantInflowNFT);
51+
function CONSTANT_OUTFLOW_NFT() external view returns (IConstantOutflowNFT);
52+
function CONSTANT_INFLOW_NFT() external view returns (IConstantInflowNFT);
5353

5454
/**************************************************************************
5555
* TokenInfo & ERC777
@@ -527,15 +527,7 @@ interface ISuperToken is ISuperfluidToken, TokenInfo, IERC20, IERC777 {
527527
*/
528528
function operationDowngrade(address account, uint256 amount) external;
529529

530-
/**************************************************************************
531-
* ERC20x-specific Functions
532-
*************************************************************************/
533-
534-
function constantOutflowNFT() external view returns (IConstantOutflowNFT);
535-
function constantInflowNFT() external view returns (IConstantInflowNFT);
536-
function poolAdminNFT() external view returns (IPoolAdminNFT);
537-
function poolMemberNFT() external view returns (IPoolMemberNFT);
538-
530+
// Flow NFT events
539531
/**
540532
* @dev Constant Outflow NFT proxy created event
541533
* @param constantOutflowNFT constant outflow nft address

0 commit comments

Comments
 (0)