Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Added overflow handling for TWAB timestamp #204

Merged
merged 2 commits into from
Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions contracts/DrawCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,16 @@ contract DrawCalculator is IDrawCalculator, Ownable {
IDrawBeacon.Draw[] memory _draws,
IPrizeDistributionHistory.PrizeDistribution[] memory _prizeDistributions
) internal view returns (uint256[] memory) {
uint32[] memory _timestampsWithStartCutoffTimes = new uint32[](_draws.length);
uint32[] memory _timestampsWithEndCutoffTimes = new uint32[](_draws.length);
uint64[] memory _timestampsWithStartCutoffTimes = new uint64[](_draws.length);
uint64[] memory _timestampsWithEndCutoffTimes = new uint64[](_draws.length);

// generate timestamps with draw cutoff offsets included
for (uint32 i = 0; i < _draws.length; i++) {
unchecked {
_timestampsWithStartCutoffTimes[i] = uint32(
_draws[i].timestamp - _prizeDistributions[i].startTimestampOffset
);
_timestampsWithEndCutoffTimes[i] = uint32(
_draws[i].timestamp - _prizeDistributions[i].endTimestampOffset
);
_timestampsWithStartCutoffTimes[i] =
_draws[i].timestamp - _prizeDistributions[i].startTimestampOffset;
_timestampsWithEndCutoffTimes[i] =
_draws[i].timestamp - _prizeDistributions[i].endTimestampOffset;
}
}

Expand Down
34 changes: 17 additions & 17 deletions contracts/Ticket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ contract Ticket is ControlledToken, ITicket {
}

/// @inheritdoc ITicket
function getBalanceAt(address _user, uint256 _target) external view override returns (uint256) {
function getBalanceAt(address _user, uint64 _target) external view override returns (uint256) {
TwabLib.Account storage account = userTwabs[_user];

return
Expand All @@ -90,25 +90,25 @@ contract Ticket is ControlledToken, ITicket {
/// @inheritdoc ITicket
function getAverageBalancesBetween(
address _user,
uint32[] calldata _startTimes,
uint32[] calldata _endTimes
uint64[] calldata _startTimes,
uint64[] calldata _endTimes
) external view override returns (uint256[] memory) {
return _getAverageBalancesBetween(userTwabs[_user], _startTimes, _endTimes);
}

/// @inheritdoc ITicket
function getAverageTotalSuppliesBetween(
uint32[] calldata _startTimes,
uint32[] calldata _endTimes
uint64[] calldata _startTimes,
uint64[] calldata _endTimes
) external view override returns (uint256[] memory) {
return _getAverageBalancesBetween(totalSupplyTwab, _startTimes, _endTimes);
}

/// @inheritdoc ITicket
function getAverageBalanceBetween(
address _user,
uint256 _startTime,
uint256 _endTime
uint64 _startTime,
uint64 _endTime
) external view override returns (uint256) {
TwabLib.Account storage account = userTwabs[_user];

Expand All @@ -123,7 +123,7 @@ contract Ticket is ControlledToken, ITicket {
}

/// @inheritdoc ITicket
function getBalancesAt(address _user, uint32[] calldata _targets)
function getBalancesAt(address _user, uint64[] calldata _targets)
external
view
override
Expand All @@ -139,7 +139,7 @@ contract Ticket is ControlledToken, ITicket {
_balances[i] = TwabLib.getBalanceAt(
twabContext.twabs,
details,
_targets[i],
uint32(_targets[i]),
uint32(block.timestamp)
);
}
Expand All @@ -148,18 +148,18 @@ contract Ticket is ControlledToken, ITicket {
}

/// @inheritdoc ITicket
function getTotalSupplyAt(uint32 _target) external view override returns (uint256) {
function getTotalSupplyAt(uint64 _target) external view override returns (uint256) {
return
TwabLib.getBalanceAt(
totalSupplyTwab.twabs,
totalSupplyTwab.details,
_target,
uint32(_target),
uint32(block.timestamp)
);
}

/// @inheritdoc ITicket
function getTotalSuppliesAt(uint32[] calldata _targets)
function getTotalSuppliesAt(uint64[] calldata _targets)
external
view
override
Expand All @@ -174,7 +174,7 @@ contract Ticket is ControlledToken, ITicket {
totalSupplies[i] = TwabLib.getBalanceAt(
totalSupplyTwab.twabs,
details,
_targets[i],
uint32(_targets[i]),
uint32(block.timestamp)
);
}
Expand Down Expand Up @@ -247,8 +247,8 @@ contract Ticket is ControlledToken, ITicket {
*/
function _getAverageBalancesBetween(
TwabLib.Account storage _account,
uint32[] calldata _startTimes,
uint32[] calldata _endTimes
uint64[] calldata _startTimes,
uint64[] calldata _endTimes
) internal view returns (uint256[] memory) {
require(_startTimes.length == _endTimes.length, "Ticket/start-end-times-length-match");

Expand All @@ -261,8 +261,8 @@ contract Ticket is ControlledToken, ITicket {
averageBalances[i] = TwabLib.getAverageBalanceBetween(
_account.twabs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be stored in a variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which? The _account.twabs ? it's a pointer- wouldn't make a difference

accountDetails,
_startTimes[i],
_endTimes[i],
uint32(_startTimes[i]),
uint32(_endTimes[i]),
currentTimestamp
);
}
Expand Down
20 changes: 10 additions & 10 deletions contracts/interfaces/ITicket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ interface ITicket is IControlledToken {
* @param timestamp Timestamp at which we want to retrieve the TWAB balance.
* @return The TWAB balance at the given timestamp.
*/
function getBalanceAt(address user, uint256 timestamp) external view returns (uint256);
function getBalanceAt(address user, uint64 timestamp) external view returns (uint256);

/**
* @notice Retrieves `user` TWAB balances.
* @param user Address of the user whose TWABs are being fetched.
* @param timestamps Timestamps range at which we want to retrieve the TWAB balances.
* @return `user` TWAB balances.
*/
function getBalancesAt(address user, uint32[] calldata timestamps)
function getBalancesAt(address user, uint64[] calldata timestamps)
external
view
returns (uint256[] memory);
Expand All @@ -149,8 +149,8 @@ interface ITicket is IControlledToken {
*/
function getAverageBalanceBetween(
address user,
uint256 startTime,
uint256 endTime
uint64 startTime,
uint64 endTime
) external view returns (uint256);

/**
Expand All @@ -162,23 +162,23 @@ interface ITicket is IControlledToken {
*/
function getAverageBalancesBetween(
address user,
uint32[] calldata startTimes,
uint32[] calldata endTimes
uint64[] calldata startTimes,
uint64[] calldata endTimes
) external view returns (uint256[] memory);

/**
* @notice Retrieves the total supply TWAB balance at the given timestamp.
* @param timestamp Timestamp at which we want to retrieve the total supply TWAB balance.
* @return The total supply TWAB balance at the given timestamp.
*/
function getTotalSupplyAt(uint32 timestamp) external view returns (uint256);
function getTotalSupplyAt(uint64 timestamp) external view returns (uint256);

/**
* @notice Retrieves the total supply TWAB balance between the given timestamps range.
* @param timestamps Timestamps range at which we want to retrieve the total supply TWAB balance.
* @return Total supply TWAB balances.
*/
function getTotalSuppliesAt(uint32[] calldata timestamps)
function getTotalSuppliesAt(uint64[] calldata timestamps)
external
view
returns (uint256[] memory);
Expand All @@ -190,7 +190,7 @@ interface ITicket is IControlledToken {
* @return The average total supplies held during the time frame.
*/
function getAverageTotalSuppliesBetween(
uint32[] calldata startTimes,
uint32[] calldata endTimes
uint64[] calldata startTimes,
uint64[] calldata endTimes
) external view returns (uint256[] memory);
}
1 change: 1 addition & 0 deletions contracts/libraries/OverflowSafeComparatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ library OverflowSafeComparatorLib {
uint32 _b,
uint32 _timestamp
) internal pure returns (bool) {

// No need to adjust if there hasn't been an overflow
if (_a <= _timestamp && _b <= _timestamp) return _a <= _b;

Expand Down
8 changes: 4 additions & 4 deletions contracts/libraries/TwabLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ library TwabLib {
);

// Difference in amount / time
return (endTwab.amount - startTwab.amount) / (endTwab.timestamp - startTwab.timestamp);
return (endTwab.amount - startTwab.amount) / OverflowSafeComparatorLib.checkedSub(endTwab.timestamp, startTwab.timestamp, _currentTime);
}

/** @notice Searches TWAB history and calculate the difference between amount(s)/timestamp(s) to return average balance
Expand Down Expand Up @@ -278,7 +278,7 @@ library TwabLib {
// The time-weighted average balance uses time measured between two epoch timestamps as
// a constaint on the measurement when calculating the time weighted average balance.
return
(afterOrAt.amount - beforeOrAt.amount) / (afterOrAt.timestamp - beforeOrAt.timestamp);
(afterOrAt.amount - beforeOrAt.amount) / OverflowSafeComparatorLib.checkedSub(afterOrAt.timestamp, beforeOrAt.timestamp, _currentTime);
}

/** @notice Calculates a user TWAB for a target timestamp using the historical TWAB records.
Expand Down Expand Up @@ -339,7 +339,7 @@ library TwabLib {
);

uint224 heldBalance = (afterOrAtStart.amount - beforeOrAtStart.amount) /
(afterOrAtStart.timestamp - beforeOrAtStart.timestamp);
OverflowSafeComparatorLib.checkedSub(afterOrAtStart.timestamp, beforeOrAtStart.timestamp, _time);

return _computeNextTwab(beforeOrAtStart, heldBalance, _targetTimestamp);
}
Expand Down Expand Up @@ -368,6 +368,7 @@ library TwabLib {
}

/// @notice Sets a new TWAB Observation at the next available index and returns the new account details.
/// @dev Note that if _currentTime is before the last observation timestamp, it appears as an overflow
/// @param _twabs The twabs array to insert into
/// @param _accountDetails The current account details
/// @param _currentTime The current time
Expand All @@ -387,7 +388,6 @@ library TwabLib {
)
{
(, ObservationLib.Observation memory _newestTwab) = newestTwab(_twabs, _accountDetails);
require(_currentTime >= _newestTwab.timestamp, "TwabLib/twab-time-monotonic");

// if we're in the same block, return
if (_newestTwab.timestamp == _currentTime) {
Expand Down
87 changes: 60 additions & 27 deletions test/Ticket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async function printTwabs(
debugLog(`Twab ${index} { amount: ${twab.amount}, timestamp: ${twab.timestamp}}`);
});

return twabs
return twabs;
}

describe('Ticket', () => {
Expand All @@ -84,10 +84,10 @@ describe('Ticket', () => {
);

// delegate for each of the users
await ticket.delegate(wallet1.address)
await ticket.connect(wallet2).delegate(wallet2.address)
await ticket.connect(wallet3).delegate(wallet3.address)
await ticket.connect(wallet4).delegate(wallet4.address)
await ticket.delegate(wallet1.address);
await ticket.connect(wallet2).delegate(wallet2.address);
await ticket.connect(wallet3).delegate(wallet3.address);
await ticket.connect(wallet4).delegate(wallet4.address);
});

describe('constructor()', () => {
Expand All @@ -102,7 +102,7 @@ describe('Ticket', () => {
expect(await ticket.name()).to.equal(ticketName);
expect(await ticket.symbol()).to.equal(ticketSymbol);
expect(await ticket.decimals()).to.equal(ticketDecimals);
expect(await ticket.controller()).to.equal(wallet1.address)
expect(await ticket.controller()).to.equal(wallet1.address);
});

it('should fail if token decimal is not greater than 0', async () => {
Expand Down Expand Up @@ -345,13 +345,13 @@ describe('Ticket', () => {
const twabs = await printTwabs(ticket, wallet1, debug);

const matchingTwabs = twabs.reduce((all: any, twab: any) => {
debug(`TWAB timestamp ${twab.timestamp}, timestamp: ${timestamp}`)
debug(twab)
debug(`TWAB timestamp ${twab.timestamp}, timestamp: ${timestamp}`);
debug(twab);
if (twab.timestamp.toString() == timestamp.toString()) {
all.push(twab)
all.push(twab);
}
return all
}, [])
return all;
}, []);

expect(matchingTwabs.length).to.equal(1);
expect(await ticket.totalSupply()).to.equal(mintAmount.mul(2));
Expand Down Expand Up @@ -865,9 +865,7 @@ describe('Ticket', () => {
await ticket.mint(wallet1.address, toWei('100'));
await ticket.delegate(wallet2.address);

await expect(
ticket.delegate(wallet2.address)
).to.not.emit(ticket, 'Delegated')
await expect(ticket.delegate(wallet2.address)).to.not.emit(ticket, 'Delegated');
});

it('should allow the delegate to be reset by passing zero', async () => {
Expand Down Expand Up @@ -942,27 +940,62 @@ describe('Ticket', () => {
it('should allow somone to delegate with a signature', async () => {
// @ts-ignore
const { user, delegate, nonce, deadline, v, r, s } = await delegateSignature({
ticket, userWallet: wallet1, delegate: wallet2.address
})
ticket,
userWallet: wallet1,
delegate: wallet2.address,
});

await ticket.connect(wallet3).delegateWithSignature(user, delegate, deadline, v, r, s)
await ticket.connect(wallet3).delegateWithSignature(user, delegate, deadline, v, r, s);

expect(await ticket.delegateOf(wallet1.address)).to.equal(wallet2.address)
})
})
expect(await ticket.delegateOf(wallet1.address)).to.equal(wallet2.address);
});
});

describe('controllerDelegateFor', () => {
it('should allow the controller to delegate for a user', async () => {
await ticket.controllerDelegateFor(wallet2.address, wallet3.address)
await ticket.controllerDelegateFor(wallet2.address, wallet3.address);

expect(await ticket.delegateOf(wallet2.address)).to.equal(wallet3.address)
})
expect(await ticket.delegateOf(wallet2.address)).to.equal(wallet3.address);
});

it('should not allow anyone else to delegate', async () => {
await expect(
ticket.connect(wallet2).controllerDelegateFor(wallet1.address, wallet3.address)
).to.be.revertedWith('ControlledToken/only-controller')
ticket.connect(wallet2).controllerDelegateFor(wallet1.address, wallet3.address),
).to.be.revertedWith('ControlledToken/only-controller');
});
});

context('when the timestamp overflows', () => {
PierrickGT marked this conversation as resolved.
Show resolved Hide resolved
let overflowMintTimestamp: number;

})
})
beforeEach(async () => {
await ticket.mint(wallet1.address, toWei('100'));
const timestamp = (await ethers.provider.getBlock('latest')).timestamp;
const timeUntilOverflow = 2 ** 32 - timestamp;
await increaseTime(timeUntilOverflow);
await ticket.mint(wallet1.address, toWei('100'));
overflowMintTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await increaseTime(100);
});

describe('getAverageBalanceBetween()', () => {
it('should function across overflow boundary', async () => {
expect(
await ticket.getAverageBalanceBetween(
wallet1.address,
overflowMintTimestamp - 100,
overflowMintTimestamp + 100,
),
).to.equal(toWei('150'));
});
});

describe('getBalanceAt', () => {
it('should function across overflow boundary', async () => {
expect(await ticket.getBalanceAt(wallet1.address, overflowMintTimestamp)).to.equal(
toWei('200'),
);
});
});
});
});
Loading