Skip to content

Commit

Permalink
fix: update currentPrice to return correct decimals (#560)
Browse files Browse the repository at this point in the history
### Description

Scales down the current price to correct decimals dividing it by the
reserve asset precision multiplier

### Other changes

added some extra tests for swapIn and swapOut with non standard token
decimals

### Tested

Unit test

### Related issues

- Fixes #559
  • Loading branch information
baroooo authored Dec 2, 2024
1 parent 98290ba commit f4e3b95
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 2 deletions.
6 changes: 4 additions & 2 deletions contracts/goodDollar/BancorExchangeProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,11 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
// calculates: reserveBalance / (tokenSupply * reserveRatio)
PoolExchange memory exchange = getPoolExchange(exchangeId);
uint256 scaledReserveRatio = uint256(exchange.reserveRatio) * 1e10;

UD60x18 denominator = wrap(exchange.tokenSupply).mul(wrap(scaledReserveRatio));
price = unwrap(wrap(exchange.reserveBalance).div(denominator));
return price;
uint256 priceScaled = unwrap(wrap(exchange.reserveBalance).div(denominator));

price = priceScaled / tokenPrecisionMultipliers[exchange.reserveAsset];
}

/* ============================================================ */
Expand Down
161 changes: 161 additions & 0 deletions test/unit/goodDollar/BancorExchangeProvider.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pragma solidity 0.8.18;

import { Test } from "forge-std/Test.sol";
import { ERC20 } from "openzeppelin-contracts-next/contracts/token/ERC20/ERC20.sol";
import { ERC20DecimalsMock } from "openzeppelin-contracts-next/contracts/mocks/ERC20DecimalsMock.sol";

import { BancorExchangeProvider } from "contracts/goodDollar/BancorExchangeProvider.sol";
import { IExchangeProvider } from "contracts/interfaces/IExchangeProvider.sol";
import { IBancorExchangeProvider } from "contracts/interfaces/IBancorExchangeProvider.sol";
Expand All @@ -30,16 +32,22 @@ contract BancorExchangeProviderTest is Test {
ERC20 public reserveToken;
ERC20 public token;
ERC20 public token2;
ERC20DecimalsMock public reserveTokenWith6Decimals;
ERC20DecimalsMock public tokenWith6Decimals;

address public reserveAddress;
address public brokerAddress;
IBancorExchangeProvider.PoolExchange public poolExchange1;
IBancorExchangeProvider.PoolExchange public poolExchange2;
IBancorExchangeProvider.PoolExchange public poolExchange3;
IBancorExchangeProvider.PoolExchange public poolExchange4;

function setUp() public virtual {
reserveToken = new ERC20("cUSD", "cUSD");
token = new ERC20("Good$", "G$");
token2 = new ERC20("Good2$", "G2$");
reserveTokenWith6Decimals = new ERC20DecimalsMock("Reserve Token", "RES", 6);
tokenWith6Decimals = new ERC20DecimalsMock("Token", "TKN", 6);

brokerAddress = makeAddr("Broker");
reserveAddress = makeAddr("Reserve");
Expand All @@ -62,6 +70,24 @@ contract BancorExchangeProviderTest is Test {
exitContribution: 1e8 * 0.01
});

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

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

vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isStableAsset.selector, address(token)),
Expand All @@ -72,11 +98,22 @@ contract BancorExchangeProviderTest is Test {
abi.encodeWithSelector(IReserve(reserveAddress).isStableAsset.selector, address(token2)),
abi.encode(true)
);
vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isStableAsset.selector, address(tokenWith6Decimals)),
abi.encode(true)
);
vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isCollateralAsset.selector, address(reserveToken)),
abi.encode(true)
);

vm.mockCall(
reserveAddress,
abi.encodeWithSelector(IReserve(reserveAddress).isCollateralAsset.selector, address(reserveTokenWith6Decimals)),
abi.encode(true)
);
}

function initializeBancorExchangeProvider() internal returns (BancorExchangeProvider) {
Expand Down Expand Up @@ -1416,6 +1453,15 @@ contract BancorExchangeProviderTest_currentPrice is BancorExchangeProviderTest {
assertEq(price, expectedPrice);
}

function test_currentPrice_whenReserveTokenHasLessThan18Decimals_shouldReturnCorrectPrice() public {
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange3);
// formula: price = reserveBalance / tokenSupply * reserveRatio
// calculation: 60_000 / 300_000 * 0.2 = 1
uint256 expectedPrice = 1e6;
uint256 price = bancorExchangeProvider.currentPrice(exchangeId);
assertEq(price, expectedPrice);
}

