From e35e5e04f3f1c5f4a5792277424285fa4d7a67d3 Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 11:05:43 +0300 Subject: [PATCH 1/8] demo-router: contract + task --- contracts/UniswapV2Router01.sol | 69 +++--- tasks/core/demo-router.ts | 387 ++++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+), 42 deletions(-) create mode 100644 tasks/core/demo-router.ts diff --git a/contracts/UniswapV2Router01.sol b/contracts/UniswapV2Router01.sol index 05937e3..d72a61e 100644 --- a/contracts/UniswapV2Router01.sol +++ b/contracts/UniswapV2Router01.sol @@ -11,10 +11,6 @@ import "./interfaces/IUniswapV2Pair.sol"; contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { address public immutable factory; - modifier ensure(uint deadline) { - require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED'); - _; - } constructor(address _factory) public { // Revert if the factory address is the zero address or an empty string @@ -24,57 +20,36 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { } function addLiquidity( - address tokenA, - address tokenB, - address to, - uint deadline, - uint salt - ) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { - if (IUniswapV2Factory(factory).getTokenPair(tokenA, tokenB) == address(0)) { - IUniswapV2Factory(factory).createPair(tokenA, tokenB, 1, salt); - } - address pair = IUniswapV2Factory(factory).getTokenPair(tokenA, tokenB); - uint tokenAId = NilCurrencyBase(tokenA).getCurrencyId(); - uint tokenBId = NilCurrencyBase(tokenB).getCurrencyId(); - + address pair, + address to + ) external override { Nil.Token[] memory tokens = Nil.msgTokens(); if (tokens.length != 2) { revert("Send only 2 tokens to add liquidity"); } - assert(tokenAId == tokens[0].id); - assert(tokenBId == tokens[1].id); + // TODO: Probably check that tokens are sent to the right pair - if (tokens.length != 2) { - revert("UniswapV2Router: Expect 2 tokens to add liquidity"); - } - sendCurrencyInternal(pair, tokenAId, tokens[0].amount); - sendCurrencyInternal(pair, tokenBId, tokens[1].amount); - liquidity = IUniswapV2Pair(pair).mint(to); - amountA = tokens[0].amount; - amountB = tokens[1].amount; + smartCall(pair, tokens, abi.encodeWithSignature("mint(address)", to)); } // **** REMOVE LIQUIDITY **** function removeLiquidity( - address tokenA, - address tokenB, - uint liquidity, - uint amountAMin, - uint amountBMin, - address to, - uint deadline - ) public override ensure(deadline) returns (uint amountA, uint amountB) { - address pair = IUniswapV2Factory(factory).getTokenPair(tokenA, tokenB); - (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB); - (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to); - (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); - require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT'); - require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT'); + address pair, + address to + ) public override { Nil.Token[] memory tokens = Nil.msgTokens(); if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } - sendCurrencyInternal(pair, tokens[0].id, tokens[0].amount); // send liquidity to pair + smartCall(pair, tokens, abi.encodeWithSignature("burn(address)", to)); + } + + function swap(address to, address pair) public override { + Nil.Token[] memory tokens = Nil.msgTokens(); + if (tokens.length != 1) { + revert("UniswapV2Router: should contains only pair token"); + } + // TODO } // **** SWAP **** @@ -143,4 +118,14 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { receive() external payable { } + + function smartCall(address dst, Nil.Token[] memory tokens, bytes memory callData) private returns (bool) { + if (Nil.getShardId(dst) == Nil.getShardId(address(dst))) { + (bool success, ) = Nil.syncCall(dst, gasleft(), 0, tokens, callData); + return success; + } else { + Nil.asyncCall(dst, address(0), address(0), 0, Nil.FORWARD_REMAINING, false, 0, tokens, callData); + return true; + } + } } \ No newline at end of file diff --git a/tasks/core/demo-router.ts b/tasks/core/demo-router.ts new file mode 100644 index 0000000..849e329 --- /dev/null +++ b/tasks/core/demo-router.ts @@ -0,0 +1,387 @@ +import { shardNumber } from "@nilfoundation/hardhat-plugin/dist/utils/conversion"; +import { waitTillCompleted } from "@nilfoundation/niljs"; +import { task } from "hardhat/config"; +import type { + Currency, + UniswapV2Factory, + UniswapV2Pair, +} from "../../typechain-types"; +import { createClient } from "../util/client"; +import { + faucetWithdrawal, + mintAndSendCurrency, + sleep, +} from "../util/currencyUtils"; +import { deployNilContract } from "../util/deploy"; +import { calculateOutputAmount } from "../util/math"; + +task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( + async (taskArgs, hre) => { + const walletAddress = process.env.WALLET_ADDR; + if (!walletAddress) { + throw new Error("WALLET_ADDR is not set in environment variables"); + } + + const faucetAddress = process.env.FAUCET_ADDR; + + const shardId = 1; + const mintAmount = 100000; + const mintCurrency0Amount = 10000; + const mintCurrency1Amount = 10000; + const swapAmount = 1000; + + const { wallet, publicClient, signer } = await createClient(); + + const { + deployedContract: factoryContract, + contractAddress: factoryAddress, + } = await deployNilContract(hre, "UniswapV2Factory", [walletAddress]); + const { + deployedContract: routerContract, + contractAddress: routerAddress, + } = await deployNilContract(hre, "UniswapV2Router01", [walletAddress, "0x0000000000000000000000000000000000000000"]); + const { + deployedContract: Currency0Contract, + contractAddress: currency0Address, + } = await deployNilContract(hre, "Currency", [ + "currency0", + await signer.getPublicKey(), + ]); + const { + deployedContract: Currency1Contract, + contractAddress: currency1Address, + } = await deployNilContract(hre, "Currency", [ + "currency1", + await signer.getPublicKey(), + ]); + + console.log("Factory deployed " + factoryAddress); + console.log("Currency0 deployed " + currency0Address); + console.log("Currency1 deployed " + currency1Address); + + const factory = factoryContract as UniswapV2Factory; + + // 1. CREATE PAIR + await factory.createPair( + currency0Address.toLowerCase(), + currency1Address.toLowerCase(), + Math.floor(Math.random() * 10000000), + shardId, + ); + + const pairAddress = await factory.getTokenPair( + currency0Address.toLowerCase(), + currency1Address.toLowerCase(), + ); + + // Log the pair address + console.log(`Pair created successfully at address: ${pairAddress}`); + + // Attach to the Currency contract for both currencies + + const firstCurrency = Currency0Contract as Currency; + const firstCurrencyId = await firstCurrency.getCurrencyId(); + console.log(`First currency ID: ${firstCurrencyId}`); + + const secondCurrency = Currency1Contract as Currency; + const secondCurrencyId = await secondCurrency.getCurrencyId(); + console.log(`Second currency ID: ${secondCurrencyId}`); + + // Attach to the newly created Uniswap V2 Pair contract + const pairContract = await hre.ethers.getContractFactory("UniswapV2Pair"); + const pair = pairContract.attach(pairAddress) as UniswapV2Pair; + + // Initialize the pair with currency addresses and IDs + await pair.initialize( + currency0Address.toLowerCase(), + currency1Address.toLowerCase(), + firstCurrencyId, + secondCurrencyId, + ); + + console.log(`Pair initialized successfully at address: ${pairAddress}`); + + // Prepare currencies + await faucetWithdrawal( + currency0Address.toLowerCase(), + 100000000000n, + faucetAddress, + hre, + publicClient, + ); + + await sleep(2000); + + await faucetWithdrawal( + currency1Address.toLowerCase(), + 100000000000n, + faucetAddress, + hre, + publicClient, + ); + + await sleep(2000); + + // 2. MINT CURRENCIES + console.log( + `Minting ${mintAmount} Currency0 to wallet ${walletAddress}...`, + ); + await mintAndSendCurrency({ + publicClient, + signer, + currencyContract: firstCurrency, + contractAddress: currency0Address.toLowerCase(), + walletAddress, + mintAmount, + hre, + }); + + // Mint and send Currency1 + console.log( + `Minting ${mintAmount} Currency1 to wallet ${walletAddress}...`, + ); + await mintAndSendCurrency({ + publicClient, + signer, + currencyContract: secondCurrency, + contractAddress: currency1Address.toLowerCase(), + walletAddress, + mintAmount, + hre, + }); + + // Verify the balance of the recipient wallet for both currencies + const recipientBalanceCurrency0 = + await firstCurrency.getCurrencyBalanceOf(walletAddress); + const recipientBalanceCurrency1 = + await secondCurrency.getCurrencyBalanceOf(walletAddress); + + console.log( + `Recipient balance after transfer - Currency0: ${recipientBalanceCurrency0}, Currency1: ${recipientBalanceCurrency1}`, + ); + + // 3. PAIR: MINT + + // Send currency amounts to the pair contract + console.log( + `Sending ${mintCurrency0Amount} currency0 and ${mintCurrency1Amount} currency1 to ${pairAddress}...`, + ); + const hash = await wallet.sendMessage({ + // @ts-ignore + to: pairAddress, + feeCredit: BigInt(10_000_000), + value: BigInt(0), + refundTo: wallet.address, + tokens: [ + { + id: await firstCurrency.getCurrencyId(), + amount: BigInt(mintCurrency0Amount), + }, + { + id: await secondCurrency.getCurrencyId(), + amount: BigInt(mintCurrency1Amount), + }, + ], + }); + + await waitTillCompleted(publicClient, shardNumber(walletAddress), hash); + + // Log balances in the pair contract + const pairCurrency0Balance = + await firstCurrency.getCurrencyBalanceOf(pairAddress); + console.log("Pair Balance of Currency0:", pairCurrency0Balance.toString()); + + const pairCurrency1Balance = + await secondCurrency.getCurrencyBalanceOf(pairAddress); + console.log("Pair Balance of Currency1:", pairCurrency1Balance.toString()); + + // Mint liquidity + console.log("Minting pair tokens..."); + await pair.mint(walletAddress); + console.log("Liquidity added..."); + + // Retrieve and log reserves from the pair + const [reserve0, reserve1] = await pair.getReserves(); + console.log( + `MINT RESULT: Reserves - Currency0: ${reserve0.toString()}, Currency1: ${reserve1.toString()}`, + ); + + // Check and log liquidity provider balance + const lpBalance = await pair.getCurrencyBalanceOf(walletAddress); + console.log( + "MINT RESULT: Liquidity provider balance in wallet:", + lpBalance.toString(), + ); + + // Retrieve and log total supply for the pair + const totalSupply = await pair.getCurrencyTotalSupply(); + console.log( + "MINT RESULT: Total supply of pair tokens:", + totalSupply.toString(), + ); + + // 4. PAIR: SWAP + const expectedOutputAmount = calculateOutputAmount( + BigInt(swapAmount), + reserve0, + reserve1, + ); + console.log( + "Expected output amount for swap:", + expectedOutputAmount.toString(), + ); + + // Log balances before the swap + const balanceCurrency0Before = + await firstCurrency.getCurrencyBalanceOf(walletAddress); + const balanceCurrency1Before = + await secondCurrency.getCurrencyBalanceOf(walletAddress); + console.log( + "Balance of currency0 before swap:", + balanceCurrency0Before.toString(), + ); + console.log( + "Balance of currency1 before swap:", + balanceCurrency1Before.toString(), + ); + + // Attach to the UserWallet contract + + // Send currency0 to the pair contract + const hash2 = await wallet.sendMessage({ + // @ts-ignore + to: pairAddress, + feeCredit: BigInt(10_000_000), + value: BigInt(0), + refundTo: wallet.address, + tokens: [ + { + id: await firstCurrency.getCurrencyId(), + amount: BigInt(swapAmount), + }, + ], + }); + + await waitTillCompleted(publicClient, shardNumber(walletAddress), hash2); + + console.log( + `Sent ${swapAmount.toString()} of currency0 to the pair contract.`, + ); + + // Execute the swap + console.log("Executing swap..."); + await pair.swap(0, expectedOutputAmount, walletAddress); + console.log("Swap executed successfully."); + + // Log balances after the swap + const balanceCurrency0After = + await firstCurrency.getCurrencyBalanceOf(walletAddress); + const balanceCurrency1After = + await secondCurrency.getCurrencyBalanceOf(walletAddress); + console.log( + "SWAP RESULT: Balance of currency0 after swap:", + balanceCurrency0After.toString(), + ); + console.log( + "SWAP RESULT: Balance of currency1 after swap:", + balanceCurrency1After.toString(), + ); + + // 5. PAIR: BURN + const total = await pair.getCurrencyTotalSupply(); + console.log("Total supply:", total.toString()); + + // Fetch and log pair balances before burn + const pairBalanceToken0 = await firstCurrency.getCurrencyBalanceOf( + pairAddress.toLowerCase(), + ); + const pairBalanceToken1 = await secondCurrency.getCurrencyBalanceOf( + pairAddress.toLowerCase(), + ); + console.log( + "Pair Balance token0 before burn:", + pairBalanceToken0.toString(), + ); + console.log( + "Pair Balance token1 before burn:", + pairBalanceToken1.toString(), + ); + + // Fetch and log user balances before burn + let userBalanceToken0 = + await firstCurrency.getCurrencyBalanceOf(walletAddress); + let userBalanceToken1 = + await secondCurrency.getCurrencyBalanceOf(walletAddress); + console.log( + "User Balance token0 before burn:", + userBalanceToken0.toString(), + ); + console.log( + "User Balance token1 before burn:", + userBalanceToken1.toString(), + ); + + const lpAddress = await pair.getCurrencyId(); + const userLpBalance = await pair.getCurrencyBalanceOf(walletAddress); + console.log("Total LP balance for user wallet:", userLpBalance.toString()); + + // Send LP tokens to the user wallet + const hash3 = await wallet.sendMessage({ + // @ts-ignore + to: pairAddress, + feeCredit: BigInt(10_000_000), + value: BigInt(0), + refundTo: walletAddress, + tokens: [ + { + id: lpAddress, + amount: BigInt(userLpBalance), + }, + ], + }); + + await waitTillCompleted(publicClient, shardNumber(walletAddress), hash3); + + // Execute burn + console.log("Executing burn..."); + await pair.burn(walletAddress); + console.log("Burn executed."); + console.log("Built tokens"); + + // Log balances after burn + const balanceToken0 = await firstCurrency.getCurrencyBalanceOf( + pairAddress.toLowerCase(), + ); + const balanceToken1 = await secondCurrency.getCurrencyBalanceOf( + pairAddress.toLowerCase(), + ); + console.log( + "BURN RESULT: Pair Balance token0 after burn:", + balanceToken0.toString(), + ); + console.log( + "BURN RESULT: Pair Balance token1 after burn:", + balanceToken1.toString(), + ); + + userBalanceToken0 = await firstCurrency.getCurrencyBalanceOf(walletAddress); + userBalanceToken1 = + await secondCurrency.getCurrencyBalanceOf(walletAddress); + console.log( + "BURN RESULT: User Balance token0 after burn:", + userBalanceToken0.toString(), + ); + console.log( + "BURN RESULT: User Balance token1 after burn:", + userBalanceToken1.toString(), + ); + + // Fetch and log reserves after burn + const reserves = await pair.getReserves(); + console.log( + "BURN RESULT: Reserves from pair after burn:", + reserves[0].toString(), + reserves[1].toString(), + ); + }, +); From c7fb044ba885d78d7c72b197750595e7f0329683 Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 12:45:56 +0300 Subject: [PATCH 2/8] demo-router: contract + task --- contracts/UniswapV2Router01.sol | 23 ++--- contracts/interfaces/IUniswapV2Router01.sol | 27 +++--- hardhat.config.ts | 1 + tasks/core/demo-router.ts | 100 +++++++++++--------- 4 files changed, 78 insertions(+), 73 deletions(-) diff --git a/contracts/UniswapV2Router01.sol b/contracts/UniswapV2Router01.sol index d72a61e..a95c3a6 100644 --- a/contracts/UniswapV2Router01.sol +++ b/contracts/UniswapV2Router01.sol @@ -22,7 +22,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { function addLiquidity( address pair, address to - ) external override { + ) public override { Nil.Token[] memory tokens = Nil.msgTokens(); if (tokens.length != 2) { revert("Send only 2 tokens to add liquidity"); @@ -44,14 +44,15 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { smartCall(pair, tokens, abi.encodeWithSignature("burn(address)", to)); } - function swap(address to, address pair) public override { + function swap(address to, address pair, uint amount0Out, uint amount1Out) public override { Nil.Token[] memory tokens = Nil.msgTokens(); if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } - // TODO + smartCall(pair, tokens, abi.encodeWithSignature("swap(address)", amount0Out, amount1Out, to)); } + // TODO: This method are used for swapping via multiple pairs. Not supported in nil for now // **** SWAP **** // requires the initial amount to have already been sent to the first pair function _swap(uint[] memory amounts, address[] memory path, address _to) private { @@ -66,13 +67,14 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { } } + // TODO: This method are used for swapping via multiple pairs. Not supported in nil for now function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline - ) external override ensure(deadline) returns (uint[] memory amounts) { + ) external override returns (uint[] memory amounts) { amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); address pair = IUniswapV2Factory(factory).getTokenPair(path[0], path[1]); @@ -81,13 +83,14 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { _swap(amounts, path, to); } + // TODO: This method are used for swapping via multiple pairs. Not supported in nil for now function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline - ) external override ensure(deadline) returns (uint[] memory amounts) { + ) external override returns (uint[] memory amounts) { amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT'); address pair = IUniswapV2Factory(factory).getTokenPair(path[0], path[1]); @@ -108,20 +111,12 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut); } - function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) { - return UniswapV2Library.getAmountsOut(factory, amountIn, path); - } - - function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) { - return UniswapV2Library.getAmountsIn(factory, amountOut, path); - } - receive() external payable { } function smartCall(address dst, Nil.Token[] memory tokens, bytes memory callData) private returns (bool) { if (Nil.getShardId(dst) == Nil.getShardId(address(dst))) { - (bool success, ) = Nil.syncCall(dst, gasleft(), 0, tokens, callData); + (bool success,) = Nil.syncCall(dst, gasleft(), 0, tokens, callData); return success; } else { Nil.asyncCall(dst, address(0), address(0), 0, Nil.FORWARD_REMAINING, false, 0, tokens, callData); diff --git a/contracts/interfaces/IUniswapV2Router01.sol b/contracts/interfaces/IUniswapV2Router01.sol index 9c2d640..87e8def 100644 --- a/contracts/interfaces/IUniswapV2Router01.sol +++ b/contracts/interfaces/IUniswapV2Router01.sol @@ -5,21 +5,20 @@ pragma solidity ^0.8.0; interface IUniswapV2Router01 { function addLiquidity( - address tokenA, - address tokenB, - address to, - uint deadline, - uint salt - ) external returns (uint amountA, uint amountB, uint liquidity); + address pair, + address to + ) external; function removeLiquidity( - address tokenA, - address tokenB, - uint liquidity, - uint amountAMin, - uint amountBMin, + address pair, + address to + ) external; + function swap( address to, - uint deadline - ) external returns (uint amountA, uint amountB); + address pair, + uint amount0Out, + uint amount1Out + ) external; + function swapExactTokensForTokens( uint amountIn, uint amountOutMin, @@ -38,6 +37,4 @@ interface IUniswapV2Router01 { function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); - function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); - function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); } \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index adb32e9..335f310 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -21,6 +21,7 @@ import "./tasks/core/factory/create-pair"; // Demo Tasks import "./tasks/core/demo"; +import "./tasks/core/demo-router"; dotenv.config(); diff --git a/tasks/core/demo-router.ts b/tasks/core/demo-router.ts index 849e329..a05eb82 100644 --- a/tasks/core/demo-router.ts +++ b/tasks/core/demo-router.ts @@ -1,21 +1,22 @@ -import { shardNumber } from "@nilfoundation/hardhat-plugin/dist/utils/conversion"; -import { waitTillCompleted } from "@nilfoundation/niljs"; -import { task } from "hardhat/config"; +import {shardNumber} from "@nilfoundation/hardhat-plugin/dist/utils/conversion"; +import {waitTillCompleted} from "@nilfoundation/niljs"; +import {task} from "hardhat/config"; +import {encodeFunctionData} from "viem"; import type { Currency, UniswapV2Factory, UniswapV2Pair, } from "../../typechain-types"; -import { createClient } from "../util/client"; +import {createClient} from "../util/client"; import { faucetWithdrawal, mintAndSendCurrency, sleep, } from "../util/currencyUtils"; -import { deployNilContract } from "../util/deploy"; -import { calculateOutputAmount } from "../util/math"; +import {deployNilContract} from "../util/deploy"; +import {calculateOutputAmount} from "../util/math"; -task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( +task("demo-router", "Run demo with Uniswap Router").setAction( async (taskArgs, hre) => { const walletAddress = process.env.WALLET_ADDR; if (!walletAddress) { @@ -30,16 +31,12 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( const mintCurrency1Amount = 10000; const swapAmount = 1000; - const { wallet, publicClient, signer } = await createClient(); + const {wallet, publicClient, signer} = await createClient(); const { deployedContract: factoryContract, contractAddress: factoryAddress, } = await deployNilContract(hre, "UniswapV2Factory", [walletAddress]); - const { - deployedContract: routerContract, - contractAddress: routerAddress, - } = await deployNilContract(hre, "UniswapV2Router01", [walletAddress, "0x0000000000000000000000000000000000000000"]); const { deployedContract: Currency0Contract, contractAddress: currency0Address, @@ -59,6 +56,15 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( console.log("Currency0 deployed " + currency0Address); console.log("Currency1 deployed " + currency1Address); + const { + deployedContract: RouterContract, + contractAddress: routerAddress, + } = await deployNilContract(hre, "UniswapV2Router01", [ + factoryAddress.toLowerCase(), + ]); + + console.log("Router deployed " + routerAddress); + const factory = factoryContract as UniswapV2Factory; // 1. CREATE PAIR @@ -160,18 +166,23 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( `Recipient balance after transfer - Currency0: ${recipientBalanceCurrency0}, Currency1: ${recipientBalanceCurrency1}`, ); - // 3. PAIR: MINT + // 3. ROUTER: ADD LIQUIDITY + const pairArtifact = await hre.artifacts.readArtifact("UniswapV2Pair"); + const routerArtifact = await hre.artifacts.readArtifact("UniswapV2Router01"); + + // Mint liquidity + console.log("Adding liquidity..."); - // Send currency amounts to the pair contract - console.log( - `Sending ${mintCurrency0Amount} currency0 and ${mintCurrency1Amount} currency1 to ${pairAddress}...`, - ); const hash = await wallet.sendMessage({ - // @ts-ignore - to: pairAddress, + to: routerAddress, feeCredit: BigInt(10_000_000), value: BigInt(0), refundTo: wallet.address, + data: encodeFunctionData({ + abi: routerArtifact.abi, + functionName: "addLiquidity", + args: [pairAddress, walletAddress], + }), tokens: [ { id: await firstCurrency.getCurrencyId(), @@ -195,32 +206,29 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( await secondCurrency.getCurrencyBalanceOf(pairAddress); console.log("Pair Balance of Currency1:", pairCurrency1Balance.toString()); - // Mint liquidity - console.log("Minting pair tokens..."); - await pair.mint(walletAddress); console.log("Liquidity added..."); // Retrieve and log reserves from the pair const [reserve0, reserve1] = await pair.getReserves(); console.log( - `MINT RESULT: Reserves - Currency0: ${reserve0.toString()}, Currency1: ${reserve1.toString()}`, + `ADDLIQUIDITY RESULT: Reserves - Currency0: ${reserve0.toString()}, Currency1: ${reserve1.toString()}`, ); // Check and log liquidity provider balance const lpBalance = await pair.getCurrencyBalanceOf(walletAddress); console.log( - "MINT RESULT: Liquidity provider balance in wallet:", + "ADDLIQUIDITY RESULT: Liquidity provider balance in wallet:", lpBalance.toString(), ); // Retrieve and log total supply for the pair const totalSupply = await pair.getCurrencyTotalSupply(); console.log( - "MINT RESULT: Total supply of pair tokens:", + "ADDLIQUIDITY RESULT: Total supply of pair tokens:", totalSupply.toString(), ); - // 4. PAIR: SWAP + // 4. ROUTER: SWAP const expectedOutputAmount = calculateOutputAmount( BigInt(swapAmount), reserve0, @@ -245,14 +253,19 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( balanceCurrency1Before.toString(), ); - // Attach to the UserWallet contract + // Execute the swap + console.log("Executing swap..."); // Send currency0 to the pair contract const hash2 = await wallet.sendMessage({ - // @ts-ignore - to: pairAddress, + to: routerAddress, feeCredit: BigInt(10_000_000), value: BigInt(0), + data: encodeFunctionData({ + abi: routerArtifact.abi, + functionName: "swap", + args: [walletAddress, pairAddress, 0, expectedOutputAmount], + }), refundTo: wallet.address, tokens: [ { @@ -268,9 +281,6 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( `Sent ${swapAmount.toString()} of currency0 to the pair contract.`, ); - // Execute the swap - console.log("Executing swap..."); - await pair.swap(0, expectedOutputAmount, walletAddress); console.log("Swap executed successfully."); // Log balances after the swap @@ -287,7 +297,7 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( balanceCurrency1After.toString(), ); - // 5. PAIR: BURN + // 5. ROUTER: REMOVE LIQUIDITY const total = await pair.getCurrencyTotalSupply(); console.log("Total supply:", total.toString()); @@ -324,13 +334,19 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( const lpAddress = await pair.getCurrencyId(); const userLpBalance = await pair.getCurrencyBalanceOf(walletAddress); console.log("Total LP balance for user wallet:", userLpBalance.toString()); - + // Execute burn + console.log("Executing burn..."); // Send LP tokens to the user wallet const hash3 = await wallet.sendMessage({ // @ts-ignore - to: pairAddress, + to: routerAddress, feeCredit: BigInt(10_000_000), value: BigInt(0), + data: encodeFunctionData({ + abi: routerArtifact.abi, + functionName: "removeLiquidity", + args: [pairAddress, walletAddress], + }), refundTo: walletAddress, tokens: [ { @@ -342,11 +358,7 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( await waitTillCompleted(publicClient, shardNumber(walletAddress), hash3); - // Execute burn - console.log("Executing burn..."); - await pair.burn(walletAddress); console.log("Burn executed."); - console.log("Built tokens"); // Log balances after burn const balanceToken0 = await firstCurrency.getCurrencyBalanceOf( @@ -356,11 +368,11 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( pairAddress.toLowerCase(), ); console.log( - "BURN RESULT: Pair Balance token0 after burn:", + "REMOVELIQUIDITY RESULT: Pair Balance token0 after burn:", balanceToken0.toString(), ); console.log( - "BURN RESULT: Pair Balance token1 after burn:", + "REMOVELIQUIDITY RESULT: Pair Balance token1 after burn:", balanceToken1.toString(), ); @@ -368,18 +380,18 @@ task("demo-router", "Run demo for Uniswap Pairs and Factory").setAction( userBalanceToken1 = await secondCurrency.getCurrencyBalanceOf(walletAddress); console.log( - "BURN RESULT: User Balance token0 after burn:", + "REMOVELIQUIDITY RESULT: User Balance token0 after burn:", userBalanceToken0.toString(), ); console.log( - "BURN RESULT: User Balance token1 after burn:", + "REMOVELIQUIDITY RESULT: User Balance token1 after burn:", userBalanceToken1.toString(), ); // Fetch and log reserves after burn const reserves = await pair.getReserves(); console.log( - "BURN RESULT: Reserves from pair after burn:", + "REMOVELIQUIDITY RESULT: Reserves from pair after burn:", reserves[0].toString(), reserves[1].toString(), ); From cc0d3159c944e03e18eaa07df165bf0103f6f16e Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 12:48:23 +0300 Subject: [PATCH 3/8] demo-router: contract + task --- tasks/core/demo-router.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tasks/core/demo-router.ts b/tasks/core/demo-router.ts index a05eb82..7930545 100644 --- a/tasks/core/demo-router.ts +++ b/tasks/core/demo-router.ts @@ -1,20 +1,20 @@ -import {shardNumber} from "@nilfoundation/hardhat-plugin/dist/utils/conversion"; -import {waitTillCompleted} from "@nilfoundation/niljs"; -import {task} from "hardhat/config"; -import {encodeFunctionData} from "viem"; +import { shardNumber } from "@nilfoundation/hardhat-plugin/dist/utils/conversion"; +import { waitTillCompleted } from "@nilfoundation/niljs"; +import { task } from "hardhat/config"; +import { encodeFunctionData } from "viem"; import type { Currency, UniswapV2Factory, UniswapV2Pair, } from "../../typechain-types"; -import {createClient} from "../util/client"; +import { createClient } from "../util/client"; import { faucetWithdrawal, mintAndSendCurrency, sleep, } from "../util/currencyUtils"; -import {deployNilContract} from "../util/deploy"; -import {calculateOutputAmount} from "../util/math"; +import { deployNilContract } from "../util/deploy"; +import { calculateOutputAmount } from "../util/math"; task("demo-router", "Run demo with Uniswap Router").setAction( async (taskArgs, hre) => { @@ -31,7 +31,7 @@ task("demo-router", "Run demo with Uniswap Router").setAction( const mintCurrency1Amount = 10000; const swapAmount = 1000; - const {wallet, publicClient, signer} = await createClient(); + const { wallet, publicClient, signer } = await createClient(); const { deployedContract: factoryContract, @@ -56,12 +56,10 @@ task("demo-router", "Run demo with Uniswap Router").setAction( console.log("Currency0 deployed " + currency0Address); console.log("Currency1 deployed " + currency1Address); - const { - deployedContract: RouterContract, - contractAddress: routerAddress, - } = await deployNilContract(hre, "UniswapV2Router01", [ - factoryAddress.toLowerCase(), - ]); + const { deployedContract: RouterContract, contractAddress: routerAddress } = + await deployNilContract(hre, "UniswapV2Router01", [ + factoryAddress.toLowerCase(), + ]); console.log("Router deployed " + routerAddress); @@ -168,7 +166,8 @@ task("demo-router", "Run demo with Uniswap Router").setAction( // 3. ROUTER: ADD LIQUIDITY const pairArtifact = await hre.artifacts.readArtifact("UniswapV2Pair"); - const routerArtifact = await hre.artifacts.readArtifact("UniswapV2Router01"); + const routerArtifact = + await hre.artifacts.readArtifact("UniswapV2Router01"); // Mint liquidity console.log("Adding liquidity..."); From 8b7499613080623fca5e7d8405d9fe6987106160 Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 13:15:51 +0300 Subject: [PATCH 4/8] demo-router: contract + task --- contracts/UniswapV2Router01.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/UniswapV2Router01.sol b/contracts/UniswapV2Router01.sol index a95c3a6..8a741d0 100644 --- a/contracts/UniswapV2Router01.sol +++ b/contracts/UniswapV2Router01.sol @@ -115,7 +115,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { } function smartCall(address dst, Nil.Token[] memory tokens, bytes memory callData) private returns (bool) { - if (Nil.getShardId(dst) == Nil.getShardId(address(dst))) { + if (Nil.getShardId(dst) == Nil.getShardId(address(this))) { (bool success,) = Nil.syncCall(dst, gasleft(), 0, tokens, callData); return success; } else { From 58cf2ad59685a3342264e457b381dce8915e3399 Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 13:16:47 +0300 Subject: [PATCH 5/8] demo-router: contract + task --- contracts/UniswapV2Router01.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/UniswapV2Router01.sol b/contracts/UniswapV2Router01.sol index 8a741d0..a38648d 100644 --- a/contracts/UniswapV2Router01.sol +++ b/contracts/UniswapV2Router01.sol @@ -27,8 +27,6 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { if (tokens.length != 2) { revert("Send only 2 tokens to add liquidity"); } - // TODO: Probably check that tokens are sent to the right pair - smartCall(pair, tokens, abi.encodeWithSignature("mint(address)", to)); } From 7560f3b18d7596835e88f0fca13c1def8bc2fa36 Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 16:32:30 +0300 Subject: [PATCH 6/8] demo-router: contract + task --- contracts/UniswapV2Router01.sol | 2 +- tasks/core/demo-router.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/UniswapV2Router01.sol b/contracts/UniswapV2Router01.sol index a38648d..20fcde5 100644 --- a/contracts/UniswapV2Router01.sol +++ b/contracts/UniswapV2Router01.sol @@ -47,7 +47,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } - smartCall(pair, tokens, abi.encodeWithSignature("swap(address)", amount0Out, amount1Out, to)); + smartCall(pair, tokens, abi.encodeWithSignature("swap(uint,uint,address)", amount0Out, amount1Out, to)); } // TODO: This method are used for swapping via multiple pairs. Not supported in nil for now diff --git a/tasks/core/demo-router.ts b/tasks/core/demo-router.ts index 7930545..f90f8e4 100644 --- a/tasks/core/demo-router.ts +++ b/tasks/core/demo-router.ts @@ -166,8 +166,7 @@ task("demo-router", "Run demo with Uniswap Router").setAction( // 3. ROUTER: ADD LIQUIDITY const pairArtifact = await hre.artifacts.readArtifact("UniswapV2Pair"); - const routerArtifact = - await hre.artifacts.readArtifact("UniswapV2Router01"); + const routerArtifact = await hre.artifacts.readArtifact("UniswapV2Router01"); // Mint liquidity console.log("Adding liquidity..."); @@ -277,7 +276,7 @@ task("demo-router", "Run demo with Uniswap Router").setAction( await waitTillCompleted(publicClient, shardNumber(walletAddress), hash2); console.log( - `Sent ${swapAmount.toString()} of currency0 to the pair contract.`, + `Sent ${swapAmount.toString()} of currency0 to the pair contract. Tx - ${hash2}`, ); console.log("Swap executed successfully."); From 64a377149178f0f08975b8142f4950a52971f869 Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 16:34:01 +0300 Subject: [PATCH 7/8] demo-router: contract + task --- contracts/UniswapV2Router01.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/UniswapV2Router01.sol b/contracts/UniswapV2Router01.sol index 20fcde5..3b1bf69 100644 --- a/contracts/UniswapV2Router01.sol +++ b/contracts/UniswapV2Router01.sol @@ -47,7 +47,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilCurrencyBase { if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } - smartCall(pair, tokens, abi.encodeWithSignature("swap(uint,uint,address)", amount0Out, amount1Out, to)); + smartCall(pair, tokens, abi.encodeWithSignature("swap(uint256,uint256,address)", amount0Out, amount1Out, to)); } // TODO: This method are used for swapping via multiple pairs. Not supported in nil for now From c4540ee5e2cf76a6e911867399fda25504dfd49f Mon Sep 17 00:00:00 2001 From: rolaman Date: Thu, 26 Sep 2024 16:36:18 +0300 Subject: [PATCH 8/8] demo-router: contract + task --- tasks/core/demo-router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks/core/demo-router.ts b/tasks/core/demo-router.ts index f90f8e4..c607a8a 100644 --- a/tasks/core/demo-router.ts +++ b/tasks/core/demo-router.ts @@ -166,7 +166,8 @@ task("demo-router", "Run demo with Uniswap Router").setAction( // 3. ROUTER: ADD LIQUIDITY const pairArtifact = await hre.artifacts.readArtifact("UniswapV2Pair"); - const routerArtifact = await hre.artifacts.readArtifact("UniswapV2Router01"); + const routerArtifact = + await hre.artifacts.readArtifact("UniswapV2Router01"); // Mint liquidity console.log("Adding liquidity...");