Skip to content

Commit

Permalink
fix: setting of last expansion timestamp (#553)
Browse files Browse the repository at this point in the history
### Description

fixes issue in how lastUpdated was set:
before if 3.5 days passed and a daily expansion was configured, 3
expansions were calculated and the last expansion timestamp was updated
to now essentially losing half a day. Over time this could have resulted
in the yearly expansion rate not being achieved.

### Other changes



### Tested

-  added tests for this behavior 

### Related issues

mento-protocol/mento-general#592

### Backwards compatibility



### Documentation
  • Loading branch information
philbow61 authored Nov 26, 2024
1 parent e840fa3 commit 86053df
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 30 deletions.
15 changes: 10 additions & 5 deletions contracts/goodDollar/GoodDollarExpansionController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,10 @@ contract GoodDollarExpansionController is IGoodDollarExpansionController, Ownabl
.getPoolExchange(exchangeId);
ExchangeExpansionConfig memory config = getExpansionConfig(exchangeId);

bool shouldExpand = block.timestamp > config.lastExpansion + config.expansionFrequency;
bool shouldExpand = block.timestamp >= config.lastExpansion + config.expansionFrequency;
if (shouldExpand || config.lastExpansion == 0) {
uint256 reserveRatioScalar = _getReserveRatioScalar(config);
uint256 reserveRatioScalar = _getReserveRatioScalar(exchangeId);

exchangeExpansionConfigs[exchangeId].lastExpansion = uint32(block.timestamp);
amountMinted = goodDollarExchangeProvider.mintFromExpansion(exchangeId, reserveRatioScalar);

IGoodDollar(exchange.tokenAddress).mint(address(distributionHelper), amountMinted);
Expand Down Expand Up @@ -220,17 +219,23 @@ contract GoodDollarExpansionController is IGoodDollarExpansionController, Ownabl

/**
* @notice Calculates the reserve ratio scalar for the given expansion config.
* @param config The expansion config.
* @param exchangeId The ID of the exchange.
* @return reserveRatioScalar The reserve ratio scalar.
*/
function _getReserveRatioScalar(ExchangeExpansionConfig memory config) internal view returns (uint256) {
function _getReserveRatioScalar(bytes32 exchangeId) internal returns (uint256) {
ExchangeExpansionConfig memory config = getExpansionConfig(exchangeId);
uint256 numberOfExpansions;

// If there was no previous expansion, we expand once.
if (config.lastExpansion == 0) {
numberOfExpansions = 1;
exchangeExpansionConfigs[exchangeId].lastExpansion = uint32(block.timestamp);
} else {
numberOfExpansions = (block.timestamp - config.lastExpansion) / config.expansionFrequency;
// slither-disable-next-line divide-before-multiply
exchangeExpansionConfigs[exchangeId].lastExpansion = uint32(
config.lastExpansion + numberOfExpansions * config.expansionFrequency
);
}

uint256 stepReserveRatioScalar = MAX_WEIGHT - config.expansionRate;
Expand Down
48 changes: 25 additions & 23 deletions test/unit/goodDollar/GoodDollarExpansionController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -402,16 +402,15 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
expansionController.mintUBIFromExpansion("NotSetExchangeId");
}

function test_mintUBIFromExpansion_whenShouldNotExpand_shouldNotExpand() public {
function test_mintUBIFromExpansion_whenLessThanExpansionFrequencyPassed_shouldNotExpand() public {
// doing one initial expansion to not be first expansion
// since on first expansion the expansion is always applied once.
expansionController.mintUBIFromExpansion(exchangeId);

IGoodDollarExpansionController.ExchangeExpansionConfig memory config = expansionController.getExpansionConfig(
exchangeId
);
uint256 lastExpansion = config.lastExpansion;
skip(lastExpansion + config.expansionFrequency - 1);
skip(config.expansionFrequency - 1);

assertEq(expansionController.mintUBIFromExpansion(exchangeId), 0);
}
Expand Down Expand Up @@ -497,14 +496,11 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
// 1 day has passed since last expansion and expansion rate is 1% so the rate passed to the exchangeProvider
// should be 0.99^1 = 0.99
uint256 reserveRatioScalar = 1e18 * 0.99;
skip(expansionFrequency + 1);
skip(expansionFrequency);

uint256 amountToMint = 1000e18;
uint256 distributionHelperBalanceBefore = token.balanceOf(distributionHelper);

vm.expectEmit(true, true, true, true);
emit ExpansionUBIMinted(exchangeId, amountToMint);

vm.expectCall(
exchangeProvider,
abi.encodeWithSelector(
Expand All @@ -518,6 +514,9 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
abi.encodeWithSelector(IDistributionHelper(distributionHelper).onDistribution.selector, amountToMint)
);

vm.expectEmit(true, true, true, true);
emit ExpansionUBIMinted(exchangeId, amountToMint);

uint256 amountMinted = expansionController.mintUBIFromExpansion(exchangeId);
IGoodDollarExpansionController.ExchangeExpansionConfig memory config = expansionController.getExpansionConfig(
exchangeId
Expand All @@ -528,7 +527,7 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
assertEq(config.lastExpansion, block.timestamp);
}

function test_mintUBIFromExpansion_whenMultipleDaysPassed_shouldCalculateCorrectRateAndExpand() public {
function test_mintUBIFromExpansion_whenThreeAndAHalfDaysPassed_shouldMintCorrectAmountAndSetLastExpansion() public {
// doing one initial expansion to not be first expansion
// since on first expansion the expansion is always applied once.
expansionController.mintUBIFromExpansion(exchangeId);
Expand All @@ -537,7 +536,12 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
// should be 0.99^3 = 0.970299
uint256 reserveRatioScalar = 1e18 * 0.970299;

skip(3 * expansionFrequency + 1);
IGoodDollarExpansionController.ExchangeExpansionConfig memory stateBefore = expansionController.getExpansionConfig(
exchangeId
);

// 3.5 days have passed since last expansion
skip((7 * expansionFrequency) / 2);

uint256 amountToMint = 1000e18;
uint256 distributionHelperBalanceBefore = token.balanceOf(distributionHelper);
Expand Down Expand Up @@ -565,7 +569,7 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp

assertEq(amountMinted, amountToMint);
assertEq(token.balanceOf(distributionHelper), distributionHelperBalanceBefore + amountToMint);
assertEq(config.lastExpansion, block.timestamp);
assertEq(config.lastExpansion, stateBefore.lastExpansion + expansionFrequency * 3);
}
}

Expand All @@ -575,18 +579,14 @@ contract GoodDollarExpansionControllerTest_getExpansionScalar is GoodDollarExpan
function setUp() public override {
super.setUp();
expansionController = new GoodDollarExpansionControllerHarness(false);
expansionController.initialize(exchangeProvider, distributionHelper, reserveAddress, avatarAddress);
}

function test_getExpansionScaler_whenExpansionRateIs0_shouldReturn1e18() public {
IGoodDollarExpansionController.ExchangeExpansionConfig memory config = IGoodDollarExpansionController
.ExchangeExpansionConfig(0, 1, 0);
assertEq(expansionController.exposed_getReserveRatioScalar(config), 1e18);
}

function test_getExpansionScaler_whenExpansionRateIs1_shouldReturn1() public {
IGoodDollarExpansionController.ExchangeExpansionConfig memory config = IGoodDollarExpansionController
.ExchangeExpansionConfig(1e18 - 1, 1, 0);
assertEq(expansionController.exposed_getReserveRatioScalar(config), 1);
function test_getExpansionScaler_whenStepReserveRatioScalerIs1_shouldReturn1() public {
vm.prank(avatarAddress);
expansionController.setExpansionConfig(exchangeId, 1e18 - 1, 1);
// stepReserveRatioScalar is 1e18 - expansionRate = 1e18 - (1e18 - 1) = 1
assertEq(expansionController.exposed_getReserveRatioScalar(exchangeId), 1);
}

function testFuzz_getExpansionScaler(
Expand All @@ -602,9 +602,11 @@ contract GoodDollarExpansionControllerTest_getExpansionScalar is GoodDollarExpan

skip(lastExpansion + timeDelta);

IGoodDollarExpansionController.ExchangeExpansionConfig memory config = IGoodDollarExpansionController
.ExchangeExpansionConfig(expansionRate, expansionFrequency, lastExpansion);
uint256 scaler = expansionController.exposed_getReserveRatioScalar(config);
vm.prank(avatarAddress);
expansionController.setExpansionConfig(exchangeId, expansionRate, expansionFrequency);
expansionController.setLastExpansion(exchangeId, lastExpansion);
uint256 scaler = expansionController.exposed_getReserveRatioScalar(exchangeId);

assert(scaler >= 0 && scaler <= 1e18);
}
}
Expand Down
8 changes: 6 additions & 2 deletions test/utils/harnesses/GoodDollarExpansionControllerHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { GoodDollarExpansionController } from "contracts/goodDollar/GoodDollarEx
contract GoodDollarExpansionControllerHarness is GoodDollarExpansionController {
constructor(bool disabled) GoodDollarExpansionController(disabled) {}

function exposed_getReserveRatioScalar(ExchangeExpansionConfig calldata config) external returns (uint256) {
return _getReserveRatioScalar(config);
function exposed_getReserveRatioScalar(bytes32 exchangeId) external returns (uint256) {
return _getReserveRatioScalar(exchangeId);
}

function setLastExpansion(bytes32 exchangeId, uint32 lastExpansion) external {
exchangeExpansionConfigs[exchangeId].lastExpansion = lastExpansion;
}
}

0 comments on commit 86053df

Please sign in to comment.