Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/scaling issues for tokens with less than 18 decimals #561

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions contracts/goodDollar/BancorExchangeProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
tokenPrecisionMultipliers[exchange.reserveAsset] = 10 ** (18 - uint256(reserveAssetDecimals));
tokenPrecisionMultipliers[exchange.tokenAddress] = 10 ** (18 - uint256(tokenDecimals));

exchange.reserveBalance = exchange.reserveBalance * tokenPrecisionMultipliers[exchange.reserveAsset];
philbow61 marked this conversation as resolved.
Show resolved Hide resolved
exchange.tokenSupply = exchange.tokenSupply * tokenPrecisionMultipliers[exchange.tokenAddress];

exchanges[exchangeId] = exchange;
exchangeIds.push(exchangeId);
emit ExchangeCreated(exchangeId, exchange.reserveAsset, exchange.tokenAddress);
Expand Down
5 changes: 2 additions & 3 deletions contracts/goodDollar/GoodDollarExchangeProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,13 @@ contract GoodDollarExchangeProvider is IGoodDollarExchangeProvider, BancorExchan
) external onlyExpansionController whenNotPaused returns (uint256 amountToMint) {
PoolExchange memory exchange = getPoolExchange(exchangeId);

uint256 reserveinterestScaled = reserveInterest * tokenPrecisionMultipliers[exchange.reserveAsset];
uint256 amountToMintScaled = unwrap(
wrap(reserveinterestScaled).mul(wrap(exchange.tokenSupply)).div(wrap(exchange.reserveBalance))
wrap(reserveInterest).mul(wrap(exchange.tokenSupply)).div(wrap(exchange.reserveBalance))
);
amountToMint = amountToMintScaled / tokenPrecisionMultipliers[exchange.tokenAddress];

exchanges[exchangeId].tokenSupply += amountToMintScaled;
exchanges[exchangeId].reserveBalance += reserveinterestScaled;
exchanges[exchangeId].reserveBalance += reserveInterest;

return amountToMint;
}
Expand Down
12 changes: 7 additions & 5 deletions contracts/goodDollar/GoodDollarExpansionController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,21 @@ contract GoodDollarExpansionController is IGoodDollarExpansionController, Ownabl
}

