Skip to content

Commit

Permalink
Fixed issue 12 and modified tests.
Browse files Browse the repository at this point in the history
While fixing this issue, we found that the due to tests' inter-dependencies, some tests were failing. My advice would be to take a clean-state approach such that the balances of the users are predictable (same) before each test. However, for now, the tests do pass. Another thing to note is that the flow rate used in tests are very large. Although the tests work as expected, it would be better to consider smaller flow rates. Also replaced `MIN_LOWER` and `MIN_UPPER` with the values in seconds i.e., previously `MIN_LOWER=2` now it's `MIN_LOWER=helper.getSeconds(2)` indicating that `MIN_LOWER` is number of seconds in 2 days.
  • Loading branch information
rashtrakoff committed Jun 17, 2022
1 parent a53a188 commit ca2cfac
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 13 deletions.
28 changes: 25 additions & 3 deletions smart-contracts/contracts/StrollManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/IERC20Mod.sol";
import "./interfaces/IStrollManager.sol";


// solhint-disable not-rely-on-time
/// @title StrollManager
/// @author Harsh Prakash <[email protected]>
Expand All @@ -29,6 +30,9 @@ contract StrollManager is IStrollManager, Ownable {
uint64 _minLower,
uint64 _minUpper
) {
if (_icfa == address(0)) revert ZeroAddress();
if (_minLower >= _minUpper) revert WrongLimits(_minLower, _minUpper);

CFA_V1 = IConstantFlowAgreementV1(_icfa);
minLower = _minLower;
minUpper = _minUpper;
Expand Down Expand Up @@ -149,6 +153,20 @@ contract StrollManager is IStrollManager, Ownable {
}
}

/// @dev IStrollManager.setLimits implementation.
function setLimits(uint64 _lowerLimit, uint64 _upperLimit)
external
onlyOwner
{
if (_lowerLimit >= _upperLimit)
revert WrongLimits(_lowerLimit, _upperLimit);

minLower = _lowerLimit;
minUpper = _upperLimit;

emit LimitsChanged(_lowerLimit, _upperLimit);
}

