The streamed amount is calculated for multi-segment streams in such a way which results loss of funds of sender in refund if he cancel the stream.
In a multi-segment stream each segment has its own function to calculate the streamed amount of that segment, the formula is:
$$
f(x) = x^{exp} * csa + \Sigma(esa)
$$
So suppose a stream of 4 segments looks like this, where each segment is 60 seconds long & exponent is 2.
So here, startTime is X. Now endTime/timestamp of 1st segment is ( X + 60 seconds), timestamp of 2nd segment is (X+120 seconds), timestamp of 3rd segment is (X + 180 seconds) & timestamp of 4th segment is (X+240 seconds). Now if I wanna check the streamed amount at (X + 181) seconds i.e just after 1 second entering the 4th segment ( I denoted that timestamp with T ) the streamed amount should be little more than 300 because till 3rd segment 300 was streamed. Let's calculate the streamed amount for (X + 181) seconds timestamp:
But as per implemented logic the value returns = 399 i.e almost total streamed amount till 4th segment.
pragma solidity ^0.8.15;
import {Base_Test} from "./Base.t.sol";
import "forge-std/src/console.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Broker, Lockup, LockupDynamic } from "../src/types/DataTypes.sol";
import { UD2x18 } from "@prb/math/src/UD2x18.sol";
import {PRBMathCastingUint256} from "@prb/math/src/casting/Uint256.sol";
contract TestDynamic is Base_Test{
uint40 public Time;
UD2x18 public exp;
Broker broker;
LockupDynamic.CreateWithTimestamps public createWithTimestamps;
mapping(uint => LockupDynamic.Segment) segments;
function setUp() public override{
Base_Test.setUp();
Time = uint40(block.timestamp);
exp = PRBMathCastingUint256.intoUD2x18(2);
setStructs();
}
function setStructs() public {
uint40 _timestamp = Time + 120 seconds;
for(uint i; i < 5; i++){
segments[i]=LockupDynamic.Segment({amount: 100, exponent: exp, timestamp: _timestamp});
createWithTimestamps.segments.push(segments[i]);
_timestamp += 60 seconds;
}
broker = Broker({account: users.broker, fee: PRBMathCastingUint256.intoUD60x18(0)});
createWithTimestamps.sender = users.alice;
createWithTimestamps.recipient = users.recipient;
createWithTimestamps.totalAmount = 500;
createWithTimestamps.asset = dai;
createWithTimestamps.cancelable = true;
createWithTimestamps.transferable = true;
createWithTimestamps.startTime = Time + 59 seconds;
createWithTimestamps.broker = broker;
console.log("Length of createWithTimestamps.segments:", createWithTimestamps.segments.length);
}
function test_lessStreamedAmount() public {
vm.stopPrank();
vm.startPrank(users.alice);
lockupDynamic.createWithTimestamps(createWithTimestamps);
assertTrue(lockupDynamic.isStream(1));
assertNotEq(block.timestamp, createWithTimestamps.segments[4].timestamp);
skip(241 seconds);
uint streamedAmount = lockupDynamic.streamedAmountOf(1);
console.log("Deposited amount:", lockupDynamic.getDepositedAmount(1));
console.log("Streamed amount:", streamedAmount);
assertEq(streamedAmount, 399);
console.log("Refundable amount:", lockupDynamic.refundableAmountOf(1));
assertEq(IERC20(dai).balanceOf(address(users.alice)), 999999999999999999999500);
lockupDynamic.cancel(1);
assertEq(IERC20(dai).balanceOf(address(users.alice)), 999999999999999999999601);
}
}
2024-05-Sablier/v2-core main* via 🍞 v1.1.8 via v18.17.1
❯ forge test --mc TestDynamic -vv
[⠊] Compiling...
[⠰] Compiling 1 files with 0.8.23
[⠔] Solc 0.8.23 finished in 1.32s
Compiler run successful!
Ran 1 test for test/TestDynamic.t.sol:TestDynamic
[PASS] test_lessStreamedAmount() (gas: 500366)
Logs:
Length of createWithTimestamps.segments: 5
Deposited amount: 500
Streamed amount: 399
Refundable amount: 101
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.02ms (595.69µs CPU time)
As you can see the refundable amount is 101, but it should be almost 200. So here sender will loss his fund in refunding if he cancel the stream.
The sender will loss fund while refunding.
Recheck the logic of calculating the streamed amount.