Skip to content

Commit

Permalink
♻️ staking: use 18 extra decimals for rewards rate
Browse files Browse the repository at this point in the history
  • Loading branch information
itofarina committed Aug 15, 2024
1 parent aa190d2 commit 491d07e
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 102 deletions.
152 changes: 76 additions & 76 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -461,100 +461,100 @@ StakedEXATest:invariantNoDuplicatedReward() (runs: 10, calls: 5000, reverts: 0)
StakedEXATest:invariantRewardsUpOnly() (runs: 10, calls: 5000, reverts: 0)
StakedEXATest:invariantShareValueIsOne() (runs: 10, calls: 5000, reverts: 0)
StakedEXATest:testAlreadyListedError() (gas: 43818)
StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1249326, ~: 1280096)
StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1235413, ~: 1266183)
StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 343934, ~: 350657)
StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1249203, ~: 1277663)
StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1235695, ~: 1264155)
StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 344091, ~: 350818)
StakedEXATest:testBalanceOfWithdraw(uint256) (runs: 256, μ: 60559, ~: 60566)
StakedEXATest:testCanChangeRewardsDurationWhenDisabled() (gas: 174255)
StakedEXATest:testClaimAfterHarvest() (gas: 854537)
StakedEXATest:testClaimAndUnstake() (gas: 1579981)
StakedEXATest:testClaimAndWithdrawAfterRefTime() (gas: 1078286)
StakedEXATest:testClaimBeforeFirstHarvest() (gas: 528369)
StakedEXATest:testDepositClaimsRewardsToReceiver() (gas: 1111826)
StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 352574, ~: 352309)
StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 834811, ~: 758223)
StakedEXATest:testDepositToAnotherWithAllowance() (gas: 397241)
StakedEXATest:testCanChangeRewardsDurationWhenDisabled() (gas: 174316)
StakedEXATest:testClaimAfterHarvest() (gas: 852964)
StakedEXATest:testClaimAndUnstake() (gas: 1577357)
StakedEXATest:testClaimAndWithdrawAfterRefTime() (gas: 1075747)
StakedEXATest:testClaimBeforeFirstHarvest() (gas: 526906)
StakedEXATest:testDepositClaimsRewardsToReceiver() (gas: 1110724)
StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 352738, ~: 352470)
StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 828763, ~: 757828)
StakedEXATest:testDepositToAnotherWithAllowance() (gas: 397402)
StakedEXATest:testDepositToAnotherWithoutAllowanceShouldFail() (gas: 122801)
StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1786870, ~: 1842082)
StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 35382, ~: 35668)
StakedEXATest:testEmergencyAdminCanPauseNotUnpause() (gas: 159215)
StakedEXATest:testFinishDistributionEmitEvent() (gas: 392727)
StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1590807)
StakedEXATest:testFinishDistributionStopsEmission() (gas: 1570525)
StakedEXATest:testFinishDistributionThatAlreadyFinished() (gas: 429609)
StakedEXATest:testFinishDistributionTransfersRemainingToSavings() (gas: 117495)
StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1781021, ~: 1837583)
StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 35380, ~: 35660)
StakedEXATest:testEmergencyAdminCanPauseNotUnpause() (gas: 159219)
StakedEXATest:testFinishDistributionEmitEvent() (gas: 392949)
StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1586309)
StakedEXATest:testFinishDistributionStopsEmission() (gas: 1566305)
StakedEXATest:testFinishDistributionThatAlreadyFinished() (gas: 429635)
StakedEXATest:testFinishDistributionTransfersRemainingToSavings() (gas: 117624)
StakedEXATest:testGrantRevokeEmergencyAdmin() (gas: 107572)
StakedEXATest:testGrantRevokePauser() (gas: 107213)
StakedEXATest:testHandlerClaim(uint8) (runs: 256, μ: 302595, ~: 302595)
StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 812097, ~: 823337)
StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 332132, ~: 329784)
StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 127386, ~: 123288)
StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 148067, ~: 169879)
StakedEXATest:testHandlerClaim(uint8) (runs: 256, μ: 302579, ~: 302579)
StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 810557, ~: 821891)
StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 331826, ~: 329949)
StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 132150, ~: 123615)
StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 151693, ~: 170012)
StakedEXATest:testHandlerSetMarket() (gas: 124946)
StakedEXATest:testHandlerWithdraw(uint256) (runs: 256, μ: 70108, ~: 70115)
StakedEXATest:testHarvest() (gas: 185153)
StakedEXATest:testHarvestAmountWithReducedAllowance() (gas: 202987)
StakedEXATest:testHarvestEffectOnRewardData() (gas: 178155)
StakedEXATest:testHarvestEmitsRewardAmountNotified() (gas: 176174)
StakedEXATest:testHarvestFailDoesntDoSDeposits() (gas: 360531)
StakedEXATest:testHarvestWhenFinished() (gas: 308488)
StakedEXATest:testHarvestZero() (gas: 255193)
StakedEXATest:testInitialValues() (gas: 89226)
StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 64191, ~: 64309)
StakedEXATest:testMaxRewardsGasConsumption() (gas: 138524347)
StakedEXATest:testMultipleClaimsVsOne() (gas: 25445352)
StakedEXATest:testMultipleHarvests() (gas: 436412)
StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1581769, ~: 1588378)
StakedEXATest:testHarvest() (gas: 185326)
StakedEXATest:testHarvestAmountWithReducedAllowance() (gas: 203152)
StakedEXATest:testHarvestEffectOnRewardData() (gas: 178243)
StakedEXATest:testHarvestEmitsRewardAmountNotified() (gas: 176339)
StakedEXATest:testHarvestFailDoesntDoSDeposits() (gas: 360622)
StakedEXATest:testHarvestWhenFinished() (gas: 308714)
StakedEXATest:testHarvestZero() (gas: 255518)
StakedEXATest:testInitialValues() (gas: 89426)
StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 64354, ~: 64473)
StakedEXATest:testMaxRewardsGasConsumption() (gas: 138485964)
StakedEXATest:testMultipleClaimsVsOne() (gas: 25392620)
StakedEXATest:testMultipleHarvests() (gas: 436741)
StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1577773, ~: 1584376)
StakedEXATest:testNotPausingRoleError() (gas: 39589)
StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 129975, ~: 129907)
StakedEXATest:testNotifyRewardWithUnderlyingAsset() (gas: 485748)
StakedEXATest:testOnlyAdminEnableReward() (gas: 1198990)
StakedEXATest:testOnlyAdminFinishDistribution() (gas: 196446)
StakedEXATest:testOnlyAdminNotifyRewardAmount() (gas: 203421)
StakedEXATest:testOnlyAdminSetProvider() (gas: 143580)
StakedEXATest:testOnlyAdminSetProviderRatio() (gas: 143273)
StakedEXATest:testOnlyAdminSetRewardsDuration() (gas: 152933)
StakedEXATest:testOnlyAdminSetSavings() (gas: 141099)
StakedEXATest:testPausable() (gas: 1169123)
StakedEXATest:testPausableClaim() (gas: 627637)
StakedEXATest:testPausableHarvest() (gas: 331855)
StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 130218, ~: 130162)
StakedEXATest:testNotifyRewardWithUnderlyingAsset() (gas: 485967)
StakedEXATest:testOnlyAdminEnableReward() (gas: 1198994)
StakedEXATest:testOnlyAdminFinishDistribution() (gas: 196511)
StakedEXATest:testOnlyAdminNotifyRewardAmount() (gas: 203589)
StakedEXATest:testOnlyAdminSetProvider() (gas: 143584)
StakedEXATest:testOnlyAdminSetProviderRatio() (gas: 143277)
StakedEXATest:testOnlyAdminSetRewardsDuration() (gas: 152937)
StakedEXATest:testOnlyAdminSetSavings() (gas: 141103)
StakedEXATest:testPausable() (gas: 1165533)
StakedEXATest:testPausableClaim() (gas: 627798)
StakedEXATest:testPausableHarvest() (gas: 332016)
StakedEXATest:testPauserCanPauseUnpause() (gas: 157853)
StakedEXATest:testPenaltyGrowthRange() (gas: 67211)
StakedEXATest:testPenaltyThresholdRange() (gas: 37112)
StakedEXATest:testPermitAndDeposit() (gas: 424117)
StakedEXATest:testPermitFailKeepsFlow() (gas: 473086)
StakedEXATest:testRemoveDepositAllowance() (gas: 489720)
StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1028086, ~: 1027837)
StakedEXATest:testResetStake() (gas: 1017878)
StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 104677, ~: 105546)
StakedEXATest:testPermitAndDeposit() (gas: 424278)
StakedEXATest:testPermitFailKeepsFlow() (gas: 473247)
StakedEXATest:testRemoveDepositAllowance() (gas: 489881)
StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1026451, ~: 1026199)
StakedEXATest:testResetStake() (gas: 1016371)
StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 106014, ~: 105746)
StakedEXATest:testRewardNotListedError() (gas: 1109543)
StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 802085, ~: 851002)
StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1589330, ~: 1588999)
StakedEXATest:testRewardsDurationSetEvent(uint40) (runs: 256, μ: 52074, ~: 52063)
StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 801042, ~: 849747)
StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1585249, ~: 1584916)
StakedEXATest:testRewardsDurationSetEvent(uint40) (runs: 256, μ: 52074, ~: 52057)
StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 58951, ~: 59204)
StakedEXATest:testSetMarketAddressZero() (gas: 37102)
StakedEXATest:testSetMarketOnlyAdmin() (gas: 1272533)
StakedEXATest:testSetMarketOnlyAdmin() (gas: 1272537)
StakedEXATest:testSetMaxRewardsTokensExceeded() (gas: 104944739)
StakedEXATest:testSetMinTime() (gas: 82094)
StakedEXATest:testSetPenaltyGrowth() (gas: 82192)
StakedEXATest:testSetPenaltyThreshold() (gas: 81970)
StakedEXATest:testSetMinTime() (gas: 82098)
StakedEXATest:testSetPenaltyGrowth() (gas: 82196)
StakedEXATest:testSetPenaltyThreshold() (gas: 81974)
StakedEXATest:testSetProviderRatioOverOneError() (gas: 37135)
StakedEXATest:testSetSavingsZeroAddressError() (gas: 37196)
StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 343471, ~: 350194)
StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 343628, ~: 350355)
StakedEXATest:testTotalSupplyWithdraw(uint256) (runs: 256, μ: 62073, ~: 62080)
StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 363144, ~: 370768)
StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 508865, ~: 508612)
StakedEXATest:testWithdrawRewardUnderlyingAsset() (gas: 466586)
StakedEXATest:testWithdrawRewardsOnlyAdmin() (gas: 227918)
StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 363301, ~: 370929)
StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 507409, ~: 507153)
StakedEXATest:testWithdrawRewardUnderlyingAsset() (gas: 466747)
StakedEXATest:testWithdrawRewardsOnlyAdmin() (gas: 227922)
StakedEXATest:testWithdrawRewardsOnlyReward() (gas: 1109561)
StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1065379, ~: 1132181)
StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 868194, ~: 867941)
StakedEXATest:testZeroRateError() (gas: 58048)
StakingPreviewerTest:testAllClaimable() (gas: 452415)
StakingPreviewerTest:testAllClaimed() (gas: 655167)
StakingPreviewerTest:testAllEarned() (gas: 338489)
StakingPreviewerTest:testAllRewards() (gas: 507362)
StakingPreviewerTest:testStaking() (gas: 558429)
StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1063466, ~: 1129102)
StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 866619, ~: 866363)
StakedEXATest:testZeroRateError() (gas: 58139)
StakingPreviewerTest:testAllClaimable() (gas: 451426)
StakingPreviewerTest:testAllClaimed() (gas: 654718)
StakingPreviewerTest:testAllEarned() (gas: 337500)
StakingPreviewerTest:testAllRewards() (gas: 505293)
StakingPreviewerTest:testStaking() (gas: 556360)
SwapperTest:testSwapBasic() (gas: 216831)
SwapperTest:testSwapWithAllowance() (gas: 481530)
SwapperTest:testSwapWithInaccurateSlippageSendsETHToAccount() (gas: 297968)
Expand Down
14 changes: 6 additions & 8 deletions contracts/StakedEXA.sol
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,15 @@ contract StakedEXA is
updateIndex(reward);
RewardData storage rewardData = rewards[reward];
if (block.timestamp >= rewardData.finishAt) {
rewardData.rate = amount / rewardData.duration;
rewardData.rate = (amount * 1e18) / rewardData.duration;
} else {
uint256 remainingRewards = (rewardData.finishAt - block.timestamp) * rewardData.rate;
rewardData.rate = (amount + remainingRewards) / rewardData.duration;
rewardData.rate = (amount * 1e18 + remainingRewards) / rewardData.duration;
}

