Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
import {LibTestArb, OrderTakerSetup} from "test/util/lib/LibTestArb.sol";
import {SpenderProxy, SplitSpenderPool} from "test/util/concrete/SplitSpenderExchange.sol";

/// When spender != pool in the exchange data encoded in takeOrders.data,
/// the approval targets the spender while the call targets the pool.
contract GenericPoolRaindexV6ArbOrderTakerSplitSpenderTest is Test {
function testSplitSpenderExchange() external {
SpenderProxy spender = new SpenderProxy();
SplitSpenderPool pool = new SplitSpenderPool(spender);
OrderTakerSetup memory setup = LibTestArb.setup(vm, address(spender), address(pool), 100e18);

setup.arb.arb5(setup.raindex, setup.takeOrdersConfig, LibTestArb.noopTask());

// Spender approval was revoked after the exchange.
assertEq(setup.outputToken.allowance(address(setup.arb), address(spender)), 0, "spender allowance revoked");
// Pool never had approval.
assertEq(setup.outputToken.allowance(address(setup.arb), address(pool)), 0, "pool never had allowance");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
import {LibTestFlashBorrowerArb, FlashBorrowerSetup} from "test/util/lib/LibTestFlashBorrowerArb.sol";
import {LibTestArb} from "test/util/lib/LibTestArb.sol";
import {SpenderProxy, SplitSpenderPool} from "test/util/concrete/SplitSpenderExchange.sol";

/// When spender != pool in exchangeData, the approval targets the spender
/// while the call targets the pool. Verifies the split-address pattern works
/// end-to-end.
contract GenericPoolRaindexV6FlashBorrowerSplitSpenderTest is Test {
function testSplitSpenderExchange() external {
SpenderProxy spender = new SpenderProxy();
SplitSpenderPool pool = new SplitSpenderPool(spender);
FlashBorrowerSetup memory setup = LibTestFlashBorrowerArb.setup(vm, address(spender), address(pool), 100e18);

setup.arb.arb4(setup.raindex, setup.takeOrdersConfig, setup.exchangeData, LibTestArb.noopTask());

// Spender approval was revoked after the exchange.
assertEq(setup.outputToken.allowance(address(setup.arb), address(spender)), 0, "spender allowance revoked");
// Pool never had approval.
assertEq(setup.outputToken.allowance(address(setup.arb), address(pool)), 0, "pool never had allowance");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
import {
RouteProcessorRaindexV6ArbOrderTaker
} from "../../../src/concrete/arb/RouteProcessorRaindexV6ArbOrderTaker.sol";

/// Direct test that fallback() accepts ETH transfers with non-empty calldata.
contract RouteProcessorRaindexV6ArbOrderTakerFallbackTest is Test {
function testFallbackAcceptsEthWithData() external {
RouteProcessorRaindexV6ArbOrderTaker arb = new RouteProcessorRaindexV6ArbOrderTaker();
vm.deal(address(this), 1 ether);

(bool success,) = address(arb).call{value: 1 ether}(hex"deadbeef");
assertTrue(success, "fallback() should accept ETH with data");
assertEq(address(arb).balance, 1 ether, "arb balance after fallback");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
import {
RouteProcessorRaindexV6ArbOrderTaker
} from "../../../src/concrete/arb/RouteProcessorRaindexV6ArbOrderTaker.sol";
import {Float} from "rain.raindex.interface/interface/IRaindexV6.sol";
import {LibRainDeploy} from "rain.deploy/lib/LibRainDeploy.sol";
import {LibTOFUTokenDecimals} from "rain.tofu.erc20-decimals/lib/LibTOFUTokenDecimals.sol";
import {LibRaindexDeploy} from "../../../src/lib/deploy/LibRaindexDeploy.sol";
import {MockToken} from "test/util/concrete/MockToken.sol";
import {MockRouteProcessor} from "test/util/concrete/MockRouteProcessor.sol";

/// Fuzz test over onTakeOrders2 Float parameters to exercise
/// toFixedDecimalLossy edge cases.
contract RouteProcessorRaindexV6ArbOrderTakerOnTakeOrders2FuzzTest is Test {
RouteProcessorRaindexV6ArbOrderTaker internal arb;
MockToken internal tokenA;
MockToken internal tokenB;

function setUp() external {
LibRainDeploy.etchZoltuFactory(vm);
LibRainDeploy.deployZoltu(LibTOFUTokenDecimals.TOFU_DECIMALS_EXPECTED_CREATION_CODE);

tokenA = new MockToken("A", "A", 18);
tokenB = new MockToken("B", "B", 18);

MockRouteProcessor mockRp = new MockRouteProcessor();
vm.etch(LibRaindexDeploy.ROUTE_PROCESSOR_DEPLOYED_ADDRESS, address(mockRp).code);

arb = new RouteProcessorRaindexV6ArbOrderTaker();
}

/// @dev onTakeOrders2 with fuzzed Float values must not leave tokens
/// stranded. It either succeeds (no tokens move because arb has none)
/// or reverts cleanly.
function testOnTakeOrders2FuzzedFloats(Float inputAmountSent, Float totalOutputAmount) external {
bytes memory route = abi.encode(hex"");

// The call may revert for invalid Float values (e.g., negative
// fixed-point conversion). We just verify it doesn't panic and
// leaves no tokens behind on success.
try arb.onTakeOrders2(address(tokenA), address(tokenB), inputAmountSent, totalOutputAmount, route) {
assertEq(tokenA.balanceOf(address(arb)), 0, "tokenA balance after");
assertEq(tokenB.balanceOf(address(arb)), 0, "tokenB balance after");
} catch {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
import {
RouteProcessorRaindexV6ArbOrderTaker
} from "../../../src/concrete/arb/RouteProcessorRaindexV6ArbOrderTaker.sol";

/// Direct test that receive() accepts ETH transfers.
contract RouteProcessorRaindexV6ArbOrderTakerReceiveTest is Test {
function testReceiveAcceptsEth() external {
RouteProcessorRaindexV6ArbOrderTaker arb = new RouteProcessorRaindexV6ArbOrderTaker();
vm.deal(address(this), 1 ether);

(bool success,) = address(arb).call{value: 1 ether}("");
assertTrue(success, "receive() should accept ETH");
assertEq(address(arb).balance, 1 ether, "arb balance after receive");
}
}
59 changes: 59 additions & 0 deletions test/concrete/raindex/RaindexV6.multicall.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {Multicall} from "openzeppelin-contracts/contracts/utils/Multicall.sol";
import {RaindexV6ExternalRealTest} from "test/util/abstract/RaindexV6ExternalRealTest.sol";
import {IRaindexV6, TaskV2} from "rain.raindex.interface/interface/IRaindexV6.sol";
import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol";

/// Test that the Multicall inherited from OpenZeppelin works correctly on the
/// OrderBook, allowing multiple deposit calls in a single transaction.
contract RaindexV6MulticallTest is RaindexV6ExternalRealTest {
function testMulticallDeposits() external {
address alice = address(uint160(uint256(keccak256("alice.rain.test"))));

// Mock transferFrom for both tokens.
vm.mockCall(
address(iToken0),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, address(iRaindex)),
abi.encode(true)
);
vm.mockCall(
address(iToken1),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, address(iRaindex)),
abi.encode(true)
);

// Encode two deposit4 calls.
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(
IRaindexV6.deposit4.selector,
address(iToken0),
bytes32(uint256(0x01)),
LibDecimalFloat.packLossless(10, 0),
new TaskV2[](0)
);
calls[1] = abi.encodeWithSelector(
IRaindexV6.deposit4.selector,
address(iToken1),
bytes32(uint256(0x02)),
LibDecimalFloat.packLossless(20, 0),
new TaskV2[](0)
);

vm.prank(alice);
Multicall(address(iRaindex)).multicall(calls);

// Verify both vault balances were set.
assertEq(
Float.unwrap(iRaindex.vaultBalance2(alice, address(iToken0), bytes32(uint256(0x01)))),
Float.unwrap(LibDecimalFloat.packLossless(10, 0))
);
assertEq(
Float.unwrap(iRaindex.vaultBalance2(alice, address(iToken1), bytes32(uint256(0x02)))),
Float.unwrap(LibDecimalFloat.packLossless(20, 0))
);
}
}
42 changes: 42 additions & 0 deletions test/concrete/raindex/RaindexV6.negativePullPush.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {RaindexV6SelfTest} from "test/util/abstract/RaindexV6SelfTest.sol";
import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol";
import {NegativePull, NegativePush} from "../../../src/concrete/raindex/RaindexV6.sol";
import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {REVERTING_MOCK_BYTECODE} from "test/util/lib/LibTestConstants.sol";

/// Direct tests that `pullTokens` and `pushTokens` revert with `NegativePull`
/// and `NegativePush` when given a negative Float amount.
contract RaindexV6NegativePullPushTest is RaindexV6SelfTest {
address internal token;

/// External wrappers so vm.expectRevert can catch the internal revert.
function externalPullTokens(address account, address token_, Float amount) external {
pullTokens(account, token_, amount);
}

function externalPushTokens(address account, address token_, Float amount) external {
pushTokens(account, token_, amount);
}

function setUp() external {
token = address(uint160(uint256(keccak256("token.rain.test"))));
vm.etch(token, REVERTING_MOCK_BYTECODE);
vm.mockCall(token, abi.encodeWithSelector(IERC20Metadata.decimals.selector), abi.encode(18));
}

function testPullTokensNegativeAmountReverts() external {
Float negativeAmount = LibDecimalFloat.packLossless(-1, 0);
vm.expectRevert(abi.encodeWithSelector(NegativePull.selector));
this.externalPullTokens(address(0xBEEF), token, negativeAmount);
}

function testPushTokensNegativeAmountReverts() external {
Float negativeAmount = LibDecimalFloat.packLossless(-1, 0);
vm.expectRevert(abi.encodeWithSelector(NegativePush.selector));
this.externalPushTokens(address(0xBEEF), token, negativeAmount);
}
}
32 changes: 32 additions & 0 deletions test/concrete/raindex/RaindexV6.negativeVaultBalance.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {RaindexV6SelfTest} from "test/util/abstract/RaindexV6SelfTest.sol";
import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol";
import {NegativeVaultBalance} from "../../../src/concrete/raindex/RaindexV6.sol";

/// Direct test that `decreaseVaultBalance` reverts with `NegativeVaultBalance`
/// when the decrease exceeds the current balance.
contract RaindexV6NegativeVaultBalanceTest is RaindexV6SelfTest {
/// External wrapper so vm.expectRevert can catch the internal revert.
function externalDecreaseVaultBalance(address owner, address token, bytes32 vaultId, Float amount) external {
decreaseVaultBalance(owner, token, vaultId, amount);
}

function testDecreaseVaultBalanceBelowZeroReverts() external {
address owner = address(0xBEEF);
address token = address(0xAAAA);
bytes32 vaultId = bytes32(uint256(1));

// Set vault balance to 1.
increaseVaultBalance(owner, token, vaultId, LibDecimalFloat.packLossless(1, 0));

// Attempt to decrease by 2 — should revert with negative result (1 - 2 = -1).
Float balance = LibDecimalFloat.packLossless(1, 0);
Float decreaseAmount = LibDecimalFloat.packLossless(2, 0);
Float expectedNewBalance = LibDecimalFloat.sub(balance, decreaseAmount);
vm.expectRevert(abi.encodeWithSelector(NegativeVaultBalance.selector, expectedNewBalance));
this.externalDecreaseVaultBalance(owner, token, vaultId, decreaseAmount);
}
}
40 changes: 40 additions & 0 deletions test/concrete/raindex/RaindexV6.negativeVaultBalanceChange.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {RaindexV6SelfTest} from "test/util/abstract/RaindexV6SelfTest.sol";
import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol";
import {NegativeVaultBalanceChange} from "../../../src/concrete/raindex/RaindexV6.sol";

/// Direct tests that `increaseVaultBalance` and `decreaseVaultBalance` revert
/// with `NegativeVaultBalanceChange` when given a negative amount.
contract RaindexV6NegativeVaultBalanceChangeTest is RaindexV6SelfTest {
/// External wrappers so vm.expectRevert can catch the internal revert.
function externalIncreaseVaultBalance(address owner, address token, bytes32 vaultId, Float amount) external {
increaseVaultBalance(owner, token, vaultId, amount);
}

function externalDecreaseVaultBalance(address owner, address token, bytes32 vaultId, Float amount) external {
decreaseVaultBalance(owner, token, vaultId, amount);
}

function testIncreaseVaultBalanceNegativeAmountReverts() external {
address owner = address(0xBEEF);
address token = address(0xAAAA);
bytes32 vaultId = bytes32(uint256(1));

Float negativeAmount = LibDecimalFloat.packLossless(-1, 0);
vm.expectRevert(abi.encodeWithSelector(NegativeVaultBalanceChange.selector, negativeAmount));
this.externalIncreaseVaultBalance(owner, token, vaultId, negativeAmount);
}

function testDecreaseVaultBalanceNegativeAmountReverts() external {
address owner = address(0xBEEF);
address token = address(0xAAAA);
bytes32 vaultId = bytes32(uint256(1));

Float negativeAmount = LibDecimalFloat.packLossless(-1, 0);
vm.expectRevert(abi.encodeWithSelector(NegativeVaultBalanceChange.selector, negativeAmount));
this.externalDecreaseVaultBalance(owner, token, vaultId, negativeAmount);
}
}
54 changes: 54 additions & 0 deletions test/concrete/raindex/RaindexV6.takeOrder.minimumIOIsOutput.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {RaindexV6ExternalRealTest} from "test/util/abstract/RaindexV6ExternalRealTest.sol";
import {LibTestTakeOrder} from "test/util/lib/LibTestTakeOrder.sol";
import {OrderV4, TakeOrdersConfigV5, TaskV2} from "rain.raindex.interface/interface/IRaindexV6.sol";
import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol";
import {MinimumIO} from "../../../src/concrete/raindex/RaindexV6.sol";

/// When `IOIsInput = false`, `minimumIO` is checked against `totalTakerOutput`.
/// Verify the revert fires correctly in this branch.
contract RaindexV6TakeOrderMinimumIOIsOutputTest is RaindexV6ExternalRealTest {
function testTakeOrderMinimumIOIsOutputRevert() external {
address alice = address(uint160(uint256(keccak256("alice.rain.test"))));
address bob = address(uint160(uint256(keccak256("bob.rain.test"))));

// Deposit to alice's output vault.
vm.mockCall(
address(iToken1),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, address(iRaindex)),
abi.encode(true)
);
vm.prank(alice);
iRaindex.deposit4(
address(iToken1), bytes32(uint256(0x01)), LibDecimalFloat.packLossless(1, 0), new TaskV2[](0)
);

// Order outputs 1e-18 at ratio 1.
OrderV4 memory order = LibTestTakeOrder.addOrderWithExpression(
vm,
alice,
"_ _:1e-18 1;:;",
address(iToken0),
bytes32(uint256(0x01)),
address(iToken1),
bytes32(uint256(0x01))
);

// IOIsInput = false means minimumIO is checked against totalTakerOutput.
TakeOrdersConfigV5 memory takeConfig = LibTestTakeOrder.defaultTakeConfig(LibTestTakeOrder.wrapSingle(order));
takeConfig.IOIsInput = false;
takeConfig.minimumIO = LibDecimalFloat.packLossless(1, 0);

vm.prank(bob);
vm.expectRevert(
abi.encodeWithSelector(
MinimumIO.selector, LibDecimalFloat.packLossless(1, 0), LibDecimalFloat.packLossless(1, -18)
)
);
iRaindex.takeOrders4(takeConfig);
}
}
Loading
Loading