Skip to content

Commit

Permalink
Added ability for users to convert timelock back into deposit (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
asselstine committed Jul 23, 2020
1 parent 41bd925 commit 3fddcc1
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 1 deletion.
24 changes: 24 additions & 0 deletions contracts/prize-pool/PrizePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ abstract contract PrizePool is OwnableUpgradeSafe, BaseRelayRecipient, Reentranc
}

event CapturedAward(uint256 amount);
event TimelockDeposited(address indexed operator, address indexed to, address indexed token, uint256 amount);
event Deposited(address indexed operator, address indexed to, address indexed token, uint256 amount);
event Awarded(address indexed winner, address indexed token, uint256 amount);
event AwardedExternal(address indexed winner, address indexed token, uint256 amount);
Expand Down Expand Up @@ -123,6 +124,29 @@ abstract contract PrizePool is OwnableUpgradeSafe, BaseRelayRecipient, Reentranc
return _canAwardExternal(_externalToken);
}

function timelockDepositTo(
address to,
uint256 amount,
address controlledToken
)
external
onlyControlledToken(controlledToken)
nonReentrant
{
require(_hasPrizeStrategy(), "PrizePool/prize-strategy-detached");
_updateAwardBalance();

address operator = _msgSender();

ControlledToken(controlledToken).controllerMint(to, amount);
timelockBalances[operator] = timelockBalances[operator].sub(amount);
timelockTotalSupply = timelockTotalSupply.sub(amount);

prizeStrategy.afterTimelockDepositTo(operator, to, amount, controlledToken);

emit TimelockDeposited(operator, to, controlledToken, amount);
}

