diff --git a/contracts/SwapHelperLib.sol b/contracts/SwapHelperLib.sol index 8d7fd53..7c5d2b2 100644 --- a/contracts/SwapHelperLib.sol +++ b/contracts/SwapHelperLib.sol @@ -5,6 +5,7 @@ import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IZRC20.sol"; import "@zetachain/protocol-contracts/contracts/zevm/SystemContract.sol"; +import "./shared/libraries/UniswapV2Library.sol"; library SwapHelperLib { uint16 internal constant MAX_DEADLINE = 200; @@ -17,6 +18,8 @@ library SwapHelperLib { error CantBeZeroAddress(); + error InvalidPathLength(); + // returns sorted token addresses, used to handle return values from pairs sorted in this order function sortTokens( address tokenA, @@ -85,6 +88,25 @@ library SwapHelperLib { IZRC20(zrc20B).balanceOf(uniswapPool) > 0; } + function _isSufficientLiquidity( + address uniswapV2Factory, + uint256 amountIn, + uint256 minAmountOut, + address[] memory path + ) internal view returns (bool) { + if (path.length != 2) revert InvalidPathLength(); + bool existsPairPool = _existsPairPool( + uniswapV2Factory, + path[0], + path[1] + ); + if (!existsPairPool) { + return false; + } + uint256[] memory amounts = UniswapV2Library.getAmountsOut(uniswapV2Factory, amountIn, path); + return amounts[amounts.length - 1] >= minAmountOut; + } + function swapExactTokensForTokens( SystemContract systemContract, address zrc20, @@ -92,6 +114,51 @@ library SwapHelperLib { address targetZRC20, uint256 minAmountOut ) internal returns (uint256) { + + address[] memory path; + path = new address[](2); + path[0] = zrc20; + path[1] = targetZRC20; + + bool isSufficientLiquidity = _isSufficientLiquidity( + systemContract.uniswapv2FactoryAddress(), + amount, + minAmountOut, + path + ); + + bool isZETA = targetZRC20 == systemContract.wZetaContractAddress() || zrc20 == systemContract.wZetaContractAddress(); + + if (!isSufficientLiquidity && !isZETA) { + path = new address[](3); + path[0] = zrc20; + path[1] = systemContract.wZetaContractAddress(); + path[2] = targetZRC20; + } + + IZRC20(zrc20).approve( + address(systemContract.uniswapv2Router02Address()), + amount + ); + uint256[] memory amounts = IUniswapV2Router01( + systemContract.uniswapv2Router02Address() + ).swapExactTokensForTokens( + amount, + minAmountOut, + path, + address(this), + block.timestamp + MAX_DEADLINE + ); + return amounts[path.length - 1]; + } + + function swapExactTokensForTokensDirectly( + SystemContract systemContract, + address zrc20, + uint256 amount, + address targetZRC20, + uint256 minAmountOut + ) internal returns (uint256) { bool existsPairPool = _existsPairPool( systemContract.uniswapv2FactoryAddress(), zrc20, @@ -166,4 +233,21 @@ library SwapHelperLib { ); return amounts[0]; } + + function getMinOutAmount(SystemContract systemContract, address zrc20, address target, uint256 amountIn) public view returns (uint256 minOutAmount) { + address[] memory path; + + path = new address[](2); + path[0] = zrc20; + path[1] = target; + uint[] memory amounts1 = UniswapV2Library.getAmountsOut(systemContract.uniswapv2FactoryAddress(), amountIn, path); + + path = new address[](3); + path[0] = zrc20; + path[1] = systemContract.wZetaContractAddress(); + path[2] = target; + uint[] memory amounts2 = UniswapV2Library.getAmountsOut(systemContract.uniswapv2FactoryAddress(), amountIn, path); + + minOutAmount = amounts1[amounts1.length - 1] > amounts2[amounts2.length - 1] ? amounts1[amounts1.length - 1] : amounts2[amounts2.length - 1]; + } } diff --git a/typechain-types/contracts/SwapHelperLib.ts b/typechain-types/contracts/SwapHelperLib.ts index e35f438..4cc0706 100644 --- a/typechain-types/contracts/SwapHelperLib.ts +++ b/typechain-types/contracts/SwapHelperLib.ts @@ -4,6 +4,7 @@ import type { BaseContract, BigNumber, + BigNumberish, BytesLike, CallOverrides, PopulatedTransaction, @@ -22,11 +23,23 @@ import type { export interface SwapHelperLibInterface extends utils.Interface { functions: { + "getMinOutAmount(SystemContract,address,address,uint256)": FunctionFragment; "uniswapv2PairFor(address,address,address)": FunctionFragment; }; - getFunction(nameOrSignatureOrTopic: "uniswapv2PairFor"): FunctionFragment; + getFunction( + nameOrSignatureOrTopic: "getMinOutAmount" | "uniswapv2PairFor" + ): FunctionFragment; + encodeFunctionData( + functionFragment: "getMinOutAmount", + values: [ + PromiseOrValue, + PromiseOrValue, + PromiseOrValue, + PromiseOrValue + ] + ): string; encodeFunctionData( functionFragment: "uniswapv2PairFor", values: [ @@ -36,6 +49,10 @@ export interface SwapHelperLibInterface extends utils.Interface { ] ): string; + decodeFunctionResult( + functionFragment: "getMinOutAmount", + data: BytesLike + ): Result; decodeFunctionResult( functionFragment: "uniswapv2PairFor", data: BytesLike @@ -71,6 +88,14 @@ export interface SwapHelperLib extends BaseContract { removeListener: OnEvent; functions: { + getMinOutAmount( + systemContract: PromiseOrValue, + zrc20: PromiseOrValue, + target: PromiseOrValue, + amountIn: PromiseOrValue, + overrides?: CallOverrides + ): Promise<[BigNumber] & { minOutAmount: BigNumber }>; + uniswapv2PairFor( factory: PromiseOrValue, tokenA: PromiseOrValue, @@ -79,6 +104,14 @@ export interface SwapHelperLib extends BaseContract { ): Promise<[string] & { pair: string }>; }; + getMinOutAmount( + systemContract: PromiseOrValue, + zrc20: PromiseOrValue, + target: PromiseOrValue, + amountIn: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + uniswapv2PairFor( factory: PromiseOrValue, tokenA: PromiseOrValue, @@ -87,6 +120,14 @@ export interface SwapHelperLib extends BaseContract { ): Promise; callStatic: { + getMinOutAmount( + systemContract: PromiseOrValue, + zrc20: PromiseOrValue, + target: PromiseOrValue, + amountIn: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + uniswapv2PairFor( factory: PromiseOrValue, tokenA: PromiseOrValue, @@ -98,6 +139,14 @@ export interface SwapHelperLib extends BaseContract { filters: {}; estimateGas: { + getMinOutAmount( + systemContract: PromiseOrValue, + zrc20: PromiseOrValue, + target: PromiseOrValue, + amountIn: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + uniswapv2PairFor( factory: PromiseOrValue, tokenA: PromiseOrValue, @@ -107,6 +156,14 @@ export interface SwapHelperLib extends BaseContract { }; populateTransaction: { + getMinOutAmount( + systemContract: PromiseOrValue, + zrc20: PromiseOrValue, + target: PromiseOrValue, + amountIn: PromiseOrValue, + overrides?: CallOverrides + ): Promise; + uniswapv2PairFor( factory: PromiseOrValue, tokenA: PromiseOrValue, diff --git a/typechain-types/factories/contracts/SwapHelperLib__factory.ts b/typechain-types/factories/contracts/SwapHelperLib__factory.ts index 2f0f5fb..006b1bc 100644 --- a/typechain-types/factories/contracts/SwapHelperLib__factory.ts +++ b/typechain-types/factories/contracts/SwapHelperLib__factory.ts @@ -10,6 +10,11 @@ import type { } from "../../contracts/SwapHelperLib"; const _abi = [ + { + inputs: [], + name: "AdditionsOverflow", + type: "error", + }, { inputs: [], name: "CantBeIdenticalAddresses", @@ -20,6 +25,36 @@ const _abi = [ name: "CantBeZeroAddress", type: "error", }, + { + inputs: [], + name: "IdenticalAddresses", + type: "error", + }, + { + inputs: [], + name: "InsufficientInputAmount", + type: "error", + }, + { + inputs: [], + name: "InsufficientLiquidity", + type: "error", + }, + { + inputs: [], + name: "InvalidPath", + type: "error", + }, + { + inputs: [], + name: "InvalidPathLength", + type: "error", + }, + { + inputs: [], + name: "MultiplicationsOverflow", + type: "error", + }, { inputs: [], name: "NotEnoughToPayGasFee", @@ -30,6 +65,45 @@ const _abi = [ name: "WrongGasContract", type: "error", }, + { + inputs: [], + name: "ZeroAddress", + type: "error", + }, + { + inputs: [ + { + internalType: "contract SystemContract", + name: "systemContract", + type: "SystemContract", + }, + { + internalType: "address", + name: "zrc20", + type: "address", + }, + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint256", + name: "amountIn", + type: "uint256", + }, + ], + name: "getMinOutAmount", + outputs: [ + { + internalType: "uint256", + name: "minOutAmount", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [ { @@ -62,7 +136,7 @@ const _abi = [ ] as const; const _bytecode = - "0x610492610053600b82828239805160001a607314610046577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063c63585cc1461003a575b600080fd5b610054600480360381019061004f919061020d565b61006a565b6040516100619190610351565b60405180910390f35b600080600061007985856100dc565b915091508582826040516020016100919291906102e3565b604051602081830303815290604052805190602001206040516020016100b892919061030f565b6040516020818303038152906040528051906020012060001c925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415610145576040517fcb1e7cfe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161061017f578284610182565b83835b8092508193505050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156101f1576040517f78b507da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9250929050565b60008135905061020781610445565b92915050565b600080600060608486031215610226576102256103e1565b5b6000610234868287016101f8565b9350506020610245868287016101f8565b9250506040610256868287016101f8565b9150509250925092565b61026981610377565b82525050565b61028061027b82610377565b6103b3565b82525050565b61029761029282610389565b6103c5565b82525050565b60006102aa60208361036c565b91506102b5826103f3565b602082019050919050565b60006102cd60018361036c565b91506102d88261041c565b600182019050919050565b60006102ef828561026f565b6014820191506102ff828461026f565b6014820191508190509392505050565b600061031a826102c0565b9150610326828561026f565b6014820191506103368284610286565b6020820191506103458261029d565b91508190509392505050565b60006020820190506103666000830184610260565b92915050565b600081905092915050565b600061038282610393565b9050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103be826103cf565b9050919050565b6000819050919050565b60006103da826103e6565b9050919050565b600080fd5b60008160601b9050919050565b7f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f600082015250565b7fff00000000000000000000000000000000000000000000000000000000000000600082015250565b61044e81610377565b811461045957600080fd5b5056fea2646970667358221220ecd86d07164105c72752ddee1f025ed5b5a1f55fabb9a16df5693cec1df5179a64736f6c63430008070033"; + "0x6113cb610053600b82828239805160001a607314610046577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100405760003560e01c8063b864987914610045578063c63585cc14610075575b600080fd5b61005f600480360381019061005a9190610e1b565b6100a5565b60405161006c9190611028565b60405180910390f35b61008f600480360381019061008a9190610dc8565b610519565b60405161009c9190610fe4565b60405180910390f35b60006060600267ffffffffffffffff8111156100c4576100c361128f565b5b6040519080825280602002602001820160405280156100f25781602001602082028036833780820191505090505b509050848160008151811061010a57610109611260565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050838160018151811061015957610158611260565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600061021d8773ffffffffffffffffffffffffffffffffffffffff1663d936a0126040518163ffffffff1660e01b815260040160206040518083038186803b1580156101de57600080fd5b505afa1580156101f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102169190610d9b565b858461058b565b9050600367ffffffffffffffff81111561023a5761023961128f565b5b6040519080825280602002602001820160405280156102685781602001602082028036833780820191505090505b50915085826000815181106102805761027f611260565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508673ffffffffffffffffffffffffffffffffffffffff1663569541b96040518163ffffffff1660e01b815260040160206040518083038186803b15801561030057600080fd5b505afa158015610314573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103389190610d9b565b8260018151811061034c5761034b611260565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050848260028151811061039b5761039a611260565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600061045f8873ffffffffffffffffffffffffffffffffffffffff1663d936a0126040518163ffffffff1660e01b815260040160206040518083038186803b15801561042057600080fd5b505afa158015610434573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104589190610d9b565b868561058b565b9050806001825161047091906110d5565b8151811061048157610480611260565b5b6020026020010151826001845161049891906110d5565b815181106104a9576104a8611260565b5b6020026020010151116104e35780600182516104c591906110d5565b815181106104d6576104d5611260565b5b602002602001015161050c565b81600183516104f291906110d5565b8151811061050357610502611260565b5b60200260200101515b9350505050949350505050565b6000806000610528858561070d565b91509150858282604051602001610540929190610f76565b60405160208183030381529060405280519060200120604051602001610567929190610fa2565b6040516020818303038152906040528051906020012060001c925050509392505050565b60606002825110156105c9576040517f20db826700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815167ffffffffffffffff8111156105e4576105e361128f565b5b6040519080825280602002602001820160405280156106125781602001602082028036833780820191505090505b509050828160008151811061062a57610629611260565b5b60200260200101818152505060005b6001835161064791906110d5565b8110156107055760008061069c8786858151811061066857610667611260565b5b60200260200101518760018761067e919061104e565b8151811061068f5761068e611260565b5b6020026020010151610829565b915091506106c58484815181106106b6576106b5611260565b5b6020026020010151838361093b565b846001856106d3919061104e565b815181106106e4576106e3611260565b5b602002602001018181525050505080806106fd9061118b565b915050610639565b509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415610776576040517fcb1e7cfe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16106107b05782846107b3565b83835b8092508193505050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610822576040517f78b507da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9250929050565b60008060006108388585610a30565b509050600080610849888888610b4c565b73ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561088e57600080fd5b505afa1580156108a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c69190610e82565b506dffffffffffffffffffffffffffff1691506dffffffffffffffffffffffffffff1691508273ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610925578082610928565b81815b8095508196505050505050935093915050565b600080841415610977576040517f098fb56100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008314806109865750600082145b156109bd576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109d46103e586610be290919063ffffffff16565b905060006109eb8483610be290919063ffffffff16565b90506000610a1683610a086103e889610be290919063ffffffff16565b610c3e90919063ffffffff16565b90508082610a2491906110a4565b93505050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415610a99576040517fbd969eb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1610610ad3578284610ad6565b83835b8092508193505050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610b45576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9250929050565b60008373ffffffffffffffffffffffffffffffffffffffff1663e6a4390584846040518363ffffffff1660e01b8152600401610b89929190610fff565b60206040518083038186803b158015610ba157600080fd5b505afa158015610bb5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd99190610d9b565b90509392505050565b600080610bf88385610c9a90919063ffffffff16565b809350819250505080610c37576040517f5797276a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5092915050565b600080610c548385610cee90919063ffffffff16565b809350819250505080610c93576040517fa259879500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5092915050565b6000806000841415610cb3576001600091509150610ce7565b6000838502905083858281610ccb57610cca611231565b5b0414610cde576000809250925050610ce7565b60018192509250505b9250929050565b6000806000838501905084811015610d0d576000809250925050610d16565b60018192509250505b9250929050565b600081359050610d2c81611322565b92915050565b600081519050610d4181611322565b92915050565b600081359050610d5681611339565b92915050565b600081519050610d6b81611350565b92915050565b600081359050610d8081611367565b92915050565b600081519050610d958161137e565b92915050565b600060208284031215610db157610db06112be565b5b6000610dbf84828501610d32565b91505092915050565b600080600060608486031215610de157610de06112be565b5b6000610def86828701610d1d565b9350506020610e0086828701610d1d565b9250506040610e1186828701610d1d565b9150509250925092565b60008060008060808587031215610e3557610e346112be565b5b6000610e4387828801610d47565b9450506020610e5487828801610d1d565b9350506040610e6587828801610d1d565b9250506060610e7687828801610d71565b91505092959194509250565b600080600060608486031215610e9b57610e9a6112be565b5b6000610ea986828701610d5c565b9350506020610eba86828701610d5c565b9250506040610ecb86828701610d86565b9150509250925092565b610ede81611109565b82525050565b610eed81611109565b82525050565b610f04610eff82611109565b6111d4565b82525050565b610f1b610f168261111b565b6111e6565b82525050565b6000610f2e602083611043565b9150610f39826112d0565b602082019050919050565b6000610f51600183611043565b9150610f5c826112f9565b600182019050919050565b610f7081611171565b82525050565b6000610f828285610ef3565b601482019150610f928284610ef3565b6014820191508190509392505050565b6000610fad82610f44565b9150610fb98285610ef3565b601482019150610fc98284610f0a565b602082019150610fd882610f21565b91508190509392505050565b6000602082019050610ff96000830184610ee4565b92915050565b60006040820190506110146000830185610ed5565b6110216020830184610ed5565b9392505050565b600060208201905061103d6000830184610f67565b92915050565b600081905092915050565b600061105982611171565b915061106483611171565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561109957611098611202565b5b828201905092915050565b60006110af82611171565b91506110ba83611171565b9250826110ca576110c9611231565b5b828204905092915050565b60006110e082611171565b91506110eb83611171565b9250828210156110fe576110fd611202565b5b828203905092915050565b600061111482611151565b9050919050565b6000819050919050565b600061113082611109565b9050919050565b60006dffffffffffffffffffffffffffff82169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600061119682611171565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156111c9576111c8611202565b5b600182019050919050565b60006111df826111f0565b9050919050565b6000819050919050565b60006111fb826112c3565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b60008160601b9050919050565b7f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f600082015250565b7fff00000000000000000000000000000000000000000000000000000000000000600082015250565b61132b81611109565b811461133657600080fd5b50565b61134281611125565b811461134d57600080fd5b50565b61135981611137565b811461136457600080fd5b50565b61137081611171565b811461137b57600080fd5b50565b6113878161117b565b811461139257600080fd5b5056fea26469706673582212200cfb8bfcd1e1ce90ae3341161db50ec139df6b1f755b577055e02710b74062a864736f6c63430008070033"; type SwapHelperLibConstructorParams = | [signer?: Signer]