diff --git a/foundry.toml b/foundry.toml index 233d2c969..dbe1239cd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -70,4 +70,4 @@ [rpc_endpoints] ethereum = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" goerli = "https://goerli.infura.io/v3/${INFURA_API_KEY}" - localhost = "http://localhost:8545" \ No newline at end of file + localhost = "http://localhost:8545" diff --git a/script/bootstrap/BootstrapProtocol.s.sol b/script/bootstrap/BootstrapProtocol.s.sol index 3ce28e8ac..b6279b455 100644 --- a/script/bootstrap/BootstrapProtocol.s.sol +++ b/script/bootstrap/BootstrapProtocol.s.sol @@ -73,9 +73,6 @@ contract BootstrapProtocol is BaseScript { }); } - // Deplete the 1st stream. - linear.withdrawMax({ streamId: 1, to: recipient }); - // Renounce the 5th stream. linear.renounce({ streamId: 5 }); @@ -87,18 +84,16 @@ contract BootstrapProtocol is BaseScript { //////////////////////////////////////////////////////////////////////////*/ // Create the default pro stream. - LockupPro.Segment[] memory segments = new LockupPro.Segment[](2); - segments[0] = LockupPro.Segment({ amount: 2_500e18, exponent: ud2x18(3.14e18), milestone: 0 }); - segments[1] = LockupPro.Segment({ amount: 7_500e18, exponent: ud2x18(0.5e18), milestone: 0 }); - uint40[] memory deltas = Solarray.uint40s(1 hours, 1 weeks); + LockupPro.SegmentWithDelta[] memory segments = new LockupPro.SegmentWithDelta[](2); + segments[0] = LockupPro.SegmentWithDelta({ amount: 2_500e18, exponent: ud2x18(3.14e18), delta: 1 hours }); + segments[1] = LockupPro.SegmentWithDelta({ amount: 7_500e18, exponent: ud2x18(0.5e18), delta: 1 weeks }); pro.createWithDeltas({ sender: sender, recipient: recipient, totalAmount: 10_000e18, - segments: segments, asset: asset, cancelable: true, - deltas: deltas, + segments: segments, broker: Broker(address(0), ud(0)) }); } diff --git a/src/SablierV2LockupLinear.sol b/src/SablierV2LockupLinear.sol index fd1f12fbb..68ecae131 100644 --- a/src/SablierV2LockupLinear.sol +++ b/src/SablierV2LockupLinear.sol @@ -79,7 +79,7 @@ contract SablierV2LockupLinear is /// @inheritdoc ISablierV2LockupLinear function getCliffTime(uint256 streamId) external view override returns (uint40 cliffTime) { - cliffTime = _streams[streamId].range.cliff; + cliffTime = _streams[streamId].cliffTime; } /// @inheritdoc ISablierV2Lockup @@ -89,12 +89,16 @@ contract SablierV2LockupLinear is /// @inheritdoc ISablierV2Lockup function getEndTime(uint256 streamId) external view override returns (uint40 endTime) { - endTime = _streams[streamId].range.end; + endTime = _streams[streamId].endTime; } /// @inheritdoc ISablierV2LockupLinear function getRange(uint256 streamId) external view override returns (LockupLinear.Range memory range) { - range = _streams[streamId].range; + range = LockupLinear.Range({ + start: _streams[streamId].startTime, + cliff: _streams[streamId].cliffTime, + end: _streams[streamId].endTime + }); } /// @inheritdoc ISablierV2Lockup @@ -111,7 +115,7 @@ contract SablierV2LockupLinear is /// @inheritdoc ISablierV2Lockup function getStartTime(uint256 streamId) external view override returns (uint40 startTime) { - startTime = _streams[streamId].range.start; + startTime = _streams[streamId].startTime; } /// @inheritdoc ISablierV2Lockup @@ -168,12 +172,12 @@ contract SablierV2LockupLinear is // always greater than the start time, this also checks whether the start time is greater than // the block timestamp. uint256 currentTime = block.timestamp; - uint256 cliffTime = uint256(_streams[streamId].range.cliff); + uint256 cliffTime = uint256(_streams[streamId].cliffTime); if (cliffTime > currentTime) { return 0; } - uint256 endTime = uint256(_streams[streamId].range.end); + uint256 endTime = uint256(_streams[streamId].endTime); // If the current time is greater than or equal to the end time, we simply return the deposit minus // the withdrawn amount. @@ -184,7 +188,7 @@ contract SablierV2LockupLinear is unchecked { // In all other cases, calculate how much has been streamed so far. // First, calculate how much time has elapsed since the stream started, and the total time of the stream. - uint256 startTime = uint256(_streams[streamId].range.start); + uint256 startTime = uint256(_streams[streamId].startTime); UD60x18 elapsedTime = ud(currentTime - startTime); UD60x18 totalTime = ud(endTime - startTime); @@ -257,7 +261,7 @@ contract SablierV2LockupLinear is streamId = _createWithRange( CreateWithRangeParams({ amounts: amounts, - broker: broker.addr, + broker: broker.account, cancelable: cancelable, recipient: recipient, sender: sender, @@ -293,7 +297,7 @@ contract SablierV2LockupLinear is streamId = _createWithRange( CreateWithRangeParams({ amounts: amounts, - broker: broker.addr, + broker: broker.account, cancelable: cancelable, recipient: recipient, sender: sender, @@ -418,11 +422,13 @@ contract SablierV2LockupLinear is // Effects: create the stream. _streams[streamId] = LockupLinear.Stream({ amounts: Lockup.Amounts({ deposit: params.amounts.deposit, withdrawn: 0 }), + asset: params.asset, + cliffTime: params.range.cliff, + endTime: params.range.end, isCancelable: params.cancelable, sender: params.sender, status: Lockup.Status.ACTIVE, - range: params.range, - asset: params.asset + startTime: params.range.start }); // Effects: bump the next stream id and record the protocol fee. diff --git a/src/SablierV2LockupPro.sol b/src/SablierV2LockupPro.sol index 06ac99efc..67466300f 100644 --- a/src/SablierV2LockupPro.sol +++ b/src/SablierV2LockupPro.sol @@ -99,12 +99,12 @@ contract SablierV2LockupPro is /// @inheritdoc ISablierV2Lockup function getEndTime(uint256 streamId) external view override returns (uint40 endTime) { - endTime = _streams[streamId].range.end; + endTime = _streams[streamId].endTime; } /// @inheritdoc ISablierV2LockupPro function getRange(uint256 streamId) external view override returns (LockupPro.Range memory range) { - range = _streams[streamId].range; + range = LockupPro.Range({ start: _streams[streamId].startTime, end: _streams[streamId].endTime }); } /// @inheritdoc ISablierV2Lockup @@ -126,7 +126,7 @@ contract SablierV2LockupPro is /// @inheritdoc ISablierV2Lockup function getStartTime(uint256 streamId) external view override returns (uint40 startTime) { - startTime = _streams[streamId].range.start; + startTime = _streams[streamId].startTime; } /// @inheritdoc ISablierV2Lockup @@ -181,12 +181,12 @@ contract SablierV2LockupPro is // If the start time is greater than or equal to the block timestamp, return zero. uint40 currentTime = uint40(block.timestamp); - if (_streams[streamId].range.start >= currentTime) { + if (_streams[streamId].startTime >= currentTime) { return 0; } uint256 segmentCount = _streams[streamId].segments.length; - uint40 endTime = _streams[streamId].range.end; + uint40 endTime = _streams[streamId].endTime; // If the current time is greater than or equal to the end time, we simply return the deposit minus // the withdrawn amount. @@ -227,14 +227,13 @@ contract SablierV2LockupPro is address sender, address recipient, uint128 totalAmount, - LockupPro.Segment[] memory segments, IERC20 asset, bool cancelable, - uint40[] calldata deltas, + LockupPro.SegmentWithDelta[] memory segments, Broker calldata broker ) external override returns (uint256 streamId) { - // Checks: check the deltas and adjust the segments accordingly. - Helpers.checkDeltasAndCalculateMilestones(segments, deltas); + // Checks: check the deltas and generate the canonical segments. + LockupPro.Segment[] memory segmentsWithMilestones = Helpers.checkDeltasAndCalculateMilestones(segments); // Safe Interactions: query the protocol fee. This is safe because it's a known Sablier contract. UD60x18 protocolFee = comptroller.getProtocolFee(asset); @@ -251,10 +250,10 @@ contract SablierV2LockupPro is streamId = _createWithMilestones( CreateWithMilestonesParams({ amounts: amounts, - broker: broker.addr, + broker: broker.account, cancelable: cancelable, recipient: recipient, - segments: segments, + segments: segmentsWithMilestones, sender: sender, asset: asset, startTime: uint40(block.timestamp) @@ -267,9 +266,9 @@ contract SablierV2LockupPro is address sender, address recipient, uint128 totalAmount, - LockupPro.Segment[] calldata segments, IERC20 asset, bool cancelable, + LockupPro.Segment[] calldata segments, uint40 startTime, Broker calldata broker ) external override returns (uint256 streamId) { @@ -288,7 +287,7 @@ contract SablierV2LockupPro is streamId = _createWithMilestones( CreateWithMilestonesParams({ amounts: amounts, - broker: broker.addr, + broker: broker.account, cancelable: cancelable, recipient: recipient, segments: segments, @@ -335,7 +334,7 @@ contract SablierV2LockupPro is previousMilestone = _streams[streamId].segments[index - 2].milestone; } else { // Otherwise, the current segment is the first, so use the start time as the previous milestone. - previousMilestone = _streams[streamId].range.start; + previousMilestone = _streams[streamId].startTime; } // Calculate how much time has elapsed since the segment started, and the total time of the segment. @@ -364,8 +363,8 @@ contract SablierV2LockupPro is SD59x18 exponent = _streams[streamId].segments[0].exponent.intoSD59x18(); // Calculate how much time has elapsed since the stream started, and the total time of the stream. - SD59x18 elapsedTime = (uint40(block.timestamp) - _streams[streamId].range.start).intoSD59x18(); - SD59x18 totalTime = (_streams[streamId].range.end - _streams[streamId].range.start).intoSD59x18(); + SD59x18 elapsedTime = (uint40(block.timestamp) - _streams[streamId].startTime).intoSD59x18(); + SD59x18 totalTime = (_streams[streamId].endTime - _streams[streamId].startTime).intoSD59x18(); // Calculate the streamed amount. SD59x18 elapsedTimePercentage = elapsedTime.div(totalTime); @@ -502,10 +501,8 @@ contract SablierV2LockupPro is unchecked { // The segment count cannot be zero at this point. - stream.range = LockupPro.Range({ - start: params.startTime, - end: params.segments[segmentCount - 1].milestone - }); + stream.endTime = params.segments[segmentCount - 1].milestone; + stream.startTime = params.startTime; // Effects: store the segments. Copying an array from memory to storage is not currently supported in // Solidity, so it has to be done manually. See https://github.com/ethereum/solidity/issues/12783 @@ -544,10 +541,10 @@ contract SablierV2LockupPro is sender: params.sender, recipient: params.recipient, amounts: params.amounts, - segments: params.segments, asset: params.asset, cancelable: params.cancelable, - range: stream.range, + segments: params.segments, + range: LockupPro.Range({ start: stream.startTime, end: stream.endTime }), broker: params.broker }); } diff --git a/src/abstracts/SablierV2Config.sol b/src/abstracts/SablierV2Config.sol index f8e8a1464..a39ce7a1a 100644 --- a/src/abstracts/SablierV2Config.sol +++ b/src/abstracts/SablierV2Config.sol @@ -81,10 +81,10 @@ abstract contract SablierV2Config is _protocolRevenues[asset] = 0; // Interactions: perform the ERC-20 transfer to pay the protocol revenues. - asset.safeTransfer(msg.sender, protocolRevenues); + asset.safeTransfer({ to: msg.sender, value: protocolRevenues }); // Log the claim of the protocol revenues. - emit Events.ClaimProtocolRevenues(msg.sender, asset, protocolRevenues); + emit Events.ClaimProtocolRevenues({ admin: msg.sender, asset: asset, protocolRevenues: protocolRevenues }); } /// @inheritdoc ISablierV2Config diff --git a/src/interfaces/ISablierV2Config.sol b/src/interfaces/ISablierV2Config.sol index b88a94abc..41d4ff91e 100644 --- a/src/interfaces/ISablierV2Config.sol +++ b/src/interfaces/ISablierV2Config.sol @@ -47,7 +47,7 @@ interface ISablierV2Config is ISablierV2Adminable { /// @dev Emits a {SetComptroller} event. /// /// Notes: - /// - It is not an error to set the same comptroller. + /// - Does not revert if the comptroller is the same. /// /// Requirements: /// - The caller must be the contract admin. diff --git a/src/interfaces/ISablierV2Lockup.sol b/src/interfaces/ISablierV2Lockup.sol index 4bef98905..b5786dcda 100644 --- a/src/interfaces/ISablierV2Lockup.sol +++ b/src/interfaces/ISablierV2Lockup.sol @@ -116,8 +116,8 @@ interface ISablierV2Lockup is /// @dev Emits multiple {CancelLockupStream} events. /// /// Notes: - /// - It is not an error if one of the stream ids points to a stream that is not active or is active but - /// is not cancelable. + /// - Does not revert if one of the stream ids points to a stream that is not active or is active but is + /// not cancelable. /// - This function will attempt to call a hook on either the sender or the recipient of each stream. /// /// Requirements: @@ -187,7 +187,7 @@ interface ISablierV2Lockup is /// @dev Emits multiple {WithdrawFromLockupStream} and {Transfer} events. /// /// Notes: - /// - It is not an error if one of the stream ids points to a stream that is not active. + /// - Does not revert if one of the stream ids points to a stream that is not active. /// - This function will attempt to call a hook on the recipient of each stream. /// /// Requirements: diff --git a/src/interfaces/ISablierV2LockupLinear.sol b/src/interfaces/ISablierV2LockupLinear.sol index 53a0a58fd..d9041c1de 100644 --- a/src/interfaces/ISablierV2LockupLinear.sol +++ b/src/interfaces/ISablierV2LockupLinear.sol @@ -77,7 +77,7 @@ interface ISablierV2LockupLinear is ISablierV2Lockup { /// @dev Emits a {CreateLockupLinearStream} and a {Transfer} event. /// /// Notes: - /// - As long as they are ordered, it is not an error to set a range in the past. + /// - As long as they are ordered, it is not an error to set a range that is in the past. /// /// Requirements: /// - `recipient` must not be the zero address. diff --git a/src/interfaces/ISablierV2LockupPro.sol b/src/interfaces/ISablierV2LockupPro.sol index 9ed6df15e..baff999bd 100644 --- a/src/interfaces/ISablierV2LockupPro.sol +++ b/src/interfaces/ISablierV2LockupPro.sol @@ -48,13 +48,11 @@ interface ISablierV2LockupPro is ISablierV2Lockup { //////////////////////////////////////////////////////////////////////////*/ /// @notice Create a stream by setting the start time to `block.timestamp` and the end time to the sum of - /// `block.timestamp` and all `deltas`. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. + /// `block.timestamp` and all segment deltas. The stream is funded by `msg.sender` and is wrapped in an + /// ERC-721 NFT. /// /// @dev Emits a {CreateLockupProStream} and a {Transfer} event. /// - /// Notes: - /// - The segment milestones should be empty, as they will be overridden. - /// /// Requirements: /// - All from {createWithMilestones}. /// @@ -63,11 +61,10 @@ interface ISablierV2LockupPro is ISablierV2Lockup { /// @param recipient The address toward which to stream the assets. /// @param totalAmount The total amount of ERC-20 assets to be paid, which includes the stream deposit and any /// potential fees. This is represented in units of the asset's decimals. - /// @param segments The segments the protocol uses to compose the custom streaming curve. /// @param asset The contract address of the ERC-20 asset to use for streaming. /// @param cancelable Boolean that indicates whether the stream is cancelable or not. - /// @param deltas The differences between the Unix timestamp milestones used to compose the custom streaming - /// curve. + /// @param segments The segments with deltas the protocol will use to compose the custom streaming curve. + /// The milestones will be be calculated by adding each delta to `block.timestamp`. /// @param broker An optional struct that encapsulates (i) the address of the broker that has helped create the /// stream and (ii) the percentage fee that the broker is paid from `totalAmount`, as an UD60x18 number. /// @return streamId The id of the newly created stream. @@ -75,20 +72,20 @@ interface ISablierV2LockupPro is ISablierV2Lockup { address sender, address recipient, uint128 totalAmount, - LockupPro.Segment[] memory segments, IERC20 asset, bool cancelable, - uint40[] memory deltas, + LockupPro.SegmentWithDelta[] memory segments, Broker calldata broker ) external returns (uint256 streamId); - /// @notice Create a stream by using the provided milestones, implying the end time from the last segment's. + /// @notice Create a stream by using the provided milestones, implying the end time from the last segment's /// milestone. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. /// /// @dev Emits a {CreateLockupProStream} and a {Transfer} event. /// /// Notes: - /// - As long as they are ordered, it is not an error to set the `startTime` and the milestones to a past range. + /// - As long as they are ordered, it is not an error to set the `startTime` and the milestones to a range that + /// is in the past /// /// Requirements: /// - `recipient` must not be the zero address. @@ -105,9 +102,9 @@ interface ISablierV2LockupPro is ISablierV2Lockup { /// @param recipient The address toward which to stream the assets. /// @param totalAmount The total amount of ERC-20 assets to be paid, which includes the stream deposit and any /// potential fees. This is represented in units of the asset's decimals. - /// @param segments The segments the protocol uses to compose the custom streaming curve. /// @param asset The contract address of the ERC-20 asset to use for streaming. /// @param cancelable Boolean that indicates whether the stream will be cancelable or not. + /// @param segments The segments the protocol uses to compose the custom streaming curve. /// @param startTime The Unix timestamp for when the stream will start. /// @param broker An optional struct that encapsulates (i) the address of the broker that has helped create the /// stream and (ii) the percentage fee that the broker is paid from `totalAmount`, as an UD60x18 number. @@ -116,9 +113,9 @@ interface ISablierV2LockupPro is ISablierV2Lockup { address sender, address recipient, uint128 totalAmount, - LockupPro.Segment[] memory segments, IERC20 asset, bool cancelable, + LockupPro.Segment[] memory segments, uint40 startTime, Broker calldata broker ) external returns (uint256 streamId); diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index b5514f5e7..101df90d9 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -100,10 +100,6 @@ library Errors { SABLIER-V2-LOCKUP-PRO //////////////////////////////////////////////////////////////////////////*/ - /// @notice Emitted when attempting to create a stream and the count of the segments does not match the - /// count of the deltas. - error SablierV2LockupPro_SegmentArrayCountsNotEqual(uint256 segmentCount, uint256 deltaCount); - /// @notice Emitted when attempting to create a stream with a deposit amount that does not equal the segment /// amounts sum. error SablierV2LockupPro_DepositAmountNotEqualToSegmentAmountsSum(uint128 depositAmount, uint128 segmentAmountsSum); diff --git a/src/libraries/Events.sol b/src/libraries/Events.sol index d75bd80d8..0902643d7 100644 --- a/src/libraries/Events.sol +++ b/src/libraries/Events.sol @@ -132,9 +132,9 @@ library Events { /// @param recipient The address toward which to stream the assets. /// @param amounts Struct that encapsulates (i) the deposit amount, (ii) the protocol fee amount, and (iii) the /// broker fee amount, each in units of the asset's decimals. - /// @param segments The segments the protocol uses to compose the custom streaming curve. /// @param asset The contract address of the ERC-20 asset used for streaming. /// @param cancelable Boolean that indicates whether the stream will be cancelable or not. + /// @param segments The segments the protocol uses to compose the custom streaming curve. /// @param range Struct that encapsulates (i) the start time of the stream, and (ii) the end time of the stream, /// both as Unix timestamps. /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. @@ -144,9 +144,9 @@ library Events { address indexed sender, address indexed recipient, Lockup.CreateAmounts amounts, - LockupPro.Segment[] segments, IERC20 asset, bool cancelable, + LockupPro.Segment[] segments, LockupPro.Range range, address broker ); diff --git a/src/libraries/Helpers.sol b/src/libraries/Helpers.sol index 373785110..d11b8764d 100644 --- a/src/libraries/Helpers.sol +++ b/src/libraries/Helpers.sol @@ -100,14 +100,10 @@ library Helpers { /// @dev Checks that the segment array counts match, and then adjusts the segments by calculating the milestones. function checkDeltasAndCalculateMilestones( - LockupPro.Segment[] memory segments, - uint40[] memory deltas - ) internal view { - // Checks: check that the segment array counts match. - uint256 deltaCount = deltas.length; - if (segments.length != deltaCount) { - revert Errors.SablierV2LockupPro_SegmentArrayCountsNotEqual(segments.length, deltaCount); - } + LockupPro.SegmentWithDelta[] memory segments + ) internal view returns (LockupPro.Segment[] memory segmentsWithMilestones) { + uint256 segmentCount = segments.length; + segmentsWithMilestones = new LockupPro.Segment[](segmentCount); // Make the current time the start time of the stream. uint40 startTime = uint40(block.timestamp); @@ -115,12 +111,20 @@ library Helpers { // It is safe to use unchecked arithmetic because the {_createWithMilestone} function will nonetheless check // the soundness of the calculated segment milestones. unchecked { - // Precompute the first milestone. - segments[0].milestone = startTime + deltas[0]; - - // Calculate the segment milestones and set them in the segments array. - for (uint256 i = 1; i < deltaCount; ++i) { - segments[i].milestone = segments[i - 1].milestone + deltas[i]; + // Precompute the first segment because of the need to add the start time to the first segment delta. + segmentsWithMilestones[0] = LockupPro.Segment({ + amount: segments[0].amount, + exponent: segments[0].exponent, + milestone: startTime + segments[0].delta + }); + + // Copy the segment amounts and exponents, and calculate the segment milestones. + for (uint256 i = 1; i < segmentCount; ++i) { + segmentsWithMilestones[i] = LockupPro.Segment({ + amount: segments[i].amount, + exponent: segments[i].exponent, + milestone: segmentsWithMilestones[i - 1].milestone + segments[i].delta + }); } } } diff --git a/src/types/DataTypes.sol b/src/types/DataTypes.sol index 2d1f9d31b..08724eaca 100644 --- a/src/types/DataTypes.sol +++ b/src/types/DataTypes.sol @@ -6,10 +6,10 @@ import { UD2x18 } from "@prb/math/UD2x18.sol"; import { UD60x18 } from "@prb/math/UD60x18.sol"; /// @notice Simple struct that encapsulates the optional broker parameters that can be passed to the create functions. -/// @custom:field addr The address of the broker the fee will be paid to. +/// @custom:field account The address of the broker the fee will be paid to. /// @custom:field fee The percentage fee that the broker is paid from the total amount, as an UD60x18 number. struct Broker { - address addr; + address account; UD60x18 fee; } @@ -59,7 +59,7 @@ library LockupLinear { uint40 total; // ─┘ } - /// @notice Range struct used as a field in the lockup linear stream. + /// @notice Simple struct that encapsulates (i) the start time, (ii) the cliff time and (iii) the end time. /// @custom:field start The Unix timestamp for when the stream will start. /// @custom:field cliff The Unix timestamp for when the cliff period will end. /// @custom:field end The Unix timestamp for when the stream will end. @@ -71,26 +71,29 @@ library LockupLinear { /// @notice Lockup linear stream struct used in the {SablierV2LockupLinear} contract. /// @dev The fields are arranged like this to save gas via tight variable packing. - /// @custom:field amounts Simple struct with the deposit and withdrawn amounts. - /// @custom:field range Struct that encapsulates (i) the start time of the stream, (ii) the cliff time of the - /// stream, and (iii) the end time of the stream, all as Unix timestamps. + /// @custom:field amounts Simple struct with the deposit and the withdrawn amount. /// @custom:field sender The address of the sender of the stream. + /// @custom:field startTime The Unix timestamp for when the stream will start. + /// @custom:field cliffTime The Unix timestamp for when the cliff period will end. /// @custom:field isCancelable Boolean that indicates whether the stream is cancelable or not. - /// @custom:field status An enum that indicates the status of the stream. /// @custom:field asset The contract address of the ERC-20 asset used for streaming. + /// @custom:field endTime The Unix timestamp for when the stream will end. + /// @custom:field status An enum that indicates the status of the stream. struct Stream { Lockup.Amounts amounts; - Range range; - address sender; // ───────┐ - bool isCancelable; // │ + address sender; // ────┐ + uint40 startTime; // │ + uint40 cliffTime; // │ + bool isCancelable; // ─┘ + IERC20 asset; // ─────────┐ + uint40 endTime; // │ Lockup.Status status; // ─┘ - IERC20 asset; } } /// @notice Quasi-namespace for the structs used in the {SablierV2LockupPro} contract. library LockupPro { - /// @notice Range struct used as a field in the lockup pro stream. + /// @notice Simple struct that encapsulates (i) the start time and (ii) the end time. /// @custom:field start The Unix timestamp for when the stream will start. /// @custom:field end The Unix timestamp for when the stream will end. struct Range { @@ -108,21 +111,32 @@ library LockupPro { uint40 milestone; // ─┘ } + /// @notice Segment struct used in the {SablierV2LockupPro-createWithDeltas} function. + /// @custom:field amount The amounts of assets to be streamed in this segment, in units of the asset's decimals. + /// @custom:field exponent The exponent of this segment, as an UD2x18 number. + /// @custom:field delta The time difference between this segment and the previous one, in seconds. + struct SegmentWithDelta { + uint128 amount; // ─┐ + UD2x18 exponent; // │ + uint40 delta; // ───┘ + } + /// @notice Pro stream struct used in the {SablierV2LockupPro} contract. /// @dev The fields are arranged like this to save gas via tight variable packing. - /// @custom:field amounts Simple struct with the deposit and withdrawn amounts. - /// @custom:field range Simple struct that encapsulates (i) the start time of the stream, and (ii) the end time of - /// of the stream, both as Unix timestamps. + /// @custom:field amounts Simple struct with the deposit and the withdrawn amount. /// @custom:field segments The segments the protocol uses to compose the custom streaming curve. /// @custom:field sender The address of the sender of the stream. + /// @custom:field startTime The Unix timestamp for when the stream will start. + /// @custom:field endTime The Unix timestamp for when the stream will end. /// @custom:field isCancelable Boolean that indicates whether the stream is cancelable or not. /// @custom:field status An enum that indicates the status of the stream. /// @custom:field asset The contract address of the ERC-20 asset used for streaming. struct Stream { Lockup.Amounts amounts; - Range range; Segment[] segments; address sender; // ───────┐ + uint40 startTime; // │ + uint40 endTime; // │ bool isCancelable; // │ Lockup.Status status; // ─┘ IERC20 asset; diff --git a/test/Base.t.sol b/test/Base.t.sol index b093ead62..3239f63bf 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -111,15 +111,6 @@ abstract contract Base_Test is Assertions, Calculations, Fuzzers, StdCheats { }); } - /*////////////////////////////////////////////////////////////////////////// - INTERNAL CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @dev Retrieves the current block timestamp as an `uint40`. - function getBlockTimestamp() internal view returns (uint40 blockTimestamp) { - blockTimestamp = uint40(block.timestamp); - } - /*////////////////////////////////////////////////////////////////////////// INTERNAL NON-CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ diff --git a/test/e2e/lockup/linear/Linear.t.sol b/test/e2e/lockup/linear/Linear.t.sol index d2397fea9..d49128869 100644 --- a/test/e2e/lockup/linear/Linear.t.sol +++ b/test/e2e/lockup/linear/Linear.t.sol @@ -107,7 +107,7 @@ abstract contract Linear_E2e_Test is E2e_Test { /// - Multiple values for the protocol fee, including zero. /// - Multiple values for the withdraw amount, including zero. function testForkFuzz_Linear_CreateWithdrawCancel(Params memory params) external { - checkUsers(params.sender, params.recipient, params.broker.addr, address(linear)); + checkUsers(params.sender, params.recipient, params.broker.account, address(linear)); vm.assume(params.range.start <= params.range.cliff && params.range.cliff < params.range.end); vm.assume(params.totalAmount != 0 && params.totalAmount <= initialHolderBalance); params.broker.fee = bound(params.broker.fee, 0, DEFAULT_MAX_FEE); @@ -130,7 +130,7 @@ abstract contract Linear_E2e_Test is E2e_Test { vars.initialProtocolRevenues = linear.getProtocolRevenues(asset); // Load the pre-create asset balances. - vars.balances = getTokenBalances(address(asset), Solarray.addresses(address(linear), params.broker.addr)); + vars.balances = getTokenBalances(address(asset), Solarray.addresses(address(linear), params.broker.account)); vars.initialLinearBalance = vars.balances[0]; vars.initialBrokerBalance = vars.balances[1]; @@ -151,7 +151,7 @@ abstract contract Linear_E2e_Test is E2e_Test { asset: asset, cancelable: true, range: params.range, - broker: params.broker.addr + broker: params.broker.account }); // Create the stream. @@ -169,9 +169,11 @@ abstract contract Linear_E2e_Test is E2e_Test { LockupLinear.Stream memory actualStream = linear.getStream(vars.streamId); assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.createAmounts.deposit, withdrawn: 0 })); assertEq(actualStream.asset, asset, "asset"); + assertEq(actualStream.cliffTime, params.range.cliff, "cliffTime"); + assertEq(actualStream.endTime, params.range.end, "endTime"); assertEq(actualStream.isCancelable, true, "isCancelable"); - assertEq(actualStream.range, params.range); assertEq(actualStream.sender, params.sender, "sender"); + assertEq(actualStream.startTime, params.range.start, "startTime"); assertEq(actualStream.status, Lockup.Status.ACTIVE); // Assert that the next stream id has been bumped. @@ -192,7 +194,7 @@ abstract contract Linear_E2e_Test is E2e_Test { // Load the post-create asset balances. vars.balances = getTokenBalances( address(asset), - Solarray.addresses(address(linear), holder, params.broker.addr) + Solarray.addresses(address(linear), holder, params.broker.account) ); vars.actualLinearBalance = vars.balances[0]; vars.actualHolderBalance = vars.balances[1]; diff --git a/test/e2e/lockup/pro/Pro.t.sol b/test/e2e/lockup/pro/Pro.t.sol index e96a8b5aa..04ae6158b 100644 --- a/test/e2e/lockup/pro/Pro.t.sol +++ b/test/e2e/lockup/pro/Pro.t.sol @@ -108,11 +108,11 @@ abstract contract Pro_E2e_Test is E2e_Test { /// - Multiple values for the protocol fee, including zero. /// - Multiple values for the withdraw amount, including zero. function testForkFuzz_Pro_CreateWithdrawCancel(Params memory params) external { - checkUsers(params.sender, params.recipient, params.broker.addr, address(pro)); + checkUsers(params.sender, params.recipient, params.broker.account, address(pro)); vm.assume(params.segments.length != 0); params.broker.fee = bound(params.broker.fee, 0, DEFAULT_MAX_FEE); params.protocolFee = bound(params.protocolFee, 0, DEFAULT_MAX_FEE); - params.startTime = boundUint40(params.startTime, 0, DEFAULT_SEGMENTS[0].milestone - 1); + params.startTime = boundUint40(params.startTime, 0, DEFAULT_START_TIME); // Fuzz the segment milestones. fuzzSegmentMilestones(params.segments, params.startTime); @@ -141,7 +141,7 @@ abstract contract Pro_E2e_Test is E2e_Test { vars.initialProtocolRevenues = pro.getProtocolRevenues(asset); // Load the pre-create asset balances. - vars.balances = getTokenBalances(address(asset), Solarray.addresses(address(pro), params.broker.addr)); + vars.balances = getTokenBalances(address(asset), Solarray.addresses(address(pro), params.broker.account)); vars.initialProBalance = vars.balances[0]; vars.initialBrokerBalance = vars.balances[1]; @@ -158,11 +158,11 @@ abstract contract Pro_E2e_Test is E2e_Test { sender: params.sender, recipient: params.recipient, amounts: vars.amounts, - segments: params.segments, asset: asset, cancelable: true, + segments: params.segments, range: range, - broker: params.broker.addr + broker: params.broker.account }); // Create the stream. @@ -170,9 +170,9 @@ abstract contract Pro_E2e_Test is E2e_Test { sender: params.sender, recipient: params.recipient, totalAmount: vars.totalAmount, - segments: params.segments, asset: asset, cancelable: true, + segments: params.segments, startTime: params.startTime, broker: params.broker }); @@ -181,10 +181,11 @@ abstract contract Pro_E2e_Test is E2e_Test { LockupPro.Stream memory actualStream = pro.getStream(vars.streamId); assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.amounts.deposit, withdrawn: 0 })); assertEq(actualStream.asset, asset, "asset"); + assertEq(actualStream.endTime, range.end, "endTime"); assertEq(actualStream.isCancelable, true, "isCancelable"); - assertEq(actualStream.range, range); assertEq(actualStream.segments, params.segments); assertEq(actualStream.sender, params.sender, "sender"); + assertEq(actualStream.startTime, range.start, "startTime"); assertEq(actualStream.status, Lockup.Status.ACTIVE); // Assert that the next stream id has been bumped. @@ -203,7 +204,10 @@ abstract contract Pro_E2e_Test is E2e_Test { assertEq(vars.actualNFTOwner, vars.expectedNFTOwner, "NFT owner"); // Load the post-create asset balances. - vars.balances = getTokenBalances(address(asset), Solarray.addresses(address(pro), holder, params.broker.addr)); + vars.balances = getTokenBalances( + address(asset), + Solarray.addresses(address(pro), holder, params.broker.account) + ); vars.actualProBalance = vars.balances[0]; vars.actualHolderBalance = vars.balances[1]; vars.actualBrokerBalance = vars.balances[2]; diff --git a/test/fuzz/lockup/linear/create-with-durations/createWithDurations.t.sol b/test/fuzz/lockup/linear/create-with-durations/createWithDurations.t.sol index 0775ceedd..c87a00cb3 100644 --- a/test/fuzz/lockup/linear/create-with-durations/createWithDurations.t.sol +++ b/test/fuzz/lockup/linear/create-with-durations/createWithDurations.t.sol @@ -123,7 +123,7 @@ contract CreateWithDurations_Linear_Fuzz_Test is Linear_Fuzz_Test { asset: DEFAULT_ASSET, cancelable: defaultParams.createWithDurations.cancelable, range: range, - broker: defaultParams.createWithDurations.broker.addr + broker: defaultParams.createWithDurations.broker.account }); // Create the stream. @@ -133,9 +133,11 @@ contract CreateWithDurations_Linear_Fuzz_Test is Linear_Fuzz_Test { LockupLinear.Stream memory actualStream = linear.getStream(streamId); assertEq(actualStream.amounts, defaultStream.amounts); assertEq(actualStream.asset, defaultStream.asset, "asset"); + assertEq(actualStream.cliffTime, range.cliff, "cliffTime"); + assertEq(actualStream.endTime, range.end, "endTime"); assertEq(actualStream.isCancelable, defaultStream.isCancelable, "isCancelable"); - assertEq(actualStream.range, range); assertEq(actualStream.sender, defaultStream.sender, "sender"); + assertEq(actualStream.startTime, range.start, "startTime"); assertEq(actualStream.status, defaultStream.status); // Assert that the next stream id has been bumped. diff --git a/test/fuzz/lockup/linear/create-with-range/createWithRange.t.sol b/test/fuzz/lockup/linear/create-with-range/createWithRange.t.sol index 716d58d04..5be78d21f 100644 --- a/test/fuzz/lockup/linear/create-with-range/createWithRange.t.sol +++ b/test/fuzz/lockup/linear/create-with-range/createWithRange.t.sol @@ -132,7 +132,7 @@ contract CreateWithRange_Linear_Fuzz_Test is Linear_Fuzz_Test { defaultParams.createWithRange.asset, defaultParams.createWithRange.cancelable, defaultParams.createWithRange.range, - Broker({ addr: users.broker, fee: brokerFee }) + Broker({ account: users.broker, fee: brokerFee }) ); } @@ -195,7 +195,7 @@ contract CreateWithRange_Linear_Fuzz_Test is Linear_Fuzz_Test { assetContract assetERC20Compliant { - vm.assume(params.funder != address(0) && params.recipient != address(0) && params.broker.addr != address(0)); + vm.assume(params.funder != address(0) && params.recipient != address(0) && params.broker.account != address(0)); vm.assume(params.totalAmount != 0); vm.assume(params.range.start <= params.range.cliff && params.range.cliff < params.range.end); params.broker.fee = bound(params.broker.fee, 0, DEFAULT_MAX_FEE); @@ -231,7 +231,7 @@ contract CreateWithRange_Linear_Fuzz_Test is Linear_Fuzz_Test { if (vars.createAmounts.brokerFee > 0) { expectTransferFromCall({ from: params.funder, - to: params.broker.addr, + to: params.broker.account, amount: vars.createAmounts.brokerFee }); } @@ -247,7 +247,7 @@ contract CreateWithRange_Linear_Fuzz_Test is Linear_Fuzz_Test { asset: DEFAULT_ASSET, cancelable: params.cancelable, range: params.range, - broker: params.broker.addr + broker: params.broker.account }); // Create the stream. @@ -265,9 +265,11 @@ contract CreateWithRange_Linear_Fuzz_Test is Linear_Fuzz_Test { LockupLinear.Stream memory actualStream = linear.getStream(streamId); assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.createAmounts.deposit, withdrawn: 0 })); assertEq(actualStream.asset, defaultStream.asset, "asset"); + assertEq(actualStream.cliffTime, params.range.cliff); + assertEq(actualStream.endTime, params.range.end); assertEq(actualStream.isCancelable, params.cancelable, "isCancelable"); - assertEq(actualStream.range, params.range); assertEq(actualStream.sender, params.sender, "sender"); + assertEq(actualStream.startTime, params.range.start); assertEq(actualStream.status, defaultStream.status); // Assert that the next stream id has been bumped. diff --git a/test/fuzz/lockup/linear/streamed-amount-of/streamedAmountOf.sol b/test/fuzz/lockup/linear/streamed-amount-of/streamedAmountOf.sol index 1ec359798..1ef9c023b 100644 --- a/test/fuzz/lockup/linear/streamed-amount-of/streamedAmountOf.sol +++ b/test/fuzz/lockup/linear/streamed-amount-of/streamedAmountOf.sol @@ -64,7 +64,7 @@ contract StreamedAmountOf_Linear_Fuzz_Test is Linear_Fuzz_Test { defaultParams.createWithRange.asset, defaultParams.createWithRange.cancelable, defaultParams.createWithRange.range, - Broker({ addr: address(0), fee: ZERO }) + Broker({ account: address(0), fee: ZERO }) ); // Run the test. diff --git a/test/fuzz/lockup/linear/withdrawable-amount-of/withdrawableAmountOf.t.sol b/test/fuzz/lockup/linear/withdrawable-amount-of/withdrawableAmountOf.t.sol index 290e9db34..1458efffb 100644 --- a/test/fuzz/lockup/linear/withdrawable-amount-of/withdrawableAmountOf.t.sol +++ b/test/fuzz/lockup/linear/withdrawable-amount-of/withdrawableAmountOf.t.sol @@ -63,7 +63,7 @@ contract WithdrawableAmountOf_Linear_Fuzz_Test is Linear_Fuzz_Test { defaultParams.createWithRange.asset, defaultParams.createWithRange.cancelable, defaultParams.createWithRange.range, - Broker({ addr: address(0), fee: ZERO }) + Broker({ account: address(0), fee: ZERO }) ); // Run the test. @@ -108,7 +108,7 @@ contract WithdrawableAmountOf_Linear_Fuzz_Test is Linear_Fuzz_Test { defaultParams.createWithRange.asset, defaultParams.createWithRange.cancelable, defaultParams.createWithRange.range, - Broker({ addr: address(0), fee: ZERO }) + Broker({ account: address(0), fee: ZERO }) ); // Make the withdrawal. linear.withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); diff --git a/test/fuzz/lockup/pro/create-with-deltas/createWithDeltas.t.sol b/test/fuzz/lockup/pro/create-with-deltas/createWithDeltas.t.sol index 95b78b940..ae4f34cd5 100644 --- a/test/fuzz/lockup/pro/create-with-deltas/createWithDeltas.t.sol +++ b/test/fuzz/lockup/pro/create-with-deltas/createWithDeltas.t.sol @@ -28,28 +28,6 @@ contract CreateWithDeltas_Pro_Fuzz_Test is Pro_Fuzz_Test { _; } - /// @dev it should revert. - function testFuzz_RevertWhen_SegmentArraysCountsNotEqual( - uint256 deltaCount - ) external loopCalculationsDoNotOverflowBlockGasLimit deltasNotZero { - deltaCount = bound(deltaCount, 1, 1_000); - vm.assume(deltaCount != DEFAULT_SEGMENTS.length); - - uint40[] memory deltas = new uint40[](deltaCount); - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierV2LockupPro_SegmentArrayCountsNotEqual.selector, - DEFAULT_SEGMENTS.length, - deltaCount - ) - ); - createDefaultStreamWithDeltas(deltas); - } - - modifier segmentArrayCountsEqual() { - _; - } - modifier milestonesCalculationsDoNotOverflow() { _; } @@ -58,35 +36,29 @@ contract CreateWithDeltas_Pro_Fuzz_Test is Pro_Fuzz_Test { uint256 actualNextStreamId; address actualNFTOwner; uint256 actualProtocolRevenues; - Lockup.CreateAmounts amounts; - uint40[] deltas; + Lockup.CreateAmounts createAmounts; uint256 expectedNextStreamId; address expectedNFTOwner; uint256 expectedProtocolRevenues; address funder; uint128 initialProtocolRevenues; + LockupPro.Segment[] segmentsWithMilestones; uint128 totalAmount; } /// @dev it should perform the ERC-20 transfers, create the stream, bump the next stream id, mint the NFT, /// record the protocol fee, and emit a {CreateLockupProStream} event. function testFuzz_CreateWithDeltas( - LockupPro.Segment[] memory segments - ) - external - loopCalculationsDoNotOverflowBlockGasLimit - deltasNotZero - segmentArrayCountsEqual - milestonesCalculationsDoNotOverflow - { + LockupPro.SegmentWithDelta[] memory segments + ) external loopCalculationsDoNotOverflowBlockGasLimit deltasNotZero milestonesCalculationsDoNotOverflow { vm.assume(segments.length != 0); - // Fuzz the deltas and update the segment milestones. + // Fuzz the deltas. Vars memory vars; - vars.deltas = fuzzSegmentDeltas(segments); + fuzzSegmentDeltas(segments); // Fuzz the segment amounts and calculate the create amounts (total, deposit, protocol fee, and broker fee). - (vars.totalAmount, vars.amounts) = fuzzSegmentAmountsAndCalculateCreateAmounts(segments); + (vars.totalAmount, vars.createAmounts) = fuzzSegmentAmountsAndCalculateCreateAmounts(segments); // Make the sender the funder of the stream. vars.funder = users.sender; @@ -101,18 +73,19 @@ contract CreateWithDeltas_Pro_Fuzz_Test is Pro_Fuzz_Test { expectTransferFromCall({ from: vars.funder, to: address(pro), - amount: vars.amounts.deposit + vars.amounts.protocolFee + amount: vars.createAmounts.deposit + vars.createAmounts.protocolFee }); // Expect the broker fee to be paid to the broker, if not zero. - if (vars.amounts.brokerFee > 0) { - expectTransferFromCall({ from: vars.funder, to: users.broker, amount: vars.amounts.brokerFee }); + if (vars.createAmounts.brokerFee > 0) { + expectTransferFromCall({ from: vars.funder, to: users.broker, amount: vars.createAmounts.brokerFee }); } // Create the range struct. + vars.segmentsWithMilestones = getSegmentsWithMilestones(segments); LockupPro.Range memory range = LockupPro.Range({ start: getBlockTimestamp(), - end: segments[segments.length - 1].milestone + end: vars.segmentsWithMilestones[vars.segmentsWithMilestones.length - 1].milestone }); // Expect a {CreateLockupProStream} event to be emitted. @@ -122,12 +95,12 @@ contract CreateWithDeltas_Pro_Fuzz_Test is Pro_Fuzz_Test { funder: vars.funder, sender: defaultParams.createWithDeltas.sender, recipient: defaultParams.createWithDeltas.recipient, - amounts: vars.amounts, - segments: segments, + amounts: vars.createAmounts, asset: defaultParams.createWithDeltas.asset, cancelable: defaultParams.createWithDeltas.cancelable, + segments: vars.segmentsWithMilestones, range: range, - broker: defaultParams.createWithDeltas.broker.addr + broker: defaultParams.createWithDeltas.broker.account }); // Create the stream. @@ -135,21 +108,21 @@ contract CreateWithDeltas_Pro_Fuzz_Test is Pro_Fuzz_Test { defaultParams.createWithDeltas.sender, defaultParams.createWithDeltas.recipient, vars.totalAmount, - segments, DEFAULT_ASSET, defaultParams.createWithDeltas.cancelable, - vars.deltas, + segments, defaultParams.createWithDeltas.broker ); // Assert that the stream has been created. LockupPro.Stream memory actualStream = pro.getStream(streamId); - assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.amounts.deposit, withdrawn: 0 })); + assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.createAmounts.deposit, withdrawn: 0 })); assertEq(actualStream.asset, defaultStream.asset, "asset"); + assertEq(actualStream.endTime, range.end, "endTime"); assertEq(actualStream.isCancelable, defaultStream.isCancelable, "isCancelable"); - assertEq(actualStream.range, range); - assertEq(actualStream.segments, segments); + assertEq(actualStream.segments, vars.segmentsWithMilestones); assertEq(actualStream.sender, defaultStream.sender, "sender"); + assertEq(actualStream.startTime, range.start, "startTime"); assertEq(actualStream.status, defaultStream.status); // Assert that the next stream id has been bumped. @@ -159,7 +132,7 @@ contract CreateWithDeltas_Pro_Fuzz_Test is Pro_Fuzz_Test { // Assert that the protocol fee has been recorded. vars.actualProtocolRevenues = pro.getProtocolRevenues(DEFAULT_ASSET); - vars.expectedProtocolRevenues = vars.initialProtocolRevenues + vars.amounts.protocolFee; + vars.expectedProtocolRevenues = vars.initialProtocolRevenues + vars.createAmounts.protocolFee; assertEq(vars.actualProtocolRevenues, vars.expectedProtocolRevenues, "protocolRevenues"); // Assert that the NFT has been minted. diff --git a/test/fuzz/lockup/pro/create-with-milestones/createWithMilestones.t.sol b/test/fuzz/lockup/pro/create-with-milestones/createWithMilestones.t.sol index 935277d14..fd99bf59f 100644 --- a/test/fuzz/lockup/pro/create-with-milestones/createWithMilestones.t.sol +++ b/test/fuzz/lockup/pro/create-with-milestones/createWithMilestones.t.sol @@ -142,11 +142,11 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, depositAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, - Broker({ addr: address(0), fee: brokerFee }) + Broker({ account: address(0), fee: brokerFee }) ); } @@ -208,11 +208,11 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, - Broker({ addr: users.broker, fee: brokerFee }) + Broker({ account: users.broker, fee: brokerFee }) ); } @@ -244,7 +244,7 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { uint256 actualNextStreamId; address actualNFTOwner; uint256 actualProtocolRevenues; - Lockup.CreateAmounts amounts; + Lockup.CreateAmounts createAmounts; uint256 expectedNextStreamId; address expectedNFTOwner; uint256 expectedProtocolRevenues; @@ -280,7 +280,7 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { assetContract assetERC20Compliant { - vm.assume(params.funder != address(0) && params.recipient != address(0) && params.broker.addr != address(0)); + vm.assume(params.funder != address(0) && params.recipient != address(0) && params.broker.account != address(0)); vm.assume(params.segments.length != 0); params.broker.fee = bound(params.broker.fee, 0, DEFAULT_MAX_FEE); params.protocolFee = bound(params.protocolFee, 0, DEFAULT_MAX_FEE); @@ -291,7 +291,7 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { // Fuzz the segment amounts and calculate the create amounts (total, deposit, protocol fee, and broker fee). Vars memory vars; - (vars.totalAmount, vars.amounts) = fuzzSegmentAmountsAndCalculateCreateAmounts({ + (vars.totalAmount, vars.createAmounts) = fuzzSegmentAmountsAndCalculateCreateAmounts({ upperBound: UINT128_MAX, segments: params.segments, protocolFee: params.protocolFee, @@ -315,12 +315,16 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { expectTransferFromCall({ from: params.funder, to: address(pro), - amount: vars.amounts.deposit + vars.amounts.protocolFee + amount: vars.createAmounts.deposit + vars.createAmounts.protocolFee }); // Expect the broker fee to be paid to the broker, if not zero. - if (vars.amounts.brokerFee > 0) { - expectTransferFromCall({ from: params.funder, to: params.broker.addr, amount: vars.amounts.brokerFee }); + if (vars.createAmounts.brokerFee > 0) { + expectTransferFromCall({ + from: params.funder, + to: params.broker.account, + amount: vars.createAmounts.brokerFee + }); } // Expect a {CreateLockupProStream} event to be emitted. @@ -334,12 +338,12 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { funder: params.funder, sender: params.sender, recipient: params.recipient, - amounts: vars.amounts, - segments: params.segments, + amounts: vars.createAmounts, asset: DEFAULT_ASSET, cancelable: params.cancelable, + segments: params.segments, range: range, - broker: params.broker.addr + broker: params.broker.account }); // Create the stream. @@ -347,21 +351,22 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { params.sender, params.recipient, vars.totalAmount, - params.segments, DEFAULT_ASSET, params.cancelable, + params.segments, params.startTime, params.broker ); // Assert that the stream has been created. LockupPro.Stream memory actualStream = pro.getStream(streamId); - assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.amounts.deposit, withdrawn: 0 })); + assertEq(actualStream.amounts, Lockup.Amounts({ deposit: vars.createAmounts.deposit, withdrawn: 0 })); assertEq(actualStream.asset, defaultStream.asset, "asset"); + assertEq(actualStream.endTime, range.end, "endTime"); assertEq(actualStream.isCancelable, params.cancelable, "isCancelable"); - assertEq(actualStream.range, range); assertEq(actualStream.sender, params.sender, "sender"); assertEq(actualStream.segments, params.segments); + assertEq(actualStream.startTime, range.start, "startTime"); assertEq(actualStream.status, defaultStream.status); // Assert that the next stream id has been bumped. @@ -371,7 +376,7 @@ contract CreateWithMilestones_Pro_Fuzz_Test is Pro_Fuzz_Test { // Assert that the protocol fee has been recorded. vars.actualProtocolRevenues = pro.getProtocolRevenues(DEFAULT_ASSET); - vars.expectedProtocolRevenues = vars.amounts.protocolFee; + vars.expectedProtocolRevenues = vars.createAmounts.protocolFee; assertEq(vars.actualProtocolRevenues, vars.expectedProtocolRevenues, "protocolRevenues"); // Assert that the NFT has been minted. diff --git a/test/fuzz/lockup/pro/withdraw/withdraw.t.sol b/test/fuzz/lockup/pro/withdraw/withdraw.t.sol index 179854974..7951de758 100644 --- a/test/fuzz/lockup/pro/withdraw/withdraw.t.sol +++ b/test/fuzz/lockup/pro/withdraw/withdraw.t.sol @@ -26,12 +26,12 @@ contract Withdraw_Pro_Fuzz_Test is Pro_Fuzz_Test, Withdraw_Fuzz_Test { struct Vars { Lockup.Status actualStatus; uint256 actualWithdrawnAmount; + Lockup.CreateAmounts createAmounts; Lockup.Status expectedStatus; uint256 expectedWithdrawnAmount; address funder; uint256 streamId; uint128 totalAmount; - Lockup.CreateAmounts amounts; uint128 withdrawAmount; uint128 withdrawableAmount; } @@ -62,7 +62,7 @@ contract Withdraw_Pro_Fuzz_Test is Pro_Fuzz_Test, Withdraw_Fuzz_Test { fuzzSegmentMilestones(params.segments, DEFAULT_START_TIME); // Fuzz the segment amounts and calculate the create amounts (total, deposit, protocol fee, and broker fee). - (vars.totalAmount, vars.amounts) = fuzzSegmentAmountsAndCalculateCreateAmounts(params.segments); + (vars.totalAmount, vars.createAmounts) = fuzzSegmentAmountsAndCalculateCreateAmounts(params.segments); // Bound the time warp. params.timeWarp = bound(params.timeWarp, 1, params.segments[params.segments.length - 1].milestone); @@ -78,11 +78,11 @@ contract Withdraw_Pro_Fuzz_Test is Pro_Fuzz_Test, Withdraw_Fuzz_Test { sender: users.sender, recipient: users.recipient, totalAmount: vars.totalAmount, - segments: params.segments, asset: DEFAULT_ASSET, cancelable: true, + segments: params.segments, startTime: DEFAULT_START_TIME, - broker: Broker({ addr: users.broker, fee: DEFAULT_BROKER_FEE }) + broker: Broker({ account: users.broker, fee: DEFAULT_BROKER_FEE }) }); // Bound the withdraw amount. diff --git a/test/fuzz/lockup/pro/withdrawable-amount-of/withdrawableAmountOf.t.sol b/test/fuzz/lockup/pro/withdrawable-amount-of/withdrawableAmountOf.t.sol index 1200ce33b..eacc42951 100644 --- a/test/fuzz/lockup/pro/withdrawable-amount-of/withdrawableAmountOf.t.sol +++ b/test/fuzz/lockup/pro/withdrawable-amount-of/withdrawableAmountOf.t.sol @@ -47,11 +47,11 @@ contract WithdrawableAmountOf_Pro_Fuzz_Test is Pro_Fuzz_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, DEFAULT_DEPOSIT_AMOUNT, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, - Broker({ addr: address(0), fee: ZERO }) + Broker({ account: address(0), fee: ZERO }) ); // Run the test. @@ -100,11 +100,11 @@ contract WithdrawableAmountOf_Pro_Fuzz_Test is Pro_Fuzz_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, DEFAULT_DEPOSIT_AMOUNT, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, - Broker({ addr: address(0), fee: ZERO }) + Broker({ account: address(0), fee: ZERO }) ); // Make the withdrawal. diff --git a/test/invariant/handlers/LockupLinearCreateHandler.t.sol b/test/invariant/handlers/LockupLinearCreateHandler.t.sol index 65417a521..86231b669 100644 --- a/test/invariant/handlers/LockupLinearCreateHandler.t.sol +++ b/test/invariant/handlers/LockupLinearCreateHandler.t.sol @@ -69,7 +69,7 @@ contract LockupLinearCreateHandler is BaseHandler { } // The protocol doesn't allow the sender, recipient or broker to be the zero address. - if (params.sender == address(0) || params.recipient == address(0) || params.broker.addr == address(0)) { + if (params.sender == address(0) || params.recipient == address(0) || params.broker.account == address(0)) { return; } @@ -124,7 +124,7 @@ contract LockupLinearCreateHandler is BaseHandler { } // The protocol doesn't allow the sender, recipient or broker to be the zero address. - if (params.sender == address(0) || params.recipient == address(0) || params.broker.addr == address(0)) { + if (params.sender == address(0) || params.recipient == address(0) || params.broker.account == address(0)) { return; } diff --git a/test/invariant/handlers/LockupProCreateHandler.t.sol b/test/invariant/handlers/LockupProCreateHandler.t.sol index 4b3caf666..6b7f9fa20 100644 --- a/test/invariant/handlers/LockupProCreateHandler.t.sol +++ b/test/invariant/handlers/LockupProCreateHandler.t.sol @@ -65,13 +65,12 @@ contract LockupProCreateHandler is BaseHandler { Broker broker; bool cancelable; address recipient; - LockupPro.Segment[] segments; + LockupPro.SegmentWithDelta[] segments; address sender; } struct CreateWithDeltasVars { uint256 streamId; - uint40[] deltas; uint128 totalAmount; } @@ -84,11 +83,11 @@ contract LockupProCreateHandler is BaseHandler { } // The protocol doesn't allow the sender, recipient or broker to be the zero address. - if (params.sender == address(0) || params.recipient == address(0) || params.broker.addr == address(0)) { + if (params.sender == address(0) || params.recipient == address(0) || params.broker.account == address(0)) { return; } - // The protocol doesn't allow empty segments. + // The protocol doesn't allow empty segments arrays. if (params.segments.length == 0) { return; } @@ -96,9 +95,9 @@ contract LockupProCreateHandler is BaseHandler { // Bound the broker fee. params.broker.fee = bound(params.broker.fee, 0, DEFAULT_MAX_FEE); - // Fuzz the deltas and update the segment milestones. + // Fuzz the deltas. CreateWithDeltasVars memory vars; - vars.deltas = fuzzSegmentDeltas(params.segments); + fuzzSegmentDeltas(params.segments); // Fuzz the segment amounts and calculate the create amounts (total, deposit, protocol fee, and broker fee). (vars.totalAmount, ) = fuzzSegmentAmountsAndCalculateCreateAmounts({ @@ -119,10 +118,9 @@ contract LockupProCreateHandler is BaseHandler { sender: params.sender, recipient: params.recipient, totalAmount: vars.totalAmount, - segments: params.segments, asset: asset, cancelable: params.cancelable, - deltas: vars.deltas, + segments: params.segments, broker: params.broker }); @@ -157,11 +155,11 @@ contract LockupProCreateHandler is BaseHandler { } // The protocol doesn't allow the sender, recipient or broker to be the zero address. - if (params.sender == address(0) || params.recipient == address(0) || params.broker.addr == address(0)) { + if (params.sender == address(0) || params.recipient == address(0) || params.broker.account == address(0)) { return; } - // The protocol doesn't allow empty segments. + // The protocol doesn't allow empty segments arrays. if (params.segments.length == 0) { return; } @@ -189,9 +187,9 @@ contract LockupProCreateHandler is BaseHandler { sender: params.sender, recipient: params.recipient, totalAmount: vars.totalAmount, - segments: params.segments, asset: asset, cancelable: params.cancelable, + segments: params.segments, startTime: params.startTime, broker: params.broker }); diff --git a/test/invariant/lockup/linear/Linear.t.sol b/test/invariant/lockup/linear/Linear.t.sol index ae21c590d..51b8407ee 100644 --- a/test/invariant/lockup/linear/Linear.t.sol +++ b/test/invariant/lockup/linear/Linear.t.sol @@ -87,17 +87,17 @@ contract Linear_Invariant_Test is Lockup_Invariant_Test { assertEq(actualStream.amounts.deposit, 0, "Invariant violated: stream null, deposit amount not zero"); assertEq(actualStream.amounts.withdrawn, 0, "Invariant violated: stream null, withdrawn amount not zero"); assertEq(address(actualStream.asset), address(0), "Invariant violated: stream null, asset not zero address"); + assertEq(actualStream.cliffTime, 0, "Invariant violated: stream null, cliff time not zero"); + assertEq(actualStream.endTime, 0, "Invariant violated: stream null, end time not zero"); assertEq(actualStream.isCancelable, false, "Invariant violated: stream null, isCancelable not false"); - assertEq(actualStream.range.cliff, 0, "Invariant violated: stream null, cliff time not zero"); - assertEq(actualStream.range.end, 0, "Invariant violated: stream null, end time not zero"); - assertEq(actualStream.range.start, 0, "Invariant violated: stream null, start time not zero"); assertEq(actualStream.sender, address(0), "Invariant violated: stream null, sender not zero address"); + assertEq(actualStream.startTime, 0, "Invariant violated: stream null, start time not zero"); assertEq(actualRecipient, address(0), "Invariant violated: stream null, recipient not zero address"); } // If the stream is not null, it should contain a non-zero deposit amount. else { assertNotEq(actualStream.amounts.deposit, 0, "Invariant violated: stream non-null, deposit amount zero"); - assertNotEq(actualStream.range.end, 0, "Invariant violated: stream non-null, end time zero"); + assertNotEq(actualStream.endTime, 0, "Invariant violated: stream non-null, end time zero"); } unchecked { i += 1; diff --git a/test/invariant/lockup/pro/Pro.t.sol b/test/invariant/lockup/pro/Pro.t.sol index dfd128609..75ff1a4ba 100644 --- a/test/invariant/lockup/pro/Pro.t.sol +++ b/test/invariant/lockup/pro/Pro.t.sol @@ -84,17 +84,17 @@ contract Pro_Invariant_Test is Lockup_Invariant_Test { assertEq(actualStream.amounts.deposit, 0, "Invariant violated: stream null, deposit amount not zero"); assertEq(actualStream.amounts.withdrawn, 0, "Invariant violated: stream null, withdrawn amount not zero"); assertEq(address(actualStream.asset), address(0), "Invariant violated: stream null, asset not zero address"); - assertEq(actualStream.range.end, 0, "Invariant violated: stream null, end time not zero"); - assertEq(actualStream.range.start, 0, "Invariant violated: stream null, start time not zero"); + assertEq(actualStream.endTime, 0, "Invariant violated: stream null, end time not zero"); assertEq(actualStream.isCancelable, false, "Invariant violated: stream null, isCancelable not false"); assertEq(actualStream.segments.length, 0, "Invariant violated: stream null, segment count not zero"); assertEq(actualStream.sender, address(0), "Invariant violated: stream null, sender not zero address"); + assertEq(actualStream.startTime, 0, "Invariant violated: stream null, start time not zero"); assertEq(actualRecipient, address(0), "Invariant violated: stream null, recipient not zero address"); } // If the stream is not null, it should contain a non-zero deposit amount. else { assertNotEq(actualStream.amounts.deposit, 0, "Invariant violated: stream non-null, deposit amount zero"); - assertNotEq(actualStream.range.end, 0, "Invariant violated: stream non-null, end time zero"); + assertNotEq(actualStream.endTime, 0, "Invariant violated: stream non-null, end time zero"); } unchecked { i += 1; diff --git a/test/shared/helpers/Assertions.t.sol b/test/shared/helpers/Assertions.t.sol index f97464ba2..31b01720d 100644 --- a/test/shared/helpers/Assertions.t.sol +++ b/test/shared/helpers/Assertions.t.sol @@ -41,19 +41,22 @@ abstract contract Assertions is PRBTest, PRBMathAssertions { /// @dev Compares two `LockupLinear.Stream` struct entities. function assertEq(LockupLinear.Stream memory a, LockupLinear.Stream memory b) internal { assertEq(a.amounts, b.amounts); + assertEq(a.cliffTime, b.cliffTime, "cliffTime"); + assertEq(a.endTime, b.endTime, "endTime"); assertEq(a.isCancelable, b.isCancelable, "isCancelable"); assertEq(a.sender, b.sender, "sender"); assertEq(a.status, b.status); - assertEq(a.range, b.range); + assertEq(a.startTime, b.startTime, "startTime"); assertEq(a.asset, b.asset, "asset"); } /// @dev Compares two `LockupPro.Stream` struct entities. function assertEq(LockupPro.Stream memory a, LockupPro.Stream memory b) internal { + assertEq(a.endTime, b.endTime, "endTime"); assertEq(a.isCancelable, b.isCancelable, "isCancelable"); assertEq(a.segments, b.segments, "segments"); assertEq(a.sender, b.sender, "sender"); - assertEq(a.range, b.range); + assertEq(a.startTime, b.startTime, "startTime"); assertEq(a.status, b.status); assertEq(a.asset, b.asset, "asset"); } diff --git a/test/shared/helpers/Constants.t.sol b/test/shared/helpers/Constants.t.sol index cf7351745..90c7dcda3 100644 --- a/test/shared/helpers/Constants.t.sol +++ b/test/shared/helpers/Constants.t.sol @@ -49,8 +49,8 @@ abstract contract Constants { LockupLinear.Range internal DEFAULT_LINEAR_RANGE; LockupPro.Range internal DEFAULT_PRO_RANGE; LockupPro.Segment[] internal DEFAULT_SEGMENTS; + LockupPro.SegmentWithDelta[] internal DEFAULT_SEGMENTS_WITH_DELTAS; LockupPro.Segment[] internal MAX_SEGMENTS; - uint40[] internal DEFAULT_SEGMENT_DELTAS = [2_500 seconds, 7_500 seconds]; /*////////////////////////////////////////////////////////////////////////// CONSTRUCTOR @@ -82,6 +82,21 @@ abstract contract Constants { }) ); + DEFAULT_SEGMENTS_WITH_DELTAS.push( + LockupPro.SegmentWithDelta({ + amount: DEFAULT_SEGMENTS[0].amount, + exponent: DEFAULT_SEGMENTS[0].exponent, + delta: 2_500 seconds + }) + ); + DEFAULT_SEGMENTS_WITH_DELTAS.push( + LockupPro.SegmentWithDelta({ + amount: DEFAULT_SEGMENTS[1].amount, + exponent: DEFAULT_SEGMENTS[1].exponent, + delta: 7_500 seconds + }) + ); + unchecked { uint128 amount = DEFAULT_DEPOSIT_AMOUNT / uint128(DEFAULT_MAX_SEGMENT_COUNT); UD2x18 exponent = ud2x18(2.71e18); diff --git a/test/shared/helpers/Fuzzers.t.sol b/test/shared/helpers/Fuzzers.t.sol index 4195c772f..a848e2c99 100644 --- a/test/shared/helpers/Fuzzers.t.sol +++ b/test/shared/helpers/Fuzzers.t.sol @@ -28,6 +28,47 @@ abstract contract Fuzzers is Constants, Utils { }); } + /// @dev Just like `fuzzSegmentAmountsAndCalculateCreateAmounts` but uses the defaults. + function fuzzSegmentAmountsAndCalculateCreateAmounts( + LockupPro.SegmentWithDelta[] memory segments + ) internal view returns (uint128 totalAmount, Lockup.CreateAmounts memory createAmounts) { + LockupPro.Segment[] memory segmentsWithMilestones = getSegmentsWithMilestones(segments); + (totalAmount, createAmounts) = fuzzSegmentAmountsAndCalculateCreateAmounts({ + upperBound: UINT128_MAX, + segments: segmentsWithMilestones, + protocolFee: DEFAULT_PROTOCOL_FEE, + brokerFee: DEFAULT_BROKER_FEE + }); + for (uint256 i = 0; i < segmentsWithMilestones.length; ) { + segments[i].amount = segmentsWithMilestones[i].amount; + unchecked { + i += 1; + } + } + } + + /// @dev Fuzzes the segment amounts and calculate the create amounts (total, deposit, protocol fee, and broker fee). + function fuzzSegmentAmountsAndCalculateCreateAmounts( + uint128 upperBound, + LockupPro.SegmentWithDelta[] memory segments, + UD60x18 protocolFee, + UD60x18 brokerFee + ) internal view returns (uint128 totalAmount, Lockup.CreateAmounts memory createAmounts) { + LockupPro.Segment[] memory segmentsWithMilestones = getSegmentsWithMilestones(segments); + (totalAmount, createAmounts) = fuzzSegmentAmountsAndCalculateCreateAmounts( + upperBound, + segmentsWithMilestones, + protocolFee, + brokerFee + ); + for (uint256 i = 0; i < segmentsWithMilestones.length; ) { + segments[i].amount = segmentsWithMilestones[i].amount; + unchecked { + i += 1; + } + } + } + /// @dev Fuzzes the segment amounts and calculate the create amounts (total, deposit, protocol fee, and broker fee). function fuzzSegmentAmountsAndCalculateCreateAmounts( uint128 upperBound, @@ -73,20 +114,17 @@ abstract contract Fuzzers is Constants, Utils { segments[segments.length - 1].amount += (createAmounts.deposit - estimatedDepositAmount); } - /// @dev Fuzzes the deltas and updates the segment milestones. - function fuzzSegmentDeltas(LockupPro.Segment[] memory segments) internal view returns (uint40[] memory deltas) { - deltas = new uint40[](segments.length); + /// @dev Fuzzes the deltas. + function fuzzSegmentDeltas(LockupPro.SegmentWithDelta[] memory segments) internal view { unchecked { // Precompute the first segment delta. - deltas[0] = uint40(bound(segments[0].milestone, 1, 100)); - segments[0].milestone = uint40(block.timestamp) + deltas[0]; + segments[0].delta = uint40(bound(segments[0].delta, 1, 100)); // Bound the deltas so that none is zero and the calculations don't overflow. - uint256 deltaCount = deltas.length; - uint40 maxDelta = (MAX_UNIX_TIMESTAMP - deltas[0]) / uint40(deltaCount); + uint256 deltaCount = segments.length; + uint40 maxDelta = (MAX_UNIX_TIMESTAMP - segments[0].delta) / uint40(deltaCount); for (uint256 i = 1; i < deltaCount; ++i) { - deltas[i] = boundUint40(segments[i].milestone, 1, maxDelta); - segments[i].milestone = segments[i - 1].milestone + deltas[i]; + segments[i].delta = boundUint40(segments[i].delta, 1, maxDelta); } } } @@ -107,7 +145,7 @@ abstract contract Fuzzers is Constants, Utils { uint40 halfStep = step / 2; uint256[] memory milestones = arange(startTime + 1, MAX_UNIX_TIMESTAMP, step); - // Fuzz the milestone in a way that preserves its order in the array. + // Fuzz the milestones in a way that preserves their order in the array. for (uint256 i = 1; i < segmentCount; ) { uint256 milestone = milestones[i]; milestone = bound(milestone, milestone - halfStep, milestone + halfStep); diff --git a/test/shared/helpers/Utils.t.sol b/test/shared/helpers/Utils.t.sol index 23bad44cf..916370fe1 100644 --- a/test/shared/helpers/Utils.t.sol +++ b/test/shared/helpers/Utils.t.sol @@ -6,7 +6,13 @@ import { SD59x18 } from "@prb/math/SD59x18.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; +import { LockupPro } from "src/types/DataTypes.sol"; + abstract contract Utils is StdUtils, PRBMathUtils { + /*////////////////////////////////////////////////////////////////////////// + BOUND + //////////////////////////////////////////////////////////////////////////*/ + /// @dev Bounds a `uint128` number. function boundUint128(uint128 x, uint128 min, uint128 max) internal view returns (uint128 result) { result = uint128(bound(uint256(x), uint256(min), uint256(max))); @@ -17,15 +23,34 @@ abstract contract Utils is StdUtils, PRBMathUtils { result = uint40(bound(uint256(x), uint256(min), uint256(max))); } - /// @dev Converts a `uint128` number to the `SD59x18` format. The casting is safe because the domain of the - /// `int256` type is larger than the domain of the `uint128` type. - function sdUint128(uint128 x) internal pure returns (SD59x18 result) { - result = SD59x18.wrap(int256(uint256(x))); + /*////////////////////////////////////////////////////////////////////////// + TYPES + //////////////////////////////////////////////////////////////////////////*/ + + /// @dev Retrieves the current block timestamp as an `uint40`. + function getBlockTimestamp() internal view returns (uint40 blockTimestamp) { + blockTimestamp = uint40(block.timestamp); } - /// @dev Converts a `uint40` number to the `SD59x18` format. The casting is safe because the domain of the - /// `int256` type is larger than the domain of the `uint40` type. - function sdUint40(uint40 x) internal pure returns (SD59x18 result) { - result = SD59x18.wrap(int256(uint256(x))); + /// @dev Turns the segment with deltas into canonical segments. + function getSegmentsWithMilestones( + LockupPro.SegmentWithDelta[] memory segments + ) internal view returns (LockupPro.Segment[] memory segmentsWithMilestones) { + unchecked { + segmentsWithMilestones = new LockupPro.Segment[](segments.length); + segmentsWithMilestones[0] = LockupPro.Segment({ + amount: segments[0].amount, + exponent: segments[0].exponent, + milestone: getBlockTimestamp() + segments[0].delta + }); + for (uint256 i = 1; i < segments.length; ) { + segmentsWithMilestones[i] = LockupPro.Segment({ + amount: segments[i].amount, + exponent: segments[i].exponent, + milestone: segmentsWithMilestones[i - 1].milestone + segments[i].delta + }); + i += 1; + } + } } } diff --git a/test/shared/lockup/linear/Linear.t.sol b/test/shared/lockup/linear/Linear.t.sol index a9edd405f..3fc34c671 100644 --- a/test/shared/lockup/linear/Linear.t.sol +++ b/test/shared/lockup/linear/Linear.t.sol @@ -62,7 +62,7 @@ abstract contract Linear_Shared_Test is Lockup_Shared_Test { asset: DEFAULT_ASSET, cancelable: true, durations: DEFAULT_DURATIONS, - broker: Broker({ addr: users.broker, fee: DEFAULT_BROKER_FEE }) + broker: Broker({ account: users.broker, fee: DEFAULT_BROKER_FEE }) }), createWithRange: CreateWithRangeParams({ sender: users.sender, @@ -71,17 +71,19 @@ abstract contract Linear_Shared_Test is Lockup_Shared_Test { asset: DEFAULT_ASSET, cancelable: true, range: DEFAULT_LINEAR_RANGE, - broker: Broker({ addr: users.broker, fee: DEFAULT_BROKER_FEE }) + broker: Broker({ account: users.broker, fee: DEFAULT_BROKER_FEE }) }) }); // Create the default stream to be used across the tests. defaultStream = LockupLinear.Stream({ amounts: DEFAULT_LOCKUP_AMOUNTS, + cliffTime: defaultParams.createWithRange.range.cliff, + endTime: defaultParams.createWithRange.range.end, isCancelable: defaultParams.createWithRange.cancelable, sender: defaultParams.createWithRange.sender, + startTime: defaultParams.createWithRange.range.start, status: Lockup.Status.ACTIVE, - range: defaultParams.createWithRange.range, asset: defaultParams.createWithRange.asset }); } diff --git a/test/shared/lockup/pro/Pro.t.sol b/test/shared/lockup/pro/Pro.t.sol index 0f1355b19..512ec4333 100644 --- a/test/shared/lockup/pro/Pro.t.sol +++ b/test/shared/lockup/pro/Pro.t.sol @@ -18,10 +18,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { IERC20 asset; Broker broker; bool cancelable; - uint40[] deltas; address recipient; address sender; - LockupPro.Segment[] segments; + LockupPro.SegmentWithDelta[] segments; uint128 totalAmount; } @@ -61,7 +60,7 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithDeltas.totalAmount = DEFAULT_TOTAL_AMOUNT; defaultParams.createWithDeltas.asset = DEFAULT_ASSET; defaultParams.createWithDeltas.cancelable = true; - defaultParams.createWithDeltas.broker = Broker({ addr: users.broker, fee: DEFAULT_BROKER_FEE }); + defaultParams.createWithDeltas.broker = Broker({ account: users.broker, fee: DEFAULT_BROKER_FEE }); defaultParams.createWithMilestones.sender = users.sender; defaultParams.createWithMilestones.recipient = users.recipient; @@ -69,21 +68,21 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.asset = DEFAULT_ASSET; defaultParams.createWithMilestones.cancelable = true; defaultParams.createWithMilestones.startTime = DEFAULT_START_TIME; - defaultParams.createWithMilestones.broker = Broker({ addr: users.broker, fee: DEFAULT_BROKER_FEE }); + defaultParams.createWithMilestones.broker = Broker({ account: users.broker, fee: DEFAULT_BROKER_FEE }); // See https://github.com/ethereum/solidity/issues/12783 for (uint256 i = 0; i < DEFAULT_SEGMENTS.length; ++i) { - defaultParams.createWithDeltas.segments.push(DEFAULT_SEGMENTS[i]); - defaultParams.createWithDeltas.deltas.push(DEFAULT_SEGMENT_DELTAS[i]); + defaultParams.createWithDeltas.segments.push(DEFAULT_SEGMENTS_WITH_DELTAS[i]); defaultParams.createWithMilestones.segments.push(DEFAULT_SEGMENTS[i]); } // Create the default stream to be used across the tests. defaultStream.amounts = DEFAULT_LOCKUP_AMOUNTS; + defaultStream.endTime = DEFAULT_END_TIME; defaultStream.isCancelable = defaultParams.createWithMilestones.cancelable; defaultStream.segments = defaultParams.createWithMilestones.segments; defaultStream.sender = defaultParams.createWithMilestones.sender; - defaultStream.range = DEFAULT_PRO_RANGE; + defaultStream.startTime = DEFAULT_START_TIME; defaultStream.status = Lockup.Status.ACTIVE; defaultStream.asset = defaultParams.createWithMilestones.asset; } @@ -98,9 +97,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -112,24 +111,24 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithDeltas.sender, defaultParams.createWithDeltas.recipient, defaultParams.createWithDeltas.totalAmount, - defaultParams.createWithDeltas.segments, defaultParams.createWithDeltas.asset, defaultParams.createWithDeltas.cancelable, - defaultParams.createWithDeltas.deltas, + defaultParams.createWithDeltas.segments, defaultParams.createWithDeltas.broker ); } /// @dev Creates the default stream with the provided deltas. - function createDefaultStreamWithDeltas(uint40[] memory deltas) internal returns (uint256 streamId) { + function createDefaultStreamWithDeltas( + LockupPro.SegmentWithDelta[] memory segments + ) internal returns (uint256 streamId) { streamId = pro.createWithDeltas( defaultParams.createWithDeltas.sender, defaultParams.createWithDeltas.recipient, defaultParams.createWithDeltas.totalAmount, - defaultParams.createWithDeltas.segments, defaultParams.createWithDeltas.asset, defaultParams.createWithDeltas.cancelable, - deltas, + segments, defaultParams.createWithDeltas.broker ); } @@ -142,9 +141,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -157,9 +156,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, isCancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -171,9 +170,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.sender, recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -185,9 +184,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -199,9 +198,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -213,9 +212,9 @@ abstract contract Pro_Shared_Test is Lockup_Shared_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); diff --git a/test/unit/lockup/linear/create-with-durations/createWithDurations.t.sol b/test/unit/lockup/linear/create-with-durations/createWithDurations.t.sol index b89c68a10..da4476912 100644 --- a/test/unit/lockup/linear/create-with-durations/createWithDurations.t.sol +++ b/test/unit/lockup/linear/create-with-durations/createWithDurations.t.sol @@ -126,12 +126,7 @@ contract CreateWithDurations_Linear_Unit_Test is Linear_Unit_Test { // Assert that the stream has been created. LockupLinear.Stream memory actualStream = linear.getStream(streamId); - assertEq(actualStream.amounts, defaultStream.amounts); - assertEq(actualStream.asset, defaultStream.asset, "asset"); - assertEq(actualStream.isCancelable, defaultStream.isCancelable, "isCancelable"); - assertEq(actualStream.range, DEFAULT_LINEAR_RANGE); - assertEq(actualStream.sender, defaultStream.sender, "sender"); - assertEq(actualStream.status, defaultStream.status); + assertEq(actualStream, defaultStream); // Assert that the next stream id has been bumped. uint256 actualNextStreamId = linear.nextStreamId(); diff --git a/test/unit/lockup/linear/create-with-range/createWithRange.t.sol b/test/unit/lockup/linear/create-with-range/createWithRange.t.sol index fb0a26a43..a13c0b2c4 100644 --- a/test/unit/lockup/linear/create-with-range/createWithRange.t.sol +++ b/test/unit/lockup/linear/create-with-range/createWithRange.t.sol @@ -146,7 +146,7 @@ contract CreateWithRange_Linear_Unit_Test is Linear_Unit_Test { defaultParams.createWithRange.asset, defaultParams.createWithRange.cancelable, defaultParams.createWithRange.range, - Broker({ addr: users.broker, fee: brokerFee }) + Broker({ account: users.broker, fee: brokerFee }) ); } @@ -270,9 +270,11 @@ contract CreateWithRange_Linear_Unit_Test is Linear_Unit_Test { LockupLinear.Stream memory actualStream = linear.getStream(streamId); assertEq(actualStream.amounts, defaultStream.amounts); assertEq(address(actualStream.asset), asset, "asset"); + assertEq(actualStream.cliffTime, defaultStream.cliffTime, "cliffTime"); + assertEq(actualStream.endTime, defaultStream.endTime, "endTime"); assertEq(actualStream.isCancelable, defaultStream.isCancelable, "isCancelable"); assertEq(actualStream.sender, defaultStream.sender, "sender"); - assertEq(actualStream.range, defaultStream.range); + assertEq(actualStream.startTime, defaultStream.startTime, "startTime"); assertEq(actualStream.status, defaultStream.status); // Assert that the next stream id has been bumped. diff --git a/test/unit/lockup/pro/create-with-deltas/createWithDeltas.t.sol b/test/unit/lockup/pro/create-with-deltas/createWithDeltas.t.sol index 369a4f699..50ad6f7a2 100644 --- a/test/unit/lockup/pro/create-with-deltas/createWithDeltas.t.sol +++ b/test/unit/lockup/pro/create-with-deltas/createWithDeltas.t.sol @@ -23,9 +23,9 @@ contract CreateWithDeltas_Pro_Unit_Test is Pro_Unit_Test { /// @dev it should revert. function test_RevertWhen_LoopCalculationOverflowsBlockGasLimit() external { - uint40[] memory deltas = new uint40[](1_000_000); + LockupPro.SegmentWithDelta[] memory segments = new LockupPro.SegmentWithDelta[](250_000); vm.expectRevert(bytes("")); - createDefaultStreamWithDeltas(deltas); + createDefaultStreamWithDeltas(segments); } modifier loopCalculationsDoNotOverflowBlockGasLimit() { @@ -35,66 +35,44 @@ contract CreateWithDeltas_Pro_Unit_Test is Pro_Unit_Test { /// @dev it should revert. function test_RevertWhen_DeltasZero() external loopCalculationsDoNotOverflowBlockGasLimit { uint40 startTime = getBlockTimestamp(); - uint40[] memory deltas = Solarray.uint40s(DEFAULT_SEGMENT_DELTAS[0], 0); + LockupPro.SegmentWithDelta[] memory segments = defaultParams.createWithDeltas.segments; + segments[1].delta = 0; uint256 index = 1; vm.expectRevert( abi.encodeWithSelector( Errors.SablierV2LockupPro_SegmentMilestonesNotOrdered.selector, index, - startTime + deltas[0], - startTime + deltas[0] + startTime + segments[0].delta, + startTime + segments[0].delta ) ); - createDefaultStreamWithDeltas(deltas); + createDefaultStreamWithDeltas(segments); } modifier deltasNotZero() { _; } - /// @dev it should revert. - function test_RevertWhen_SegmentArrayCountsNotEqual() - external - loopCalculationsDoNotOverflowBlockGasLimit - deltasNotZero - { - uint40[] memory deltas = new uint40[](defaultParams.createWithDeltas.segments.length + 1); - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierV2LockupPro_SegmentArrayCountsNotEqual.selector, - defaultParams.createWithDeltas.segments.length, - deltas.length - ) - ); - createDefaultStreamWithDeltas(deltas); - } - - modifier segmentArrayCountsEqual() { - _; - } - /// @dev it should revert. function test_RevertWhen_MilestonesCalculationsOverflows_StartTimeNotLessThanFirstSegmentMilestone() external loopCalculationsDoNotOverflowBlockGasLimit deltasNotZero - segmentArrayCountsEqual { - uint40 startTime = getBlockTimestamp(); - uint40[] memory deltas = Solarray.uint40s(UINT40_MAX, 1); - LockupPro.Segment[] memory segments = defaultParams.createWithDeltas.segments; unchecked { - segments[0].milestone = startTime + deltas[0]; - segments[1].milestone = deltas[0] + deltas[1]; + uint40 startTime = getBlockTimestamp(); + LockupPro.SegmentWithDelta[] memory segments = defaultParams.createWithDeltas.segments; + segments[0].delta = UINT40_MAX; + segments[1].delta = 1; + vm.expectRevert( + abi.encodeWithSelector( + Errors.SablierV2LockupPro_StartTimeNotLessThanFirstSegmentMilestone.selector, + startTime, + startTime + segments[0].delta + ) + ); + createDefaultStreamWithDeltas(segments); } - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierV2LockupPro_StartTimeNotLessThanFirstSegmentMilestone.selector, - startTime, - segments[0].milestone - ) - ); - createDefaultStreamWithDeltas(deltas); } /// @dev it should revert. @@ -102,51 +80,46 @@ contract CreateWithDeltas_Pro_Unit_Test is Pro_Unit_Test { external loopCalculationsDoNotOverflowBlockGasLimit deltasNotZero - segmentArrayCountsEqual { - uint40 startTime = getBlockTimestamp(); - - // Create the deltas such that they overflow. - uint40[] memory deltas = Solarray.uint40s(1, UINT40_MAX, 1); - - // Create new segments that overflow when the milestones are eventually calculated. - LockupPro.Segment[] memory segments = new LockupPro.Segment[](3); unchecked { - segments[0] = LockupPro.Segment({ amount: 0, exponent: ud2x18(1e18), milestone: startTime + deltas[0] }); - segments[1] = LockupPro.Segment({ - amount: DEFAULT_SEGMENTS[0].amount, - exponent: DEFAULT_SEGMENTS[0].exponent, - milestone: segments[0].milestone + deltas[1] + uint40 startTime = getBlockTimestamp(); + + // Create new segments that overflow when the milestones are eventually calculated. + LockupPro.SegmentWithDelta[] memory segments = new LockupPro.SegmentWithDelta[](3); + segments[0] = LockupPro.SegmentWithDelta({ amount: 0, exponent: ud2x18(1e18), delta: startTime + 1 }); + segments[1] = LockupPro.SegmentWithDelta({ + amount: DEFAULT_SEGMENTS_WITH_DELTAS[0].amount, + exponent: DEFAULT_SEGMENTS_WITH_DELTAS[0].exponent, + delta: UINT40_MAX }); - segments[2] = LockupPro.Segment({ - amount: DEFAULT_SEGMENTS[1].amount, - exponent: DEFAULT_SEGMENTS[1].exponent, - milestone: segments[1].milestone + deltas[2] + segments[2] = LockupPro.SegmentWithDelta({ + amount: DEFAULT_SEGMENTS_WITH_DELTAS[1].amount, + exponent: DEFAULT_SEGMENTS_WITH_DELTAS[1].exponent, + delta: 1 }); - } - - // Expect a {SegmentMilestonesNotOrdered} error. - uint256 index = 1; - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierV2LockupPro_SegmentMilestonesNotOrdered.selector, - index, - segments[0].milestone, - segments[1].milestone - ) - ); - // Create the stream. - pro.createWithDeltas( - defaultParams.createWithDeltas.sender, - defaultParams.createWithDeltas.recipient, - defaultParams.createWithDeltas.totalAmount, - segments, - defaultParams.createWithDeltas.asset, - defaultParams.createWithDeltas.cancelable, - deltas, - defaultParams.createWithDeltas.broker - ); + // Expect a {SegmentMilestonesNotOrdered} error. + uint256 index = 1; + vm.expectRevert( + abi.encodeWithSelector( + Errors.SablierV2LockupPro_SegmentMilestonesNotOrdered.selector, + index, + startTime + segments[0].delta, + startTime + segments[0].delta + segments[1].delta + ) + ); + + // Create the stream. + pro.createWithDeltas( + defaultParams.createWithDeltas.sender, + defaultParams.createWithDeltas.recipient, + defaultParams.createWithDeltas.totalAmount, + defaultParams.createWithDeltas.asset, + defaultParams.createWithDeltas.cancelable, + segments, + defaultParams.createWithDeltas.broker + ); + } } modifier milestonesCalculationsDoNotOverflow() { @@ -159,7 +132,6 @@ contract CreateWithDeltas_Pro_Unit_Test is Pro_Unit_Test { external loopCalculationsDoNotOverflowBlockGasLimit deltasNotZero - segmentArrayCountsEqual milestonesCalculationsDoNotOverflow { // Make the sender the funder of the stream. @@ -186,9 +158,9 @@ contract CreateWithDeltas_Pro_Unit_Test is Pro_Unit_Test { sender: users.sender, recipient: users.recipient, amounts: DEFAULT_LOCKUP_CREATE_AMOUNTS, - segments: DEFAULT_SEGMENTS, asset: DEFAULT_ASSET, cancelable: true, + segments: DEFAULT_SEGMENTS, range: DEFAULT_PRO_RANGE, broker: users.broker }); @@ -198,13 +170,7 @@ contract CreateWithDeltas_Pro_Unit_Test is Pro_Unit_Test { // Assert that the stream has been created. LockupPro.Stream memory actualStream = pro.getStream(streamId); - assertEq(actualStream.amounts, defaultStream.amounts); - assertEq(actualStream.asset, defaultStream.asset, "asset"); - assertEq(actualStream.isCancelable, defaultStream.isCancelable, "isCancelable"); - assertEq(actualStream.range, defaultStream.range); - assertEq(actualStream.segments, defaultStream.segments); - assertEq(actualStream.sender, defaultStream.sender, "sender"); - assertEq(actualStream.status, defaultStream.status); + assertEq(actualStream, defaultStream); // Assert that the next stream id has been bumped. uint256 actualNextStreamId = pro.nextStreamId(); diff --git a/test/unit/lockup/pro/create-with-deltas/createWithDeltas.tree b/test/unit/lockup/pro/create-with-deltas/createWithDeltas.tree index c0d27dbf0..1b7b025db 100644 --- a/test/unit/lockup/pro/create-with-deltas/createWithDeltas.tree +++ b/test/unit/lockup/pro/create-with-deltas/createWithDeltas.tree @@ -2,17 +2,14 @@ createWithDeltas.t.sol ├── when the loop calculations overflow the block gas limit │ └── it should revert └── when the loop calculations do not overflow the block gas limit - ├── when the delta count is not equal to the segment count - │ └── it should revert - └── when the delta count is equal to the segment count - ├── when at least one of the deltas at index one or greater is zero - │ └── it should revert - └── when none of the deltas is zero - ├── when the segment milestone calculations overflow uint256 - │ ├── when the start time is not less than the first segment milestone - │ │ └── it should revert - │ └── when the segment milestones are not ordered - │ └── it should revert - └── when the segment milestone calculations do not overflow uint256 - └── it should perform the ERC-20 transfers, create the stream, bump the next stream id, mint the NFT, record the protocol fee, and emit a {CreateLockupProStream} event + ├── when at least one of the deltas at index one or greater is zero + │ └── it should revert + └── when none of the deltas is zero + ├── when the segment milestone calculations overflow uint256 + │ ├── when the start time is not less than the first segment milestone + │ │ └── it should revert + │ └── when the segment milestones are not ordered + │ └── it should revert + └── when the segment milestone calculations do not overflow uint256 + └── it should perform the ERC-20 transfers, create the stream, bump the next stream id, mint the NFT, record the protocol fee, and emit a {CreateLockupProStream} event diff --git a/test/unit/lockup/pro/create-with-milestones/createWithMilestones.t.sol b/test/unit/lockup/pro/create-with-milestones/createWithMilestones.t.sol index bbcb71d64..01e657775 100644 --- a/test/unit/lockup/pro/create-with-milestones/createWithMilestones.t.sol +++ b/test/unit/lockup/pro/create-with-milestones/createWithMilestones.t.sol @@ -217,11 +217,11 @@ contract CreateWithMilestones_Pro_Unit_Test is Pro_Unit_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, depositAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, - Broker({ addr: address(0), fee: brokerFee }) + Broker({ account: address(0), fee: brokerFee }) ); } @@ -281,11 +281,11 @@ contract CreateWithMilestones_Pro_Unit_Test is Pro_Unit_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.asset, defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, - Broker({ addr: users.broker, fee: brokerFee }) + Broker({ account: users.broker, fee: brokerFee }) ); } @@ -322,9 +322,9 @@ contract CreateWithMilestones_Pro_Unit_Test is Pro_Unit_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, IERC20(nonContract), defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -419,9 +419,9 @@ contract CreateWithMilestones_Pro_Unit_Test is Pro_Unit_Test { defaultParams.createWithMilestones.sender, defaultParams.createWithMilestones.recipient, defaultParams.createWithMilestones.totalAmount, - defaultParams.createWithMilestones.segments, IERC20(asset), defaultParams.createWithMilestones.cancelable, + defaultParams.createWithMilestones.segments, defaultParams.createWithMilestones.startTime, defaultParams.createWithMilestones.broker ); @@ -431,9 +431,10 @@ contract CreateWithMilestones_Pro_Unit_Test is Pro_Unit_Test { assertEq(actualStream.amounts, defaultStream.amounts); assertEq(address(actualStream.asset), asset, "asset"); assertEq(actualStream.isCancelable, defaultStream.isCancelable, "isCancelable"); - assertEq(actualStream.range, defaultStream.range); + assertEq(actualStream.endTime, defaultStream.endTime, "endTime"); assertEq(actualStream.sender, defaultStream.sender, "sender"); assertEq(actualStream.segments, defaultStream.segments); + assertEq(actualStream.startTime, defaultStream.startTime, "startTime"); assertEq(actualStream.status, defaultStream.status); // Assert that the next stream id has been bumped.