Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for non underlaying tokens and solve the decimal issue #2

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ Wrapper contract around Uniswap V3 to allow customers to swap super tokens. This

- Constructor Argument
- `ISwapRouter02 _swapRouter` - Contract address for the [Uniswap V3](https://docs.uniswap.org/protocol/reference/deployments) SwapRouter02 contract.

## Note

- You will manually have to change `@uniswap/swap-router-contracts/contracts/interfaces/ISwapRouter02.sol` `^0.7.6` ==> `^0.8.0`
244 changes: 142 additions & 102 deletions contracts/RexSuperSwap.sol
Original file line number Diff line number Diff line change
@@ -1,104 +1,144 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;

import {ISuperToken} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import "@uniswap/swap-router-contracts/contracts/interfaces/ISwapRouter02.sol";

contract RexSuperSwap {
ISwapRouter02 public immutable swapRouter;

event SuperSwapComplete(uint256 amountOut);

constructor(ISwapRouter02 _swapRouter) {
swapRouter = _swapRouter;
}

/**
* @dev Swaps `amountIn` of `_from` SuperToken for at least `amountOutMin`
* of `_to` SuperToken through `path` with `poolFees` fees for each pair.
*
* Returns the amount of `_to` SuperToken received.
*/
function swap(
ISuperToken _from,
ISuperToken _to,
uint256 amountIn,
uint256 amountOutMin,
address[] memory path,
uint24[] memory poolFees // Example: 0.3% * 10000 = 3000
) external returns (uint256 amountOut) {
require(amountIn > 0, "Amount cannot be 0");
require(path.length > 1, "Incorrect path");
require(
poolFees.length == path.length - 1,
"Incorrect poolFees length"
);

// Step 1: Get underlying tokens and verify path
address fromBase = _from.getUnderlyingToken();
address toBase = _to.getUnderlyingToken();

require(path[0] == fromBase, "Invalid 'from' base token");
require(path[path.length - 1] == toBase, "Invalid 'to' base token");

// Step 2: Transfer SuperTokens from sender
TransferHelper.safeTransferFrom(
address(_from),
msg.sender,
address(this),
amountIn
);

// Step 3: Downgrade
_from.downgrade(amountIn);

// Step 4: Approve and Swap

// Encode the path for swap
bytes memory encodedPath;
for (uint256 i = 0; i < path.length; i++) {
if (i == path.length - 1) {
encodedPath = abi.encodePacked(encodedPath, path[i]);
} else {
encodedPath = abi.encodePacked(
encodedPath,
path[i],
poolFees[i]
);
}
}

// Approve the router to spend token supplied (fromBase).
TransferHelper.safeApprove(
fromBase,
address(swapRouter),
amountIn
);

IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter
.ExactInputParams({
path: encodedPath,
recipient: address(this),
amountIn: amountIn,
amountOutMinimum: amountOutMin
});

// Execute the swap
amountOut = swapRouter.exactInput(params);

// Step 5: Upgrade and send tokens back
TransferHelper.safeApprove(address(toBase), address(_to), amountOut);
_to.upgrade(amountOut);
TransferHelper.safeApprove(address(_to), msg.sender, amountOut);
TransferHelper.safeTransferFrom(
address(_to),
address(this),
msg.sender,
amountOut
);

emit SuperSwapComplete(amountOut);
}
pragma solidity ^0.8.0;
pragma abicoder v2;

import {ISuperToken} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import "@uniswap/swap-router-contracts/contracts/interfaces/ISwapRouter02.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interface/IWMATIC.sol";

contract RexSuperSwap {
ISwapRouter02 public immutable swapRouter;
address public nativeToken;
IWMATIC public nativeWrappedToken;

event SuperSwapComplete(uint256 amountOut);

constructor(ISwapRouter02 _swapRouter, address _nativeToken, IWMATIC _nativeWrappedToken) {
swapRouter = _swapRouter;
nativeToken = _nativeToken;
nativeWrappedToken = _nativeWrappedToken;
}

receive() external payable {}

// Having unlimited approvals rather then dealing with decimal converisons.
// Not a problem as contract is not storing any tokens.
function approve(IERC20 _token, address _spender) internal {
if (_token.allowance(_spender, msg.sender) == 0) {
TransferHelper.safeApprove(address(_token), address(_spender), ((2 ** 256) - 1));
}
}

/**
* @dev Swaps `amountIn` of `_from` SuperToken for at least `amountOutMin`
* of `_to` SuperToken through `path` with `poolFees` fees for each pair.
*
* Returns the amount of `_to` SuperToken received.
*/
function swap(
ISuperToken _from,
ISuperToken _to,
uint256 amountIn,
uint256 amountOutMin, // Use underlaying decimals
address[] memory path,
uint24[] memory poolFees, // Example: 0.3% * 10000 = 3000
bool _hasUnderlyingFrom,
bool _hasUnderlyingTo
) external payable returns (uint256 amountOut) {
require(amountIn > 0, "Amount cannot be 0");
require(path.length > 1, "Incorrect path");
require(
poolFees.length == path.length - 1,
"Incorrect poolFees length"
);

// Step 1: Get underlying tokens and verify path
address fromBase = address(_from);
if (_hasUnderlyingFrom) {
fromBase = _from.getUnderlyingToken();
}

bool isSourceNative = false;
if (fromBase == nativeToken) {
require(msg.value == amountIn, "Amount must match msg.value");
nativeWrappedToken.deposit{ value: amountIn };
fromBase = address(nativeWrappedToken);
isSourceNative = true;
}
shreyaspapi marked this conversation as resolved.
Show resolved Hide resolved

address toBase = address(_to);
if (_hasUnderlyingTo) {
toBase = _to.getUnderlyingToken();
}

require(path[0] == fromBase, "Invalid 'from' base token");
require(path[path.length - 1] == toBase, "Invalid 'to' base token");

// Step 2: Transfer SuperTokens from sender
if (!isSourceNative) {
TransferHelper.safeTransferFrom(
address(_from),
msg.sender,
address(this),
amountIn
);
}

// Step 3: Downgrade
if (_hasUnderlyingFrom) {
_from.downgrade(amountIn);
}

// Step 4: Approve and Swap

// Encode the path for swap
bytes memory encodedPath;
for (uint256 i = 0; i < path.length; i++) {
if (i == path.length - 1) {
encodedPath = abi.encodePacked(encodedPath, path[i]);
} else {
encodedPath = abi.encodePacked(
encodedPath,
path[i],
poolFees[i]
);
}
}

// Approve the router to spend token supplied (fromBase).
approve(IERC20(fromBase), address(swapRouter));

IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter
.ExactInputParams({
path: encodedPath,
recipient: address(this),
amountIn: IERC20(fromBase).balanceOf(address(this)),
amountOutMinimum: amountOutMin
});

// Execute the swap
amountOut = swapRouter.exactInput(params);

// use this if didnt work
// uint256 toBaseAmount = amountOut // toBase.balanceOf(address(this));

// Step 5: Upgrade and send tokens back
approve(IERC20(toBase), address(_to));
uint toBaseAmount = amountOut;
if (_hasUnderlyingTo) {
_to.upgrade(amountOut);
toBaseAmount = (amountOut * _to.decimals()) / (10 ** ISuperToken(toBase).decimals());
}

approve(IERC20(_to), msg.sender);
TransferHelper.safeTransfer(
address(_to),
msg.sender,
toBaseAmount
);

emit SuperSwapComplete(amountOut);
}
}
8 changes: 8 additions & 0 deletions contracts/interface/IWMATIC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

interface IWMATIC {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
9 changes: 7 additions & 2 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.7.6",
solidity: "0.8.0",
networks: {
ropsten: {
url: process.env.ROPSTEN_URL || "",
Expand All @@ -34,8 +34,13 @@ module.exports = {
accounts:
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
goerli: {
url: process.env.GOERLI_URL || "",
accounts:
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
polygon: {
url: process.env.POLYGON_NODE_URL,
url: process.env.POLYGON_URL,
accounts: [process.env.PRIVATE_KEY],
blockGasLimit: 20000000,
gasPrice: 35000000000 // 35 Gwei
Expand Down
Loading