From c5089c62e1e001682a0db9104a3b3dbcff070129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joseph-Andr=C3=A9=20Turk?= Date: Tue, 5 Mar 2024 18:40:00 +0100 Subject: [PATCH] feat: rolled back to EncryptedERC20 on 32 bits --- contracts/EncryptedDEXPair.sol | 229 ++++++++++++++-------------- contracts/EncryptedERC20.sol | 141 +++++------------ contracts/utils/EncryptedErrors.sol | 140 ----------------- hardhat.config.ts | 4 +- test/EncryptedDEX/EncryptedDEX.ts | 220 +++++++++++++------------- 5 files changed, 263 insertions(+), 471 deletions(-) delete mode 100644 contracts/utils/EncryptedErrors.sol diff --git a/contracts/EncryptedDEXPair.sol b/contracts/EncryptedDEXPair.sol index 2fc25c8..bc1a275 100644 --- a/contracts/EncryptedDEXPair.sol +++ b/contracts/EncryptedDEXPair.sol @@ -6,48 +6,48 @@ import "./EncryptedERC20.sol"; contract EncryptedDEXPair is EncryptedERC20 { struct DecryptionResults { - uint64 reserve0PendingAddDec; - uint64 reserve1PendingAddDec; - uint64 mintedTotal; - uint64 amount0In; - uint64 amount1In; - uint64 burnedTotal; + uint32 reserve0PendingAddDec; + uint32 reserve1PendingAddDec; + uint32 mintedTotal; + uint32 amount0In; + uint32 amount1In; + uint32 burnedTotal; } - euint64 private ZERO = TFHE.asEuint64(0); - uint64 public constant MINIMIMUM_LIQUIDITY = 100 * 2 ** 32; + euint32 private ZERO = TFHE.asEuint32(0); + uint32 public constant MINIMIMUM_LIQUIDITY = 100; uint256 public constant MIN_DELAY_SETTLEMENT = 2; 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 => euint64 mintedLiquidity)) internal pendingMints; - mapping(uint256 tradingEpoch => euint64 mintedTotalLiquidity) internal pendingTotalMints; - mapping(uint256 tradingEpoch => uint64 mintedTotalLiquidity) internal decryptedTotalMints; - - mapping(uint256 tradingEpoch => mapping(address user => euint64 burnedLiquidity)) internal pendingBurns; - mapping(uint256 tradingEpoch => euint64 burnedTotalLiquidity) internal pendingTotalBurns; - mapping(uint256 tradingEpoch => uint64 burnedTotalLiquidity) internal decryptedTotalBurns; - mapping(uint256 tradingEpoch => uint64 totalToken0) internal totalToken0ClaimableBurn; - mapping(uint256 tradingEpoch => uint64 totalToken1) internal totalToken1ClaimableBurn; - - mapping(uint256 tradingEpoch => mapping(address user => euint64 swappedToken0In)) internal pendingToken0In; - mapping(uint256 tradingEpoch => euint64 swappedTotalToken0In) internal pendingTotalToken0In; - mapping(uint256 tradingEpoch => uint64 swappedTotalToken0In) internal decryptedTotalToken0In; - mapping(uint256 tradingEpoch => mapping(address user => euint64 swappedToken1In)) internal pendingToken1In; - mapping(uint256 tradingEpoch => euint64 swappedTotalToken1In) internal pendingTotalToken1In; - mapping(uint256 tradingEpoch => uint64 swappedTotalToken1In) internal decryptedTotalToken1In; - mapping(uint256 tradingEpoch => uint64 totalToken0) internal totalToken0ClaimableSwap; - mapping(uint256 tradingEpoch => uint64 totalToken1) internal totalToken1ClaimableSwap; + mapping(uint256 tradingEpoch => mapping(address user => euint32 mintedLiquidity)) internal pendingMints; + mapping(uint256 tradingEpoch => euint32 mintedTotalLiquidity) internal pendingTotalMints; + mapping(uint256 tradingEpoch => uint32 mintedTotalLiquidity) internal decryptedTotalMints; + + mapping(uint256 tradingEpoch => mapping(address user => euint32 burnedLiquidity)) internal pendingBurns; + mapping(uint256 tradingEpoch => euint32 burnedTotalLiquidity) internal pendingTotalBurns; + mapping(uint256 tradingEpoch => uint32 burnedTotalLiquidity) internal decryptedTotalBurns; + mapping(uint256 tradingEpoch => uint32 totalToken0) internal totalToken0ClaimableBurn; + mapping(uint256 tradingEpoch => uint32 totalToken1) internal totalToken1ClaimableBurn; + + mapping(uint256 tradingEpoch => mapping(address user => euint32 swappedToken0In)) internal pendingToken0In; + mapping(uint256 tradingEpoch => euint32 swappedTotalToken0In) internal pendingTotalToken0In; + mapping(uint256 tradingEpoch => uint32 swappedTotalToken0In) internal decryptedTotalToken0In; + mapping(uint256 tradingEpoch => mapping(address user => euint32 swappedToken1In)) internal pendingToken1In; + mapping(uint256 tradingEpoch => euint32 swappedTotalToken1In) internal pendingTotalToken1In; + mapping(uint256 tradingEpoch => uint32 swappedTotalToken1In) internal decryptedTotalToken1In; + mapping(uint256 tradingEpoch => uint32 totalToken0) internal totalToken0ClaimableSwap; + mapping(uint256 tradingEpoch => uint32 totalToken1) internal totalToken1ClaimableSwap; address public factory; EncryptedERC20 public token0; EncryptedERC20 public token1; - uint64 private reserve0; - uint64 private reserve1; + uint32 private reserve0; + uint32 private reserve1; - euint64 private reserve0PendingAdd; - euint64 private reserve1PendingAdd; + euint32 private reserve0PendingAdd; + euint32 private reserve1PendingAdd; uint256 private unlocked = 1; @@ -69,7 +69,7 @@ contract EncryptedDEXPair is EncryptedERC20 { z = x < y ? x : y; } - function getReserves() public view returns (uint64 _reserve0, uint64 _reserve1) { + function getReserves() public view returns (uint32 _reserve0, uint32 _reserve1) { _reserve0 = reserve0; _reserve1 = reserve1; } @@ -78,14 +78,14 @@ contract EncryptedDEXPair is EncryptedERC20 { factory = msg.sender; } - function _mintPartial(uint64 mintedAmount) internal { + function _mintPartial(uint32 mintedAmount) internal { // this is a partial mint, balances are updated later during the claim _totalSupply = _totalSupply + mintedAmount; emit Mint(address(this), mintedAmount); } - function _burn(uint64 burnedAmount) internal { - balances[address(this)] = TFHE.sub(balances[address(this)], burnedAmount); // check underflow is impossible when used from the contract logic + function _burn(uint32 burnedAmount) internal { + balances[address(this)] = TFHE.sub(balances[address(this)], burnedAmount); // underflow is impossible when used from the contract logic _totalSupply = _totalSupply - burnedAmount; emit Burn(burnedAmount); } @@ -104,42 +104,42 @@ contract EncryptedDEXPair is EncryptedERC20 { address to, uint256 deadline ) external ensure(deadline) { - euint64 balance0Before = token0.balanceOfMe(); - euint64 balance1Before = token1.balanceOfMe(); - euint64 amount0 = TFHE.shl(TFHE.shr(TFHE.asEuint64(encryptedAmount0), 32), 32); - euint64 amount1 = TFHE.shl(TFHE.shr(TFHE.asEuint64(encryptedAmount1), 32), 32); + euint32 balance0Before = token0.balanceOfMe(); + euint32 balance1Before = token1.balanceOfMe(); + euint32 amount0 = TFHE.asEuint32(encryptedAmount0); + euint32 amount1 = TFHE.asEuint32(encryptedAmount1); token0.transferFrom(msg.sender, address(this), amount0); token1.transferFrom(msg.sender, address(this), amount1); - euint64 balance0After = token0.balanceOfMe(); - euint64 balance1After = token1.balanceOfMe(); - euint64 sentAmount0 = balance0After - balance0Before; - euint64 sentAmount1 = balance1After - balance1Before; + euint32 balance0After = token0.balanceOfMe(); + euint32 balance1After = token1.balanceOfMe(); + euint32 sentAmount0 = balance0After - balance0Before; + euint32 sentAmount1 = balance1After - balance1Before; mint(to, sentAmount0, sentAmount1); } - function mint(address to, euint64 amount0, euint64 amount1) internal { + function mint(address to, euint32 amount0, euint32 amount1) internal { uint256 currentEpoch = currentTradingEpoch; if (firstBlockPerEpoch[currentEpoch] == 0) { firstBlockPerEpoch[currentEpoch] = block.number; } reserve0PendingAdd = reserve0PendingAdd + amount0; reserve1PendingAdd = reserve1PendingAdd + amount1; - euint64 liquidity; + euint32 liquidity; if (totalSupply() == 0) { // this condition is equivalent to currentEpoch==0 (see batchSettlement logic) liquidity = TFHE.shr(amount0, 1) + TFHE.shr(amount1, 1); ebool isBelowMinimum = TFHE.lt(liquidity, MINIMIMUM_LIQUIDITY); liquidity = TFHE.cmux(isBelowMinimum, ZERO, liquidity); - euint64 amount0Back = TFHE.cmux(isBelowMinimum, amount0, ZERO); - euint64 amount1Back = TFHE.cmux(isBelowMinimum, amount1, ZERO); + euint32 amount0Back = TFHE.cmux(isBelowMinimum, amount0, ZERO); + euint32 amount1Back = TFHE.cmux(isBelowMinimum, amount1, ZERO); token0.transfer(msg.sender, amount0Back); // refund first liquidity if it is below the minimal amount token1.transfer(msg.sender, amount1Back); // refund first liquidity if it is below the minimal amount reserve0PendingAdd = reserve0PendingAdd - amount0Back; reserve1PendingAdd = reserve1PendingAdd - amount1Back; } else { - euint64 liquidity0 = TFHE.div(TFHE.mul(TFHE.shr(amount0, 32), _totalSupply >> 32), reserve0 >> 32); - euint64 liquidity1 = TFHE.div(TFHE.mul(TFHE.shr(amount1, 32), _totalSupply >> 32), reserve1 >> 32); - liquidity = TFHE.shl(TFHE.min(liquidity0, liquidity1), 32); + euint64 liquidity0 = TFHE.div(TFHE.mul(TFHE.asEuint64(amount0), uint64(_totalSupply)), uint64(reserve0)); // to avoid overflows + euint64 liquidity1 = TFHE.div(TFHE.mul(TFHE.asEuint64(amount1), uint64(_totalSupply)), uint64(reserve1)); // to avoid overflows + liquidity = TFHE.asEuint32(TFHE.min(liquidity0, liquidity1)); // this always fit in a euint32 from the logic of the contract } pendingMints[currentEpoch][to] = pendingMints[currentEpoch][to] + liquidity; pendingTotalMints[currentEpoch] = pendingTotalMints[currentEpoch] + liquidity; @@ -151,10 +151,10 @@ contract EncryptedDEXPair is EncryptedERC20 { if (firstBlockPerEpoch[currentEpoch] == 0) { firstBlockPerEpoch[currentEpoch] = block.number; } - euint64 liquidityBefore = balances[address(this)]; - transfer(address(this), TFHE.shl(TFHE.shr(TFHE.asEuint64(encryptedLiquidity), 32), 32)); // only allow removing multiple of 2**32 to keep total supply a multiple of 2**32 - euint64 liquidityAfter = balances[address(this)]; - euint64 burntLiquidity = liquidityAfter - liquidityBefore; + euint32 liquidityBefore = balances[address(this)]; + transfer(address(this), TFHE.asEuint32(encryptedLiquidity)); + euint32 liquidityAfter = balances[address(this)]; + euint32 burntLiquidity = liquidityAfter - liquidityBefore; pendingBurns[currentEpoch][to] = pendingBurns[currentEpoch][to] + burntLiquidity; pendingTotalBurns[currentEpoch] = pendingTotalBurns[currentEpoch] + burntLiquidity; } @@ -170,16 +170,16 @@ contract EncryptedDEXPair is EncryptedERC20 { if (firstBlockPerEpoch[currentEpoch] == 0) { firstBlockPerEpoch[currentEpoch] = block.number; } - euint64 balance0Before = token0.balanceOfMe(); - euint64 balance1Before = token1.balanceOfMe(); - euint64 amount0In = TFHE.shl(TFHE.shr(TFHE.asEuint64(encryptedAmount0In), 32), 32); // even if amount is null, do a transfer to obfuscate trade direction - euint64 amount1In = TFHE.shl(TFHE.shr(TFHE.asEuint64(encryptedAmount1In), 32), 32); // even if amount is null, do a transfer to obfuscate trade direction + euint32 balance0Before = token0.balanceOfMe(); + euint32 balance1Before = token1.balanceOfMe(); + euint32 amount0In = TFHE.asEuint32(encryptedAmount0In); // even if amount is null, do a transfer to obfuscate trade direction + euint32 amount1In = TFHE.asEuint32(encryptedAmount1In); // even if amount is null, do a transfer to obfuscate trade direction token0.transferFrom(msg.sender, address(this), amount0In); token1.transferFrom(msg.sender, address(this), amount1In); - euint64 balance0After = token0.balanceOfMe(); - euint64 balance1After = token1.balanceOfMe(); - euint64 sent0 = balance0After - balance0Before; - euint64 sent1 = balance1After - balance1Before; + euint32 balance0After = token0.balanceOfMe(); + euint32 balance1After = token1.balanceOfMe(); + euint32 sent0 = balance0After - balance0Before; + euint32 sent1 = balance1After - balance1Before; pendingToken0In[currentEpoch][to] = pendingToken0In[currentEpoch][to] + sent0; pendingTotalToken0In[currentEpoch] = pendingTotalToken0In[currentEpoch] + sent0; pendingToken1In[currentEpoch][to] = pendingToken1In[currentEpoch][to] + sent1; @@ -189,8 +189,7 @@ contract EncryptedDEXPair is EncryptedERC20 { function claimMint(uint256 tradingEpoch, address user) external { require(tradingEpoch < currentTradingEpoch, "tradingEpoch is not settled yet"); if (tradingEpoch == 0) { - balances[user] = TFHE.sub(balances[user] + pendingMints[tradingEpoch][user], MINIMIMUM_LIQUIDITY); // this could fail in the very theoretical case where several market makers would mint individually - // less than MINIMIMUM_LIQUIDITY LP tokens but their sum is above MINIMIMUM_LIQUIDITY. NOT a vulnerability, as long as the first market makers are aware that the avarage sent amounts during first tradingEpoch must be above MINIMIMUM_LIQUIDITY. + balances[user] = TFHE.sub(balances[user] + pendingMints[tradingEpoch][user], MINIMIMUM_LIQUIDITY); // this could never underflow from the contract logic } else { balances[user] = balances[user] + pendingMints[tradingEpoch][user]; } @@ -199,19 +198,17 @@ contract EncryptedDEXPair is EncryptedERC20 { function claimBurn(uint256 tradingEpoch, address user) external { require(tradingEpoch < currentTradingEpoch, "tradingEpoch is not settled yet"); - euint64 pendingBurn = pendingBurns[tradingEpoch][user]; - uint64 decryptedTotalBurn = decryptedTotalBurns[tradingEpoch]; + euint32 pendingBurn = pendingBurns[tradingEpoch][user]; + uint32 decryptedTotalBurn = decryptedTotalBurns[tradingEpoch]; require(decryptedTotalBurn != 0, "No liquidity was burnt during tradingEpoch"); - uint64 totalToken0 = totalToken0ClaimableBurn[tradingEpoch]; - uint64 totalToken1 = totalToken1ClaimableBurn[tradingEpoch]; - euint64 token0Claimable = TFHE.shl( - TFHE.div(TFHE.mul(TFHE.shr(pendingBurn, 32), totalToken0 >> 32), decryptedTotalBurn >> 32), - 32 - ); - euint64 token1Claimable = TFHE.shl( - TFHE.div(TFHE.mul(TFHE.shr(pendingBurn, 32), totalToken1 >> 32), decryptedTotalBurn >> 32), - 32 - ); + uint32 totalToken0 = totalToken0ClaimableBurn[tradingEpoch]; + uint32 totalToken1 = totalToken1ClaimableBurn[tradingEpoch]; + euint32 token0Claimable = TFHE.asEuint32( + TFHE.div(TFHE.mul(TFHE.asEuint64(pendingBurn), uint64(totalToken0)), uint64(decryptedTotalBurn)) + ); // this always fit in a euint32 + euint32 token1Claimable = TFHE.asEuint32( + TFHE.div(TFHE.mul(TFHE.asEuint64(pendingBurn), uint64(totalToken1)), uint64(decryptedTotalBurn)) + ); // this always fit in a euint32 token0.transfer(user, token0Claimable); token1.transfer(user, token1Claimable); pendingBurns[tradingEpoch][user] = ZERO; @@ -219,29 +216,27 @@ contract EncryptedDEXPair is EncryptedERC20 { function claimSwap(uint256 tradingEpoch, address user) external { require(tradingEpoch < currentTradingEpoch, "tradingEpoch is not settled yet"); - euint64 pending0In = pendingToken0In[tradingEpoch][user]; - uint64 totalToken0In = decryptedTotalToken0In[tradingEpoch]; - euint64 pending1In = pendingToken1In[tradingEpoch][user]; - uint64 totalToken1In = decryptedTotalToken1In[tradingEpoch]; + euint32 pending0In = pendingToken0In[tradingEpoch][user]; + uint32 totalToken0In = decryptedTotalToken0In[tradingEpoch]; + euint32 pending1In = pendingToken1In[tradingEpoch][user]; + uint32 totalToken1In = decryptedTotalToken1In[tradingEpoch]; - uint64 totalToken0Out = totalToken0ClaimableSwap[tradingEpoch]; - uint64 totalToken1Out = totalToken1ClaimableSwap[tradingEpoch]; + uint32 totalToken0Out = totalToken0ClaimableSwap[tradingEpoch]; + uint32 totalToken1Out = totalToken1ClaimableSwap[tradingEpoch]; - euint64 amount0Out; - euint64 amount1Out; + euint32 amount0Out; + euint32 amount1Out; if (totalToken1In != 0) { - amount0Out = TFHE.shl( - TFHE.div(TFHE.mul(TFHE.shr(pending1In, 32), totalToken0Out >> 32), totalToken1In >> 32), - 32 - ); + amount0Out = TFHE.asEuint32( + TFHE.div(TFHE.mul(TFHE.asEuint64(pending1In), uint64(totalToken0Out)), uint64(totalToken1In)) + ); // this always fit in a euint32 token0.transfer(user, amount0Out); } if (totalToken0In != 0) { - amount1Out = TFHE.shl( - TFHE.div(TFHE.mul(TFHE.shr(pending0In, 32), totalToken1Out >> 32), totalToken0In >> 32), - 32 - ); + amount1Out = TFHE.asEuint32( + TFHE.div(TFHE.mul(TFHE.asEuint64(pending0In), uint64(totalToken1Out)), uint64(totalToken0In)) + ); // this always fit in a euint32 token1.transfer(user, amount1Out); } @@ -250,12 +245,12 @@ contract EncryptedDEXPair is EncryptedERC20 { } function requestAllDecryptions() internal view returns (DecryptionResults memory) { - uint64 reserve0PendingAddDec; - uint64 reserve1PendingAddDec; - uint64 mintedTotal; - uint64 amount0In; - uint64 amount1In; - uint64 burnedTotal; + uint32 reserve0PendingAddDec; + uint32 reserve1PendingAddDec; + uint32 mintedTotal; + uint32 amount0In; + uint32 amount1In; + uint32 burnedTotal; if (TFHE.isInitialized(reserve0PendingAdd)) reserve0PendingAddDec = TFHE.decrypt(reserve0PendingAdd); if (TFHE.isInitialized(reserve1PendingAdd)) reserve1PendingAddDec = TFHE.decrypt(reserve1PendingAdd); if (TFHE.isInitialized(pendingTotalMints[currentTradingEpoch])) @@ -298,7 +293,7 @@ contract EncryptedDEXPair is EncryptedERC20 { tradingEpoch != 0 || decResults.mintedTotal >= MINIMIMUM_LIQUIDITY, "Initial minted liquidity amount should be greater than MINIMIMUM_LIQUIDITY" ); // this is to lock forever at least MINIMIMUM_LIQUIDITY liquidity tokens inside the pool, so totalSupply of liquidity - // would remain above MINIMIMUM_LIQUIDITY 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 + // would remain above MINIMIMUM_LIQUIDITY to avoid security issues, for e.g. if a market maker burns the entire liquidity in a single transaction, making the pool unusable if (decResults.mintedTotal > 0) { _mintPartial(decResults.mintedTotal); decryptedTotalMints[tradingEpoch] = decResults.mintedTotal; @@ -307,28 +302,32 @@ contract EncryptedDEXPair is EncryptedERC20 { // Token Swaps decryptedTotalToken0In[tradingEpoch] = decResults.amount0In; decryptedTotalToken1In[tradingEpoch] = decResults.amount1In; - uint64 amount0InMinusFee = ((99 * (decResults.amount0In >> 32)) / 100) << 32; // 1% fee for liquidity providers - uint64 amount1InMinusFee = ((99 * (decResults.amount1In >> 32)) / 100) << 32; // 1% fee for liquidity providers - bool priceToken1Increasing = (uint128(decResults.amount0In) * uint128(reserve1) > - uint128(decResults.amount1In) * uint128(reserve0)); - uint64 amount0Out; - uint64 amount1Out; + uint32 amount0InMinusFee = uint32((99 * uint64(decResults.amount0In)) / 100); // 1% fee for liquidity providers + uint32 amount1InMinusFee = uint32((99 * uint64(decResults.amount1In)) / 100); // 1% fee for liquidity providers + bool priceToken1Increasing = (uint64(decResults.amount0In) * uint64(reserve1) > + uint64(decResults.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 = (((amount1InMinusFee >> 32) * (reserve0 >> 32)) / (reserve1 >> 32)) << 32; + amount0Out = uint32((uint64(amount1InMinusFee) * uint64(reserve0)) / uint64(reserve1)); amount1Out = amount1InMinusFee + reserve1 - - ((((reserve1 >> 32) * (reserve0 >> 32)) / - ((reserve0 >> 32) + (amount0InMinusFee >> 32) - (amount0Out >> 32))) << 32); + uint32( + (uint64(reserve1) * uint64(reserve0)) / + (uint64(reserve0) + uint64(amount0InMinusFee) - uint64(amount0Out)) + ); } else { // here we do the opposite, first sell token0 at current token0 price then swap remaining token1 according to AMM formula - amount1Out = (((amount0InMinusFee >> 32) * (reserve1 >> 32)) / (reserve0 >> 32)) << 32; + amount1Out = uint32((uint64(amount0InMinusFee) * uint64(reserve1)) / uint64(reserve0)); amount0Out = amount0InMinusFee + reserve0 - - ((((reserve0 >> 32) * (reserve1 >> 32)) / - ((reserve1 >> 32) + (amount1InMinusFee >> 32) - (amount1Out >> 32))) << 32); + uint32( + (uint64(reserve0) * uint64(reserve1)) / + (uint64(reserve1) + uint64(amount1InMinusFee) - uint64(amount1Out)) + ); } totalToken0ClaimableSwap[tradingEpoch] = amount0Out; totalToken1ClaimableSwap[tradingEpoch] = amount1Out; @@ -338,10 +337,12 @@ contract EncryptedDEXPair is EncryptedERC20 { // Liquidity Burns if (decResults.burnedTotal > 0) { decryptedTotalBurns[tradingEpoch] = decResults.burnedTotal; - uint64 amount0Claimable = (((decResults.burnedTotal >> 32) * (reserve0 >> 32)) / (_totalSupply >> 32)) << - 32; - uint64 amount1Claimable = (((decResults.burnedTotal >> 32) * (reserve1 >> 32)) / (_totalSupply >> 32)) << - 32; + uint32 amount0Claimable = uint32( + (uint64(decResults.burnedTotal) * uint64(reserve0)) / uint64(_totalSupply) + ); + uint32 amount1Claimable = uint32( + (uint64(decResults.burnedTotal) * uint64(reserve1)) / uint64(_totalSupply) + ); totalToken0ClaimableBurn[tradingEpoch] = amount0Claimable; totalToken1ClaimableBurn[tradingEpoch] = amount1Claimable; reserve0 -= amount0Claimable; diff --git a/contracts/EncryptedERC20.sol b/contracts/EncryptedERC20.sol index 2977e8b..e9724e7 100644 --- a/contracts/EncryptedERC20.sol +++ b/contracts/EncryptedERC20.sol @@ -2,45 +2,27 @@ pragma solidity ^0.8.20; -import "fhevm/lib/TFHE.sol"; import "fhevm/abstracts/Reencrypt.sol"; -import "./utils/EncryptedErrors.sol"; +import "fhevm/lib/TFHE.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; -contract EncryptedERC20 is Reencrypt, Ownable2Step, EncryptedErrors { - enum ErrorCodes { - NO_ERROR, - UNSUFFICIENT_BALANCE, - UNSUFFICIENT_APPROVAL - } - - struct AllowedErrorReencryption { - address spender; // account's address allowed to reencrypt errorCode - euint8 errorCode; - } - - event Transfer(uint256 indexed transferId, address indexed from, address indexed to); +contract EncryptedERC20 is Reencrypt, Ownable2Step { + event Transfer(address indexed from, address indexed to); event Approval(address indexed owner, address indexed spender); - event Mint(address indexed to, uint64 amount); + event Mint(address indexed to, uint32 amount); - uint64 internal _totalSupply; + uint32 internal _totalSupply; string private _name; string private _symbol; - uint8 public constant decimals = 6; - - // A mapping from transferId to the AllowedErrorReencryption. - mapping(uint256 => AllowedErrorReencryption) internal allowedErrorReencryptions; + uint8 public constant decimals = 0; // A mapping from address to an encrypted balance. - mapping(address => euint64) internal balances; + mapping(address => euint32) internal balances; // A mapping of the form mapping(owner => mapping(spender => allowance)). - mapping(address => mapping(address => euint64)) internal allowances; + mapping(address => mapping(address => euint32)) internal allowances; - constructor( - string memory name_, - string memory symbol_ - ) Ownable(msg.sender) EncryptedErrors(uint8(type(ErrorCodes).max)) { + constructor(string memory name_, string memory symbol_) Ownable(msg.sender) { _name = name_; _symbol = symbol_; } @@ -55,35 +37,29 @@ contract EncryptedERC20 is Reencrypt, Ownable2Step, EncryptedErrors { return _symbol; } - // Returns the total supply of the token. - function totalSupply() public view virtual returns (uint64) { + // Returns the total supply of the token + function totalSupply() public view virtual returns (uint32) { return _totalSupply; } - // Increase owner's balance by the given `mintedAmount`. - function mint(uint64 mintedAmount) public virtual onlyOwner { - _mint(mintedAmount); - } - - // Increase sender's balance by the given `amount`. - function _mint(uint64 amount) internal virtual { - balances[msg.sender] = TFHE.add(balances[msg.sender], amount); // overflow impossible because of next line - _totalSupply = _totalSupply + amount; - emit Mint(msg.sender, amount); + // Sets the balance of the owner to the given encrypted balance. + function mint(uint32 mintedAmount) public virtual onlyOwner { + balances[owner()] = TFHE.add(balances[owner()], mintedAmount); // overflow impossible because of next line + _totalSupply = _totalSupply + mintedAmount; + emit Mint(owner(), mintedAmount); } // Transfers an encrypted amount from the message sender address to the `to` address. function transfer(address to, bytes calldata encryptedAmount) public virtual returns (bool) { - transfer(to, TFHE.asEuint64(encryptedAmount)); + transfer(to, TFHE.asEuint32(encryptedAmount)); return true; } // Transfers an amount from the message sender address to the `to` address. - function transfer(address to, euint64 amount) public virtual returns (bool) { + function transfer(address to, euint32 amount) public virtual returns (bool) { // makes sure the owner has enough tokens ebool canTransfer = TFHE.le(amount, balances[msg.sender]); - euint8 errorCode = defineErrorIfNot(canTransfer, uint8(ErrorCodes.UNSUFFICIENT_BALANCE)); - _transfer(msg.sender, to, amount, canTransfer, errorCode); + _transfer(msg.sender, to, amount, canTransfer); return true; } @@ -93,23 +69,25 @@ contract EncryptedERC20 is Reencrypt, Ownable2Step, EncryptedErrors { bytes32 publicKey, bytes calldata signature ) public view virtual onlySignedPublicKey(publicKey, signature) returns (bytes memory) { - require(wallet == msg.sender, "User cannot reencrypt a non-owned wallet balance"); - return TFHE.reencrypt(balances[wallet], publicKey, 0); + if (wallet == msg.sender) { + return TFHE.reencrypt(balances[wallet], publicKey, 0); + } + return TFHE.reencrypt(TFHE.asEuint32(0), publicKey, 0); } // Returns the encrypted balance of the caller. - function balanceOfMe() public view virtual returns (euint64) { + function balanceOfMe() public view returns (euint32) { return balances[msg.sender]; } // Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens. function approve(address spender, bytes calldata encryptedAmount) public virtual returns (bool) { - approve(spender, TFHE.asEuint64(encryptedAmount)); + approve(spender, TFHE.asEuint32(encryptedAmount)); return true; } // Sets the `amount` as the allowance of `spender` over the caller's tokens. - function approve(address spender, euint64 amount) public virtual returns (bool) { + function approve(address spender, euint32 amount) public virtual returns (bool) { address owner = msg.sender; _approve(owner, spender, amount); emit Approval(owner, spender); @@ -117,94 +95,59 @@ contract EncryptedERC20 is Reencrypt, Ownable2Step, EncryptedErrors { } // Returns the remaining number of tokens that `spender` is allowed to spend - // on behalf of the `owner`. The returned ciphertext is under the caller's `publicKey`. + // on behalf of the caller. The returned ciphertext is under the caller public FHE key. function allowance( address owner, address spender, bytes32 publicKey, bytes calldata signature ) public view virtual onlySignedPublicKey(publicKey, signature) returns (bytes memory) { - require(owner == msg.sender || spender == msg.sender, "Caller must be owner or spender"); + require(owner == msg.sender || spender == msg.sender); return TFHE.reencrypt(_allowance(owner, spender), publicKey); } // Transfers `encryptedAmount` tokens using the caller's allowance. function transferFrom(address from, address to, bytes calldata encryptedAmount) public virtual returns (bool) { - transferFrom(from, to, TFHE.asEuint64(encryptedAmount)); + transferFrom(from, to, TFHE.asEuint32(encryptedAmount)); return true; } // Transfers `amount` tokens using the caller's allowance. - function transferFrom(address from, address to, euint64 amount) public virtual returns (bool) { + function transferFrom(address from, address to, euint32 amount) public virtual returns (bool) { address spender = msg.sender; - (ebool isTransferable, euint8 errorCode) = _updateAllowance(from, spender, amount); - _transfer(from, to, amount, isTransferable, errorCode); + ebool isTransferable = _updateAllowance(from, spender, amount); + _transfer(from, to, amount, isTransferable); return true; } - function _approve(address owner, address spender, euint64 amount) internal virtual { + function _approve(address owner, address spender, euint32 amount) internal virtual { allowances[owner][spender] = amount; } - function _allowance(address owner, address spender) internal view virtual returns (euint64) { + function _allowance(address owner, address spender) internal view virtual returns (euint32) { if (TFHE.isInitialized(allowances[owner][spender])) { return allowances[owner][spender]; } else { - return TFHE.asEuint64(0); + return TFHE.asEuint32(0); } } - function _updateAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool, euint8) { - euint64 currentAllowance = _allowance(owner, spender); + function _updateAllowance(address owner, address spender, euint32 amount) internal virtual returns (ebool) { + euint32 currentAllowance = _allowance(owner, spender); // makes sure the allowance suffices ebool allowedTransfer = TFHE.le(amount, currentAllowance); - euint8 errorCode = defineErrorIfNot(allowedTransfer, uint8(ErrorCodes.UNSUFFICIENT_APPROVAL)); // makes sure the owner has enough tokens ebool canTransfer = TFHE.le(amount, balances[owner]); ebool isTransferable = TFHE.and(canTransfer, allowedTransfer); _approve(owner, spender, TFHE.cmux(isTransferable, currentAllowance - amount, currentAllowance)); - ebool isNotTransferableButIsApproved = TFHE.and(TFHE.not(canTransfer), allowedTransfer); - errorCode = changeErrorIf( - isNotTransferableButIsApproved, // should indeed check that spender is approved to not leak information - // on balance of `from` to unauthorized spender via calling reencryptTransferError afterwards - uint8(ErrorCodes.UNSUFFICIENT_BALANCE), - errorCode - ); - return (isTransferable, errorCode); + return isTransferable; } // Transfers an encrypted amount. - function _transfer( - address from, - address to, - euint64 amount, - ebool isTransferable, - euint8 errorCode - ) internal virtual { + function _transfer(address from, address to, euint32 amount, ebool isTransferable) internal virtual { // Add to the balance of `to` and subract from the balance of `from`. - euint64 amountTransferred = TFHE.cmux(isTransferable, amount, TFHE.asEuint64(0)); - balances[to] = balances[to] + amountTransferred; - balances[from] = balances[from] - amountTransferred; - uint256 transferId = saveError(errorCode); - emit Transfer(transferId, from, to); - AllowedErrorReencryption memory allowedErrorReencryption = AllowedErrorReencryption( - msg.sender, - getError(transferId) - ); - allowedErrorReencryptions[transferId] = allowedErrorReencryption; - } - - // Returns the error code corresponding to transferId. - // The returned ciphertext is under the caller's `publicKey`. - function reencryptError( - uint256 transferId, - bytes32 publicKey, - bytes calldata signature - ) external view virtual onlySignedPublicKey(publicKey, signature) returns (bytes memory) { - AllowedErrorReencryption memory allowedErrorReencryption = allowedErrorReencryptions[transferId]; - euint8 errorCode = allowedErrorReencryption.errorCode; - require(TFHE.isInitialized(errorCode), "Invalid transferId"); - require(msg.sender == allowedErrorReencryption.spender, "Only spender can reencrypt his error"); - return TFHE.reencrypt(errorCode, publicKey); + balances[to] = balances[to] + TFHE.cmux(isTransferable, amount, TFHE.asEuint32(0)); + balances[from] = balances[from] - TFHE.cmux(isTransferable, amount, TFHE.asEuint32(0)); + emit Transfer(from, to); } } diff --git a/contracts/utils/EncryptedErrors.sol b/contracts/utils/EncryptedErrors.sol deleted file mode 100644 index b076c01..0000000 --- a/contracts/utils/EncryptedErrors.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear - -pragma solidity ^0.8.20; - -import "fhevm/lib/TFHE.sol"; - -/** - * This abstract contract is used for error handling in the fhEVM. - * - * Error codes are trivially encrypted during construction inside the `errorCodes` array. - * - * WARNING: `errorCodes[0]` should always refer to the `NO_ERROR` code, by default. - * - */ -abstract contract EncryptedErrors { - uint8 private immutable totalNumErrors; - euint8[] private errorCodes; - uint256 private counterErrors; // used to keep track of each error index - - // A mapping from errorId to the errorCode - mapping(uint256 => euint8) private errorCodesMapping; - - /** - * @notice Sets the non-null value for `numErrors` corresponding to the total number of errors. - * @param numErrors the total number of different errors. - * @dev `numErrors` must be non-null, note that `errorCodes[0]` corresponds to the `NO_ERROR` code. - */ - constructor(uint8 numErrors) { - require(numErrors != 0, "numErrors must be greater than 0"); - for (uint256 i = 0; i <= numErrors; i++) { - errorCodes.push(TFHE.asEuint8(i)); - } - totalNumErrors = numErrors; - } - - /** - * @notice Returns the encrypted error code at index `indexCode`. - * @param indexCode the index of the requested error code. - * @return the encrypted error code located at `indexCode`. - */ - function getErrorCode(uint8 indexCode) internal view returns (euint8) { - return errorCodes[indexCode]; - } - - /** - * @notice Returns the total number of error codes currently stored in `errorCodesMapping`. - * @return the number of error codes stored in the `errorCodesMapping` mapping. - */ - function getErrorCounter() internal view returns (uint256) { - return counterErrors; - } - - /** - * @notice Returns the total number of the possible errors. - * @return the total number of the different possible errors. - */ - function getNumErrors() internal view returns (uint8) { - return totalNumErrors; - } - - /** - * @notice Returns the encrypted error code which was stored in the mapping at key `errorId`. - * @param errorId the requested key stored in the `errorCodesMapping` mapping. - * @return the encrypted error code located at the `errorId` key. - * @dev `errorId` must be a valid id, i.e below the error counter. - */ - function getError(uint256 errorId) internal view returns (euint8) { - require(errorId < counterErrors, "errorId must be a valid id"); - return errorCodesMapping[errorId]; - } - - /** - * @notice Computes an encrypted error code, result will be either a reencryption of - * `errorCodes[indexCode]` if `condition` is an encrypted `true` or of `NO_ERROR` otherwise. - * @param condition the encrypted boolean used in the cmux. - * @param indexCode the index of the selected error code if `condition` encrypts `true`. - * @return the reencrypted error code depending on `condition` value. - * @dev `indexCode` must be non-null and below the total number of error codes. - */ - function defineErrorIf(ebool condition, uint8 indexCode) internal view returns (euint8) { - require(indexCode != 0, "indexCode must be greater than 0"); - require(indexCode <= totalNumErrors, "indexCode must be a valid error code"); - euint8 errorCode = TFHE.cmux(condition, errorCodes[indexCode], errorCodes[0]); - return errorCode; - } - - /** - * @notice Does the opposite of `defineErrorIf`, i.e result will be either a reencryption of - * `errorCodes[indexCode]` if `condition` is an encrypted `false` or of `NO_ERROR` otherwise. - * @param condition the encrypted boolean used in the cmux. - * @param indexCode the index of the selected error code if `condition` encrypts `false`. - * @return the reencrypted error code depending on `condition` value. - * @dev `indexCode` must be non-null and below the total number of error codes. - */ - function defineErrorIfNot(ebool condition, uint8 indexCode) internal view returns (euint8) { - require(indexCode != 0, "indexCode must be greater than 0"); - require(indexCode <= totalNumErrors, "indexCode must be a valid error code"); - euint8 errorCode = TFHE.cmux(condition, errorCodes[0], errorCodes[indexCode]); - return errorCode; - } - - /** - * @notice Computes an encrypted error code, result will be either a reencryption of - * `errorCodes[indexCode]` if `condition` is an encrypted `true` or of `errorCode` otherwise. - * @param condition the encrypted boolean used in the cmux. - * @param errorCode the selected error code if `condition` encrypts `true`. - * @return the reencrypted error code depending on `condition` value. - * @dev `indexCode` must be below the total number of error codes. - */ - function changeErrorIf(ebool condition, uint8 indexCode, euint8 errorCode) internal view returns (euint8) { - require(indexCode <= totalNumErrors, "indexCode must be a valid error code"); - return TFHE.cmux(condition, errorCodes[indexCode], errorCode); - } - - /** - * @notice Does the opposite of `changeErrorIf`, i.e result will be either a reencryption of - * `errorCodes[indexCode]` if `condition` is an encrypted `false` or of `errorCode` otherwise. - * @param condition the encrypted boolean used in the cmux. - * @param errorCode the selected error code if `condition` encrypts `false`. - * @return the reencrypted error code depending on `condition` value. - * @dev `indexCode` must be below the total number of error codes. - */ - function changeErrorIfNot(ebool condition, uint8 indexCode, euint8 errorCode) internal view returns (euint8) { - require(indexCode <= totalNumErrors, "indexCode must be a valid error code"); - return TFHE.cmux(condition, errorCode, errorCodes[indexCode]); - } - - /** - * @notice Saves `errorCode` in storage, in the `errorCodesMapping` mapping, at the lowest unused key. - * This is the only stateful function of `EncryptedErrors` abstract contract. - * @param errorCode the encrypted error code to be saved in storage. - * @return the `errorId` key in `errorCodesMapping` where `errorCode` is stored. - */ - function saveError(euint8 errorCode) internal returns (uint256) { - uint256 errorId = counterErrors; - counterErrors++; - errorCodesMapping[errorId] = errorCode; - return errorId; - } -} diff --git a/hardhat.config.ts b/hardhat.config.ts index 3a90583..b46d2aa 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -152,7 +152,7 @@ const config: HardhatUserConfig = { tests: "./test", }, solidity: { - version: "0.8.24", + version: "0.8.22", settings: { metadata: { // Not including the metadata hash @@ -163,7 +163,7 @@ const config: HardhatUserConfig = { // https://hardhat.org/hardhat-network/#solidity-optimizer-support optimizer: { enabled: true, - runs: 0, + runs: 800, }, evmVersion: "shanghai", }, diff --git a/test/EncryptedDEX/EncryptedDEX.ts b/test/EncryptedDEX/EncryptedDEX.ts index 1724593..166d296 100644 --- a/test/EncryptedDEX/EncryptedDEX.ts +++ b/test/EncryptedDEX/EncryptedDEX.ts @@ -13,7 +13,6 @@ describe("Private DEX", function () { }); it("Encrypted DEX Pool - multiple epochs", async function () { - const COIN = 2n ** 32n; let token0 = await deployEncryptedERC20Fixture(); let token0Address = await token0.getAddress(); let token1 = await deployEncryptedERC20Fixture(); @@ -23,83 +22,83 @@ describe("Private DEX", function () { token0Address = await token0.getAddress(); token1Address = await token1.getAddress(); - const tx0 = await token0.mint(2_000_000_000n * COIN); + 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 * COIN); + 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 * COIN); + expect(balance).to.equal(2_000_000_000n); const totalSupply = await token0.totalSupply(); - expect(totalSupply).to.equal(2_000_000_000n * COIN); + expect(totalSupply).to.equal(2_000_000_000n); const tx2 = await token0["transfer(address,bytes)"]( this.signers.bob.address, - instances0.alice.encrypt64(100_000_000n * COIN), + instances0.alice.encrypt32(100_000_000), ); await tx2.wait(); balance = await getPrivateBalanceERC20(token0Address, "alice"); - expect(balance).to.equal(1_900_000_000n * COIN); + expect(balance).to.equal(1_900_000_000n); balance = await getPrivateBalanceERC20(token0Address, "bob"); - expect(balance).to.equal(100_000_000n * COIN); + expect(balance).to.equal(100_000_000n); const tx3 = await token0["transfer(address,bytes)"]( this.signers.carol.address, - instances0.alice.encrypt64(200_000_000n * COIN), + instances0.alice.encrypt32(200_000_000), ); await tx3.wait(); const tx4 = await token1["transfer(address,bytes)"]( this.signers.bob.address, - instances1.alice.encrypt64(200_000_000n * COIN), + instances1.alice.encrypt32(200_000_000), ); await tx4.wait(); const tx5 = await token1["transfer(address,bytes)"]( this.signers.carol.address, - instances1.alice.encrypt64(400_000_000n * COIN), + instances1.alice.encrypt32(400_000_000), ); await tx5.wait(); balance = await getPrivateBalanceERC20(token1Address, "carol"); - expect(balance).to.equal(400_000_000n * COIN); + 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")) / COIN); - console.log("Bob's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "bob")) / COIN); - console.log("Carol's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "carol")) / COIN); - console.log("Carol's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "carol")) / COIN); + 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.encrypt64(1_000_000n * COIN), + instances0.alice.encrypt32(1_000_000), ); await tx6.wait(); const tx6bis = await token1["transfer(address,bytes)"]( this.signers.dave.address, - instances1.alice.encrypt64(0n * COIN), // to obfuscate the direction of the swap later, needed to initialize dave's token1 balance + 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.encrypt64(1_000_000n * COIN), + instances1.alice.encrypt32(1_000_000), ); await tx7.wait(); const tx7bis = await token0["transfer(address,bytes)"]( this.signers.eve.address, - instances0.alice.encrypt64(0n * COIN), // to obfuscate the direction of the swap later, needed to initialize eve's token0 balance + 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")) / COIN); - console.log("Dave's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "dave")) / COIN); - console.log("Eve's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "eve")) / COIN); - console.log("Eve's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "eve")) / COIN, "\n"); + 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); @@ -113,18 +112,18 @@ describe("Private DEX", function () { const tx9 = await token0 .connect(this.signers.bob) - ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt64(100_000_000n * COIN)); + ["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.encrypt64(200_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(200_000_000)); await tx10.wait(); const tx11 = await pair .connect(this.signers.bob) .addLiquidity( - instancesPair.bob.encrypt64(100_000_000n * COIN), - instancesPair.bob.encrypt64(200_000_000n * COIN), + instancesPair.bob.encrypt32(100_000_000), + instancesPair.bob.encrypt32(200_000_000), this.signers.bob.address, 0n, ); @@ -133,17 +132,17 @@ describe("Private DEX", function () { const tx12 = await token0 .connect(this.signers.carol) - ["approve(address,bytes)"](pairAddress, instances0.carol.encrypt64(200_000_000n * COIN)); + ["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.encrypt64(400_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances0.carol.encrypt32(400_000_000)); await tx13.wait(); const tx14 = await pair .connect(this.signers.carol) .addLiquidity( - instancesPair.carol.encrypt64(200_000_000n * COIN), - instancesPair.carol.encrypt64(400_000_000n * COIN), + instancesPair.carol.encrypt32(200_000_000), + instancesPair.carol.encrypt32(400_000_000), this.signers.carol.address, 0n, ); @@ -159,31 +158,31 @@ describe("Private DEX", function () { ); console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); let [reserve0, reserve1] = await pair.getReserves(); - console.log("Reserve token0 ", reserve0 / COIN); - console.log("Reserve token1 ", reserve1 / COIN, "\n"); + 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 / COIN, " liquidity tokens"); + 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 / COIN, " liquidity tokens \n"); + 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.encrypt64(100_000_000n * COIN)); + ["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.encrypt64(100_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(100_000_000)); await tx19.wait(); const tx20 = await pair .connect(this.signers.dave) .swapTokens( - instancesPair.dave.encrypt64(1_000_000n * COIN), - instancesPair.dave.encrypt64(0n * COIN), + instancesPair.dave.encrypt32(1_000_000), + instancesPair.dave.encrypt32(0), this.signers.dave.address, 1n, ); @@ -192,20 +191,15 @@ describe("Private DEX", function () { const tx21 = await token0 .connect(this.signers.eve) - ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt64(100_000_000n * COIN)); + ["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.encrypt64(100_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(100_000_000)); await tx22.wait(); const tx23 = await pair .connect(this.signers.eve) - .swapTokens( - instancesPair.eve.encrypt64(0n * COIN), - instancesPair.eve.encrypt64(1_000_000n * COIN), - this.signers.eve.address, - 1n, - ); + .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"); @@ -218,8 +212,8 @@ describe("Private DEX", function () { ); console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); [reserve0, reserve1] = await pair.getReserves(); - console.log("Reserve token0 ", reserve0 / COIN); - console.log("Reserve token1 ", reserve1 / COIN, "\n"); + console.log("Reserve token0 ", reserve0); + console.log("Reserve token1 ", reserve1, "\n"); const tx25 = await pair.claimSwap(1n, this.signers.dave.address); await tx25.wait(); @@ -227,27 +221,27 @@ describe("Private DEX", function () { await tx26.wait(); console.log("New balances of traders (Dave and Eve) : "); - console.log("Dave's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "dave")) / COIN); - console.log("Dave's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "dave")) / COIN); - console.log("Eve's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "eve")) / COIN); - console.log("Eve's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "eve")) / COIN, "\n"); + 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.encrypt64((149999900n / 2n) * COIN), this.signers.bob.address, 2n); + .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.encrypt64((299999900n / 2n) * COIN), this.signers.carol.address, 2n); + .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 / COIN, " liquidity tokens"); + 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 / COIN, " liquidity tokens \n"); + console.log("Carol now owns a private balance of ", balance, " liquidity tokens \n"); const tx29 = await pair.batchSettlement({ gasLimit: 10_000_000 }); await tx29.wait(); @@ -258,8 +252,8 @@ describe("Private DEX", function () { ); console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); [reserve0, reserve1] = await pair.getReserves(); - console.log("Reserve token0 ", reserve0 / COIN); - console.log("Reserve token1 ", reserve1 / COIN, "\n"); + console.log("Reserve token0 ", reserve0); + console.log("Reserve token1 ", reserve1, "\n"); const tx30 = await pair.claimBurn(2n, this.signers.bob.address); await tx30.wait(); @@ -267,14 +261,13 @@ describe("Private DEX", function () { await tx31.wait(); console.log("New balances of market makers (Bob and Carol) : "); - console.log("Bob's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "bob")) / COIN); - console.log("Bob's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "bob")) / COIN); - console.log("Carol's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "carol")) / COIN); - console.log("Carol's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "carol")) / COIN); + 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")); }); it("Encrypted DEX Pool - single epoch (addLiquidity+swap)", async function () { - const COIN = 2n ** 32n; let token0 = await deployEncryptedERC20Fixture(); let token0Address = await token0.getAddress(); let token1 = await deployEncryptedERC20Fixture(); @@ -284,83 +277,83 @@ describe("Private DEX", function () { token0Address = await token0.getAddress(); token1Address = await token1.getAddress(); - const tx0 = await token0.mint(2_000_000_000n * COIN); + 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 * COIN); + 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 * COIN); + expect(balance).to.equal(2_000_000_000n); const totalSupply = await token0.totalSupply(); - expect(totalSupply).to.equal(2_000_000_000n * COIN); + expect(totalSupply).to.equal(2_000_000_000n); const tx2 = await token0["transfer(address,bytes)"]( this.signers.bob.address, - instances0.alice.encrypt64(100_000_000n * COIN), + instances0.alice.encrypt32(100_000_000), ); await tx2.wait(); balance = await getPrivateBalanceERC20(token0Address, "alice"); - expect(balance).to.equal(1_900_000_000n * COIN); + expect(balance).to.equal(1_900_000_000n); balance = await getPrivateBalanceERC20(token0Address, "bob"); - expect(balance).to.equal(100_000_000n * COIN); + expect(balance).to.equal(100_000_000n); const tx3 = await token0["transfer(address,bytes)"]( this.signers.carol.address, - instances0.alice.encrypt64(200_000_000n * COIN), + instances0.alice.encrypt32(200_000_000), ); await tx3.wait(); const tx4 = await token1["transfer(address,bytes)"]( this.signers.bob.address, - instances1.alice.encrypt64(200_000_000n * COIN), + instances1.alice.encrypt32(200_000_000), ); await tx4.wait(); const tx5 = await token1["transfer(address,bytes)"]( this.signers.carol.address, - instances1.alice.encrypt64(400_000_000n * COIN), + instances1.alice.encrypt32(400_000_000), ); await tx5.wait(); balance = await getPrivateBalanceERC20(token1Address, "carol"); - expect(balance).to.equal(400_000_000n * COIN); + 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")) / COIN); - console.log("Bob's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "bob")) / COIN); - console.log("Carol's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "carol")) / COIN); - console.log("Carol's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "carol")) / COIN); + 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.encrypt64(1_000_000n * COIN), + instances0.alice.encrypt32(1_000_000), ); await tx6.wait(); const tx6bis = await token1["transfer(address,bytes)"]( this.signers.dave.address, - instances1.alice.encrypt64(0n * COIN), // to obfuscate the direction of the swap later, needed to initialize dave's token1 balance + 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.encrypt64(1_000_000n * COIN), + instances1.alice.encrypt32(1_000_000), ); await tx7.wait(); const tx7bis = await token0["transfer(address,bytes)"]( this.signers.eve.address, - instances0.alice.encrypt64(0n * COIN), // to obfuscate the direction of the swap later, needed to initialize eve's token0 balance + 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")) / COIN); - console.log("Dave's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "dave")) / COIN); - console.log("Eve's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "eve")) / COIN); - console.log("Eve's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "eve")) / COIN, "\n"); + 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); @@ -374,18 +367,18 @@ describe("Private DEX", function () { const tx9 = await token0 .connect(this.signers.bob) - ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt64(100_000_000n * COIN)); + ["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.encrypt64(200_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(200_000_000)); await tx10.wait(); const tx11 = await pair .connect(this.signers.bob) .addLiquidity( - instancesPair.bob.encrypt64(100_000_000n * COIN), - instancesPair.bob.encrypt64(200_000_000n * COIN), + instancesPair.bob.encrypt32(100_000_000), + instancesPair.bob.encrypt32(200_000_000), this.signers.bob.address, 0n, ); @@ -394,17 +387,17 @@ describe("Private DEX", function () { const tx12 = await token0 .connect(this.signers.carol) - ["approve(address,bytes)"](pairAddress, instances0.carol.encrypt64(200_000_000n * COIN)); + ["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.encrypt64(400_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances0.carol.encrypt32(400_000_000)); await tx13.wait(); const tx14 = await pair .connect(this.signers.carol) .addLiquidity( - instancesPair.carol.encrypt64(200_000_000n * COIN), - instancesPair.carol.encrypt64(400_000_000n * COIN), + instancesPair.carol.encrypt32(200_000_000), + instancesPair.carol.encrypt32(400_000_000), this.signers.carol.address, 0n, ); @@ -413,17 +406,17 @@ describe("Private DEX", function () { const tx18 = await token0 .connect(this.signers.dave) - ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt64(100_000_000n * COIN)); + ["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.encrypt64(100_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(100_000_000)); await tx19.wait(); const tx20 = await pair .connect(this.signers.dave) .swapTokens( - instancesPair.dave.encrypt64(1_000_000n * COIN), - instancesPair.dave.encrypt64(0n * COIN), + instancesPair.dave.encrypt32(1_000_000), + instancesPair.dave.encrypt32(0), this.signers.dave.address, 1n, ); @@ -432,20 +425,15 @@ describe("Private DEX", function () { const tx21 = await token0 .connect(this.signers.eve) - ["approve(address,bytes)"](pairAddress, instances0.bob.encrypt64(100_000_000n * COIN)); + ["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.encrypt64(100_000_000n * COIN)); + ["approve(address,bytes)"](pairAddress, instances1.bob.encrypt32(100_000_000)); await tx22.wait(); const tx23 = await pair .connect(this.signers.eve) - .swapTokens( - instancesPair.eve.encrypt64(0n * COIN), - instancesPair.eve.encrypt64(1_000_000n * COIN), - this.signers.eve.address, - 1n, - ); + .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"); @@ -458,8 +446,8 @@ describe("Private DEX", function () { ); console.log("New reserves for tradingEpoch ", await pair.currentTradingEpoch(), " are now publicly revealed : "); let [reserve0, reserve1] = await pair.getReserves(); - console.log("Reserve token0 ", reserve0 / COIN); - console.log("Reserve token1 ", reserve1 / COIN, "\n"); + console.log("Reserve token0 ", reserve0); + console.log("Reserve token1 ", reserve1, "\n"); const tx25 = await pair.claimSwap(0n, this.signers.dave.address); await tx25.wait(); @@ -471,14 +459,14 @@ describe("Private DEX", function () { 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 / COIN, " liquidity tokens"); + 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 / COIN, " liquidity tokens \n"); + console.log("Carol now owns a private balance of ", balance, " liquidity tokens \n"); console.log("New balances of traders (Dave and Eve) : "); - console.log("Dave's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "dave")) / COIN); - console.log("Dave's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "dave")) / COIN); - console.log("Eve's token0 balance : ", (await getPrivateBalanceERC20(token0Address, "eve")) / COIN); - console.log("Eve's token1 balance : ", (await getPrivateBalanceERC20(token1Address, "eve")) / COIN, "\n"); + 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"); }); });