/// @dev IStrollManager.getTopUp implementation.
function getTopUp(
address _user,
Expand Down Expand Up @@ -194,7 +212,7 @@ contract StrollManager is IStrollManager, Ownable {
TopUp storage topUp = topUps[_index];

address user = topUp.user;

if (user != msg.sender && topUp.expiry >= block.timestamp)
revert UnauthorizedCaller(msg.sender, user);

Expand Down Expand Up @@ -244,8 +262,12 @@ contract StrollManager is IStrollManager, Ownable {
uint256 superBalance = topUp.superToken.balanceOf(topUp.user);
uint256 positiveFlowRate = uint256(uint96(-1 * flowRate));

if (superBalance <= (positiveFlowRate * topUp.lowerLimit)) {
return positiveFlowRate * topUp.upperLimit;
// Selecting max between user defined limits and global limits.
uint64 maxLowerLimit = (topUp.lowerLimit < minLower)? minLower: topUp.lowerLimit;
uint64 maxUpperLimit = (topUp.upperLimit < minUpper)? minUpper: topUp.upperLimit;

if (superBalance <= (positiveFlowRate * maxLowerLimit)) {
return positiveFlowRate * maxUpperLimit;
}
}

Expand Down
33 changes: 30 additions & 3 deletions smart-contracts/contracts/interfaces/IStrollManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ interface IStrollManager {
uint256 lowerLimit,
uint256 upperLimit
);
event TopUpDeleted(bytes32 indexed id, address indexed user, address indexed superToken, address strategy, address liquidityToken);
event TopUpDeleted(
bytes32 indexed id,
address indexed user,
address indexed superToken,
address strategy,
address liquidityToken
);
event PerformedTopUp(bytes32 indexed id, uint256 topUpAmount);
event AddedApprovedStrategy(address indexed strategy);
event RemovedApprovedStrategy(address indexed strategy);
event LimitsChanged(uint64 lowerLimit, uint64 upperLimit);

/// Custom error to indicate that null address has been passed.
error ZeroAddress();
Expand Down Expand Up @@ -50,6 +57,11 @@ interface IStrollManager {
/// @param minLimit Minimum limit (upper/lower) expected.
error InsufficientLimits(uint64 limitGiven, uint64 minLimit);

/// Custom error to indicate that the limits are wrong (lower limit >= upper limit).
/// @param lowerLimit Limit (upper/lower) given by the user.
/// @param upperLimit Minimum limit (upper/lower) expected.
error WrongLimits(uint64 lowerLimit, uint64 upperLimit);

/**
* @notice Struct representing a top-up.
* @param user Address of the user who created the top-up.
Expand Down Expand Up @@ -82,6 +94,15 @@ interface IStrollManager {
*/
function removeApprovedStrategy(address _strategy) external;

/**
* @notice Sets the global limits for top-ups.
* @param _lowerLimit Triggers top up if stream can't be continued for this amount of seconds.
* @param _upperLimit Increase supertoken balance to continue stream for this amount of seconds.
* @dev If the previous top-ups don't adhere to the current global limits, the global limits will be enforced.
* i.e., max(global limit, user defined limit) is always taken.
*/
function setLimits(uint64 _lowerLimit, uint64 _upperLimit) external;

/**
* @notice Creates a new top up task.
* @param _superToken The supertoken to monitor/top up.
Expand Down Expand Up @@ -118,7 +139,10 @@ interface IStrollManager {
* @param _index Index of top up.
* @return The top up.
*/
function getTopUpByIndex(bytes32 _index) external view returns (TopUp memory);
function getTopUpByIndex(bytes32 _index)
external
view
returns (TopUp memory);

/**
* @notice Gets a top up by index.
Expand All @@ -138,7 +162,10 @@ interface IStrollManager {
* @param _index Index of top up.
* @return _amount The amount of supertoken to top up.
*/
function checkTopUpByIndex(bytes32 _index) external view returns (uint256 _amount);
function checkTopUpByIndex(bytes32 _index)
external
view
returns (uint256 _amount);

/**
* @notice Checks if a top up is required.
Expand Down
86 changes: 79 additions & 7 deletions smart-contracts/test/StrollManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
/* eslint-disable no-undef */

const { parseUnits } = require("@ethersproject/units");
const { expect } = require("chai");
const { expect, assert } = require("chai");

const zeroAddress = "0x0000000000000000000000000000000000000000";
const helper = require("./../helpers/helpers");
const devEnv = require("./utils/setEnv");

const MIN_LOWER = 2;
const MIN_UPPER = 7;
const MIN_LOWER = helper.getSeconds(2);
const MIN_UPPER = helper.getSeconds(5);

let accounts, owner, user, streamReceiver;
let env;
Expand Down Expand Up @@ -370,16 +370,24 @@ describe("#3 - StrollManager: Register TopUps", function () {
strategy.address,
dai.address,
expiry + 1000,
20,
20
helper.getSeconds(20),
helper.getSeconds(20)
);
const topUp = await strollManager.getTopUp(
user.address,
daix.address,
dai.address
);
assert.equal(topUp.lowerLimit, 20, "wrong lowerLimit on update");
assert.equal(topUp.upperLimit, 20, "wrong upperLimit on update");
assert.equal(
topUp.lowerLimit,
helper.getSeconds(20),
"wrong lowerLimit on update"
);
assert.equal(
topUp.upperLimit,
helper.getSeconds(20),
"wrong upperLimit on update"
);
assert.equal(topUp.expiry, expiry + 1000, "wrong expiry on update");
});
});
Expand Down Expand Up @@ -799,4 +807,68 @@ describe("#6 - perform Top Up", function () {
strollManager.performTopUp(user.address, daix.address, dai.address)
).to.be.revertedWith(`TopUpNotRequired("${result}")`);
});
it("Case #6.4 - TopUp using max values (global limits)", async () => {
const flowRate = parseUnits("300", 18).div(
helper.getBigNumber(helper.getSeconds(30))
);
await strollManager
.connect(user)
.createTopUp(
daix.address,
strategy.address,
dai.address,
helper.getTimeStampNow() + helper.getSeconds(365),
helper.getSeconds(5),
helper.getSeconds(5)
);

let tx = await strollManager.setLimits(
helper.getSeconds(6),
helper.getSeconds(8)
);
const limitsChangedEvent = await helper.getEvents(tx, "LimitsChanged");
assert.equal(
limitsChangedEvent[0].args.lowerLimit,
helper.getSeconds(6),
"wrong lower limit"
);
assert.equal(
limitsChangedEvent[0].args.upperLimit,
helper.getSeconds(8),
"wrong upper limit"
);

// approve superToken
await dai.connect(user).approve(daix.address, parseUnits("10000", 18));
// approve strategy
await dai.connect(user).approve(strategy.address, parseUnits("10000", 18));
// get some superToken
await daix.connect(user).upgrade(parseUnits("20", 18));
await createStream(daix, user, streamReceiver, flowRate.toString(), "0x");

let balance = await daix.balanceOf(user.address);
console.log("Balance before increase time: ", balance.toString());

await helper.increaseTime(3600 * 24 * 5);

balance = await daix.balanceOf(user.address);
console.log("Balance after increase time: ", balance.toString());

tx = await strollManager.performTopUp(
user.address,
daix.address,
dai.address
);

const TopUpEvent = await helper.getEvents(tx, "PerformedTopUp");
const after = await daix.balanceOf(user.address);

console.log("After: ", after.toString());

expect(after.sub(balance)).to.be.closeTo(
TopUpEvent[0].args.topUpAmount,
parseUnits("0.01", 18)
);
assert.isAbove(after, balance, "balance should go up");
});
});

0 comments on commit ca2cfac

Please sign in to comment.