Skip to content

Commit

Permalink
Merge pull request #35 from rainprotocol/2023-10-27-clear-test
Browse files Browse the repository at this point in the history
2023 10 27 clear test
  • Loading branch information
thedavidmeister authored Oct 30, 2023
2 parents 691c48d + f9ac0d9 commit 182c126
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 23 deletions.
49 changes: 26 additions & 23 deletions src/concrete/OrderBook.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.19;

import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
import {Multicall} from "lib/openzeppelin-contracts/contracts/utils/Multicall.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";

import "lib/rain.math.fixedpoint/src/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
import "lib/rain.math.fixedpoint/src/lib/LibFixedPointDecimalScale.sol";
import "lib/rain.interpreter/src/lib/caller/LibEncodedDispatch.sol";
import "lib/rain.interpreter/src/lib/caller/LibContext.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
import {Multicall} from "openzeppelin-contracts/contracts/utils/Multicall.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";

import "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
import "rain.math.fixedpoint/lib/LibFixedPointDecimalScale.sol";
import "rain.interpreter/src/lib/caller/LibEncodedDispatch.sol";
import "rain.interpreter/src/lib/caller/LibContext.sol";
import {
DeployerDiscoverableMetaV2,
DeployerDiscoverableMetaV2ConstructionConfig,
LibMeta
} from "lib/rain.interpreter/src/abstract/DeployerDiscoverableMetaV2.sol";
import "lib/rain.interpreter/src/lib/bytecode/LibBytecode.sol";
} from "rain.interpreter/src/abstract/DeployerDiscoverableMetaV2.sol";
import "rain.interpreter/src/lib/bytecode/LibBytecode.sol";

