diff --git a/contracts/UniswapV2Factory.sol b/contracts/EncryptedDEXFactory.sol similarity index 67% rename from contracts/UniswapV2Factory.sol rename to contracts/EncryptedDEXFactory.sol index b6657d2..3e13763 100644 --- a/contracts/UniswapV2Factory.sol +++ b/contracts/EncryptedDEXFactory.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; -import "./UniswapV2Pair.sol"; +import "./EncryptedDEXPair.sol"; -contract UniswapV2Factory { +contract EncryptedDEXFactory { mapping(address => mapping(address => address)) public getPair; address[] public allPairs; @@ -16,13 +16,13 @@ contract UniswapV2Factory { // tokenA and tokenB should be EncryptedERC20_32 contracts function createPair(address tokenA, address tokenB) external returns (address pair) { - require(tokenA != tokenB, "UniswapV2: IDENTICAL_ADDRESSES"); + require(tokenA != tokenB, "EncryptedDEX: IDENTICAL_ADDRESSES"); (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); - require(token0 != address(0), "UniswapV2: ZERO_ADDRESS"); - require(getPair[token0][token1] == address(0), "UniswapV2: PAIR_EXISTS"); // single check is sufficient + require(token0 != address(0), "EncryptedDEX: ZERO_ADDRESS"); + require(getPair[token0][token1] == address(0), "EncryptedDEX: PAIR_EXISTS"); // single check is sufficient bytes32 _salt = keccak256(abi.encodePacked(token0, token1)); - pair = address(new UniswapV2Pair{ salt: _salt }()); - UniswapV2Pair(pair).initialize(token0, token1); + pair = address(new EncryptedDEXPair{ salt: _salt }()); + EncryptedDEXPair(pair).initialize(token0, token1); getPair[token0][token1] = pair; getPair[token1][token0] = pair; // populate mapping in the reverse direction allPairs.push(pair); diff --git a/contracts/UniswapV2Pair.sol b/contracts/EncryptedDEXPair.sol similarity index 70% rename from contracts/UniswapV2Pair.sol rename to contracts/EncryptedDEXPair.sol index 8e1100c..b15abad 100644 --- a/contracts/UniswapV2Pair.sol +++ b/contracts/EncryptedDEXPair.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.20; import "./EncryptedERC20.sol"; -contract UniswapV2Pair is EncryptedERC20 { +contract EncryptedDEXPair is EncryptedERC20 { euint32 private ZERO = TFHE.asEuint32(0); uint256 public constant MIN_DELAY_SETTLEMENT = 2; - uint256 internal currentTradingEpoch; + uint256 public currentTradingEpoch; mapping(uint256 tradingEpoch => uint256 firstOrderBlock) internal firstBlockPerEpoch; // set to current block number for any first order (mint, burn or swap) in an epoch mapping(uint256 tradingEpoch => mapping(address user => euint32 mintedLiquidity)) internal pendingMints; @@ -42,15 +42,17 @@ contract UniswapV2Pair is EncryptedERC20 { uint256 private unlocked = 1; + event Burn(uint256 burnedAmount); + modifier lock() { - require(unlocked == 1, "UniswapV2: LOCKED"); + require(unlocked == 1, "EncryptedDEX: LOCKED"); unlocked = 0; _; unlocked = 1; } modifier ensure(uint256 deadlineEpochNo) { - require(deadlineEpochNo >= currentTradingEpoch, "UniswapV2Router: EXPIRED"); + require(deadlineEpochNo >= currentTradingEpoch, "EncryptedDEXRouter: EXPIRED"); _; } @@ -68,7 +70,7 @@ contract UniswapV2Pair is EncryptedERC20 { } function _mint(uint32 mintedAmount) internal { - balances[address(this)] = TFHE.add(balances[address(this)], mintedAmount); + // this is a partial mint, balances are updated later during the claim _totalSupply = _totalSupply + mintedAmount; emit Mint(address(this), mintedAmount); } @@ -76,11 +78,12 @@ contract UniswapV2Pair is EncryptedERC20 { function _burn(uint32 burnedAmount) internal { balances[address(this)] = TFHE.sub(balances[address(this)], burnedAmount); // check underflow is impossible when used from the contract logic _totalSupply = _totalSupply - burnedAmount; + emit Burn(burnedAmount); } // called once by the factory at time of deployment function initialize(address _token0, address _token1) external { - require(msg.sender == factory, "UniswapV2: FORBIDDEN"); + require(msg.sender == factory, "EncryptedDEX: FORBIDDEN"); token0 = EncryptedERC20(_token0); token1 = EncryptedERC20(_token1); } @@ -91,7 +94,7 @@ contract UniswapV2Pair is EncryptedERC20 { bytes calldata encryptedAmount1, address to, uint256 deadline - ) external ensure(deadline) returns (euint32 liquidity) { + ) external ensure(deadline) { euint32 balance0Before = token0.balanceOfMe(); euint32 balance1Before = token1.balanceOfMe(); euint32 amount0 = TFHE.asEuint32(encryptedAmount0); @@ -102,10 +105,10 @@ contract UniswapV2Pair is EncryptedERC20 { euint32 balance1After = token1.balanceOfMe(); euint32 sentAmount0 = balance0After - balance0Before; euint32 sentAmount1 = balance1After - balance1Before; - liquidity = mint(to, sentAmount0, sentAmount1); + mint(to, sentAmount0, sentAmount1); } - function mint(address to, euint32 amount0, euint32 amount1) internal returns (euint32 liquidity) { + function mint(address to, euint32 amount0, euint32 amount1) internal { if (firstBlockPerEpoch[currentTradingEpoch] == 0) { firstBlockPerEpoch[currentTradingEpoch] = block.number; } @@ -113,6 +116,7 @@ contract UniswapV2Pair is EncryptedERC20 { reserve0PendingAdd = reserve0PendingAdd + amount0; reserve1PendingAdd = reserve1PendingAdd + amount1; + euint32 liquidity; if (totalSupply() == 0) { // this condition is equivalent to currentTradingEpoch==0 (see batchSettlement logic) liquidity = TFHE.shr(amount0, 1) + TFHE.shr(amount1, 1); @@ -127,14 +131,10 @@ contract UniswapV2Pair is EncryptedERC20 { } // **** REMOVE LIQUIDITY **** - function removeLiquidity( - bytes calldata encryptedLiquidity, - address to, - uint deadline - ) public ensure(deadline) returns (uint amountA, uint amountB) { - euint32 liquidityBefore = balanceOfMe(); - transferFrom(msg.sender, address(this), encryptedLiquidity); - euint32 liquidityAfter = balanceOfMe(); + function removeLiquidity(bytes calldata encryptedLiquidity, address to, uint256 deadline) public ensure(deadline) { + euint32 liquidityBefore = balances[address(this)]; + transfer(address(this), encryptedLiquidity); + euint32 liquidityAfter = balances[address(this)]; euint32 burntLiquidity = liquidityAfter - liquidityBefore; pendingBurns[currentTradingEpoch][to] = pendingBurns[currentTradingEpoch][to] + burntLiquidity; pendingTotalBurns[currentTradingEpoch] = pendingTotalBurns[currentTradingEpoch] + burntLiquidity; @@ -142,12 +142,10 @@ contract UniswapV2Pair is EncryptedERC20 { // **** SWAP **** // typically either AmountAIn or AmountBIn is null function swapTokens( - address tokenA, - address tokenB, bytes calldata encryptedAmount0In, bytes calldata encryptedAmount1In, address to, - uint deadline + uint256 deadline ) external ensure(deadline) { euint32 balance0Before = token0.balanceOfMe(); euint32 balance1Before = token1.balanceOfMe(); @@ -159,15 +157,20 @@ contract UniswapV2Pair is EncryptedERC20 { euint32 balance1After = token1.balanceOfMe(); euint32 sent0 = balance0After - balance0Before; euint32 sent1 = balance1After - balance1Before; - pendingToken0In[currentTradingEpoch][msg.sender] = pendingToken0In[currentTradingEpoch][msg.sender] + sent0; + pendingToken0In[currentTradingEpoch][to] = pendingToken0In[currentTradingEpoch][to] + sent0; pendingTotalToken0In[currentTradingEpoch] = pendingTotalToken0In[currentTradingEpoch] + sent0; - pendingToken1In[currentTradingEpoch][msg.sender] = pendingToken1In[currentTradingEpoch][msg.sender] + sent1; + pendingToken1In[currentTradingEpoch][to] = pendingToken1In[currentTradingEpoch][to] + sent1; pendingTotalToken1In[currentTradingEpoch] = pendingTotalToken1In[currentTradingEpoch] + sent1; } function claimMint(uint256 tradingEpoch, address user) external { require(tradingEpoch < currentTradingEpoch, "tradingEpoch is not settled yet"); - transfer(user, pendingMints[tradingEpoch][user]); + if (tradingEpoch == 0) { + balances[user] = TFHE.sub(balances[user] + pendingMints[tradingEpoch][user], 100); // this could fail in the very theoretical case where several market makers would mint individually + // less than 100 LP tokens but their sum is above 100. NOT a vulnerability, as long as the first market makers are aware that the avarage sent amounts during first tradingEpoch must be above 100. + } else { + balances[user] = balances[user] + pendingMints[tradingEpoch][user]; + } pendingMints[tradingEpoch][user] = ZERO; } @@ -219,53 +222,87 @@ contract UniswapV2Pair is EncryptedERC20 { pendingToken1In[tradingEpoch][user] = ZERO; } + function requestAllDecryptions() + internal + view + returns ( + uint32 reserve0PendingAddDec, + uint32 reserve1PendingAddDec, + uint32 mintedTotal, + uint32 amount0In, + uint32 amount1In, + uint32 burnedTotal + ) + { + reserve0PendingAddDec = TFHE.decrypt(reserve0PendingAdd); + reserve1PendingAddDec = TFHE.decrypt(reserve1PendingAdd); + mintedTotal = TFHE.decrypt(pendingTotalMints[currentTradingEpoch]); + amount0In = TFHE.decrypt(pendingTotalToken0In[currentTradingEpoch]); + amount1In = TFHE.decrypt(pendingTotalToken1In[currentTradingEpoch]); + burnedTotal = TFHE.decrypt(pendingTotalBurns[currentTradingEpoch]); + } + function batchSettlement() external { require( - firstBlockPerEpoch[currentTradingEpoch] - block.number >= MIN_DELAY_SETTLEMENT, + block.number - firstBlockPerEpoch[currentTradingEpoch] >= MIN_DELAY_SETTLEMENT, "First order of current epoch is more recent than minimum delay" ); + // get all needed decryptions in a single call (this pattern is helpful to later adapt the design when TFHE.decrypt wil become asynchronous) + ( + uint32 reserve0PendingAddDec, + uint32 reserve1PendingAddDec, + uint32 mintedTotal, + uint32 amount0In, + uint32 amount1In, + uint32 burnedTotal + ) = requestAllDecryptions(); // update reserves after new liquidity deposits - uint32 reserve0PendingAddDec = TFHE.decrypt(reserve0PendingAdd); - uint32 reserve1PendingAddDec = TFHE.decrypt(reserve1PendingAdd); + reserve0 += reserve0PendingAddDec; reserve1 += reserve1PendingAddDec; reserve0PendingAdd = ZERO; reserve1PendingAdd = ZERO; // Liquidity Mints - uint32 _mintedTotal = TFHE.decrypt(pendingTotalMints[currentTradingEpoch]); - _mint(_mintedTotal); - if (currentTradingEpoch == 0) { - require(_mintedTotal >= 100, "Initial minted liquidity should be greater than 100"); - decryptedTotalMints[currentTradingEpoch] = _mintedTotal - 100; // this is to lock forever 100 liquidity tokens inside the pool, so totalSupply of liquidity would remain above 100 to avoid security issues - } else { - decryptedTotalMints[currentTradingEpoch] = _mintedTotal; + if (mintedTotal > 0) { + _mint(mintedTotal); + decryptedTotalMints[currentTradingEpoch] = mintedTotal; } + require( + currentTradingEpoch != 0 || mintedTotal >= 100, + "Initial minted liquidity amount should be greater than 100" + ); // this is to lock forever at least 100 liquidity tokens inside the pool, so totalSupply of liquidity + // would remain above 100 to avoid security issues, for instance if a single market maker wants to burn the whole liquidity in a single transaction, making the pool unusable // Token Swaps - uint32 amount0In = TFHE.decrypt(pendingTotalToken0In[currentTradingEpoch]); - uint32 amount1In = TFHE.decrypt(pendingTotalToken1In[currentTradingEpoch]); decryptedTotalToken0In[currentTradingEpoch] = amount0In; decryptedTotalToken1In[currentTradingEpoch] = amount1In; - bool priceToken1Increasing = (uint64(amount0In) * uint64(reserve1) > - uint64(amount1In) * uint64(reserve0)); + bool priceToken1Increasing = (uint64(amount0In) * uint64(reserve1) > uint64(amount1In) * uint64(reserve0)); uint32 amount0Out; uint32 amount1Out; if (priceToken1Increasing) { // in this case, first sell all amount1In at current fixed token1 price to get amount0Out, then swap remaining (amount0In-amount0Out) to get amount1out_remaining according to AMM formula amount0Out = uint32((uint64(amount1In) * uint64(reserve0)) / uint64(reserve1)); - amount1Out = amount1In + + amount1Out = + amount1In + reserve1 - - uint32((uint64(reserve1) * uint64(reserve0)) / (uint64(reserve0) + uint64(amount0In) - uint64(amount0Out))); + uint32( + (uint64(reserve1) * uint64(reserve0)) / (uint64(reserve0) + uint64(amount0In) - uint64(amount0Out)) + ); + amount0Out = uint32((99 * uint64(amount0Out)) / 100); // 1% fee for liquidity providers amount1Out = uint32((99 * uint64(amount1Out)) / 100); // 1% fee for liquidity providers } else { // here we do the opposite, first sell token0 at current token0 price then swap remaining token1 according to AMM formula amount1Out = uint32((uint64(amount0In) * uint64(reserve1)) / uint64(reserve0)); - amount0Out = amount0In + + amount0Out = + amount0In + reserve0 - - uint32((uint64(reserve0) * uint64(reserve1)) / (uint64(reserve1) + uint64(amount1In) - uint64(amount1Out))); + uint32( + (uint64(reserve0) * uint64(reserve1)) / (uint64(reserve1) + uint64(amount1In) - uint64(amount1Out)) + ); amount0Out = uint32((99 * uint64(amount0Out)) / 100); // 1% fee for liquidity providers + amount1Out = uint32((99 * uint64(amount1Out)) / 100); // 1% fee for liquidity providers } totalToken0ClaimableSwap[currentTradingEpoch] = amount0Out; totalToken1ClaimableSwap[currentTradingEpoch] = amount1Out; @@ -273,15 +310,16 @@ contract UniswapV2Pair is EncryptedERC20 { reserve1 = reserve1 + amount1In - amount1Out; // Liquidity Burns - uint32 _burnedTotal = TFHE.decrypt(pendingTotalBurns[currentTradingEpoch]); - decryptedTotalBurns[currentTradingEpoch] = _burnedTotal; - uint32 amount0Claimable = (_burnedTotal * reserve0) / _totalSupply; - uint32 amount1Claimable = (_burnedTotal * reserve1) / _totalSupply; - totalToken0ClaimableBurn[currentTradingEpoch] = amount0Claimable; - totalToken1ClaimableBurn[currentTradingEpoch] = amount1Claimable; - reserve0 -= amount0Claimable; - reserve1 -= amount1Claimable; - _burn(_burnedTotal); + if (burnedTotal > 0) { + decryptedTotalBurns[currentTradingEpoch] = burnedTotal; + uint32 amount0Claimable = uint32((uint64(burnedTotal) * uint64(reserve0)) / uint64(_totalSupply)); + uint32 amount1Claimable = uint32((uint64(burnedTotal) * uint64(reserve1)) / uint64(_totalSupply)); + totalToken0ClaimableBurn[currentTradingEpoch] = amount0Claimable; + totalToken1ClaimableBurn[currentTradingEpoch] = amount1Claimable; + reserve0 -= amount0Claimable; + reserve1 -= amount1Claimable; + _burn(burnedTotal); + } currentTradingEpoch++; diff --git a/test/EncryptedDEX/EncryptedDEX.ts b/test/EncryptedDEX/EncryptedDEX.ts new file mode 100644 index 0000000..fe61ff4 --- /dev/null +++ b/test/EncryptedDEX/EncryptedDEX.ts @@ -0,0 +1,269 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { createInstances } from "../instance"; +import { getSigners, initSigners } from "../signers"; +import { deployEncryptedDEXFactoryFixture } from "./EncryptedDEXFactory.fixture"; +import { deployEncryptedERC20Fixture, getPrivateBalanceERC20 } from "./EncryptedERC20.fixture"; + +describe("Private DEX", function () { + before(async function () { + await initSigners(); + this.signers = await getSigners(); + }); + + it("Encrypted DEX Pool", async function () { + let token0 = await deployEncryptedERC20Fixture(); + let token0Address = await token0.getAddress(); + let token1 = await deployEncryptedERC20Fixture(); + let token1Address = await token1.getAddress(); + + BigInt(token0Address) > BigInt(token1Address) ? ([token0, token1] = [token1, token0]) : null; // sort tokens according to addresses + token0Address = await token0.getAddress(); + token1Address = await token1.getAddress(); + + const tx0 = await token0.mint(2_000_000_000n); + await tx0.wait(); + const instances0 = await createInstances(token0Address, ethers, this.signers); + const tx1 = await token1.mint(2_000_000_000n); + await tx1.wait(); + const instances1 = await createInstances(token1Address, ethers, this.signers); + let balance = await getPrivateBalanceERC20(token0Address, "alice"); + expect(balance).to.equal(2_000_000_000n); + const totalSupply = await token0.totalSupply(); + expect(totalSupply).to.equal(2_000_000_000n); + + const tx2 = await token0["transfer(address,bytes)"]( + this.signers.bob.address, + instances0.alice.encrypt32(100_000_000), + ); + await tx2.wait(); + + balance = await getPrivateBalanceERC20(token0Address, "alice"); + expect(balance).to.equal(1_900_000_000n); + balance = await getPrivateBalanceERC20(token0Address, "bob"); + expect(balance).to.equal(100_000_000n); + + const tx3 = await token0["transfer(address,bytes)"]( + this.signers.carol.address, + instances0.alice.encrypt32(200_000_000), + ); + await tx3.wait(); + + const tx4 = await token1["transfer(address,bytes)"]( + this.signers.bob.address, + instances1.alice.encrypt32(200_000_000), + ); + await tx4.wait(); + + const tx5 = await token1["transfer(address,bytes)"]( + this.signers.carol.address, + instances1.alice.encrypt32(400_000_000), + ); + await tx5.wait(); + + balance = await getPrivateBalanceERC20(token1Address, "carol"); + expect(balance).to.equal(400_000_000n); + // BOB and CAROL are market makers, BOB starts with 100M token0 and 200M token1, CAROL starts with 200M token0 and 400M token1 + + console.log("Initial balances of market makers (Bob and Carol) : "); + console.log("Bob's token0 balance : ", await getPrivateBalanceERC20(token0Address, "bob")); + console.log("Bob's token1 balance : ", await getPrivateBalanceERC20(token1Address, "bob")); + console.log("Carol's token0 balance : ", await getPrivateBalanceERC20(token0Address, "carol")); + console.log("Carol's token1 balance : ", await getPrivateBalanceERC20(token1Address, "carol")); + + const tx6 = await token0["transfer(address,bytes)"]( + this.signers.dave.address, + instances0.alice.encrypt32(1_000_000), + ); + await tx6.wait(); + const tx6bis = await token1["transfer(address,bytes)"]( + this.signers.dave.address, + instances1.alice.encrypt32(0), // to obfuscate the direction of the swap later, needed to initialize dave's token1 balance + ); + await tx6bis.wait(); + + const tx7 = await token1["transfer(address,bytes)"]( + this.signers.eve.address, + instances1.alice.encrypt32(1_000_000), + ); + await tx7.wait(); + const tx7bis = await token0["transfer(address,bytes)"]( + this.signers.eve.address, + instances0.alice.encrypt32(0), // to obfuscate the direction of the swap later, needed to initialize eve's token0 balance + ); + await tx7bis.wait(); + + console.log("Initial balances of traders (Dave and Eve) : "); + console.log("Dave's token0 balance : ", await getPrivateBalanceERC20(token0Address, "dave")); + console.log("Dave's token1 balance : ", await getPrivateBalanceERC20(token1Address, "dave")); + console.log("Eve's token0 balance : ", await getPrivateBalanceERC20(token0Address, "eve")); + console.log("Eve's token1 balance : ", await getPrivateBalanceERC20(token1Address, "eve"), "\n"); + + const dexFactory = await deployEncryptedDEXFactoryFixture(); + const tx8 = await dexFactory.createPair(token0Address, token1Address); + await tx8.wait(); + + const pairAddress = await dexFactory.getPair(token0Address, token1Address); + const pair = await ethers.getContractAt("EncryptedDEXPair", pairAddress); + console.log("DEX contract was deployed for pair token0/token1 \n"); + + const instancesPair = await createInstances(pairAddress, ethers, this.signers); + + const tx9 = await token0 + .connect(this.signers.bob) + ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt32(100_000_000)); + await tx9.wait(); + const tx10 = await token1 + .connect(this.signers.bob) + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(200_000_000)); + await tx10.wait(); + + const tx11 = await pair + .connect(this.signers.bob) + .addLiquidity( + instancesPair.bob.encrypt32(100_000_000), + instancesPair.bob.encrypt32(200_000_000), + this.signers.bob.address, + 0n, + ); + await tx11.wait(); + console.log("Bob submitted an addLiquidity order at tradingEpoch ", await pair.currentTradingEpoch()); + + const tx12 = await token0 + .connect(this.signers.carol) + ["approve(address,bytes)"](pairAddress, instances0.carol.encrypt32(200_000_000)); + await tx12.wait(); + const tx13 = await token1 + .connect(this.signers.carol) + ["approve(address,bytes)"](pairAddress, instances0.carol.encrypt32(400_000_000)); + await tx13.wait(); + const tx14 = await pair + .connect(this.signers.carol) + .addLiquidity( + instancesPair.carol.encrypt32(200_000_000), + instancesPair.carol.encrypt32(400_000_000), + this.signers.carol.address, + 0n, + ); + await tx14.wait(); + console.log("Carol submitted an addLiquidity order at tradingEpoch ", await pair.currentTradingEpoch(), "\n"); + + const tx15 = await pair.batchSettlement(); + await tx15.wait(); + + console.log( + "Batch Settlement was confirmed with threshold decryptions for tradingEpoch ", + (await pair.currentTradingEpoch()) - 1n, + ); + console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); + let [reserve0, reserve1] = await pair.getReserves(); + console.log("Reserve token0 ", reserve0); + console.log("Reserve token1 ", reserve1, "\n"); + + const tx16 = await pair.claimMint(0n, this.signers.bob.address); + await tx16.wait(); + const tx17 = await pair.claimMint(0n, this.signers.carol.address); + await tx17.wait(); + balance = await getPrivateBalanceERC20(pairAddress, "bob"); + console.log("Bob now owns a private balance of ", balance, " liquidity tokens"); + balance = await getPrivateBalanceERC20(pairAddress, "carol"); + console.log("Carol now owns a private balance of ", balance, " liquidity tokens \n"); + + const tx18 = await token0 + .connect(this.signers.dave) + ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt32(100_000_000)); + await tx18.wait(); + const tx19 = await token1 + .connect(this.signers.dave) + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(100_000_000)); + await tx19.wait(); + const tx20 = await pair + .connect(this.signers.dave) + .swapTokens( + instancesPair.dave.encrypt32(1_000_000), + instancesPair.dave.encrypt32(0), + this.signers.dave.address, + 1n, + ); + await tx20.wait(); + console.log("Dave submitted a swap order at tradingEpoch ", await pair.currentTradingEpoch(), "\n"); + + const tx21 = await token0 + .connect(this.signers.eve) + ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt32(100_000_000)); + await tx21.wait(); + const tx22 = await token1 + .connect(this.signers.eve) + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(100_000_000)); + await tx22.wait(); + const tx23 = await pair + .connect(this.signers.eve) + .swapTokens(instancesPair.eve.encrypt32(0), instancesPair.eve.encrypt32(1_000_000), this.signers.eve.address, 1n); + await tx23.wait(); + console.log("Eve submitted a swap order at tradingEpoch ", await pair.currentTradingEpoch(), "\n"); + + const tx24 = await pair.batchSettlement(); + await tx24.wait(); + + console.log( + "Batch Settlement was confirmed with threshold decryptions for tradingEpoch ", + (await pair.currentTradingEpoch()) - 1n, + ); + console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); + [reserve0, reserve1] = await pair.getReserves(); + console.log("Reserve token0 ", reserve0); + console.log("Reserve token1 ", reserve1, "\n"); + + const tx25 = await pair.claimSwap(1n, this.signers.dave.address); + await tx25.wait(); + const tx26 = await pair.claimSwap(1n, this.signers.eve.address); + await tx26.wait(); + + console.log("New balances of traders (Dave and Eve) : "); + console.log("Dave's token0 balance : ", await getPrivateBalanceERC20(token0Address, "dave")); + console.log("Dave's token1 balance : ", await getPrivateBalanceERC20(token1Address, "dave")); + console.log("Eve's token0 balance : ", await getPrivateBalanceERC20(token0Address, "eve")); + console.log("Eve's token1 balance : ", await getPrivateBalanceERC20(token1Address, "eve"), "\n"); + + const tx27 = await pair + .connect(this.signers.bob) + .removeLiquidity(instancesPair.bob.encrypt32(149999900 / 2), this.signers.bob.address, 2n); + await tx27.wait(); + console.log("Bob submitted a removeLiquidity order at tradingEpoch ", await pair.currentTradingEpoch()); + + const tx28 = await pair + .connect(this.signers.carol) + .removeLiquidity(instancesPair.carol.encrypt32(299999900 / 2), this.signers.carol.address, 2n); + await tx28.wait(); + console.log("Carol submitted a removeLiquidity order at tradingEpoch ", await pair.currentTradingEpoch(), "\n"); + + balance = await getPrivateBalanceERC20(pairAddress, "bob"); + console.log("Bob now owns a private balance of ", balance, " liquidity tokens"); + balance = await getPrivateBalanceERC20(pairAddress, "carol"); + console.log("Carol now owns a private balance of ", balance, " liquidity tokens \n"); + + const tx29 = await pair.batchSettlement(); + await tx29.wait(); + + console.log( + "Batch Settlement was confirmed with threshold decryptions for tradingEpoch ", + (await pair.currentTradingEpoch()) - 1n, + ); + console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); + [reserve0, reserve1] = await pair.getReserves(); + console.log("Reserve token0 ", reserve0); + console.log("Reserve token1 ", reserve1, "\n"); + + const tx30 = await pair.claimBurn(2n, this.signers.bob.address); + await tx30.wait(); + const tx31 = await pair.claimBurn(2n, this.signers.carol.address); + await tx31.wait(); + + console.log("New balances of market makers (Bob and Carol) : "); + console.log("Bob's token0 balance : ", await getPrivateBalanceERC20(token0Address, "bob")); + console.log("Bob's token1 balance : ", await getPrivateBalanceERC20(token1Address, "bob")); + console.log("Carol's token0 balance : ", await getPrivateBalanceERC20(token0Address, "carol")); + console.log("Carol's token1 balance : ", await getPrivateBalanceERC20(token1Address, "carol")); + }); +}); diff --git a/test/EncryptedDEX/EncryptedDEXFactory.fixture.ts b/test/EncryptedDEX/EncryptedDEXFactory.fixture.ts new file mode 100644 index 0000000..1c8066b --- /dev/null +++ b/test/EncryptedDEX/EncryptedDEXFactory.fixture.ts @@ -0,0 +1,14 @@ +import { ethers } from "hardhat"; + +import type { EncryptedDEXFactory } from "../../types"; +import { getSigners } from "../signers"; + +export async function deployEncryptedDEXFactoryFixture(): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("EncryptedDEXFactory"); + const contract = await contractFactory.connect(signers.alice).deploy(); + await contract.waitForDeployment(); + + return contract; +} diff --git a/test/EncryptedDEX/EncryptedERC20.fixture.ts b/test/EncryptedDEX/EncryptedERC20.fixture.ts new file mode 100644 index 0000000..ad835a1 --- /dev/null +++ b/test/EncryptedDEX/EncryptedERC20.fixture.ts @@ -0,0 +1,30 @@ +import { ethers } from "hardhat"; + +import type { EncryptedERC20 } from "../../types"; +import { createInstances } from "../instance"; +import { getSigners } from "../signers"; + +export async function deployEncryptedERC20Fixture(): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("EncryptedERC20"); + const contract = await contractFactory.connect(signers.alice).deploy("Naraggara", "NARA"); // City of Zama's battle + await contract.waitForDeployment(); + + return contract; +} + +export async function getPrivateBalanceERC20(erc20Address: string, userName: string): Promise { + const signers = await getSigners(); + const erc20 = await ethers.getContractAt("EncryptedERC20", erc20Address); + const instances = await createInstances(erc20Address, ethers, signers); + const token = instances[userName].getPublicKey(erc20Address) || { + signature: "", + publicKey: "", + }; + const encryptedBalance = await erc20 + .connect(signers[userName]) + .balanceOf(signers[userName], token.publicKey, token.signature); + const balance = instances.alice.decrypt(erc20Address, encryptedBalance); + return balance; +} diff --git a/test/Uniswap.ts.old b/test/Uniswap.ts.old deleted file mode 100644 index 20a815a..0000000 --- a/test/Uniswap.ts.old +++ /dev/null @@ -1,95 +0,0 @@ -import { ethers } from "hardhat"; -import { getSigners, initSigners } from "./signers"; - -describe("UniswapV2", function () { - before(async function () { - await initSigners(); - this.signers = await getSigners(); - }); - - it("Regular UniswapV2 Pool", async function () { - const signers = await getSigners(); - const aliceAddress = await signers.alice.getAddress(); - const bobAddress = await signers.bob.getAddress(); - const tokenFactory = await ethers.getContractFactory("Token"); - const tokenA = await tokenFactory.deploy("TokenA", "TOKA"); - await tokenA.waitForDeployment(); - const tokenAAddress = await tokenA.getAddress(); - console.log("ERC20 TOKENA ADDRESS : ", tokenAAddress); - - let tx = await tokenA.mint(aliceAddress, 2000000000n); - await tx.wait(); - - console.log("BALANCE ALICE A : ", await tokenA.balanceOf(aliceAddress)); - - const tokenB = await tokenFactory.deploy("TokenB", "TOKB"); - await tokenB.waitForDeployment(); - const tokenBAddress = await tokenB.getAddress(); - console.log("ERC20 TOKENB ADDRESS : ", tokenBAddress); - - let tx2 = await tokenB.mint(aliceAddress, 1000000000n); - await tx2.wait(); - console.log("BALANCE ALICE B : ", await tokenB.balanceOf(aliceAddress)); - - const uniswapFactoryFactory = await ethers.getContractFactory("UniswapV2Factory"); - const uniswapFactory = await uniswapFactoryFactory.connect(signers.alice).deploy(); - await uniswapFactory.waitForDeployment(); - console.log("UNISWAP FACTORY ADDRESS : ", await uniswapFactory.getAddress()); - - let tx3 = await uniswapFactory.createPair(tokenAAddress, tokenBAddress); - await tx3.wait(); - - const pairContractAddress = await uniswapFactory.getPair(tokenAAddress, tokenBAddress); - console.log("Pair Contract Address : ", pairContractAddress); - - const uniswapV2RouterFactory = await ethers.getContractFactory("UniswapV2Router"); - const uniswapV2Router = await uniswapV2RouterFactory.deploy(await uniswapFactory.getAddress()); - await uniswapV2Router.waitForDeployment(); - const routerAddress = await uniswapV2Router.getAddress(); - - const tx4 = await tokenA.approve(routerAddress, 20000000n); - await tx4.wait(); - const tx5 = await tokenB.approve(routerAddress, 10000000n); - await tx5.wait(); - - - const currentTime = (await ethers.provider.getBlock("latest"))?.timestamp ?? 0; - const tx6 = await uniswapV2Router.addLiquidity( - tokenAAddress, - tokenBAddress, - 20000000n, - 10000000n, - 20000000n, - 10000000n, - aliceAddress, - currentTime + 60, - ); - await tx6.wait(); - - const XY_before = (await tokenA.balanceOf(pairContractAddress)) * (await tokenB.balanceOf(pairContractAddress)); - console.log("XY before swap : ", XY_before); - - await tokenA.transfer(bobAddress, 100000n); - const tx7 = await tokenA.connect(signers.bob).approve(routerAddress, 100000n); - await tx7.wait(); - const tx8 = await uniswapV2Router - .connect(signers.bob) - .swapExactTokensForTokens(100000n, 0n, [tokenAAddress, tokenBAddress], bobAddress, currentTime + 120); - await tx8.wait(); - console.log("bob bal B : ", await tokenB.balanceOf(bobAddress)); - const XY_after = (await tokenA.balanceOf(pairContractAddress)) * (await tokenB.balanceOf(pairContractAddress)); - console.log("XY before swap : ", XY_after); - - const tx9 = await tokenB.connect(signers.bob).approve(routerAddress, 100000000n); - await tx9.wait(); - const tx10 = await uniswapV2Router - .connect(signers.bob) - .swapTokensForExactTokens(1000n, 10000000000000n, [tokenBAddress, tokenAAddress], bobAddress, currentTime + 180); - await tx10.wait(); - - console.log("bob bal B after second swap : ", await tokenB.balanceOf(bobAddress)); - console.log("bob bal A after second swap : ", await tokenA.balanceOf(bobAddress)); - const XY_after2 = (await tokenA.balanceOf(pairContractAddress)) * (await tokenB.balanceOf(pairContractAddress)); - console.log("XY after second swap : ", XY_after2); - }); -}); \ No newline at end of file