Skip to content

Commit

Permalink
Add unit-tests for redeemTicketsInstantly
Browse files Browse the repository at this point in the history
  • Loading branch information
robsecord committed Jun 22, 2020
1 parent 768eaf8 commit a7d711e
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 28 deletions.
40 changes: 27 additions & 13 deletions contracts/periodic-prize-pool/PeriodicPrizePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,11 @@ abstract contract PeriodicPrizePool is Timelock, BaseRelayRecipient, ReentrancyG
// Ticket Minting/Redeeming
//

function mintTickets(address to, uint256 amount, bytes calldata data) external nonReentrant {
function mintTickets(address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external nonReentrant {
console.log("PeriodicPrizePool mint tickets: %s", amount);
_token().transferFrom(_msgSender(), address(this), amount);
_supply(amount);
_mintTickets(to, amount, data, "");
_mintTickets(to, amount, data, operatorData);
_mintedTickets(amount);
}

Expand Down Expand Up @@ -362,7 +362,7 @@ abstract contract PeriodicPrizePool is Timelock, BaseRelayRecipient, ReentrancyG
// burn the tickets
_burnTickets(from, tickets, data, operatorData);
// burn the interestTracker
_redeemTicketInterestShares(from, tickets, userInterestRatioMantissa);
_redeemTicketInterestShares(from, tickets, userInterestRatioMantissa, data, operatorData);

// redeem the tickets less the fee
uint256 amount = tickets.sub(exitFee);
Expand Down Expand Up @@ -394,39 +394,53 @@ abstract contract PeriodicPrizePool is Timelock, BaseRelayRecipient, ReentrancyG
return FixedPoint.calculateMantissa(_balanceOfTicketInterest(user, tickets), tickets);
}

function redeemTicketsInstantly(uint256 tickets, bytes calldata data) external nonReentrant returns (uint256) {
function redeemTicketsInstantly(
uint256 tickets,
bytes calldata data,
bytes calldata operatorData
)
external nonReentrant returns (uint256)
{
address sender = _msgSender();
require(__ticket.balanceOf(sender) >= tickets, "Insufficient balance");
uint256 userInterestRatioMantissa = _ticketInterestRatioMantissa(sender);


uint256 exitFee = calculateExitFee(
tickets,
userInterestRatioMantissa
);

// burn the tickets
_burnTickets(sender, tickets, data, "");
_burnTickets(sender, tickets, data, operatorData);

// now calculate how much interest needs to be redeemed to maintain the interest ratio
_redeemTicketInterestShares(sender, tickets, userInterestRatioMantissa);
_redeemTicketInterestShares(sender, tickets, userInterestRatioMantissa, data, operatorData);

uint256 ticketsLessFee = tickets.sub(exitFee);

// redeem the interestTracker less the fee
_redeem(ticketsLessFee);
_token().transfer(sender, ticketsLessFee);

emit TicketsRedeemedInstantly(sender, sender, tickets, exitFee, data, "");
emit TicketsRedeemedInstantly(sender, sender, tickets, exitFee, data, operatorData);

// return the exit fee
return exitFee;
}

function _redeemTicketInterestShares(address sender, uint256 tickets, uint256 userInterestRatioMantissa) internal {
function _redeemTicketInterestShares(
address sender,
uint256 tickets,
uint256 userInterestRatioMantissa,
bytes memory data,
bytes memory operatorData
)
internal
{
uint256 ticketInterest = FixedPoint.multiplyUintByMantissa(tickets, userInterestRatioMantissa);
uint256 burnedShares = redeemCollateral(tickets.add(ticketInterest));
ticketInterestShares[sender] = ticketInterestShares[sender].sub(burnedShares);
ticketCredit.controllerMint(sender, ticketInterest, "", "");
ticketCredit.controllerMint(sender, ticketInterest, data, operatorData);
}

function operatorRedeemTicketsWithTimelock(
Expand Down Expand Up @@ -697,7 +711,7 @@ abstract contract PeriodicPrizePool is Timelock, BaseRelayRecipient, ReentrancyG
// otherwise we need to transfer the collateral from one user to the other
// the from's collateralization will increase, so credit them
uint256 fromTicketInterestRatio = _ticketInterestRatioMantissa(from);
_redeemTicketInterestShares(from, amount, fromTicketInterestRatio);
_redeemTicketInterestShares(from, amount, fromTicketInterestRatio, "", "");
_mintTicketInterestShares(to, amount);
}

Expand Down
9 changes: 7 additions & 2 deletions contracts/test/CompoundPeriodicPrizePoolHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ contract CompoundPeriodicPrizePoolHarness is CompoundPeriodicPrizePool {
previousPrize = _previousPrize;
}

function setPrizeAverageTickets(uint256 _prizeAverageTickets) external {
prizeAverageTickets = _prizeAverageTickets;
}

function setCurrentTime(uint256 _time) external {
time = _time;
}
Expand All @@ -26,8 +30,9 @@ contract CompoundPeriodicPrizePoolHarness is CompoundPeriodicPrizePool {
return time;
}

function setInterestSharesForTest(address user, uint256 amount) external {
ticketInterestShares[user] = amount;
function setInterestSharesForTest(address user, uint256 _collateral) external {
uint256 shares = FixedPoint.divideUintByMantissa(_collateral, _exchangeRateMantissa());
ticketInterestShares[user] = shares;
}

function setSponsorshipInterestSharesForTest(address user, uint256 amount) external {
Expand Down
21 changes: 11 additions & 10 deletions test/Integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {

const toWei = ethers.utils.parseEther
const fromWei = ethers.utils.formatEther
const EMPTY_STR = []

const debug = require('debug')('ptv3:Integration.test')

Expand Down Expand Up @@ -82,7 +83,7 @@ describe('Integration Test', () => {

let tx, receipt

await prizePool.mintTickets(wallet._address, toWei('100'), [], overrides)
await prizePool.mintTickets(wallet._address, toWei('100'), EMPTY_STR, EMPTY_STR, overrides)

debug('Accrue custom...')

Expand All @@ -98,7 +99,7 @@ describe('Integration Test', () => {

debug('completing award...')

await prizeStrategy.completeAward(prizePool.address, [])
await prizeStrategy.completeAward(prizePool.address, EMPTY_STR)

debug('completed award')

Expand All @@ -107,13 +108,13 @@ describe('Integration Test', () => {

debug('Redeem tickets with timelock...')

await prizePool.redeemTicketsWithTimelock(toWei('122'), [])
await prizePool.redeemTicketsWithTimelock(toWei('122'), EMPTY_STR)

debug('Second award...')

await increaseTime(prizePeriodSeconds * 2)
await prizeStrategy.startAward(prizePool.address)
await prizeStrategy.completeAward(prizePool.address, [])
await prizeStrategy.completeAward(prizePool.address, EMPTY_STR)

debug('Sweep timelocked funds...')

Expand All @@ -127,7 +128,7 @@ describe('Integration Test', () => {
it('should support instant redemption', async () => {
debug('Minting tickets...')
await token.approve(prizePool.address, toWei('100'))
await prizePool.mintTickets(wallet._address, toWei('100'), [], overrides)
await prizePool.mintTickets(wallet._address, toWei('100'), EMPTY_STR, EMPTY_STR, overrides)

debug('accruing...')

Expand All @@ -139,7 +140,7 @@ describe('Integration Test', () => {

debug('redeeming tickets...')

await prizePool.redeemTicketsInstantly(toWei('100'), [])
await prizePool.redeemTicketsInstantly(toWei('100'), EMPTY_STR, EMPTY_STR)

debug('checking balance...')

Expand All @@ -155,7 +156,7 @@ describe('Integration Test', () => {

debug('1.2')

await prizePool2.mintTickets(wallet2._address, toWei('100'), [], overrides)
await prizePool2.mintTickets(wallet2._address, toWei('100'), EMPTY_STR, EMPTY_STR, overrides)

debug('1.5')

Expand All @@ -167,19 +168,19 @@ describe('Integration Test', () => {

// second user has not collateralized
await token.approve(prizePool.address, toWei('100'))
await prizePool.mintTickets(wallet._address, toWei('100'), [], overrides)
await prizePool.mintTickets(wallet._address, toWei('100'), EMPTY_STR, EMPTY_STR, overrides)

debug('3')

await prizeStrategy.startAward(prizePool.address)
await prizeStrategy.completeAward(prizePool.address, [])
await prizeStrategy.completeAward(prizePool.address, EMPTY_STR)

debug('4')

// when second user withdraws, they must pay a fee
let balanceBeforeWithdrawal = await token.balanceOf(wallet._address)

await prizePool.redeemTicketsInstantly(toWei('100'), [])
await prizePool.redeemTicketsInstantly(toWei('100'), EMPTY_STR, EMPTY_STR)

debug('5')

Expand Down
55 changes: 54 additions & 1 deletion test/PeriodicPrizePool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('PeriodicPrizePool contract', function() {

let ticket, ticketCredit, sponsorship, sponsorshipCredit

let prizePeriodSeconds = toWei('1000')
let prizePeriodSeconds = 1000

beforeEach(async () => {
[wallet, wallet2] = await buidler.ethers.getSigners()
Expand Down Expand Up @@ -99,6 +99,55 @@ describe('PeriodicPrizePool contract', function() {
})
})

//
// Ticket Minting/Redeeming
//

describe('redeemTicketsInstantly()', () => {
it('should not allow a user to redeem more tickets than they hold', async () => {
const amount = toWei('10')

// Pre-fund
await prizePool.setInterestSharesForTest(wallet._address, amount)

// Mocks
await cToken.mock.balanceOfUnderlying.withArgs(prizePool.address).returns(amount)
await ticket.mock.balanceOf.withArgs(wallet._address).returns(amount)

// Test revert
await expect(prizePool.redeemTicketsInstantly(amount.mul(2), EMPTY_STR, EMPTY_STR))
.to.be.revertedWith('Insufficient balance')
})

it('should allow a user to redeem their tickets', async () => {
const averagePrize = toWei('100')
const amount = toWei('10')

// Pre-fund
await cToken.mock.balanceOfUnderlying.withArgs(prizePool.address).returns(toWei('0'))
await prizePool.supplyCollateralForTest(amount)
await prizePool.setInterestSharesForTest(wallet._address, amount)
await prizePool.setPrizeAverageTickets(averagePrize)

// Mocks
await cToken.mock.balanceOfUnderlying.withArgs(prizePool.address).returns(amount)
await ticket.mock.balanceOf.withArgs(wallet._address).returns(amount)
await cToken.mock.redeemUnderlying.withArgs(amount).returns(amount)
await token.mock.transfer.withArgs(wallet._address, amount).returns(true)
await ticket.mock.controllerBurn.withArgs(wallet._address, amount, EMPTY_STR, EMPTY_STR).returns()
await ticketCredit.mock.controllerMint.withArgs(wallet._address, toWei('0'), EMPTY_STR, EMPTY_STR).returns()

// Test redeemTicketsInstantly
await expect(prizePool.redeemTicketsInstantly(amount, EMPTY_STR, EMPTY_STR))
.to.emit(prizePool, 'TicketsRedeemedInstantly')
.withArgs(wallet._address, wallet._address, amount, toWei('0'), EMPTY_STR, EMPTY_STR)
})
})

//
// Sponsorship Minting/Redeeming
//

describe('supplySponsorship()', () => {
it('should mint sponsorship tokens', async () => {
const supplyAmount = toWei('10')
Expand Down Expand Up @@ -202,6 +251,10 @@ describe('PeriodicPrizePool contract', function() {
})
})

//
// Sponsorship Sweep
//

describe('sweepSponsorship()', () => {
it('should allow anyone to sweep sponsorship for a list of users', async () => {
const amounts = [toWei('10'), toWei('98765'), toWei('100'), toWei('100000000'), toWei('10101101')]
Expand Down
5 changes: 3 additions & 2 deletions test/features/support/PoolEnv.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const debug = require('debug')('ptv3:cucumber:world')

const toWei = (val) => ethers.utils.parseEther('' + val)
const fromWei = (val) => ethers.utils.formatEther('' + val)
const EMPTY_STR = []

function PoolEnv() {

Expand Down Expand Up @@ -61,7 +62,7 @@ function PoolEnv() {
}

await token.approve(prizePool.address, amount, this.overrides)
await prizePool.mintTickets(wallet._address, amount, [], this.overrides)
await prizePool.mintTickets(wallet._address, amount, EMPTY_STR, EMPTY_STR, this.overrides)

debug(`Bought tickets`)
}
Expand Down Expand Up @@ -127,7 +128,7 @@ function PoolEnv() {
await this.env.rng.setRandomNumber(randomNumber, this.overrides)

debug(`Completing award...`)
await this.env.prizeStrategy.completeAward(this._prizePool.address, [], this.overrides)
await this.env.prizeStrategy.completeAward(this._prizePool.address, EMPTY_STR, this.overrides)

debug('award completed')

Expand Down

0 comments on commit a7d711e

Please sign in to comment.