Skip to content

Commit

Permalink
test: added testing + cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
philbow61 committed Nov 25, 2024
1 parent c52eb1e commit 3d6a1f8
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 55 deletions.
50 changes: 33 additions & 17 deletions contracts/goodDollar/BancorExchangeProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,13 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
) external view virtual returns (uint256 amountOut) {
PoolExchange memory exchange = getPoolExchange(exchangeId);
uint256 scaledAmountIn = amountIn * tokenPrecisionMultipliers[tokenIn];

if (tokenIn == exchange.tokenAddress) {
require(scaledAmountIn < exchange.tokenSupply, "amountIn is greater than tokenSupply");
// apply exit contribution
scaledAmountIn = (scaledAmountIn * (MAX_WEIGHT - exchange.exitContribution)) / MAX_WEIGHT;
}

uint256 scaledAmountOut = _getScaledAmountOut(exchange, tokenIn, tokenOut, scaledAmountIn);
amountOut = scaledAmountOut / tokenPrecisionMultipliers[tokenOut];
return amountOut;
Expand All @@ -141,10 +144,13 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
PoolExchange memory exchange = getPoolExchange(exchangeId);
uint256 scaledAmountOut = amountOut * tokenPrecisionMultipliers[tokenOut];
uint256 scaledAmountIn = _getScaledAmountIn(exchange, tokenIn, tokenOut, scaledAmountOut);

if (tokenIn == exchange.tokenAddress) {
// apply exit contribution
scaledAmountIn = (scaledAmountIn * MAX_WEIGHT) / (MAX_WEIGHT - exchange.exitContribution);
require(scaledAmountIn < exchange.tokenSupply, "amountIn is greater than tokenSupply");
}

amountIn = scaledAmountIn / tokenPrecisionMultipliers[tokenIn];
return amountIn;
}
Expand Down Expand Up @@ -204,21 +210,23 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
uint256 amountIn
) public virtual onlyBroker returns (uint256 amountOut) {
PoolExchange memory exchange = getPoolExchange(exchangeId);
uint256 exitContribution;
uint256 scaledAmountIn = amountIn * tokenPrecisionMultipliers[tokenIn];
uint256 exitContribution;

if (tokenIn == exchange.tokenAddress) {
require(scaledAmountIn < exchange.tokenSupply, "amountIn is greater than tokenSupply");
// apply exit contribution
exitContribution = (scaledAmountIn * exchange.exitContribution) / MAX_WEIGHT;
scaledAmountIn -= exitContribution;
}

uint256 scaledAmountOut = _getScaledAmountOut(exchange, tokenIn, tokenOut, scaledAmountIn);
executeSwap(exchangeId, tokenIn, scaledAmountIn, scaledAmountOut);

executeSwap(exchangeId, tokenIn, scaledAmountIn, scaledAmountOut);
if (exitContribution > 0) {
accountExitContribution(exchangeId, exitContribution);
}

amountOut = scaledAmountOut / tokenPrecisionMultipliers[tokenOut];
return amountOut;
}
Expand All @@ -233,16 +241,17 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
PoolExchange memory exchange = getPoolExchange(exchangeId);
uint256 scaledAmountOut = amountOut * tokenPrecisionMultipliers[tokenOut];
uint256 scaledAmountIn = _getScaledAmountIn(exchange, tokenIn, tokenOut, scaledAmountOut);

uint256 exitContribution;
if (tokenIn == exchange.tokenAddress) {
// apply exit contribution
uint256 scaledAmountInWithExitContribution = (scaledAmountIn * MAX_WEIGHT) /
(MAX_WEIGHT - exchange.exitContribution);
require(scaledAmountInWithExitContribution < exchange.tokenSupply, "amountIn is greater than tokenSupply");
exitContribution = scaledAmountInWithExitContribution - scaledAmountIn;
}

executeSwap(exchangeId, tokenIn, scaledAmountIn, scaledAmountOut);

if (exitContribution > 0) {
accountExitContribution(exchangeId, exitContribution);
scaledAmountIn = scaledAmountIn + exitContribution;
Expand All @@ -252,20 +261,6 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
return amountIn;
}

function accountExitContribution(bytes32 exchangeId, uint256 exitContribution) internal {
PoolExchange memory exchange = getPoolExchange(exchangeId);
// newRatio = (Supply * oldRatio) / (Supply - exitContribution)
uint256 scaledReserveRatio = uint256(exchange.reserveRatio) * 1e10;
UD60x18 nominator = wrap(exchange.tokenSupply).mul(wrap(scaledReserveRatio));
UD60x18 denominator = wrap(exchange.tokenSupply - exitContribution);
UD60x18 newRatio = nominator.div(denominator);

uint256 newRatioScaled = unwrap(newRatio) / 1e10;

exchanges[exchangeId].reserveRatio = uint32(newRatioScaled);
exchanges[exchangeId].tokenSupply -= exitContribution;
}