/// @notice Deposit assets into the Prize Pool to Purchase Tickets
/// @param to The address receiving the Tickets
/// @param amount The amount of assets to deposit to purchase tickets
Expand Down
10 changes: 9 additions & 1 deletion contracts/prize-pool/PrizeStrategyInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ interface PrizeStrategyInterface {
function beforeTokenTransfer(address from, address to, uint256 amount, address controlledToken) external;

/// @dev Inheriting contract must handle deposits into the Prize Pool and account for balance changes
/// @param to The address of the account who performed the deposit
/// @param to The address of the account who is receiving the deposit
/// @param amount The amount of the deposit to account for
/// @param controlledToken The address of the token that was deposited
function afterDepositTo(address to, uint256 amount, address controlledToken) external;

/// @notice Called by the Prize Pool after a user converts their timelocked tokens into a deposit
/// @dev Inheriting contract must handle deposits into the Prize Pool and account for balance changes
/// @param operator The user whose timelock was re-deposited
/// @param to The address of the account who is receiving the deposit
/// @param amount The amount of the deposit to account for
/// @param controlledToken The address of the token that was deposited
function afterTimelockDepositTo(address operator, address to, uint256 amount, address controlledToken) external;

/// @dev Inheriting contract must provide a view into the unlock timestamp for a timelocked withdrawal
/// @param from The address of the account to withdraw from
/// @param amount The amount of the withdrawal to account for
Expand Down
26 changes: 26 additions & 0 deletions contracts/prize-strategy/PrizeStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,32 @@ contract PrizeStrategy is PrizeStrategyStorage,
/// @param amount The amount of collateral they deposited
/// @param controlledToken The type of collateral they deposited
function afterDepositTo(address to, uint256 amount, address controlledToken) external override onlyPrizePool requireNotLocked {
_afterDepositTo(to, amount, controlledToken);
}

/// @notice Called by the prize pool after a deposit has been made.
/// @param to The user who deposited collateral
/// @param amount The amount of collateral they deposited
/// @param controlledToken The type of collateral they deposited
function afterTimelockDepositTo(
address,
address to,
uint256 amount,
address controlledToken
)
external
override
onlyPrizePool
requireNotLocked
{
_afterDepositTo(to, amount, controlledToken);
}

/// @notice Called by the prize pool after a deposit has been made.
/// @param to The user who deposited collateral
/// @param amount The amount of collateral they deposited
/// @param controlledToken The type of collateral they deposited
function _afterDepositTo(address to, uint256 amount, address controlledToken) internal {
if (controlledToken == address(ticket)) {
uint256 toBalance = ticket.balanceOf(to);
_accrueCredit(to, toBalance.sub(amount));
Expand Down
45 changes: 45 additions & 0 deletions test/features/support/PoolEnv.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ function PoolEnv() {
return await buidler.ethers.getContractAt('ControlledToken', ticketAddress, wallet)
}

this.sponsorship = async function (wallet) {
let prizePool = await this.prizeStrategy(wallet)
let sponsorshipAddress = await prizePool.sponsorship()
return await buidler.ethers.getContractAt('ControlledToken', sponsorshipAddress, wallet)
}

this.wallet = async function (id) {
let wallet = this.wallets[id]
return wallet
Expand Down Expand Up @@ -101,6 +107,38 @@ function PoolEnv() {
debug(`Bought tickets`)
}

this.timelockBuyTickets = async function ({ user, tickets }) {
debug(`Buying tickets with timelocked tokens...`)
let wallet = await this.wallet(user)

debug('wallet is ', wallet._address)

let ticket = await this.ticket(wallet)
let prizePool = await this.prizePool(wallet)

let amount = toWei('' + tickets)

await prizePool.timelockDepositTo(wallet._address, amount, ticket.address, this.overrides)

debug(`Bought tickets with timelocked tokens`)
}

this.timelockBuySponsorship = async function ({ user, sponsorship }) {
debug(`Buying sponsorship with timelocked tokens...`)
let wallet = await this.wallet(user)

debug('wallet is ', wallet._address)

let sponsorshipContract = await this.sponsorship(wallet)
let prizePool = await this.prizePool(wallet)

let amount = toWei('' + sponsorship)

await prizePool.timelockDepositTo(wallet._address, amount, sponsorshipContract.address, this.overrides)

debug(`Bought sponsorship with timelocked tokens`)
}

this.buyTicketsAtTime = async function ({ user, tickets, elapsed }) {
await this.atTime(elapsed, async () => {
await this.buyTickets({ user, tickets })
Expand Down Expand Up @@ -135,6 +173,13 @@ function PoolEnv() {
expect(await token.balanceOf(wallet._address)).to.equalish(amount, 300)
}

this.expectUserToHaveSponsorship = async function ({ user, sponsorship }) {
let wallet = await this.wallet(user)
let sponsorshipContract = await this.sponsorship(wallet)
let amount = toWei(sponsorship)
expect(await sponsorshipContract.balanceOf(wallet._address)).to.equalish(amount, 300)
}

this.poolAccrues = async function ({ tickets }) {
debug(`poolAccrues(${tickets.toString()})...`)
await this.env.cToken.accrueCustom(toWei(tickets))
Expand Down
48 changes: 48 additions & 0 deletions test/features/timelockDeposit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { PoolEnv } = require('./support/PoolEnv')

describe('Re-deposit Timelocked Tokens', () => {

let env

beforeEach(() => {
env = new PoolEnv()
})

describe('convert timelock to tickets', () => {
it('should allow the user to re-deposit timelock as tickets', async () => {
await env.createPool({ prizePeriodSeconds: 10, exitFee: '0.1', creditRate: '0.01' })
// buy at time zero so that it is considered a 'full' ticket
await env.buyTicketsAtTime({ user: 1, tickets: 100, elapsed: 0 })
await env.withdrawWithTimelockAtTime({ user: 1, tickets: 100, elapsed: 0 })

// tickets are converted to timelock
await env.expectUserToHaveTimelock({ user: 1, timelock: 100 })
await env.expectUserTimelockAvailableAt({ user: 1, elapsed: 10 })

await env.timelockBuyTickets({ user: 1, tickets: 100 })

// expect balance
await env.expectUserToHaveTickets({ user: 1, tickets: 100 })
await env.expectUserToHaveTimelock({ user: 1, timelock: 0 })
})
})

describe('convert timelock to sponsorship', () => {
it('should allow the user to re-deposit timelock as tickets', async () => {
await env.createPool({ prizePeriodSeconds: 10, exitFee: '0.1', creditRate: '0.01' })
// buy at time zero so that it is considered a 'full' ticket
await env.buyTicketsAtTime({ user: 1, tickets: 100, elapsed: 0 })
await env.withdrawWithTimelockAtTime({ user: 1, tickets: 100, elapsed: 0 })

// tickets are converted to timelock
await env.expectUserToHaveTimelock({ user: 1, timelock: 100 })
await env.expectUserTimelockAvailableAt({ user: 1, elapsed: 10 })

await env.timelockBuySponsorship({ user: 1, sponsorship: 100 })

// expect balance
await env.expectUserToHaveSponsorship({ user: 1, sponsorship: 100 })
await env.expectUserToHaveTimelock({ user: 1, timelock: 0 })
})
})
})

0 comments on commit 3fddcc1

Please sign in to comment.