Skip to content

Commit

Permalink
Re-did maxExitFeeMultiple refactor to maxExitFeeMantissa
Browse files Browse the repository at this point in the history
  • Loading branch information
asselstine committed Jul 22, 2020
1 parent 678929f commit dd55534
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 73 deletions.
29 changes: 13 additions & 16 deletions contracts/prize-pool/PrizePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ abstract contract PrizePool is Initializable, BaseRelayRecipient, ReentrancyGuar
event InstantWithdrawal(address indexed operator, address indexed from, address indexed token, uint256 amount, uint256 exitFee, uint256 sponsoredExitFee);
event TimelockedWithdrawal(address indexed operator, address indexed from, address indexed token, uint256 amount, uint256 unlockTimestamp);
event TimelockedWithdrawalSwept(address indexed operator, address indexed from, uint256 amount);
event PrincipalSupplied(address from, uint256 amount);
event PrincipalRedeemed(address from, uint256 amount);
event PrizeStrategyDetached();

MappedSinglyLinkedList.Mapping internal _tokens;
PrizeStrategyInterface public prizeStrategy;

uint256 public maxExitFeeMultiple;
uint256 public maxExitFeeMantissa;
uint256 public maxTimelockDuration;

uint256 public timelockTotalSupply;
Expand All @@ -49,13 +47,13 @@ abstract contract PrizePool is Initializable, BaseRelayRecipient, ReentrancyGuar
/// @param _trustedForwarder Address of the Forwarding Contract for GSN Meta-Txs
/// @param _prizeStrategy Address of the component-controller that manages the prize-strategy
/// @param _controlledTokens Array of addresses for the Ticket and Sponsorship Tokens controlled by the Prize Pool
/// @param _maxExitFeeMultiple The maximum exit fee size, relative to the withdrawal amount
/// @param _maxExitFeeMantissa The maximum exit fee size, relative to the withdrawal amount
/// @param _maxTimelockDuration The maximum length of time the withdraw timelock could be
function initialize (
address _trustedForwarder,
PrizeStrategyInterface _prizeStrategy,
address[] memory _controlledTokens,
uint256 _maxExitFeeMultiple,
uint256 _maxExitFeeMantissa,
uint256 _maxTimelockDuration
)
public
Expand All @@ -70,7 +68,7 @@ abstract contract PrizePool is Initializable, BaseRelayRecipient, ReentrancyGuar
__ReentrancyGuard_init();
trustedForwarder = _trustedForwarder;
prizeStrategy = _prizeStrategy;
maxExitFeeMultiple = _maxExitFeeMultiple;
maxExitFeeMantissa = _maxExitFeeMantissa;
maxTimelockDuration = _maxTimelockDuration;
}

Expand Down Expand Up @@ -146,13 +144,13 @@ abstract contract PrizePool is Initializable, BaseRelayRecipient, ReentrancyGuar
/// @param from The address to withdraw assets from by redeeming tickets
/// @param amount The amount of assets to redeem for tickets
/// @param controlledToken The address of the asset token being withdrawn
/// @param prepaidExitFee An optional amount of assets paid by the operator used to cover exit fees
/// @param sponsorAmount An optional amount of assets paid by the operator used to cover exit fees
/// @return exitFee The amount of the fairness fee paid
function withdrawInstantlyFrom(
address from,
uint256 amount,
address controlledToken,
uint256 prepaidExitFee
uint256 sponsorAmount
)
external
nonReentrant
Expand All @@ -166,19 +164,18 @@ abstract contract PrizePool is Initializable, BaseRelayRecipient, ReentrancyGuar
exitFee = prizeStrategy.beforeWithdrawInstantlyFrom(from, amount, controlledToken);
}

uint256 mantissa = FixedPoint.calculateMantissa(maxExitFeeMultiple, 100);
uint256 maxFee = FixedPoint.multiplyUintByMantissa(amount, mantissa);
uint256 maxFee = FixedPoint.multiplyUintByMantissa(amount, maxExitFeeMantissa);
if (exitFee > maxFee) {
exitFee = maxFee;
}

address operator = _msgSender();
uint256 sponsoredExitFee = (exitFee > prepaidExitFee) ? prepaidExitFee : exitFee;
uint256 userExitFee = exitFee.sub(sponsoredExitFee);
uint256 sponsoredExitFeePortion = (exitFee > sponsorAmount) ? sponsorAmount : exitFee;
uint256 userExitFee = exitFee.sub(sponsoredExitFeePortion);

