Skip to content

Commit

Permalink
feat: improve tests around the expansion controller (#533)
Browse files Browse the repository at this point in the history
  • Loading branch information
bowd authored Oct 23, 2024
1 parent d3a31fa commit fcd3d02
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 73 deletions.
37 changes: 25 additions & 12 deletions contracts/goodDollar/GoodDollarExpansionController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -168,23 +168,13 @@ contract GoodDollarExpansionController is IGoodDollarExpansionController, Pausab

/// @inheritdoc IGoodDollarExpansionController
function mintUBIFromExpansion(bytes32 exchangeId) external returns (uint256 amountMinted) {
ExchangeExpansionConfig memory config = getExpansionConfig(exchangeId);
IBancorExchangeProvider.PoolExchange memory exchange = IBancorExchangeProvider(address(goodDollarExchangeProvider))
.getPoolExchange(exchangeId);
ExchangeExpansionConfig memory config = getExpansionConfig(exchangeId);

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

// Special case for first expansion
if (config.lastExpansion == 0) {
numberOfExpansions = 1;
} else {
numberOfExpansions = (block.timestamp - config.lastExpansion) / config.expansionFrequency;
}

uint256 stepExpansionScaler = MAX_WEIGHT - config.expansionRate;
uint256 expansionScaler = unwrap(powu(wrap(stepExpansionScaler), numberOfExpansions));
uint256 expansionScaler = _getExpansionScaler(config);

exchangeExpansionConfigs[exchangeId].lastExpansion = uint32(block.timestamp);
amountMinted = goodDollarExchangeProvider.mintFromExpansion(exchangeId, expansionScaler);
Expand Down Expand Up @@ -217,9 +207,32 @@ contract GoodDollarExpansionController is IGoodDollarExpansionController, Pausab
/* ==================== Private Functions ==================== */
/* =========================================================== */

/**
* @notice Sets the distribution helper address.
* @param _distributionHelper The address of the distribution helper contract.
*/
function _setDistributionHelper(address _distributionHelper) internal {
require(_distributionHelper != address(0), "Distribution helper address must be set");
distributionHelper = IDistributionHelper(_distributionHelper);
emit DistributionHelperUpdated(_distributionHelper);
}

/**
* @notice Calculates the expansion scaler for the given expansion config.
* @param config The expansion config.
* @return expansionScaler The expansion scaler.
*/
function _getExpansionScaler(ExchangeExpansionConfig memory config) internal view returns (uint256) {
uint256 numberOfExpansions;

// If there was no previous expansion, we expand once.
if (config.lastExpansion == 0) {
numberOfExpansions = 1;
} else {
numberOfExpansions = (block.timestamp - config.lastExpansion) / config.expansionFrequency;
}

uint256 stepExpansionScaler = MAX_WEIGHT - config.expansionRate;
return unwrap(powu(wrap(stepExpansionScaler), numberOfExpansions));
}
}
120 changes: 59 additions & 61 deletions test/unit/goodDollar/GoodDollarExpansionController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { IGoodDollarExchangeProvider } from "contracts/interfaces/IGoodDollarExc
import { IBancorExchangeProvider } from "contracts/interfaces/IBancorExchangeProvider.sol";
import { IDistributionHelper } from "contracts/goodDollar/interfaces/IGoodProtocol.sol";

import { GoodDollarExpansionControllerHarness } from "test/utils/harnesses/GoodDollarExpansionControllerHarness.sol";

contract GoodDollarExpansionControllerTest is Test {
/* ------- Events from IGoodDollarExpansionController ------- */

Expand Down Expand Up @@ -46,6 +48,8 @@ contract GoodDollarExpansionControllerTest is Test {
uint64 expansionRate = 1e18 * 0.01;
uint32 expansionFrequency = uint32(1 days);

IBancorExchangeProvider.PoolExchange pool;

function setUp() public virtual {
reserveToken = new ERC20Mock("cUSD", "cUSD", address(this), 1);
token = new ERC20Mock("Good$", "G$", address(this), 1);
Expand All @@ -54,6 +58,21 @@ contract GoodDollarExpansionControllerTest is Test {
distributionHelper = makeAddr("DistributionHelper");
reserveAddress = makeAddr("Reserve");
avatarAddress = makeAddr("Avatar");

pool = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken),
tokenAddress: address(token),
tokenSupply: 7 * 1e9 * 1e18,
reserveBalance: 200_000 * 1e18,
reserveRatio: 0.2 * 1e8, // 20%
exitContribution: 0.1 * 1e8 // 10%
});

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IBancorExchangeProvider(exchangeProvider).getPoolExchange.selector),
abi.encode(pool)
);
}

function initializeGoodDollarExpansionController() internal returns (GoodDollarExpansionController) {
Expand Down Expand Up @@ -248,26 +267,11 @@ contract GoodDollarExpansionControllerTest_mintUBIFromInterest is GoodDollarExpa
vm.prank(avatarAddress);
expansionController.setExpansionConfig(exchangeId, expansionRate, expansionFrequency);

IBancorExchangeProvider.PoolExchange memory pool = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken),
tokenAddress: address(token),
tokenSupply: 300_000 * 1e18,
reserveBalance: 60_000 * 1e18,
reserveRatio: 200000,
exitContribution: 10000
});

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IGoodDollarExchangeProvider(exchangeProvider).mintFromInterest.selector),
abi.encode(1000e18)
);

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IBancorExchangeProvider(exchangeProvider).getPoolExchange.selector),
abi.encode(pool)
);
}