import "../interface/unstable/IOrderBookV3.sol";
import "../interface/unstable/IOrderBookV3OrderTaker.sol";
Expand Down Expand Up @@ -569,17 +569,17 @@ contract OrderBook is IOrderBookV3, ReentrancyGuard, Multicall, OrderBookV3Flash
// Emit the Clear event before `eval`.
emit Clear(msg.sender, alice, bob, clearConfig);
}
OrderIOCalculation memory aliceOrderIOCalculation_ = calculateOrderIO(
OrderIOCalculation memory aliceOrderIOCalculation = calculateOrderIO(
alice, clearConfig.aliceInputIOIndex, clearConfig.aliceOutputIOIndex, bob.owner, bobSignedContext
);
OrderIOCalculation memory bobOrderIOCalculation_ = calculateOrderIO(
OrderIOCalculation memory bobOrderIOCalculation = calculateOrderIO(
bob, clearConfig.bobInputIOIndex, clearConfig.bobOutputIOIndex, alice.owner, aliceSignedContext
);
ClearStateChange memory clearStateChange =
calculateClearStateChange(aliceOrderIOCalculation_, bobOrderIOCalculation_);
calculateClearStateChange(aliceOrderIOCalculation, bobOrderIOCalculation);

recordVaultIO(alice, clearStateChange.aliceInput, clearStateChange.aliceOutput, aliceOrderIOCalculation_);
recordVaultIO(bob, clearStateChange.bobInput, clearStateChange.bobOutput, bobOrderIOCalculation_);
recordVaultIO(alice, clearStateChange.aliceInput, clearStateChange.aliceOutput, aliceOrderIOCalculation);
recordVaultIO(bob, clearStateChange.bobInput, clearStateChange.bobOutput, bobOrderIOCalculation);

{
// At least one of these will overflow due to negative bounties if
Expand Down Expand Up @@ -793,16 +793,19 @@ contract OrderBook is IOrderBookV3, ReentrancyGuard, Multicall, OrderBookV3Flash
OrderIOCalculation memory aliceOrderIOCalculation,
OrderIOCalculation memory bobOrderIOCalculation
) internal pure returns (ClearStateChange memory clearStateChange) {
calculateClearStateAlice(clearStateChange, aliceOrderIOCalculation, bobOrderIOCalculation);
// Calculate the clear state change for Alice.
(clearStateChange.aliceInput, clearStateChange.aliceOutput) =
calculateClearStateAlice(aliceOrderIOCalculation, bobOrderIOCalculation);

// Flip alice and bob to calculate bob's output.
calculateClearStateAlice(clearStateChange, bobOrderIOCalculation, aliceOrderIOCalculation);
(clearStateChange.bobInput, clearStateChange.bobOutput) =
calculateClearStateAlice(bobOrderIOCalculation, aliceOrderIOCalculation);
}

function calculateClearStateAlice(
ClearStateChange memory clearStateChange,
OrderIOCalculation memory aliceOrderIOCalculation,
OrderIOCalculation memory bobOrderIOCalculation
) internal pure {
) internal pure returns (uint256 aliceInput, uint256 aliceOutput) {
// Always round IO calculations up so that the counterparty pays more.
// This is the max input that bob can afford, given his own IO ratio
// and maximum spend/output.
Expand All @@ -818,15 +821,15 @@ contract OrderBook is IOrderBookV3, ReentrancyGuard, Multicall, OrderBookV3Flash
}
// Alice's final output is the scaled version of the 18 decimal output,
// rounded down to benefit Alice.
clearStateChange.aliceOutput = Output18Amount.unwrap(aliceOutputMax18).scaleN(
aliceOutput = Output18Amount.unwrap(aliceOutputMax18).scaleN(
aliceOrderIOCalculation.order.validOutputs[aliceOrderIOCalculation.outputIOIndex].decimals, 0
);

// Alice's input is her bob-capped output * her IO ratio, rounded up.
Input18Amount aliceInput18 = Input18Amount.wrap(
Output18Amount.unwrap(aliceOutputMax18).fixedPointMul(aliceOrderIOCalculation.IORatio, Math.Rounding.Up)
);
clearStateChange.aliceInput =
aliceInput =
// Use bob's output decimals as alice's input decimals.
//
// This is only safe if we have previously checked that the decimals
Expand Down
114 changes: 114 additions & 0 deletions test/concrete/OrderBook.clear.mock.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.19;

import "lib/forge-std/src/Test.sol";

import "test/util/abstract/OrderBookExternalMockTest.sol";
import {LibTestAddOrder} from "test/util/lib/LibTestAddOrder.sol";

/// @title OrderBookClearTest
/// Tests clearing an order.
contract OrderBookClearTest is OrderBookExternalMockTest {
function testClearSimple(
address alice,
OrderConfigV2 memory aliceConfig,
uint256 aliceVaultId,
address bob,
OrderConfigV2 memory bobConfig,
uint256 bobVaultId,
address expression,
address bountyBot,
uint256 aliceBountyVaultId,
uint256 bobBountyVaultId
) public {
// Different accounts
vm.assume(alice != bob);
vm.assume(alice != bountyBot);
vm.assume(bob != bountyBot);

// -- Add two orders with similar IO tokens (swapped)
// Add alice order with a input token (iToken0) and output token (iToken1)
(Order memory aliceOrder, bytes32 aliceOrderHash) =
_addOrderMockInternal(alice, aliceConfig, expression, iToken0, iToken1);
assertTrue(iOrderbook.orderExists(aliceOrderHash));

// Add bob order with a input token (iToken1) and output token (iToken0)
(Order memory bobOrder, bytes32 bobOrderHash) =
_addOrderMockInternal(bob, bobConfig, expression, iToken1, iToken0);
assertTrue(iOrderbook.orderExists(bobOrderHash));

// 2e18 tokens will be deposit for both (alice and bob)
uint256 amount = 2e18;

// Alice deposit his output token
_depositInternal(alice, iToken1, aliceVaultId, amount);

// Bob deposit his output token
_depositInternal(bob, iToken0, bobVaultId, amount);

// Since all the IO are just 1 length, the IOIndex will be zero (0).
// And vaultIds for the clearer
ClearConfig memory configClear = ClearConfig(0, 0, 0, 0, aliceBountyVaultId, bobBountyVaultId);

// Mock the interpreter.eval that is used inside clear().calculateOrderIO()
// Produce the stack output for OB
uint256[] memory orderStack = new uint256[](2);
orderStack[0] = 1e18; // orderOutputMax
orderStack[1] = 1e18; // orderIORatio
vm.mockCall(
address(iInterpreter),
abi.encodeWithSelector(IInterpreterV1.eval.selector),
abi.encode(orderStack, new uint256[](0))
);

// Clear the order using `bountyBot` address as caller clearer.
vm.prank(bountyBot);
iOrderbook.clear(aliceOrder, bobOrder, configClear, new SignedContextV1[](0), new SignedContextV1[](0));
}

/// Add an order using an owner (the caller) and modify the valid IOs to have
/// just one valid IO from an input and output tokens.
function _addOrderMockInternal(
address owner,
OrderConfigV2 memory config,
address expression,
IERC20 inputToken,
IERC20 outputToken
) internal returns (Order memory, bytes32) {
vm.assume(config.validInputs.length > 0);
vm.assume(config.validOutputs.length > 0);
config.evaluableConfig.bytecode = hex"02000000040000000000000000";
config.meta = new bytes(0);

config.validInputs = _helperBuildIO(config.validInputs, address(inputToken), 18);
config.validOutputs = _helperBuildIO(config.validOutputs, address(outputToken), 18);

return addOrderWithChecks(owner, config, expression);
}

/// Make a deposit to the OB mocking the internal transferFrom call.
function _depositInternal(address depositor, IERC20 token, uint256 vaultId, uint256 amount) internal {
vm.prank(depositor);
vm.mockCall(
address(token),
abi.encodeWithSelector(IERC20.transferFrom.selector, depositor, address(iOrderbook), amount),
abi.encode(true)
);
iOrderbook.deposit(address(token), vaultId, amount);

// Check that the vaultBalance was updated
assertEq(iOrderbook.vaultBalance(depositor, address(token), vaultId), amount);
}

/// Edit a given IO array to have only one index, with a given token and decimal.
/// This is useful to make matched Orders to do clears.
function _helperBuildIO(IO[] memory io, address newToken, uint8 newDecimals) internal pure returns (IO[] memory) {
IO[] memory ioAux = new IO[](1);

ioAux[0] = io[0];
ioAux[0].token = newToken;
ioAux[0].decimals = newDecimals;

return ioAux;
}
}

0 comments on commit 182c126

Please sign in to comment.