Skip to content

Commit

Permalink
add and remove liquidity, swap with ETH features add
Browse files Browse the repository at this point in the history
  • Loading branch information
astryp212 committed Nov 10, 2024
1 parent 617cede commit e9a528a
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 30 deletions.
216 changes: 198 additions & 18 deletions contracts/Core.sol
Original file line number Diff line number Diff line change
@@ -1,66 +1,246 @@
pragma solidity >=0.8.0;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Import necessary contracts and interfaces
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IUniswapV2Router02.sol";

import "./interfaces/IUniswapV2Factory.sol";
import "./TestToken.sol";

contract Core {
// Use SafeERC20 for secure token transfers
using SafeERC20 for IERC20;

// Instances of the Uniswap V2 Router and Factory contracts
IUniswapV2Router02 public router;
IUniswapV2Factory public factory;

constructor(IUniswapV2Router02 _router) {
/**
* @notice Constructs the Core contract with the necessary Uniswap V2 Router and Factory instances.
* @param _router The address of the Uniswap V2 Router contract.
* @param _factory The address of the Uniswap V2 Factory contract.
*/
constructor(IUniswapV2Router02 _router, IUniswapV2Factory _factory) {
router = _router;
factory = _factory;
}

/**
* @notice Adds liquidity to a Uniswap V2 pair.
* @param token1 The address of the first token in the pair.
* @param token2 The address of the second token in the pair.
* @param amount1 The amount of token1 to add.
* @param amount2 The amount of token2 to add.
* @return liquidity The amount of liquidity tokens received.
*/
function addLiquidity(
IERC20 token1,
IERC20 token2,
uint256 amount1,
uint256 amount2
) external {
) external returns (uint256 liquidity) {
// Transfer specified amounts of tokens from the user to this contract
token1.safeTransferFrom(msg.sender, address(this), amount1);
token2.safeTransferFrom(msg.sender, address(this), amount2);

// Approve the router to spend the tokens on behalf of this contract
token1.approve(address(router), amount1);
token2.approve(address(router), amount2);

(uint amountA, uint amountB, uint liquidity) = router.addLiquidity(
// Add liquidity to the Uniswap pair
(uint256 amountA, uint256 amountB, uint256 liquidityReceived) = router
.addLiquidity(
address(token1),
address(token2),
amount1,
amount2,
amount1 / 2, // Minimum amount of token1 to add
amount2 / 2, // Minimum amount of token2 to add
msg.sender, // Recipient of the liquidity tokens
block.timestamp // Current timestamp as deadline
);

// Return the amount of liquidity tokens received
return liquidityReceived;
}

/**
* Adds liquidity to an ETH-token pair on a decentralized exchange.
*
* @param token - The ERC20 token to add liquidity for.
* @param amountIn - The amount of the token to deposit.
* @return liquidity - The amount of liquidity received.
*/
function addLiquidityETH(
IERC20 token,
uint256 amountIn
) external payable returns (uint256 liquidity) {
// Transfer the token amount from the sender to the contract
token.safeTransferFrom(msg.sender, address(this), amountIn);

// Approve the router contract to spend the token amount
token.approve(address(router), amountIn);

// Add liquidity to the ETH-token pair
(uint256 amountA, uint256 amountB, uint256 liquidityReceived) = router
.addLiquidityETH{ value: msg.value }(
address(token),
amountIn,
0, // Slippage is set to 0
0, // Slippage is set to 0
msg.sender,
block.timestamp
);

// Return the amount of liquidity received
return liquidityReceived;
}

/**
* @notice Removes liquidity from a Uniswap V2 pair.
* @param token1 The address of the first token in the pair.
* @param token2 The address of the second token in the pair.
* @param liquidity The amount of liquidity tokens to remove.
* @return amountOut1 The amount of token1 received.
* @return amountOut2 The amount of token2 received.
*/
function removeLiquidity(
IERC20 token1,
IERC20 token2,
uint256 liquidity
) external returns (uint256 amountOut1, uint256 amountOut2) {
// Get the address of the liquidity pair for the two tokens
address pair = factory.getPair(address(token1), address(token2));
require(pair != address(0), "Pair does not exist");

// Transfer liquidity tokens from the user to this contract
token1.safeTransferFrom(msg.sender, address(this), liquidity);

// Approve the router to spend the liquidity tokens
token1.approve(address(router), liquidity);

// Remove liquidity from the pair
(amountOut1, amountOut2) = router.removeLiquidity(
address(token1),
address(token2),
amount1,
amount2,
amount1 / 2,
amount2 / 2,
liquidity,
1, // Minimum amount of token1 to receive
1, // Minimum amount of token2 to receive
msg.sender, // Recipient of the tokens
block.timestamp // Current timestamp as deadline
);

// Return the amounts of tokens received
return (amountOut1, amountOut2);
}

/**
* @notice Removes liquidity from the ETH-token pair.
* @param token The ERC20 token to remove liquidity for.
* @param liquidity The amount of liquidity to remove.
* @return amountOut1 The amount of the first token received.
* @return amountOut2 The amount of the second token (ETH) received.
*/
function removeLiquidityETH(
IERC20 token,
uint256 liquidity
) external returns (uint256 amountOut1, uint256 amountOut2) {
// Get the address of the token-ETH pair
address pair = factory.getPair(address(token), router.WETH());
// Revert if the pair does not exist
require(pair != address(0), "Pair does not exist");

// Transfer the liquidity from the caller to this contract
token.safeTransferFrom(msg.sender, address(this), liquidity);
// Approve the router to spend the liquidity
token.approve(address(router), liquidity);

// Remove the liquidity from the pair
(amountOut1, amountOut2) = router.removeLiquidityETH(
address(token),
liquidity,
1,
1,
msg.sender,
block.timestamp
);

return (amountOut1, amountOut2);
}

/**
* @notice Swaps an exact amount of one token for another token.
* @param token1 The address of the token to swap from.
* @param token2 The address of the token to swap to.
* @param amountIn The amount of token1 to swap.
* @param amountOutMin The minimum amount of token2 to receive from the swap.
* @return amountOut The actual amount of token2 received from the swap.
*/
function swapTokens(
IERC20 token1,
IERC20 token2,
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256 amountOut) {
// Transfer the specified amount of token1 from the user to this contract
token1.safeTransferFrom(msg.sender, address(this), amountIn);

// Approve the router to spend token1 on behalf of this contract
token1.approve(address(router), amountIn);

// Define the path for the swap (token1 -> token2)
address[] memory path = new address[](2);
path[0] = address(token1);
path[1] = address(token2);
path[0] = address(token1); // From token1
path[1] = address(token2); // To token2

// Execute the swap
uint[] memory amounts = router.swapExactTokensForTokens(
amountIn,
amountOutMin,
path,
msg.sender,
block.timestamp
amountIn, // Amount of token1 to swap
amountOutMin, // Minimum amount of token2 to receive
path, // The path of the swap
msg.sender, // Recipient of the output tokens
block.timestamp // Deadline for the swap
);

// Return the actual amount of token2 received
return amounts[1];
}

/**
* @notice Swaps an exact amount of ERC20 token for ETH.
* @param token The ERC20 token to swap.
* @param amountIn The amount of token to swap.
* @param amountOutMin The minimum amount of ETH to receive from the swap.
* @return amountOut The actual amount of ETH received from the swap.
*/
function swapTokenWithETH(
address wethAddress,
IERC20 token,
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256 amountOut) {
// Transfer the specified amount of token from the user to this contract
token.safeTransferFrom(msg.sender, address(this), amountIn);

// Approve the router to spend the token on behalf of this contract
token.approve(address(router), amountIn);

// Define the path for the swap (token -> WETH)
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = wethAddress;

// Execute the swap
uint[] memory amounts = router.swapExactTokensForETH(
amountIn, // Amount of token to swap
amountOutMin, // Minimum amount of ETH to receive
path, // The path of the swap
msg.sender, // Recipient of the output ETH
block.timestamp // Deadline for the swap
);

// Return the actual amount of ETH received
return amounts[1];
}
}
32 changes: 22 additions & 10 deletions contracts/interfaces/IUniswapV2Factory.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
event PairCreated(
address indexed token0,
address indexed token1,
address pair,
uint
);

function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);

function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function getPair(
address tokenA,
address tokenB
) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);