/* =========================================================== */
/* ==================== Private Functions ==================== */
/* =========================================================== */
Expand Down Expand Up @@ -336,6 +331,27 @@ contract BancorExchangeProvider is IExchangeProvider, IBancorExchangeProvider, B
exchanges[exchangeId].tokenSupply = exchange.tokenSupply;
}

/**
* @dev Accounting of exit contribution without changing the current price of an exchange.
* this is done by updating the reserve ratio and subtracting the exit contribution from the token supply.
* Formula: newRatio = (Supply * oldRatio) / (Supply - exitContribution)
* @notice Accounting of exit contribution on a swap.
* @param exchangeId The ID of the pool
* @param exitContribution The amount of the token to be removed from the pool, scaled to 18 decimals
*/
function accountExitContribution(bytes32 exchangeId, uint256 exitContribution) internal {
PoolExchange memory exchange = getPoolExchange(exchangeId);
uint256 scaledReserveRatio = uint256(exchange.reserveRatio) * 1e10;
UD60x18 nominator = wrap(exchange.tokenSupply).mul(wrap(scaledReserveRatio));
UD60x18 denominator = wrap(exchange.tokenSupply - exitContribution);
UD60x18 newRatio = nominator.div(denominator);

uint256 newRatioScaled = unwrap(newRatio) / 1e10;

exchanges[exchangeId].reserveRatio = uint32(newRatioScaled);
exchanges[exchangeId].tokenSupply -= exitContribution;
}