function test_mintUBIFromInterest_whenReserveInterestIs0_shouldRevert() public {
Expand Down Expand Up @@ -302,34 +306,18 @@ contract GoodDollarExpansionControllerTest_mintUBIFromInterest is GoodDollarExpa

contract GoodDollarExpansionControllerTest_mintUBIFromReserveBalance is GoodDollarExpansionControllerTest {
GoodDollarExpansionController expansionController;
IBancorExchangeProvider.PoolExchange pool;

function setUp() public override {
super.setUp();
expansionController = initializeGoodDollarExpansionController();
vm.prank(avatarAddress);
expansionController.setExpansionConfig(exchangeId, expansionRate, expansionFrequency);

pool = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken),
tokenAddress: address(token),
tokenSupply: 300_000 * 1e18,
reserveBalance: 60_000 * 1e18,
reserveRatio: 200000,
exitContribution: 10000
});

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IGoodDollarExchangeProvider(exchangeProvider).mintFromInterest.selector),
abi.encode(1000e18)
);

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IBancorExchangeProvider(exchangeProvider).getPoolExchange.selector),
abi.encode(pool)
);
}

function test_mintUBIFromReserveBalance_whenAdditionalReserveBalanceIs0_shouldReturn0() public {
Expand Down Expand Up @@ -364,27 +352,12 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
vm.prank(avatarAddress);
expansionController.setExpansionConfig(exchangeId, expansionRate, expansionFrequency);

IBancorExchangeProvider.PoolExchange memory pool = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken),
tokenAddress: address(token),
tokenSupply: 300_000 * 1e18,
reserveBalance: 60_000 * 1e18,
reserveRatio: 200000,
exitContribution: 10000
});

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IGoodDollarExchangeProvider(exchangeProvider).mintFromExpansion.selector),
abi.encode(1000e18)
);

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IBancorExchangeProvider(exchangeProvider).getPoolExchange.selector),
abi.encode(pool)
);

vm.mockCall(
distributionHelper,
abi.encodeWithSelector(IDistributionHelper(distributionHelper).onDistribution.selector),
Expand Down Expand Up @@ -564,6 +537,46 @@ contract GoodDollarExpansionControllerTest_mintUBIFromExpansion is GoodDollarExp
}
}

contract GoodDollarExpansionControllerTest_getExpansionScalar is GoodDollarExpansionControllerTest {
GoodDollarExpansionControllerHarness expansionController;

function setUp() public override {
super.setUp();
expansionController = new GoodDollarExpansionControllerHarness(false);
}

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

function test_getExpansionScaler_whenExpansionRateIs1_shouldReturn1() public {
IGoodDollarExpansionController.ExchangeExpansionConfig memory config = IGoodDollarExpansionController
.ExchangeExpansionConfig(1e18 - 1, 1, 0);
assertEq(expansionController.exposed_getExpansionScaler(config), 1);
}

function testFuzz_getExpansionScaler(
uint256 _expansionRate,
uint256 _expansionFrequency,
uint256 _lastExpansion,
uint256 _timeDelta
) public {
uint64 expansionRate = uint64(bound(_expansionRate, 1, 1e18 - 1));
uint32 expansionFrequency = uint32(bound(_expansionFrequency, 1, 1e6));
uint32 lastExpansion = uint32(bound(_lastExpansion, 0, 1e6));
uint32 timeDelta = uint32(bound(_timeDelta, 0, 1e6));

skip(lastExpansion + timeDelta);

IGoodDollarExpansionController.ExchangeExpansionConfig memory config = IGoodDollarExpansionController
.ExchangeExpansionConfig(expansionRate, expansionFrequency, lastExpansion);
uint256 scaler = expansionController.exposed_getExpansionScaler(config);
assert(scaler >= 0 && scaler <= 1e18);
}
}

contract GoodDollarExpansionControllerTest_mintRewardFromReserveRatio is GoodDollarExpansionControllerTest {
GoodDollarExpansionController expansionController;

Expand All @@ -573,26 +586,11 @@ contract GoodDollarExpansionControllerTest_mintRewardFromReserveRatio is GoodDol
vm.prank(avatarAddress);
expansionController.setExpansionConfig(exchangeId, expansionRate, expansionFrequency);

IBancorExchangeProvider.PoolExchange memory pool = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken),
tokenAddress: address(token),
tokenSupply: 300_000 * 1e18,
reserveBalance: 60_000 * 1e18,
reserveRatio: 200000,
exitContribution: 10000
});

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IGoodDollarExchangeProvider(exchangeProvider).updateRatioForReward.selector),
abi.encode(true)
);

vm.mockCall(
exchangeProvider,
abi.encodeWithSelector(IBancorExchangeProvider(exchangeProvider).getPoolExchange.selector),
abi.encode(pool)
);
}

function test_mintRewardFromReserveRatio_whenCallerIsNotAvatar_shouldRevert() public {
Expand Down
13 changes: 13 additions & 0 deletions test/utils/harnesses/GoodDollarExpansionControllerHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;
// solhint-disable func-name-mixedcase

import { GoodDollarExpansionController } from "contracts/goodDollar/GoodDollarExpansionController.sol";

contract GoodDollarExpansionControllerHarness is GoodDollarExpansionController {
constructor(bool disabled) GoodDollarExpansionController(disabled) {}

function exposed_getExpansionScaler(ExchangeExpansionConfig calldata config) external returns (uint256) {
return _getExpansionScaler(config);
}
}

0 comments on commit fcd3d02

Please sign in to comment.