-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
72c2d34
commit 7f2dd50
Showing
8 changed files
with
221 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.22; | ||
|
||
import { IERC4906 } from "@openzeppelin/contracts/interfaces/IERC4906.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
import { ISablierFlow } from "src/interfaces/ISablierFlow.sol"; | ||
|
||
import { Integration_Test } from "../../Integration.t.sol"; | ||
|
||
contract RefundMax_Integration_Concrete_Test is Integration_Test { | ||
function setUp() public override { | ||
Integration_Test.setUp(); | ||
|
||
// Deposit to the default stream. | ||
depositToDefaultStream(); | ||
} | ||
|
||
function test_RevertWhen_DelegateCall() external { | ||
bytes memory callData = abi.encodeCall(flow.refundMax, (defaultStreamId)); | ||
expectRevert_DelegateCall(callData); | ||
} | ||
|
||
function test_RevertGiven_Null() external whenNoDelegateCall { | ||
bytes memory callData = abi.encodeCall(flow.refundMax, (nullStreamId)); | ||
expectRevert_Null(callData); | ||
} | ||
|
||
function test_RevertWhen_CallerRecipient() external whenNoDelegateCall givenNotNull whenCallerNotSender { | ||
bytes memory callData = abi.encodeCall(flow.refundMax, (defaultStreamId)); | ||
expectRevert_CallerRecipient(callData); | ||
} | ||
|
||
function test_RevertWhen_CallerMaliciousThirdParty() external whenNoDelegateCall givenNotNull whenCallerNotSender { | ||
bytes memory callData = abi.encodeCall(flow.refundMax, (defaultStreamId)); | ||
expectRevert_CallerMaliciousThirdParty(callData); | ||
} | ||
|
||
function test_GivenPaused() external whenNoDelegateCall givenNotNull whenCallerSender { | ||
flow.pause(defaultStreamId); | ||
|
||
// It should make the refund. | ||
_test_RefundMax({ streamId: defaultStreamId, token: usdc, depositedAmount: DEPOSIT_AMOUNT_6D }); | ||
} | ||
|
||
function test_GivenNotPaused() external whenNoDelegateCall givenNotNull whenCallerSender { | ||
// It should make the refund. | ||
_test_RefundMax({ streamId: defaultStreamId, token: usdc, depositedAmount: DEPOSIT_AMOUNT_6D }); | ||
} | ||
|
||
function _test_RefundMax(uint256 streamId, IERC20 token, uint128 depositedAmount) private { | ||
uint256 previousAggregateAmount = flow.aggregateBalance(token); | ||
uint128 refundableAmount = flow.refundableAmountOf(streamId); | ||
|
||
// It should emit 1 {Transfer}, 1 {RefundFromFlowStream}, 1 {MetadataUpdate} events. | ||
vm.expectEmit({ emitter: address(token) }); | ||
emit IERC20.Transfer({ from: address(flow), to: users.sender, value: refundableAmount }); | ||
|
||
vm.expectEmit({ emitter: address(flow) }); | ||
emit ISablierFlow.RefundFromFlowStream({ streamId: streamId, sender: users.sender, amount: refundableAmount }); | ||
|
||
vm.expectEmit({ emitter: address(flow) }); | ||
emit IERC4906.MetadataUpdate({ _tokenId: streamId }); | ||
|
||
// It should perform the ERC-20 transfer. | ||
expectCallToTransfer({ token: token, to: users.sender, amount: refundableAmount }); | ||
flow.refundMax(streamId); | ||
|
||
// It should update the stream balance. | ||
uint128 actualStreamBalance = flow.getBalance(streamId); | ||
uint128 expectedStreamBalance = depositedAmount - refundableAmount; | ||
assertEq(actualStreamBalance, expectedStreamBalance, "stream balance"); | ||
|
||
// It should decrease the aggregate amount. | ||
assertEq(flow.aggregateBalance(token), previousAggregateAmount - refundableAmount, "aggregate amount"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
RefundMax_Integration_Concrete_Test | ||
├── when delegate call | ||
│ └── it should revert | ||
└── when no delegate call | ||
├── given null | ||
│ └── it should revert | ||
└── given not null | ||
├── when caller not sender | ||
│ ├── when caller recipient | ||
│ │ └── it should revert | ||
│ └── when caller malicious third party | ||
│ └── it should revert | ||
└── when caller sender | ||
├── given paused | ||
│ └── it should make the refund | ||
└── given not paused | ||
├── it should make the refund | ||
├── it should update the stream balance | ||
├── it should decrease the aggregate amount | ||
├── it should perform the ERC20 transfer | ||
└── it should emit 1 {Transfer}, 1 {RefundFromFlowStream}, 1 {MetadataUpdate} event |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.22; | ||
|
||
import { IERC4906 } from "@openzeppelin/contracts/interfaces/IERC4906.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
import { ISablierFlow } from "src/interfaces/ISablierFlow.sol"; | ||
|
||
import { Shared_Integration_Fuzz_Test } from "./Fuzz.t.sol"; | ||
|
||
contract RefundMax_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { | ||
/// @dev Checklist: | ||
/// - It should refund the refundable amount of tokens from a stream. | ||
/// - It should emit the following events: {Transfer}, {MetadataUpdate}, {RefundFromFlowStream} | ||
/// | ||
/// Given enough runs, all of the following scenarios should be fuzzed: | ||
/// - Multiple streams to refund from, each with different token decimals and rate per second. | ||
/// - Multiple points in time prior to depletion period. | ||
function testFuzz_RefundMax( | ||
uint256 streamId, | ||
uint40 timeJump, | ||
uint8 decimals | ||
) | ||
external | ||
whenNoDelegateCall | ||
givenNotNull | ||
{ | ||
(streamId,,) = useFuzzedStreamOrCreate(streamId, decimals); | ||
|
||
// Bound the time jump so that it is less than the depletion timestamp. | ||
uint40 depletionPeriod = uint40(flow.depletionTimeOf(streamId)); | ||
timeJump = boundUint40(timeJump, getBlockTimestamp(), depletionPeriod - 1); | ||
|
||
// Simulate the passage of time. | ||
vm.warp({ newTimestamp: timeJump }); | ||
|
||
uint128 refundableAmount = flow.refundableAmountOf(streamId); | ||
|
||
// Ensure refundable amount is not zero. It could be zero for a small time range upto the depletion time due to | ||
// precision error. | ||
vm.assume(refundableAmount != 0); | ||
|
||
// Following variables are used during assertions. | ||
uint256 initialAggregateAmount = flow.aggregateBalance(token); | ||
uint256 initialTokenBalance = token.balanceOf(address(flow)); | ||
uint128 initialStreamBalance = flow.getBalance(streamId); | ||
|
||
// Expect the relevant events to be emitted. | ||
vm.expectEmit({ emitter: address(token) }); | ||
emit IERC20.Transfer({ from: address(flow), to: users.sender, value: refundableAmount }); | ||
|
||
vm.expectEmit({ emitter: address(flow) }); | ||
emit ISablierFlow.RefundFromFlowStream({ streamId: streamId, sender: users.sender, amount: refundableAmount }); | ||
|
||
vm.expectEmit({ emitter: address(flow) }); | ||
emit IERC4906.MetadataUpdate({ _tokenId: streamId }); | ||
|
||
// Request the maximum refund. | ||
flow.refundMax(streamId); | ||
|
||
// Assert that the token balance of stream has been updated. | ||
uint256 actualTokenBalance = token.balanceOf(address(flow)); | ||
uint256 expectedTokenBalance = initialTokenBalance - refundableAmount; | ||
assertEq(actualTokenBalance, expectedTokenBalance, "token balanceOf"); | ||
|
||
// Assert that stored balance in stream has been updated. | ||
uint256 actualStreamBalance = flow.getBalance(streamId); | ||
uint256 expectedStreamBalance = initialStreamBalance - refundableAmount; | ||
assertEq(actualStreamBalance, expectedStreamBalance, "stream balance"); | ||
|
||
// Assert that the aggregate amount has been updated. | ||
uint256 actualAggregateAmount = flow.aggregateBalance(token); | ||
uint256 expectedAggregateAmount = initialAggregateAmount - refundableAmount; | ||
assertEq(actualAggregateAmount, expectedAggregateAmount, "aggregate amount"); | ||
} | ||
} |