Skip to content

Commit

Permalink
bi-directional swap
Browse files Browse the repository at this point in the history
  • Loading branch information
ycryptx committed Jun 22, 2023
1 parent 85353ad commit 688d068
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 45 deletions.
32 changes: 20 additions & 12 deletions evm/src/XOilSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ contract XOilSwap is ReentrancyGuard {
/// @notice Emitted on a successful swap
/// @param caller The user who performed the swap
/// @param amount The amount swapped
event Swap(address indexed caller, uint256 amount);
event Swap(address indexed caller, uint256 amount, bool indexed toNativeToken);

/// @dev Prevents a swap when:
/// (a) the caller hasn't approved this contract to spend xOilToken on its behalf or
/// (b) this contract doesn't have enough native token balance
modifier eligibleToSwap(uint256 amount) {
/// (a) the caller hasn't approved this contract to spend input token on its behalf or
/// (b) this contract doesn't have enough output token balance
/// @param amount the amount to swap
/// @param toNativeToken true if xOil to native token swap, false if native token to xOil swap
modifier eligibleToSwap(uint256 amount, bool toNativeToken) {
IERC20 inputToken = toNativeToken ? xOilToken : nativeToken;
IERC20 outputToken = toNativeToken ? nativeToken : xOilToken;
require(
xOilToken.allowance(msg.sender, xOilSwapAddress) >= amount,
"xOilSwap: amount exceeds allowance"
inputToken.allowance(msg.sender, xOilSwapAddress) >= amount,
"xOilSwap: amount exceeds input token allowance"
);
require(
nativeToken.balanceOf(xOilSwapAddress) >= amount,
"xOilSwap: amount exceeds native token balance"
outputToken.balanceOf(xOilSwapAddress) >= amount,
"xOilSwap: amount exceeds output token balance"
);
_;
}
Expand All @@ -39,10 +43,14 @@ contract XOilSwap is ReentrancyGuard {
xOilSwapAddress = address(this);
}

/// @notice Do a 1-to-1 swap from xOil to native token
/// @notice Do a 1-to-1 swap between xOil and native token
/// @param amount the amount to swap
function swap(uint256 amount) external eligibleToSwap(amount) nonReentrant {
xOilToken.safeTransferFrom(msg.sender, xOilSwapAddress, amount);
nativeToken.safeTransfer(msg.sender, amount);
/// @param toNativeToken true if xOil to native token swap, false if native token to xOil swap
function swap(uint256 amount, bool toNativeToken) external eligibleToSwap(amount, toNativeToken) nonReentrant {
IERC20 inputToken = toNativeToken ? xOilToken : nativeToken;
IERC20 outputToken = toNativeToken ? nativeToken : xOilToken;
inputToken.safeTransferFrom(msg.sender, xOilSwapAddress, amount);
outputToken.safeTransfer(msg.sender, amount);
emit Swap(msg.sender, amount, toNativeToken);
}
}
161 changes: 128 additions & 33 deletions evm/test/XOilSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,178 @@
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "../src/XOilSwap.sol";
import "../src/CustomMintableToken.sol";

contract OilSwapTest is Test {
contract XOilSwapToNativeTokenTest is Test {
XOilSwap private xOilSwap;
CustomMintableToken private xOilContract;
CustomMintableToken private IscContract;
IERC20 private inputTokenContract;
IERC20 private outputTokenContract;
address private alice;
bool private toNativeToken;

function setUp() public {
toNativeToken = true;
alice = 0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326;
xOilContract = new CustomMintableToken(1000, "Wormhole xOil", "xOIL");
IscContract = new CustomMintableToken(
CustomMintableToken xOilContract = new CustomMintableToken(1000, "Wormhole xOil", "xOIL");
CustomMintableToken iscContract = new CustomMintableToken(
1000,
"International Stable Currency",
"ISC"
);

// init swap contract
xOilSwap = new XOilSwap(address(xOilContract), address(IscContract));
xOilSwap = new XOilSwap(address(xOilContract), address(iscContract));

// in this test we're swapping from xOIL to native token,
// so input is xOIL and output is native token (ISC)
inputTokenContract = xOilContract;
outputTokenContract = iscContract;

// fund a user with some xOil
xOilContract.transfer(address(alice), 100);
inputTokenContract.transfer(address(alice), 100);

// fund the swap contract with native token
IscContract.transfer(address(xOilSwap), 50);
outputTokenContract.transfer(address(xOilSwap), 50);
}

function testNotEnoughXOilAllowance() public {
function testRevertWhenNotEnoughXOilAllowanceSetByUser() public {
vm.startPrank(address(alice));
vm.expectRevert("xOilSwap: amount exceeds allowance");
xOilSwap.swap(100);
vm.expectRevert("xOilSwap: amount exceeds input token allowance");
xOilSwap.swap(100, toNativeToken);
vm.stopPrank();

// sanity check that no balances changed
assertEq(xOilContract.balanceOf(address(alice)), 100);
assertEq(IscContract.balanceOf(address(xOilSwap)), 50);
assertEq(inputTokenContract.balanceOf(address(alice)), 100);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 50);
}

function testNotEnoughNativeTokenBalance() public {
function testRevertWhenNotEnoughNativeTokenBalanceInTheSwapContract() public {
vm.startPrank(address(alice));
xOilContract.approve(address(xOilSwap), 100);
vm.expectRevert("xOilSwap: amount exceeds native token balance");
xOilSwap.swap(100);
inputTokenContract.approve(address(xOilSwap), 100);
vm.expectRevert("xOilSwap: amount exceeds output token balance");
xOilSwap.swap(100, toNativeToken);
vm.stopPrank();

// sanity check that no balances changed
assertEq(xOilContract.balanceOf(address(alice)), 100);
assertEq(IscContract.balanceOf(address(xOilSwap)), 50);
assertEq(inputTokenContract.balanceOf(address(alice)), 100);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 50);
}

function testSuccessfulSwap() public {
assertEq(IscContract.balanceOf(address(alice)), 0);
assertEq(outputTokenContract.balanceOf(address(alice)), 0);
vm.startPrank(address(alice));
xOilContract.approve(address(xOilSwap), 100);
xOilSwap.swap(45);
inputTokenContract.approve(address(xOilSwap), 100);
xOilSwap.swap(45, toNativeToken);
vm.stopPrank();

assertEq(xOilContract.balanceOf(address(alice)), 55);
assertEq(IscContract.balanceOf(address(alice)), 45);
assertEq(IscContract.balanceOf(address(xOilSwap)), 5);
assertEq(inputTokenContract.balanceOf(address(alice)), 55);
assertEq(outputTokenContract.balanceOf(address(alice)), 45);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 5);
}

function testSwapDrainsContractBalance() public {
assertEq(IscContract.balanceOf(address(alice)), 0);
assertEq(outputTokenContract.balanceOf(address(alice)), 0);
vm.startPrank(address(alice));
xOilContract.approve(address(xOilSwap), 100);
xOilSwap.swap(50);
inputTokenContract.approve(address(xOilSwap), 100);
xOilSwap.swap(50, toNativeToken);
vm.stopPrank();

assertEq(xOilContract.balanceOf(address(alice)), 50);
assertEq(IscContract.balanceOf(address(alice)), 50);
assertEq(IscContract.balanceOf(address(xOilSwap)), 0);
assertEq(inputTokenContract.balanceOf(address(alice)), 50);
assertEq(outputTokenContract.balanceOf(address(alice)), 50);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 0);

vm.startPrank(address(alice));
vm.expectRevert("xOilSwap: amount exceeds native token balance");
xOilSwap.swap(1);
vm.expectRevert("xOilSwap: amount exceeds output token balance");
xOilSwap.swap(1, toNativeToken);
vm.stopPrank();
}
}

contract XOilSwapToXOilTokenTest is Test {
XOilSwap private xOilSwap;
IERC20 private inputTokenContract;
IERC20 private outputTokenContract;
address private alice;
bool private toNativeToken;

function setUp() public {
toNativeToken = false;
alice = 0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326;
CustomMintableToken xOilContract = new CustomMintableToken(1000, "Wormhole xOil", "xOIL");
CustomMintableToken iscContract = new CustomMintableToken(
1000,
"International Stable Currency",
"ISC"
);

// init swap contract
xOilSwap = new XOilSwap(address(xOilContract), address(iscContract));


// this test is the inverse of the previous test suite,
// which means we're swapping from native token to xOil,
// so input is native token (ISC) and output is xOIL
inputTokenContract = iscContract;
outputTokenContract = xOilContract;

// fund a user with some xOil
inputTokenContract.transfer(address(alice), 100);

// fund the swap contract with native token
outputTokenContract.transfer(address(xOilSwap), 50);
}

function testRevertWhenNotEnoughNativeTokenAllowanceSetByUser() public {
vm.startPrank(address(alice));
vm.expectRevert("xOilSwap: amount exceeds input token allowance");
xOilSwap.swap(100, toNativeToken);
vm.stopPrank();

// sanity check that no balances changed
assertEq(inputTokenContract.balanceOf(address(alice)), 100);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 50);
}

function testRevertWhenNotEnoughXOilBalanceInTheSwapContract() public {
vm.startPrank(address(alice));
inputTokenContract.approve(address(xOilSwap), 100);
vm.expectRevert("xOilSwap: amount exceeds output token balance");
xOilSwap.swap(100, toNativeToken);
vm.stopPrank();

// sanity check that no balances changed
assertEq(inputTokenContract.balanceOf(address(alice)), 100);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 50);
}

function testSuccessfulSwap() public {
assertEq(outputTokenContract.balanceOf(address(alice)), 0);
vm.startPrank(address(alice));
inputTokenContract.approve(address(xOilSwap), 100);
xOilSwap.swap(45, toNativeToken);
vm.stopPrank();

assertEq(inputTokenContract.balanceOf(address(alice)), 55);
assertEq(outputTokenContract.balanceOf(address(alice)), 45);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 5);
}

function testSwapDrainsContractBalance() public {
assertEq(outputTokenContract.balanceOf(address(alice)), 0);
vm.startPrank(address(alice));
inputTokenContract.approve(address(xOilSwap), 100);
xOilSwap.swap(50, toNativeToken);
vm.stopPrank();

assertEq(inputTokenContract.balanceOf(address(alice)), 50);
assertEq(outputTokenContract.balanceOf(address(alice)), 50);
assertEq(outputTokenContract.balanceOf(address(xOilSwap)), 0);

vm.startPrank(address(alice));
vm.expectRevert("xOilSwap: amount exceeds output token balance");
xOilSwap.swap(1, toNativeToken);
vm.stopPrank();
}
}

0 comments on commit 688d068

Please sign in to comment.