if (rewardData.rate == 0) revert ZeroRate();
if (
rewardData.rate * rewardData.duration >
uint256(rewardData.duration).mulWadDown(rewardData.rate) >
reward.balanceOf(address(this)) - (address(reward) == asset() ? totalAssets() : 0)
) revert InsufficientBalance();

Expand Down Expand Up @@ -297,9 +297,7 @@ contract StakedEXA is

return
rewardData.index +
(rewardData.rate * (lastTimeRewardApplicable(rewardData.finishAt) - rewardData.updatedAt)).divWadDown(
totalSupply()
);
(lastTimeRewardApplicable(rewardData.finishAt) - rewardData.updatedAt).mulDivDown(rewardData.rate, totalSupply());
}

/// @notice Returns the average index for a reward token and account.
Expand Down Expand Up @@ -446,7 +444,7 @@ contract StakedEXA is
if (block.timestamp < rewards[reward].finishAt) {
uint256 finishAt = rewards[reward].finishAt;
rewards[reward].finishAt = uint40(block.timestamp);
reward.safeTransfer(savings, (finishAt - block.timestamp) * rewards[reward].rate);
reward.safeTransfer(savings, (finishAt - block.timestamp).mulWadDown(rewards[reward].rate));
}

if (reward == IERC20(address(market.asset()))) setProvider(address(0));
Expand Down Expand Up @@ -614,7 +612,7 @@ struct RewardData {
uint40 finishAt;
uint40 updatedAt;
uint256 index;
uint256 rate;
uint256 rate; // with 18 decimals
}