if (sponsoredExitFee > 0) {
if (sponsoredExitFeePortion > 0) {
// transfer the fee to this contract
_token().transferFrom(operator, address(this), sponsoredExitFee);
_token().transferFrom(operator, address(this), sponsoredExitFeePortion);
}

// burn the tickets
Expand All @@ -190,10 +187,10 @@ abstract contract PrizePool is Initializable, BaseRelayRecipient, ReentrancyGuar
_token().transfer(from, amountLessFee);

if (hasPrizeStrategy) {
prizeStrategy.afterWithdrawInstantlyFrom(operator, from, amount, controlledToken, exitFee, sponsoredExitFee);
prizeStrategy.afterWithdrawInstantlyFrom(operator, from, amount, controlledToken, exitFee, sponsoredExitFeePortion);
}

emit InstantWithdrawal(operator, from, controlledToken, amount, exitFee, sponsoredExitFee);
emit InstantWithdrawal(operator, from, controlledToken, amount, exitFee, sponsoredExitFeePortion);
}

/// @notice Withdraw assets from the Prize Pool with a timelock on the assets
Expand Down
10 changes: 3 additions & 7 deletions contracts/prize-pool/compound/CompoundPrizePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ contract CompoundPrizePool is PrizePool {
/// @param _trustedForwarder Address of the Forwarding Contract for GSN Meta-Txs
/// @param _prizeStrategy Address of the component-controller that manages the prize-strategy
/// @param _controlledTokens Array of addresses for the Ticket and Sponsorship Tokens controlled by the Prize Pool
/// @param _maxExitFeeMultiple The maximum exit fee size, relative to the withdrawal amount
/// @param _maxExitFeeMantissa The maximum exit fee size, relative to the withdrawal amount
/// @param _maxTimelockDuration The maximum length of time the withdraw timelock could be
/// @param _cToken Address of the Compound cToken interface
function initialize (
address _trustedForwarder,
PrizeStrategyInterface _prizeStrategy,
address[] memory _controlledTokens,
uint256 _maxExitFeeMultiple,
uint256 _maxExitFeeMantissa,
uint256 _maxTimelockDuration,
CTokenInterface _cToken
)
Expand All @@ -38,7 +38,7 @@ contract CompoundPrizePool is PrizePool {
_trustedForwarder,
_prizeStrategy,
_controlledTokens,
_maxExitFeeMultiple,
_maxExitFeeMantissa,
_maxTimelockDuration
);
cToken = _cToken;
Expand Down Expand Up @@ -85,8 +85,6 @@ contract CompoundPrizePool is PrizePool {
IERC20 assetToken = _token();
assetToken.approve(address(cToken), amount);
cToken.mint(amount);

emit PrincipalSupplied(msg.sender, amount);
}

/// @dev Checks with the Prize Pool if a specific token type may be awarded as a prize enhancement
Expand All @@ -101,8 +99,6 @@ contract CompoundPrizePool is PrizePool {
/// @param amount The amount of yield-bearing tokens to be redeemed
function _redeem(uint256 amount) internal override {
cToken.redeemUnderlying(amount);

emit PrincipalRedeemed(msg.sender, amount);
}

/// @dev Gets the underlying asset token used by the Yield Service
Expand Down
8 changes: 4 additions & 4 deletions contracts/prize-strategy/PrizeStrategyBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract PrizeStrategyBuilder is Initializable {
string ticketSymbol;
string sponsorshipName;
string sponsorshipSymbol;
uint256 maxExitFeeMultiple;
uint256 maxExitFeeMantissa;
uint256 maxTimelockDuration;
uint256 exitFeeMantissa;
uint256 creditRateMantissa;
Expand Down Expand Up @@ -70,7 +70,7 @@ contract PrizeStrategyBuilder is Initializable {
config.ticketSymbol,
config.sponsorshipName,
config.sponsorshipSymbol,
config.maxExitFeeMultiple,
config.maxExitFeeMantissa,
config.maxTimelockDuration
);

Expand Down Expand Up @@ -103,7 +103,7 @@ contract PrizeStrategyBuilder is Initializable {
string memory ticketSymbol,
string memory sponsorshipName,
string memory sponsorshipSymbol,
uint256 _maxExitFeeMultiple,
uint256 _maxExitFeeMantissa,
uint256 _maxTimelockDuration
) internal returns (CompoundPrizePool prizePool, address[] memory tokens) {
prizePool = compoundPrizePoolProxyFactory.create();
Expand All @@ -114,7 +114,7 @@ contract PrizeStrategyBuilder is Initializable {
trustedForwarder,
prizeStrategy,
tokens,
_maxExitFeeMultiple,
_maxExitFeeMantissa,
_maxTimelockDuration,
_cToken
);
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/CompoundPrizePoolHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ contract CompoundPrizePoolHarness is CompoundPrizePool {
address _trustedForwarder,
PrizeStrategyInterface _prizeStrategy,
address[] memory _controlledTokens,
uint256 _maxExitFeeMultiple,
uint256 _maxExitFeeMantissa,
uint256 _maxTimelockDuration,
CTokenInterface _cToken
)
Expand All @@ -21,7 +21,7 @@ contract CompoundPrizePoolHarness is CompoundPrizePool {
_trustedForwarder,
_prizeStrategy,
_controlledTokens,
_maxExitFeeMultiple,
_maxExitFeeMantissa,
_maxTimelockDuration,
_cToken
);
Expand Down
4 changes: 2 additions & 2 deletions js/deployTestPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const debug = require('debug')('ptv3:deployTestPool')
async function deployTestPool({
wallet,
prizePeriodSeconds,
maxExitFeePercentage,
maxExitFeeMantissa,
maxTimelockDuration,
exitFee,
creditRate,
Expand Down Expand Up @@ -62,7 +62,7 @@ async function deployTestPool({
forwarder.address,
prizeStrategy.address,
[ticket.address, sponsorship.address],
maxExitFeePercentage || toWei('0.5'),
maxExitFeeMantissa || toWei('0.5'),
maxTimelockDuration || prizePeriodSeconds,
cToken.address
)
Expand Down
50 changes: 10 additions & 40 deletions test/CompoundPrizePool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let overrides = { gasLimit: 20000000 }

const FORWARDER = '0x5f48a3371df0F8077EC741Cc2eB31c84a4Ce332a'

describe('PrizePool contract', function() {
describe('CompoundPrizePool', function() {
let wallet, wallet2

let prizePool, token, cToken, prizeStrategy
Expand Down Expand Up @@ -55,7 +55,7 @@ describe('PrizePool contract', function() {
FORWARDER,
prizeStrategy.address,
[ticket.address],
'50', // Max Exit Fee
toWei('0.5'), // Max Exit Fee
'10000', // Max Timelock
cToken.address
)
Expand Down Expand Up @@ -112,9 +112,6 @@ describe('PrizePool contract', function() {

describe('withdrawWithTimelockFrom()', () => {
it('should allow a user to withdraw with a timelock', async () => {
const currentBlock = await getBlockNumber()
const timelockBlock = currentBlock + 10

// updateAwardBalance
await cToken.mock.balanceOfUnderlying.returns('0')
await ticket.mock.totalSupply.returns('0')
Expand All @@ -125,7 +122,7 @@ describe('PrizePool contract', function() {
// ensure withdraw is later than now
await prizeStrategy.mock.beforeWithdrawWithTimelockFrom
.withArgs(wallet._address, toWei('10'), ticket.address)
.returns(timelockBlock)
.returns('10')

// expect a ticket burn
await ticket.mock.controllerBurnFrom.withArgs(wallet._address, wallet._address, toWei('10')).returns()
Expand All @@ -137,7 +134,7 @@ describe('PrizePool contract', function() {
await prizePool.withdrawWithTimelockFrom(wallet._address, toWei('10'), ticket.address)

expect(await prizePool.timelockBalanceOf(wallet._address)).to.equal(toWei('10'))
expect(await prizePool.timelockBalanceAvailableAt(wallet._address)).to.equal(timelockBlock)
expect(await prizePool.timelockBalanceAvailableAt(wallet._address)).to.equal('10')
expect(await prizePool.timelockTotalSupply()).to.equal(toWei('10'))
})
})
Expand All @@ -160,8 +157,6 @@ describe('PrizePool contract', function() {
})

it('should sweep only balances that are unlocked', async () => {
const currentBlock = await getBlockNumber()

// updateAwardBalance
await cToken.mock.balanceOfUnderlying.returns(toWei('33'))
await ticket.mock.totalSupply.returns('0')
Expand All @@ -173,17 +168,17 @@ describe('PrizePool contract', function() {
await ticket.mock.controllerBurnFrom.returns()

// withdraw for a user, and it's eligible at 10 seconds
await prizeStrategy.mock.beforeWithdrawWithTimelockFrom.returns(currentBlock + 10)
await prizeStrategy.mock.beforeWithdrawWithTimelockFrom.returns(10)
await prizeStrategy.mock.afterWithdrawWithTimelockFrom.withArgs(wallet._address, toWei('11'), ticket.address).returns()
await prizePool.withdrawWithTimelockFrom(wallet._address, toWei('11'), ticket.address)

// withdraw for a user, and it's eligible at 20 seconds
await prizeStrategy.mock.beforeWithdrawWithTimelockFrom.returns(currentBlock + 20)
await prizeStrategy.mock.beforeWithdrawWithTimelockFrom.returns(20)
await prizeStrategy.mock.afterWithdrawWithTimelockFrom.withArgs(wallet2._address, toWei('22'), ticket.address).returns()
await prizePool.withdrawWithTimelockFrom(wallet2._address, toWei('22'), ticket.address)

// Only first deposit is unlocked
await prizePool.setCurrentTime(currentBlock + 15)
await prizePool.setCurrentTime(15)

// expect the redeem && transfer for only the unlocked amount
await cToken.mock.redeemUnderlying.withArgs(toWei('11')).returns('0')
Expand All @@ -201,14 +196,12 @@ describe('PrizePool contract', function() {

// second has not
expect(await prizePool.timelockBalanceOf(wallet2._address)).to.equal(toWei('22'))
expect(await prizePool.timelockBalanceAvailableAt(wallet2._address)).to.equal(currentBlock + 20)
expect(await prizePool.timelockBalanceAvailableAt(wallet2._address)).to.equal(20)

expect(await prizePool.timelockTotalSupply()).to.equal(toWei('22'))
})

it('should sweep timelock balances that have unlocked', async () => {
const currentBlock = await getBlockNumber()

// updateAwardBalance
await cToken.mock.balanceOfUnderlying.returns('0')
await ticket.mock.totalSupply.returns('0')
Expand All @@ -219,7 +212,7 @@ describe('PrizePool contract', function() {
// ensure withdraw is later than now
await prizeStrategy.mock.beforeWithdrawWithTimelockFrom
.withArgs(wallet._address, toWei('10'), ticket.address)
.returns(currentBlock + 10)
.returns(10)

// expect a ticket burn
await ticket.mock.controllerBurnFrom.withArgs(wallet._address, wallet._address, toWei('10')).returns()
Expand All @@ -236,7 +229,7 @@ describe('PrizePool contract', function() {
await prizeStrategy.mock.afterSweepTimelockedWithdrawal.withArgs(wallet._address, wallet._address, toWei('10')).returns()

// ensure time is after
await prizePool.setCurrentTime(currentBlock + 11)
await prizePool.setCurrentTime(11)

// now execute timelock withdrawal
await expect(prizePool.sweepTimelockBalances([wallet._address]))
Expand All @@ -260,29 +253,6 @@ describe('PrizePool contract', function() {
})
})

describe('supply()', () => {
it('should give the first depositer tokens at the initial exchange rate', async function () {
await token.mock.transferFrom.withArgs(wallet._address, prizePool.address, toWei('1')).returns(true)
await token.mock.approve.withArgs(cToken.address, toWei('1')).returns(true)
await cToken.mock.mint.withArgs(toWei('1')).returns(0)

await expect(prizePool.supply(toWei('1')))
.to.emit(prizePool, 'PrincipalSupplied')
.withArgs(wallet._address, toWei('1'))
})
})

describe('redeem()', () => {
it('should allow redeeming principal', async function () {
await cToken.mock.redeemUnderlying.withArgs(toWei('1')).returns('0')
await token.mock.transfer.withArgs(wallet._address, toWei('1')).returns(true)

await expect(prizePool.redeem(toWei('1')))
.to.emit(prizePool, 'PrincipalRedeemed')
.withArgs(wallet._address, toWei('1'));
})
})

describe('balance()', () => {
it('should return zero if no deposits have been made', async () => {
await cToken.mock.balanceOfUnderlying.returns(toWei('11'))
Expand Down
4 changes: 2 additions & 2 deletions test/PrizeStrategyBuilder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('PrizeStrategyBuilder', () => {
ticketSymbol: "TICK",
sponsorshipName: "Sponsorship",
sponsorshipSymbol: "SPON",
maxExitFeeMultiple: 50,
maxExitFeeMantissa: toWei('0.5'),
maxTimelockDuration: 1000,
exitFeeMantissa: toWei('0.1'),
creditRateMantissa: toWei('0.001'),
Expand All @@ -63,7 +63,7 @@ describe('PrizeStrategyBuilder', () => {
expect(await sponsorship.name()).to.equal(config.sponsorshipName)
expect(await sponsorship.symbol()).to.equal(config.sponsorshipSymbol)

expect(await prizePool.maxExitFeeMultiple()).to.equal(config.maxExitFeeMultiple)
expect(await prizePool.maxExitFeeMantissa()).to.equal(config.maxExitFeeMantissa)
expect(await prizePool.maxTimelockDuration()).to.equal(config.maxTimelockDuration)
expect(await prizeStrategy.exitFeeMantissa()).to.equal(config.exitFeeMantissa)
expect(await prizeStrategy.creditRateMantissa()).to.equal(config.creditRateMantissa)
Expand Down

0 comments on commit dd55534

Please sign in to comment.