/**
* @notice Calculate the scaledAmountIn of tokenIn for a given scaledAmountOut of tokenOut
* @param exchange The pool exchange to operate on
Expand Down
120 changes: 82 additions & 38 deletions test/unit/goodDollar/BancorExchangeProvider.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -487,19 +487,15 @@ contract BancorExchangeProviderTest_getAmountIn is BancorExchangeProviderTest {
assertEq(amountIn, 0);
}

function test_getAmountIn_whenTokenInIsTokenAndAmountOutEqualReserveBalance_shouldReturnSupply() public {
// need to set exit contribution to 0 to make the formula work otherwise amountOut would need to be adjusted
// to be equal to reserveBalance after exit contribution is applied
poolExchange1.exitContribution = 0;
function test_getAmountIn_whenTokenInIsTokenAndAmountInIsLargerOrEqualSupply_shouldRevert() public {
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
uint256 expectedAmountIn = poolExchange1.tokenSupply;
uint256 amountIn = bancorExchangeProvider.getAmountIn({
vm.expectRevert("amountIn is greater than tokenSupply");
bancorExchangeProvider.getAmountIn({
exchangeId: exchangeId,
tokenIn: address(token),
tokenOut: address(reserveToken),
amountOut: poolExchange1.reserveBalance
});
assertEq(amountIn, expectedAmountIn);
}

function test_getAmountIn_whenTokenInIsTokenAndReserveRatioIs100Percent_shouldReturnCorrectAmount() public {
Expand Down Expand Up @@ -1035,18 +1031,6 @@ contract BancorExchangeProviderTest_getAmountOut is BancorExchangeProviderTest {
assertEq(amountOut, expectedAmountOut);
}

function test_getAmountOut_whenTokenInIsTokenAndSupplyIsZero_shouldRevert() public {
poolExchange1.tokenSupply = 0;
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
vm.expectRevert("ERR_INVALID_SUPPLY");
bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
tokenIn: address(token),
tokenOut: address(reserveToken),
amountIn: 1e18
});
}

function test_getAmountOut_whenTokenInIsTokenAndReserveBalanceIsZero_shouldRevert() public {
poolExchange1.reserveBalance = 0;
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
Expand All @@ -1059,19 +1043,6 @@ contract BancorExchangeProviderTest_getAmountOut is BancorExchangeProviderTest {
});
}

function test_getAmountOut_whenTokenInIsTokenAndAmountLargerSupply_shouldRevert() public {
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
uint256 amountIn = (poolExchange1.tokenSupply * 1e8) / (1e8 - poolExchange1.exitContribution);

vm.expectRevert("ERR_INVALID_AMOUNT");
bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
tokenIn: address(token),
tokenOut: address(reserveToken),
amountIn: amountIn + 2
});
}

function test_getAmountOut_whenTokenInIsTokenAndAmountIsZero_shouldReturnZero() public {
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
uint256 amountOut = bancorExchangeProvider.getAmountOut({
Expand All @@ -1083,17 +1054,15 @@ contract BancorExchangeProviderTest_getAmountOut is BancorExchangeProviderTest {
assertEq(amountOut, 0);
}

function test_getAmountOut_whenTokenInIsTokenAndAmountIsSupplyPlusExitContribution_shouldReturnReserveBalance()
public
{
function test_getAmountOut_whenTokenInIsTokenAndAmountInIsLargerOrEqualSupply_shouldRevert() public {
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
uint256 amountOut = bancorExchangeProvider.getAmountOut({
vm.expectRevert("amountIn is greater than tokenSupply");
bancorExchangeProvider.getAmountOut({
exchangeId: exchangeId,
tokenIn: address(token),
tokenOut: address(reserveToken),
amountIn: (poolExchange1.tokenSupply * 1e8) / (1e8 - poolExchange1.exitContribution) + 1 // +1 to account for rounding
amountIn: (poolExchange1.tokenSupply)
});
assertEq(amountOut, poolExchange1.reserveBalance);
}

function test_getAmountOut_whenTokenInIsTokenAndReserveRatioIs100Percent_shouldReturnCorrectAmount() public {
Expand Down Expand Up @@ -1561,6 +1530,14 @@ contract BancorExchangeProviderTest_swapIn is BancorExchangeProviderTest {
bancorExchangeProvider.swapIn(exchangeId, address(token), address(token), 1e18);
}

function test_swapIn_whenTokenInIsTokenAndAmountIsLargerOrEqualSupply_shouldRevert() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
vm.prank(brokerAddress);
vm.expectRevert("amountIn is greater than tokenSupply");
bancorExchangeProvider.swapIn(exchangeId, address(token), address(reserveToken), poolExchange1.tokenSupply);
}

function test_swapIn_whenTokenInIsReserveAsset_shouldSwapIn() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountIn = 1e18;
Expand Down Expand Up @@ -1608,6 +1585,39 @@ contract BancorExchangeProviderTest_swapIn is BancorExchangeProviderTest {
assertEq(reserveBalanceAfter, reserveBalanceBefore - amountOut);
assertEq(tokenSupplyAfter, tokenSupplyBefore - amountIn);
}

function test_swapIn_whenTokenInIsTokenAndExitContributionIsNonZero_shouldReturnSameAmountWhenSellIsDoneInMultipleSteps()
public
{
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountToSell = 100_000 * 1e18;
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
vm.prank(brokerAddress);
uint256 amountOutInOneSell = bancorExchangeProvider.swapIn(
exchangeId,
address(token),
address(reserveToken),
amountToSell
);

// destroy and recreate the exchange to reset everything
vm.prank(bancorExchangeProvider.owner());
bancorExchangeProvider.destroyExchange(exchangeId, 0);
exchangeId = bancorExchangeProvider.createExchange(poolExchange1);

uint256 amountOutInMultipleSells;
for (uint256 i = 0; i < 100_000; i++) {
vm.prank(brokerAddress);
amountOutInMultipleSells += bancorExchangeProvider.swapIn(
exchangeId,
address(token),
address(reserveToken),
1e18
);
}
// we allow up to 0.1% difference due to precision loss on exitContribution accounting
assertApproxEqRel(amountOutInOneSell, amountOutInMultipleSells, 1e18 * 0.001);
}
}

contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {
Expand Down Expand Up @@ -1650,6 +1660,14 @@ contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {
bancorExchangeProvider.swapOut(exchangeId, address(token), address(token), 1e18);
}

function test_swapOut_whenTokenInIsTokenAndAmountInIsLargerOrEqualSupply_shouldRevert() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
vm.prank(brokerAddress);
vm.expectRevert("amountIn is greater than tokenSupply");
bancorExchangeProvider.swapOut(exchangeId, address(token), address(reserveToken), poolExchange1.reserveBalance);
}

function test_swapOut_whenTokenInIsReserveAsset_shouldSwapOut() public {
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountOut = 1e18;
Expand Down Expand Up @@ -1698,4 +1716,30 @@ contract BancorExchangeProviderTest_swapOut is BancorExchangeProviderTest {

assertEq(tokenSupplyAfter, tokenSupplyBefore - amountIn);
}

function test_swapOut_whenTokenInIsTokenAndExitContributionIsNonZero_shouldReturnSameAmountWhenSellIsDoneInMultipleSteps()
public
{
BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
uint256 amountToBuy = 50_000 * 1e18;
bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);
vm.prank(brokerAddress);
uint256 amountInInOneBuy = bancorExchangeProvider.swapOut(
exchangeId,
address(token),
address(reserveToken),
amountToBuy
);

bancorExchangeProvider.destroyExchange(exchangeId, 0);
exchangeId = bancorExchangeProvider.createExchange(poolExchange1);

uint256 amountInInMultipleBuys;
for (uint256 i = 0; i < 50_000; i++) {
vm.prank(brokerAddress);
amountInInMultipleBuys += bancorExchangeProvider.swapOut(exchangeId, address(token), address(reserveToken), 1e18);
}
// we allow up to 0.1% difference due to precision loss on exitContribution accounting
assertApproxEqRel(amountInInOneBuy, amountInInMultipleBuys, 1e18 * 0.001);
}
}

0 comments on commit 3d6a1f8

Please sign in to comment.