/// @inheritdoc IGoodDollarExpansionController
function mintUBIFromInterest(bytes32 exchangeId, uint256 reserveInterest) external {
function mintUBIFromInterest(bytes32 exchangeId, uint256 reserveInterest) external returns (uint256 amountMinted) {
require(reserveInterest > 0, "Reserve interest must be greater than 0");
IBancorExchangeProvider.PoolExchange memory exchange = IBancorExchangeProvider(address(goodDollarExchangeProvider))
.getPoolExchange(exchangeId);

uint256 amountToMint = goodDollarExchangeProvider.mintFromInterest(exchangeId, reserveInterest);

require(IERC20(exchange.reserveAsset).transferFrom(msg.sender, reserve, reserveInterest), "Transfer failed");
IGoodDollar(exchange.tokenAddress).mint(address(distributionHelper), amountToMint);

uint256 reserveInterestScaled = reserveInterest * (10 ** (18 - IERC20Metadata(exchange.reserveAsset).decimals()));
amountMinted = goodDollarExchangeProvider.mintFromInterest(exchangeId, reserveInterestScaled);

IGoodDollar(exchange.tokenAddress).mint(address(distributionHelper), amountMinted);

// Ignored, because contracts only interacts with trusted contracts and tokens
// slither-disable-next-line reentrancy-events
emit InterestUBIMinted(exchangeId, amountToMint);
emit InterestUBIMinted(exchangeId, amountMinted);
}

/// @inheritdoc IGoodDollarExpansionController
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/IGoodDollarExpansionController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ interface IGoodDollarExpansionController {
* @notice Mints UBI as G$ tokens for a given pool from collected reserve interest.
* @param exchangeId The ID of the pool to mint UBI for.
* @param reserveInterest The amount of reserve tokens collected from interest.
* @return amountMinted The amount of G$ tokens minted.
*/
function mintUBIFromInterest(bytes32 exchangeId, uint256 reserveInterest) external;
function mintUBIFromInterest(bytes32 exchangeId, uint256 reserveInterest) external returns (uint256 amountMinted);

/**
* @notice Mints UBI as G$ tokens for a given pool by comparing the contract's reserve balance to the virtual balance.
Expand Down
47 changes: 35 additions & 12 deletions test/unit/goodDollar/BancorExchangeProvider.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ contract BancorExchangeProviderTest is Test {
IBancorExchangeProvider.PoolExchange public poolExchange2;
IBancorExchangeProvider.PoolExchange public poolExchange3;
IBancorExchangeProvider.PoolExchange public poolExchange4;
IBancorExchangeProvider.PoolExchange public poolExchange5;

function setUp() public virtual {
reserveToken = new ERC20("cUSD", "cUSD");
Expand Down Expand Up @@ -74,20 +75,29 @@ contract BancorExchangeProviderTest is Test {
reserveAsset: address(reserveTokenWith6Decimals),
tokenAddress: address(token),
tokenSupply: 300_000 * 1e18,
reserveBalance: 60_000 * 1e18,
reserveBalance: 60_000 * 1e6,
reserveRatio: 1e8 * 0.2,
exitContribution: 1e8 * 0.01
});

poolExchange4 = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken),
tokenAddress: address(tokenWith6Decimals),
tokenSupply: 300_000 * 1e18,
tokenSupply: 300_000 * 1e6,
reserveBalance: 60_000 * 1e18,
reserveRatio: 1e8 * 0.2,
exitContribution: 1e8 * 0.01
});

poolExchange5 = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveTokenWith6Decimals),
tokenAddress: address(tokenWith6Decimals),
tokenSupply: 300_000 * 1e6,
reserveBalance: 60_000 * 1e6,
reserveRatio: 1e8 * 0.2,
exitContribution: 1e8 * 0.01
});

vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isStableAsset.selector, address(token)),
Expand Down Expand Up @@ -383,6 +393,23 @@ contract BancorExchangeProviderTest_createExchange is BancorExchangeProviderTest
assertEq(bancorExchangeProvider.tokenPrecisionMultipliers(address(reserveToken)), 1);
assertEq(bancorExchangeProvider.tokenPrecisionMultipliers(address(token)), 1);
}

function test_createExchange_whenTokensHasLessThan18Decimals_shouldCreateExchangeWithCorrectSupplyAndBalance()
public
{
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange5);

IBancorExchangeProvider.PoolExchange memory poolExchange = bancorExchangeProvider.getPoolExchange(exchangeId);
assertEq(poolExchange.reserveAsset, poolExchange5.reserveAsset);
assertEq(poolExchange.tokenAddress, poolExchange5.tokenAddress);
assertEq(poolExchange.tokenSupply, poolExchange5.tokenSupply * 1e12);
assertEq(poolExchange.reserveBalance, poolExchange5.reserveBalance * 1e12);
assertEq(poolExchange.reserveRatio, poolExchange5.reserveRatio);
assertEq(poolExchange.exitContribution, poolExchange5.exitContribution);

assertEq(bancorExchangeProvider.tokenPrecisionMultipliers(address(reserveTokenWith6Decimals)), 1e12);
assertEq(bancorExchangeProvider.tokenPrecisionMultipliers(address(tokenWith6Decimals)), 1e12);
}
}

contract BancorExchangeProviderTest_destroyExchange is BancorExchangeProviderTest {
Expand Down Expand Up @@ -737,7 +764,7 @@ contract BancorExchangeProviderTest_getAmountIn is BancorExchangeProviderTest {
reserveAsset: address(reserveToken6),
tokenAddress: address(stableToken18),
tokenSupply: 100_000 * 1e18, // 100,000
reserveBalance: 50_000 * 1e18, // 50,000
reserveBalance: 50_000 * 1e6, // 50,000
reserveRatio: 1e8 * 0.5, // 50%
exitContribution: 0
});
Expand Down Expand Up @@ -1225,7 +1252,7 @@ contract BancorExchangeProviderTest_getAmountOut is BancorExchangeProviderTest {
reserveAsset: address(reserveToken6),
tokenAddress: address(stableToken18),
tokenSupply: 100_000 * 1e18, // 100,000
reserveBalance: 50_000 * 1e18, // 50,000
reserveBalance: 50_000 * 1e6, // 50,000
reserveRatio: 1e8 * 0.5, // 50%
exitContribution: 0
});
Expand Down Expand Up @@ -1563,8 +1590,7 @@ contract BancorExchangeProviderTest_swapIn is BancorExchangeProviderTest {
uint256 amountIn = 1e6;

bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange3);
uint256 reserveBalanceBefore = poolExchange3.reserveBalance;
uint256 tokenSupplyBefore = poolExchange3.tokenSupply;
(, , uint256 tokenSupplyBefore, uint256 reserveBalanceBefore, , ) = bancorExchangeProvider.exchanges(exchangeId);

uint256 expectedAmountOut = bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
Expand Down Expand Up @@ -1616,8 +1642,7 @@ contract BancorExchangeProviderTest_swapIn is BancorExchangeProviderTest {
uint256 amountIn = 1e18;

bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange4);
uint256 reserveBalanceBefore = poolExchange4.reserveBalance;
uint256 tokenSupplyBefore = poolExchange4.tokenSupply;
(, , uint256 tokenSupplyBefore, uint256 reserveBalanceBefore, , ) = bancorExchangeProvider.exchanges(exchangeId);

uint256 expectedAmountOut = bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
Expand Down Expand Up @@ -1751,8 +1776,7 @@ contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {
uint256 amountOut = 1e18;

bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange3);
uint256 reserveBalanceBefore = poolExchange3.reserveBalance;
uint256 tokenSupplyBefore = poolExchange3.tokenSupply;
(, , uint256 tokenSupplyBefore, uint256 reserveBalanceBefore, , ) = bancorExchangeProvider.exchanges(exchangeId);

uint256 expectedAmountIn = bancorExchangeProvider.getAmountIn({
exchangeId: exchangeId,
Expand Down Expand Up @@ -1804,8 +1828,7 @@ contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {
uint256 amountOut = 1e18;

bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange4);
uint256 reserveBalanceBefore = poolExchange4.reserveBalance;
uint256 tokenSupplyBefore = poolExchange4.tokenSupply;
(, , uint256 tokenSupplyBefore, uint256 reserveBalanceBefore, , ) = bancorExchangeProvider.exchanges(exchangeId);

uint256 expectedAmountIn = bancorExchangeProvider.getAmountIn({
exchangeId: exchangeId,
Expand Down
83 changes: 83 additions & 0 deletions test/unit/goodDollar/GoodDollarExpansionController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { Test } from "forge-std/Test.sol";
import { ERC20Mock } from "openzeppelin-contracts-next/contracts/mocks/ERC20Mock.sol";
import { ERC20DecimalsMock } from "openzeppelin-contracts-next/contracts/mocks/ERC20DecimalsMock.sol";
import { GoodDollarExpansionController } from "contracts/goodDollar/GoodDollarExpansionController.sol";
import { GoodDollarExchangeProvider } from "contracts/goodDollar/GoodDollarExchangeProvider.sol";

import { IGoodDollarExpansionController } from "contracts/interfaces/IGoodDollarExpansionController.sol";
import { IGoodDollarExchangeProvider } from "contracts/interfaces/IGoodDollarExchangeProvider.sol";
import { IBancorExchangeProvider } from "contracts/interfaces/IBancorExchangeProvider.sol";
import { IDistributionHelper } from "contracts/goodDollar/interfaces/IGoodProtocol.sol";
import { IReserve } from "contracts/interfaces/IReserve.sol";

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

Expand Down Expand Up @@ -679,3 +681,84 @@ contract GoodDollarExpansionControllerTest_mintRewardFromReserveRatio is GoodDol
assertEq(token.balanceOf(to), toBalanceBefore + amountToMint);
}
}

contract GoodDollarExpansionControllerIntegrationTest is GoodDollarExpansionControllerTest {
address brokerAddress = makeAddr("Broker");
GoodDollarExpansionController _expansionController;
GoodDollarExchangeProvider _exchangeProvider;
ERC20DecimalsMock reserveToken6DecimalsMock;

function setUp() public override {
super.setUp();
_exchangeProvider = new GoodDollarExchangeProvider(false);
_expansionController = new GoodDollarExpansionController(false);

_expansionController.initialize(address(_exchangeProvider), distributionHelper, reserveAddress, avatarAddress);
_exchangeProvider.initialize(brokerAddress, reserveAddress, address(_expansionController), avatarAddress);

reserveToken6DecimalsMock = new ERC20DecimalsMock("Reserve Token", "RES", 6);
IBancorExchangeProvider.PoolExchange memory poolExchange = IBancorExchangeProvider.PoolExchange({
reserveAsset: address(reserveToken6DecimalsMock),
tokenAddress: address(token),
tokenSupply: 7 * 1e9 * 1e18,
reserveBalance: 200_000 * 1e6,
reserveRatio: 0.2 * 1e8, // 20%
exitContribution: 0.1 * 1e8 // 10%
});

vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isStableAsset.selector, address(token)),
abi.encode(true)
);
vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isCollateralAsset.selector, address(reserveToken6DecimalsMock)),
abi.encode(true)
);
vm.prank(avatarAddress);
exchangeId = _exchangeProvider.createExchange(poolExchange);
}

function test_mintUBIFromReserveBalance_whenReserveTokenHas6Decimals_shouldMintAndEmit() public {
uint256 reserveInterest = 1000e6;
// amountToMint = reserveInterest * tokenSupply / reserveBalance
uint256 amountToMint = 35_000_000e18;

deal(address(reserveToken6DecimalsMock), reserveAddress, 200_000 * 1e6 + reserveInterest);
uint256 distributionHelperBalanceBefore = token.balanceOf(distributionHelper);

vm.expectEmit(true, true, true, true);
emit InterestUBIMinted(exchangeId, amountToMint);
uint256 amountMinted = _expansionController.mintUBIFromReserveBalance(exchangeId);

assertEq(amountMinted, amountToMint);
assertEq(token.balanceOf(distributionHelper), distributionHelperBalanceBefore + amountToMint);
}

function test_mintUBIFromInterest_whenReserveTokenHas6Decimals_shouldMintAndEmit() public {
uint256 reserveInterest = 1000e6;
// amountToMint = reserveInterest * tokenSupply / reserveBalance
uint256 amountToMint = 35_000_000e18;
address interestCollector = makeAddr("InterestCollector");

deal(address(reserveToken6DecimalsMock), interestCollector, reserveInterest);

vm.startPrank(interestCollector);
reserveToken6DecimalsMock.approve(address(_expansionController), reserveInterest);

uint256 interestCollectorBalanceBefore = reserveToken6DecimalsMock.balanceOf(interestCollector);
uint256 reserveBalanceBefore = reserveToken6DecimalsMock.balanceOf(reserveAddress);
uint256 distributionHelperBalanceBefore = token.balanceOf(distributionHelper);

vm.expectEmit(true, true, true, true);
emit InterestUBIMinted(exchangeId, amountToMint);
uint256 amountMinted = _expansionController.mintUBIFromInterest(exchangeId, reserveInterest);

assertEq(amountMinted, amountToMint);

assertEq(reserveToken6DecimalsMock.balanceOf(reserveAddress), reserveBalanceBefore + reserveInterest);
assertEq(token.balanceOf(distributionHelper), distributionHelperBalanceBefore + amountToMint);
assertEq(reserveToken6DecimalsMock.balanceOf(interestCollector), interestCollectorBalanceBefore - reserveInterest);
}
}
Loading