struct Permit {
Expand Down
38 changes: 20 additions & 18 deletions test/StakedEXA.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,9 @@ contract StakedEXATest is Test {
if (rDuration == 0) vm.expectRevert(stdError.divisionError);
else if (
(
block.timestamp >= finishAt ? assets / rDuration : (assets + ((finishAt - block.timestamp) * rate)) / rDuration
block.timestamp >= finishAt
? (uint256(assets) * 1e18) / rDuration
: (uint256(assets) * 1e18 + (finishAt - block.timestamp) * rate) / rDuration
) == 0
) vm.expectRevert(ZeroRate.selector);
stEXA.notifyRewardAmount(reward, assets);
Expand All @@ -462,7 +464,7 @@ contract StakedEXATest is Test {
(, uint40 finishAt, , , uint256 rate) = stEXA.rewards(reward);

if (finishAt > block.timestamp) {
uint256 remainingRewards = rate * (finishAt - block.timestamp);
uint256 remainingRewards = rate.mulWadDown(finishAt - block.timestamp);

stEXA.finishDistribution(reward);
assertEq(reward.balanceOf(SAVINGS), savingsBalance + remainingRewards, "missing remaining savings");
Expand Down Expand Up @@ -494,15 +496,15 @@ contract StakedEXATest is Test {
assertEq(duration0, duration);
assertEq(finishAt0, block.timestamp + duration);
assertEq(index0, 0);
assertEq(rate0, initialAmount / duration);
assertEq(rate0, (initialAmount * 1e18) / duration);
assertEq(updatedAt0, block.timestamp);

(uint256 duration1, uint256 finishAt1, uint256 updatedAt1, uint256 index1, uint256 rate1) = stEXA.rewards(rB);

assertEq(duration1, duration);
assertEq(finishAt1, block.timestamp + duration);
assertEq(index1, 0);
assertEq(rate1, initialAmount / duration);
assertEq(rate1, (initialAmount * 1e18) / duration);
assertEq(updatedAt1, block.timestamp);

assertEq(stEXA.totalSupply(), 0);
Expand Down Expand Up @@ -615,9 +617,9 @@ contract StakedEXATest is Test {
exa.mint(address(this), assets);

stEXA.deposit(assets, address(this));
uint256 rate = initialAmount / duration;
uint256 rate = (initialAmount * 1e18) / duration;
skip(duration / 2);
uint256 earned_ = rate * (duration / 2);
uint256 earned_ = rate.mulWadDown(duration / 2);
assertApproxEqAbs(earned(rA, address(this)), earned_, 2e6, "earned != expected");

uint256 thisClaimable = claimable(rA, address(this));
Expand Down Expand Up @@ -691,9 +693,9 @@ contract StakedEXATest is Test {

uint256 expectedRate = 0;
if (block.timestamp >= finishAt) {
expectedRate = amount / duration;
expectedRate = (amount * 1e18) / duration;
} else {
expectedRate = (amount + (finishAt - block.timestamp) * rate) / duration;
expectedRate = ((amount * 1e18) + (finishAt - block.timestamp) * rate) / duration;
}

rA.mint(address(stEXA), amount);
Expand Down Expand Up @@ -761,7 +763,7 @@ contract StakedEXATest is Test {
stEXA.deposit(assets, address(this));

skip(time);
uint256 thisRewards = rate * time;
uint256 thisRewards = rate.mulWadDown(time);

exa.mint(BOB, assets);
vm.startPrank(BOB);
Expand All @@ -771,7 +773,7 @@ contract StakedEXATest is Test {

skip(time);

uint256 bobRewards = (rate * time) / 2;
uint256 bobRewards = rate.mulWadDown(time) / 2;
thisRewards += bobRewards;

assertApproxEqAbs(earned(rA, address(this)), thisRewards, 1e7, "this rewards != earned expected");
Expand All @@ -795,13 +797,13 @@ contract StakedEXATest is Test {
uint256 assets = 1_000e18;

uint256 time = duration / 2;
uint256 rate = initialAmount / duration;
uint256 rate = (initialAmount * 1e18) / duration;
exa.mint(address(this), assets);
stEXA.deposit(assets, address(this));

skip(time);

uint256 thisRewards = rate * time;
uint256 thisRewards = rate.mulWadDown(time);

exa.mint(BOB, assets);
vm.startPrank(BOB);
Expand All @@ -811,11 +813,11 @@ contract StakedEXATest is Test {

skip(time);

uint256 bobRewards = (rate * time) / 2;
uint256 bobRewards = rate.mulWadDown(time / 2);
thisRewards += bobRewards;

assertApproxEqAbs(earned(rA, address(this)), thisRewards, 600, "this rewards != earned expected");
assertApproxEqAbs(earned(rA, BOB), bobRewards, 200, "bob rewards != earned expected");
assertApproxEqAbs(earned(rA, address(this)), thisRewards, 2e3, "this rewards != earned expected");
assertApproxEqAbs(earned(rA, BOB), bobRewards, 1e3, "bob rewards != earned expected");

skip(timeAfterPeriod);

Expand Down Expand Up @@ -1122,7 +1124,7 @@ contract StakedEXATest is Test {
assertEq(providerDuration, 1 weeks);
assertEq(finishAt, block.timestamp + 1 weeks);
assertEq(index, 0);
assertEq(rate, assets.mulWadDown(providerRatio) / 1 weeks);
assertEq(rate, (assets * providerRatio) / 1 weeks);
assertEq(updatedAt, block.timestamp);
}

Expand Down Expand Up @@ -1300,7 +1302,7 @@ contract StakedEXATest is Test {
uint256 savingsBalance = rA.balanceOf(SAVINGS);

(, uint256 finishAt, , , uint256 rate) = stEXA.rewards(rA);
uint256 remainingRewards = rate * (finishAt - block.timestamp);
uint256 remainingRewards = rate.mulWadDown(finishAt - block.timestamp);

stEXA.finishDistribution(rA);
assertEq(rA.balanceOf(SAVINGS), savingsBalance + remainingRewards);
Expand All @@ -1319,7 +1321,7 @@ contract StakedEXATest is Test {

(, uint256 finishAt, , , uint256 rate) = stEXA.rewards(rA);

uint256 remainingRewards = finishAt > block.timestamp ? rate * (finishAt - block.timestamp) : 0;
uint256 remainingRewards = finishAt > block.timestamp ? rate.mulWadDown(finishAt - block.timestamp) : 0;

assertEq(remainingRewards, 0);

Expand Down

0 comments on commit 491d07e

Please sign in to comment.