function test_currentPrice_fuzz(uint256 reserveBalance, uint256 tokenSupply, uint256 reserveRatio) public {
// reserveBalance range between 1 token and 10_000_000 tokens
reserveBalance = bound(reserveBalance, 1e18, 10_000_000 * 1e18);
Expand Down Expand Up @@ -1512,6 +1558,35 @@ contract BancorExchangeProviderTest_swapIn is BancorExchangeProviderTest {
assertEq(tokenSupplyAfter, tokenSupplyBefore + amountOut);
}

function test_swapIn_whenTokenInIsReserveAssetWith6Decimals_shouldSwapIn() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountIn = 1e6;

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

uint256 expectedAmountOut = bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
tokenIn: address(reserveTokenWith6Decimals),
tokenOut: address(token),
amountIn: amountIn
});
vm.prank(brokerAddress);
uint256 amountOut = bancorExchangeProvider.swapIn(
exchangeId,
address(reserveTokenWith6Decimals),
address(token),
amountIn
);
assertEq(amountOut, expectedAmountOut);

(, , uint256 tokenSupplyAfter, uint256 reserveBalanceAfter, , ) = bancorExchangeProvider.exchanges(exchangeId);

assertEq(reserveBalanceAfter, reserveBalanceBefore + amountIn * 1e12);
assertEq(tokenSupplyAfter, tokenSupplyBefore + amountOut);
}

function test_swapIn_whenTokenInIsToken_shouldSwapIn() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountIn = 1e18;
Expand All @@ -1536,6 +1611,35 @@ contract BancorExchangeProviderTest_swapIn is BancorExchangeProviderTest {
assertEq(tokenSupplyAfter, tokenSupplyBefore - amountIn);
}

function test_swapIn_whenTokenIsTokenWith6Decimals_shouldSwapIn() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountIn = 1e18;

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

uint256 expectedAmountOut = bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
tokenIn: address(reserveToken),
tokenOut: address(tokenWith6Decimals),
amountIn: amountIn
});
vm.prank(brokerAddress);
uint256 amountOut = bancorExchangeProvider.swapIn(
exchangeId,
address(reserveToken),
address(tokenWith6Decimals),
amountIn
);
assertEq(amountOut, expectedAmountOut);

(, , uint256 tokenSupplyAfter, uint256 reserveBalanceAfter, , ) = bancorExchangeProvider.exchanges(exchangeId);

assertEq(reserveBalanceAfter, reserveBalanceBefore + amountIn);
assertApproxEqRel(tokenSupplyAfter, tokenSupplyBefore + amountOut * 1e12, 1e18 * 0.0001);
}

function test_swapIn_whenTokenInIsTokenAndExitContributionIsNonZero_shouldReturnSameAmountWhenSellIsDoneInMultipleSteps()
public
{
Expand Down Expand Up @@ -1642,6 +1746,35 @@ contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {
assertEq(tokenSupplyAfter, tokenSupplyBefore + amountOut);
}

function test_swapOut_whenTokenInIsReserveAssetWith6Decimals_shouldSwapIn() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountOut = 1e18;

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

uint256 expectedAmountIn = bancorExchangeProvider.getAmountIn({
exchangeId: exchangeId,
tokenIn: address(reserveTokenWith6Decimals),
tokenOut: address(token),
amountOut: amountOut
});
vm.prank(brokerAddress);
uint256 amountIn = bancorExchangeProvider.swapOut(
exchangeId,
address(reserveTokenWith6Decimals),
address(token),
amountOut
);
assertEq(amountIn, expectedAmountIn);

(, , uint256 tokenSupplyAfter, uint256 reserveBalanceAfter, , ) = bancorExchangeProvider.exchanges(exchangeId);

assertApproxEqRel(reserveBalanceAfter, reserveBalanceBefore + amountIn * 1e12, 1e18 * 0.0001);
assertEq(tokenSupplyAfter, tokenSupplyBefore + amountOut);
}

function test_swapOut_whenTokenInIsToken_shouldSwapOut() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountOut = 1e18;
Expand All @@ -1666,6 +1799,34 @@ contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {
assertEq(tokenSupplyAfter, tokenSupplyBefore - amountIn);
}

function test_swapOut_whenTokenInIsTokenWith6Decimals_shouldSwapIn() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountOut = 1e18;

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

uint256 expectedAmountIn = bancorExchangeProvider.getAmountIn({
exchangeId: exchangeId,
tokenIn: address(tokenWith6Decimals),
tokenOut: address(reserveToken),
amountOut: amountOut
});
vm.prank(brokerAddress);
uint256 amountIn = bancorExchangeProvider.swapOut(
exchangeId,
address(tokenWith6Decimals),
address(reserveToken),
amountOut
);
assertEq(amountIn, expectedAmountIn);

(, , uint256 tokenSupplyAfter, uint256 reserveBalanceAfter, , ) = bancorExchangeProvider.exchanges(exchangeId);

assertEq(reserveBalanceAfter, reserveBalanceBefore - amountOut);
assertApproxEqRel(tokenSupplyAfter, tokenSupplyBefore - amountIn * 1e12, 1e18 * 0.0001);
}
function test_swapOut_whenTokenInIsTokenAndExitContributionIsNonZero_shouldReturnSameAmountWhenSellIsDoneInMultipleSteps()
public
{
Expand Down

0 comments on commit f4e3b95

Please sign in to comment.