function createPair(address tokenA, address tokenB) external returns (address pair);
function createPair(
address tokenA,
address tokenB
) external returns (address pair);

function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
1 change: 1 addition & 0 deletions contracts/interfaces/IUniswapV2Pair.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IUniswapV2Pair {
Expand Down
1 change: 1 addition & 0 deletions contracts/interfaces/IUniswapV2Router01.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IUniswapV2Router01 {
Expand Down
1 change: 1 addition & 0 deletions contracts/interfaces/IUniswapV2Router02.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "./IUniswapV2Router01.sol";
Expand Down
33 changes: 31 additions & 2 deletions test/Core.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { expect } from "chai"
import { Signer, parseEther } from "ethers"
import { Signer, accessListify, parseEther } from "ethers"

Check warning on line 2 in test/Core.ts

View workflow job for this annotation

GitHub Actions / Check Eslint of TS and Other Files

'accessListify' is defined but never used
import { deployments, ethers, getNamedAccounts } from "hardhat"

Check warning on line 3 in test/Core.ts

View workflow job for this annotation

GitHub Actions / Check Eslint of TS and Other Files

'deployments' is defined but never used

Check warning on line 3 in test/Core.ts

View workflow job for this annotation

GitHub Actions / Check Eslint of TS and Other Files

'getNamedAccounts' is defined but never used
import { Core, IUniswapV2Factory, IUniswapV2Router02, TestToken } from "typechain-types"

describe("Core", () => {
const ADDRESS__UNISWAP_V2_ROUTER = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
const ADDRESS__UNISWAP_V2_FACTORY = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
const ADDRESS__WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"

let deployer: Signer, alice: Signer, bob: Signer

Check warning on line 11 in test/Core.ts

View workflow job for this annotation

GitHub Actions / Check Eslint of TS and Other Files

'bob' is assigned a value but never used
let token1: TestToken
Expand All @@ -26,7 +27,7 @@ describe("Core", () => {
v2Factory = await ethers.getContractAt("IUniswapV2Factory", ADDRESS__UNISWAP_V2_FACTORY)

const CoreFactory = await ethers.getContractFactory("Core")
core = await CoreFactory.deploy(ADDRESS__UNISWAP_V2_ROUTER)
core = await CoreFactory.deploy(ADDRESS__UNISWAP_V2_ROUTER, ADDRESS__UNISWAP_V2_FACTORY)
})

it("test after construction", async () => {
Expand Down Expand Up @@ -76,4 +77,32 @@ describe("Core", () => {
console.log("Balance of token1 : ", await token1.balanceOf(alice))
console.log("Balance of token2 : ", await token2.balanceOf(alice))
})

it("test swapTokenWithETH", async () => {
// Transfer tokens to Alice
await token1.connect(deployer).transfer(await alice.getAddress(), parseEther("200"))

// Approve tokens for the core
await token1.connect(alice).approve(await core.getAddress(), parseEther("100"))

// Add liquidity
await core
.connect(alice)
.addLiqudityETH(await token1.getAddress(), parseEther("100"), { value: parseEther("20") })

// Transfer tokens to Alice
await token1.connect(deployer).transfer(await alice.getAddress(), parseEther("100"))

// Approve tokens for the core
await token1.connect(alice).approve(await core.getAddress(), parseEther("100"))

// Swap Token
const amountOut = await core
.connect(alice)
.swapTokenWithETH(ADDRESS__WETH, await token1.getAddress(), parseEther("100"), parseEther("0"))

// Check the Amount
console.log("Balance of token1 : ", await token1.balanceOf(alice))
console.log("ETH amount out : ", amountOut.value)
})
})

0 comments on commit e9a528a

Please sign in to comment.