## Strategy 3.0 Business Logic ![](https://hackmd.io/_uploads/r1G5GZ94h.png) *Token and Pool Reference* | token pair | tick spacing | fee | token0 (decimal) | token1 (decimal) | pool address | |:---------- |:------------:|:-----:|:----------------:|:----------------:|:-------------------------------------------------------------------------------------------------------------------- | | USDT-WBNB | 10 | 0.05% | USDT(18) | WBNB(18) | [0x36696169C63e42cd08ce11f5deeBbCeBae652050](https://bscscan.com/address/0x36696169C63e42cd08ce11f5deeBbCeBae652050) | | USDT-BTCB | 10 | 0.05% | USDT(18) | BTCB(18) | [0x46Cf1cF8c69595804ba91dFdd8d6b960c9B0a7C4](https://bscscan.com/address/0x46Cf1cF8c69595804ba91dFdd8d6b960c9B0a7C4) | | WBNB-BUSD | 10 | 0.05% | WBNB(18) | BUSD(18) | [0x85FAac652b707FDf6907EF726751087F9E0b6687](https://bscscan.com/address/0x85FAac652b707FDf6907EF726751087F9E0b6687) | | BTCB-BUSD | 10 | 0.05% | BTCB(18) | BUSD(18) | [0x369482C78baD380a036cAB827fE677C1903d1523](https://bscscan.com/address/0x369482C78baD380a036cAB827fE677C1903d1523) | | ETH-USDC | 10 | 0.05% | ETH(18) | USDC(18) | [0x539e0EBfffd39e54A0f7E5F8FEc40ade7933A664](https://bscscan.com/address/0x539e0EBfffd39e54A0f7E5F8FEc40ade7933A664) | | BTCB-WBNB | 10 | 0.05% | BTCB(18) | WBNB(18) | [0x6bbc40579ad1BBD243895cA0ACB086BB6300d636](https://bscscan.com/address/0x6bbc40579ad1BBD243895cA0ACB086BB6300d636) | | ETH-WBNB | 10 | 0.05% | ETH(18) | WBNB(18) | [0xD0e226f674bBf064f54aB47F42473fF80DB98CBA](https://bscscan.com/address/0xD0e226f674bBf064f54aB47F42473fF80DB98CBA) | | ETH-BTCB | 10 | 0.05% | ETH(18) | BTCB(18) | [0x4BBA1018b967e59220b22Ca03f68821A3276c9a6](https://bscscan.com/address/0x4BBA1018b967e59220b22Ca03f68821A3276c9a6) | | BTCB-WBNB | 50 | 0.25% | BTCB(18) | WBNB(18) | [0xFC75f4E78bf71eD5066dB9ca771D4CcB7C1264E0](https://bscscan.com/address/0xFC75f4E78bf71eD5066dB9ca771D4CcB7C1264E0) | | ETH-WBNB | 50 | 0.25% | ETH(18) | WBNB(18) | [0x7d05c84581f0C41AD80ddf677A510360bae09a5A](https://bscscan.com/address/0x7d05c84581f0C41AD80ddf677A510360bae09a5A) | | ETH-BTCB | 50 | 0.25% | ETH(18) | BTCB(18) | [0xD4dCA84E1808da3354924cD243c66828cf775470](https://bscscan.com/address/0xD4dCA84E1808da3354924cD243c66828cf775470) | *USDT token address: [0x55d398326f99059fF775485246999027B3197955](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) WBNB token address: [0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c](https://bscscan.com/address/0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c) BTCB token address: [0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c](https://bscscan.com/address/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c) BUSD token address: [0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56](https://bscscan.com/address/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56) ETH token address: [0x2170Ed0880ac9A755fd29B2688956BD959F933F8](https://bscscan.com/address/0x2170Ed0880ac9A755fd29B2688956BD959F933F8) USDC token address: [0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d)* ### ==Basic Concept== * each strategy contract manages only **one pair token & pool fee** (correspond to a PancakeswapV3Pool contract) * each strategy contract contains only **one liquidity NFT available** at a time * use variable "**tickSpread**" to set tickUpper and tickLower automatically * each strategy contract contains an **ERC20 tracker token** * [optional] strategy deployer contract ### ==Share Management== * **share** definition * share represents the **liquidity user-provided** (liquidity conversion refer to next section) * when **deposit**, * user's share increase (based on increased liquidity conversion) * when **withdraw**, * user's share decrease (based on user provided decreased amount) * **valid-user** definition * valid-user has the **amount of shares more than 0** * valid-user's address is stored in the userList * valid-user are eligible to **receive usdt reward distribution** (based to share ratio) * **tracker token** * when **deposit**, * user **receives tracker token** in the amount of **increased share** from strategy contract * when **withdraw**, * user **provides tracker token** in the amount of **decreased share** to strategy contract * Between deposit and withdraw, * user can invest their tracker token * [Next Version] add tracker token staking contract and define RXD reward mechanism * Tracker Token Name format: `RemixDAO {token0 symbol}-{token1 symbol}-{strategy pool fee}` * Tracker Token Symbol format: `RD {token0 symbol}-{token1 symbol}-{strategy pool fee}` ### ==Share Ratio & Liquidity Conversion== * **user share ratio** * user share ratio = user share / total share * **conversion of share & liquidity** * share to liquidity unit = total share / total liquidity * despoit share = increased liquidity * share to liquidity unit * withdraw liquidity = decreased share / share to liquidity unit :::spoiler *Share & Liquidity Conversion Formula Explanation* #### ==[Elements & Basic Formula]== ``` user liquidity: UL total liquidity: TL user share: US total share: TS constant shareToLiquidityRatio: R US/TS = UL/TL constant R = TS/TL ``` #### ==[Deposit Formula]== Before increase > total liquidity = TL > total share = TS After increase n liquidity > increased liquidity = n > total liquidity = (TL+n) > increased share = ? > total share = (TS+?) = (TL*R+?) ``` n/(TL+n) = ?/(TL*R+?) n*(TL*R+?) = ?*(TL+n) n*TL*R + n*? = TL*? + n*? n*TL*R = TL*? ? = n*R ``` #### ==[Withdraw Formula]== Before decrease > total liquidity = TL > total share = TS After decrease n share > decreased liquidity = ? > total liquidity = (TL-?) > decreased share = n > total share = (TS-n) = (TL*R-n) ``` ?/(TL-?) = n/(TL*R-n) ?*(TL*R-n) = n*(TL-?) TL*R*? - n*? = TL*n - n*? ? = n/R ``` #### ==[Rescale Formula]== Before rescale ``` total share = TS user share = US total liquidity = TL shareToLiquidityRatio(R) = TS/TL user liquidity = UL = TL * (US/TS) = US/R ``` After rescale ``` total share = TS user share = US total liquidity = TLA shareToLiquidityRatio(RA) = TS/TLA user liquidity = ULA = TLA * (US/TS) = US/RA ``` ::: ### ==Reward Token Management== * **withdraw** * collect **trade fee rewards** * store **trade fee rewards** in the contract temporarily * **no distribution** * **earn** * collect **trade fee rewards** and **staking rewards** * swap all the available **trade fee rewards** and **staking rewards** to **USDT** * **distribute** all the available USDT reward according to strategy * **rescale** * collect **trade fee rewards** and **staking rewards** * store **trade fee rewards** and **staking rewards** in the contract temporarily * **no distribution** ### ==Dust Token Management== * **deposit** * might be 2 kinds of dust tokens: **Token0**, **Token1** * **send back** to user * **earn** * might be 1 kind of dust token: **USDT** * **USDT** dust token stores in contract until next **earn** * **rescale** * might be 2 kinds of dust tokens: **Token0**, **Token1** * **Token0**, **Token1** dust tokens store in contract until next **rescale** ### ==Function Locked== * **earn** function lock * **deposit**, **withdraw** ,**rescale** and **depositDustToken** functions are locked when earning * flag is **isEarning** (isEarning is true when earning is progressing) ### ==Auto Rescaling== :::danger **tick & tickLower Duplication Verification** add one more chack related to tick and tickLower duplication ::: * check rescale condition every **1 minute** (adjustable), rescale conditions as below * tick is at least one tick spacing away from tick boundary * price valid range `(tickLower - tickSpacing)` < tick < `(tickUpper + tickSpacing)` * verification implmented in Controller **isWithinOneTickSpacingRange** function * trigger **rescale** when returning **false** * forbid rescale if **tickLower > tick** && **tick % tickSpacing == 0** * retrieve tickLower by StrategyInfoQuerier **getTickLowerAndPrice** function * retrieve tick by StrategyInfoQuerier **getTickAndPrice** function * retrieve tickSpacing by StrategyInfoQuerier **getTickSpacing** function * query the token price every **60 seconds** (adjustable) * separate scheduler server from server of other functions --- ## Main Functions in Strategy Contract > *Only farm & controller contract can access functions in strategy contract* ### Variables :::warning **Constant Variable** Token | Variable | Address | |:-------- |:------------------------------------------ | | wbnb | 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c | | usdt | 0x55d398326f99059fF775485246999027B3197955 | | cake | 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82 | Black Hole (for buyBack usage) | Variable | Address | |:-------------------------- |:------------------------------------------ | | blackHole | 0x000000000000000000000000000000000000dEaD | Pancakeswap V3 | Variable | Address | |:-------------------------- |:------------------------------------------ | | nonfungiblePositionManager | 0x46A15B0b27311cedF172AB29E4f4766fbE7F4364 | | masterChefV3 | 0x556B9306565093C855AEA9AE92A594704c2Cd59e | Our Own Contracts | Variable | Address | |:-------------------------- |:------------------------------------------ | | farm | ?? | | controller | ?? | | swapRateCalculator | ?? | | zap | ?? | Denominator | Variable | uint24 | |:---------------------------- |:------- | | buybackDenominator | 1,000,000 | | fundManagerProfitDenominator | 1,000,000 | ::: :::danger **expand fund manager to 10 address** array[7] -> array[10] **replace tickSpread with tickSpreadUpper, tickSpreadLower** tickSpread -> tickSpreadUpper, tickSpreadLower **stake control** add Stake-Control-related Variable ::: :::warning ``` variable with * means operator adjustable ``` **Pancakeswap-Transaction-related Variable** *for mint, increase liquidity and decrease liquidity functions* | Variable | unit | Fixed | Explanation | |:-------------- |:------- |:-----:|:----------------------------------------- | | transactionDeadlineDuration* | uint256 | X | default 300 seconds | **Liquidity-NFT-related Variable** | Variable | unit | Fixed | Explanation | |:---------------- |:------- |:-----:|:------------------------------------------------- | | liquidityNftId | uint256 | X | available liquidity nft id | | tickSpreadUpper* | int24 | X | calculate tick boundary when tick break tickUpper | | tickSpreadLower* | int24 | X | calculate tick boundary when tick break tickLower | | tickSpacing | int24 | O | PancakeswapV3Pool contract’s tickSpacing | **Pool-related Variable** | Variable | unit | Fixed | Explanation | |:------------- |:------- |:-----:|:------------------------------------ | | poolAddress | address | O | PancakeswapV3Pool contract's address | | poolFee | uint24 | O | PancakeswapV3Pool contract's fee | | token0Address | address | O | PancakeswapV3Pool contract's token0 | | token1Address | address | O | PancakeswapV3Pool contract's token1 | **Tracker-Token-related Variable** | Variable | unit | Fixed | Explanation | |:------------------- |:------- |:-----:|:------------------------------------------- | | trackerTokenAddress | address | O | tracker token address for strategy contract | **User-Management-related Variable** | Variable | unit | Fixed | Explanation | |:------------ |:--------------------------- |:-----:|:---------------------------- | | userList | address[] | X | list of valid users | | userIndex | mapping(address => uint256) | X | for userList management | | isInUserList | mapping(address => bool) | X | check user is in list or not | **User-Share-Management-related Variable** | Variable | unit | Fixed | Explanation | |:-------------- |:--------------------------- |:-----:|:---------------- | | userShare | mapping(address => uint256) | X | user share | | totalUserShare | uint256 | X | total user share | **Reward-Management-related Variable** | Variable | unit | Fixed | Explanation | |:------------------ |:------- |:-----:|:----------------------------------- | | rewardToken0Amount | uint256 | X | record available token0 for earning | | rewardToken1Amount | uint256 | X | record available token1 for earning | | rewardCakeAmount | uint256 | X | record available cake for earning | | rewardUsdtAmount | uint256 | X | record available usdt for earning | **User-Reward-Management-related Variable** | Variable | unit | Fixed | Explanation | |:------------------- |:--------------------------- |:-----:|:---------------------- | | userUsdtReward | mapping(address => uint256) | X | user usdt reward | | totalUserUsdtReward | uint256 | X | total user usdt reward | **Buyback-related Variable** | Variable | unit | Fixed | Explanation | |:----------------------- |:--------- |:-----:|:---------------------------------- | | buyBackToken* | address | X | buyback RXD token address | | buyBackNumerator* | uint24 | X | buyback numerator | | usdtToBuyBackTokenPath* | address[] | X | use pancakeswap V2 for RXD buyback | **Fund-Manager-related Variable** | Variable | unit | Fixed | Explanation | |:------------- |:-------- |:-----:|:----------------------------------------- | | fundManagers* | array[10] | X | array with elements in FundManager struct | ```solidity struct FundManager { address fundManagerAddress; uint24 fundManagerProfitNumerator; } FundManager[10] public fundManagers; ``` **Earn-Loop-Control-related Variable** | Variable | unit | Fixed | Explanation | |:------------------------- |:------- |:-----:|:------------------------------------------------------ | | earnLoopSegmentSize* | uint256 | X | maximum number of user distribution for each earn loop segment | | earnLoopDistributedAmount | uint256 | X | distributed amount before last earn loop | | earnLoopStartIndex | uint256 | X | the start index for next earn segment | | isEarning | bool | X | earn flag | **Rescale-related Variable** | Variable | unit | Fixed | Explanation | |:---------------- |:------- |:-----:|:------------------------------ | | dustToken0Amount | uint256 | X | available token0 for rescaling | | dustToken1Amount | uint256 | X | available token1 for rescaling | **Stake-Control-related Variable** | Variable | unit | Fixed | Explanation | |:---------- |:---- |:-----:|:-------------------------------------------- | | isStaking | bool | X | return true if owner of NFT is MasterChef | | stakeNext* | bool | X | define next rescale created NFT stake or not | ::: --- ### Auto Add Liquidity NFT in Deployment **Related Variables** :::warning **Constant Variable** wbnb, zap, nonfungiblePositionManager, masterChefV3 **Liquidity-NFT-related Variable** liquidityNftId, tickSpacing **Pool-related Variable** poolAddress, poolFee, token0Address, token1Address **Tracker-Token-related Variable** trackerTokenAddress **Rescale-related Variable** dustToken0Amount, dustToken1Amount **Stake-Control-related Variable** stakeNext* ::: **Function Parameters** :::info bool **isTestnet** adddress **poolAddress** uint24 **tickSpread** bool **stakeNext** ::: **Steps** :::danger **replace tickSpread with tickSpreadUpper, tickSpreadLower** use input parameter tickSpread but not save in the variable **stake control** add parameter stakeNext and initialize stakeNext Stake liquidity NFT if stakeNext == true ::: :::info **Parameter Verification** > **Verify poolAddress is valid** > ( poolAddress != address(0) ) > > **Verify tickSpread is more than or equal to 0** > ( tickSpread >= 0 ) **Deploy Strategy Contract with BNB** > **Deploy strategy contract with initialize spec & msg.value 0.1 BNB** > ( initialize spec include: poolAddress, stakeNext ) > > **Query and set pool related variables** > ( pool related variables include: > poolAddress, tickSpacing, poolFee, token0Address, token1Address ) > > **Query pancakeswap related constants** > ( if isTestnet is true, use testnet value ) > ( if isTestnet is false, use mainnet value ) **Create Tracker Token** > **Deploy tracker token** > > **Set tracker token address** > ( set trackerTokenAddress = deployed tracker token address ) **Swap BNB to Pair Token** > **Swap BNB to WBNB** > > **Swap half WBNB to Token0** > ( skip if token0Address == wbnb ) > > **Swap half WBNB to Token1** > ( skip if token1Address == wbnb ) **Mint New Liquidity NFT** > **Calculate tickUpper & tickLower** > ( set tickUpper and Lower according to tickSpread, tickSpacing and token price ) > > **Mint liquidity NFT** > ( approve NonfungiblePositionManager ) > ( put all the swapOutToken0 and swapOutToken1 to new liquidity NFT ) > > **Stake liquidity NFT if stakeNext == true** > ( if stakeNext == true, safeTransferFrom strategy to masterChefV3 ) > > **Record liquidity NFT id** > ( set liquidityNftId to new id ) > > **Update amount of dust tokens** > ( update dustToken0Amount and dustToken1Amount ) > ( skip if amount of token is 0 ) ::: **Events** :::success ``` MintLiquidityNFT ( uint256 indexed liquidityNftId, uint128 initialliquidity, uint256 initialFundToken0Amount, uint256 initialFundToken1Amount, uint256 dustToken0Amount, uint256 dustToken1Amount ) ``` ::: --- ### User Deposit Liquidity **Related Variables** :::warning **Constant Variable** wbnb, nonfungiblePositionManager, masterChefV3, farm, swapRateCalculator, zap **Liquidity-NFT-related Variable** liquidityNftId **Pool-related Variable** token0Address, token1Address **Tracker-Token-related Variable** trackerTokenAddress **User-Management-related Variable** userList, userIndex, isInUserList **User-Share-Management-related Variable** userShare, totalUserShare **Earn-Loop-Control-related Variable** isEarning **Stake-Control-related Variable** isStaking ::: **Function Parameters** :::info bool **isBNB** address **userAddress** address **inputToken** uint256 **inputAmount** uint256 **swapInAmount** uint256 **minimumSwapOutAmount** ::: Frontend preparation ``` if isBNB == true, user needs to transfer BNB to farm contract if isBNB == false, user needs to approve token to strategy contract ``` **Steps** 1 common :::danger **tick & tickLower Duplication Verification** add Verify tick and tickLower are not duplicated ::: :::info **Parameter Verification** > **Verify userAddress & inputToken is valid** > ( userAddress != address(0) && inputToken != address(0) ) > > **Verify inputAmount is more than 0** > ( inputAmount > 0 ) **Verification** > **Verify caller is farm contract** > ( msg.sender == farm ) > > **Verify is not earning** > ( isEarning == false ) > > **Verify tick and tickLower are not duplicated** > ( tickLower != tick ) ::: 2 :::danger **sandwich attack prevention** * remove internal swapInAmount calculation (use input parameter swapInAmount when swap) * remove internal minimumSwapOutAmount calculation (use input parameter minimumSwapOutAmount when swap) ::: 2-1 input BNB :::info **Verification** > **Verify isBNB is true** > ( isBNB == true ) > > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) > > **Verify msg.value is equal to input amount** > ( msg.value == inputAmount ) **Swap BNB to another token** > **Swap "swapAmount" BNB to WBNB** > ( use input parameter swapInAmount ) > ( skip if swapAmount == 0 ) > > **Swap "swapAmount" WBNB to another token** > ( use input parameter swapInAmount ) > ( use input parameter minimumSwapOutAmount ) > ( skip if swapAmount == 0 ) ::: 2-2 input WBNB :::info **Verification** > **Verify isBNB is false** > ( isBNB == false ) > > **Verify input token is token0 or token1** > ( token0Address == inputToken || token1Address == inputToken ) > > **Verify user input token allowance is more or equal to input amount** > ( user inputToken allowance >= inputAmount ) > > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) > > **Verify input token is WBNB** > ( wbnb == inputToken ) **Transfer Input Token to Strategy Contract** > **Transfer input token in input amount from user to strategy contract** > ( safeTransferFrom user to strategy contract ) **Swap WBNB to BNB** > **Swap ("inputAmount"-"swapAmount") WBNB to BNB** > ( use input parameter swapInAmount ) > ( skip if inputAmount-swapAmount == 0 ) **Swap WBNB to another token** > **Swap "swapAmount" WBNB to another token** > ( use input parameter swapInAmount ) > ( use input parameter minimumSwapOutAmount ) > ( skip if swapAmount == 0 ) ::: 2-3 input BEP20-token & token pair include WBNB :::info **Verification** > **Verify isBNB is false** > ( isBNB == false ) > > **Verify input token is token0 or token1** > ( token0Address == inputToken || token1Address == inputToken ) > > **Verify user input token allowance is more or equal to input amount** > ( user inputToken allowance >= inputAmount ) > > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) > > **Verify input token is not WBNB** > ( wbnb != inputToken ) **Transfer Input Token to Strategy Contract** > **Transfer input token in input amount from user to strategy contract** > ( safeTransferFrom user to strategy contract ) **Swap token to BNB** > **Swap "swapAmount" token to WBNB** > ( use input parameter swapInAmount ) > ( use input parameter minimumSwapOutAmount ) > ( skip if swapAmount == 0 ) > > **Swap "swapAmount" WBNB to BNB** > ( use input parameter swapInAmount ) > ( skip if swapAmount == 0 ) ::: 2-4 input BEP20-token & token pair not include WBNB :::info **Verification** > **Verify isBNB is false** > ( isBNB == false ) > > **Verify input token is token0 or token1** > ( token0Address == inputToken || token1Address == inputToken ) > > **Verify user input token allowance is more or equal to input amount** > ( user inputToken allowance >= inputAmount ) > > **Verify token0 and token1 is not WBNB** > ( token0Address != WBNB && token1Address != WBNB ) **Transfer Input Token to Strategy Contract** > **Transfer input token in input amount from user to strategy contract** > ( safeTransferFrom user to strategy contract ) **Swap token to another token** > **Swap "swapAmount" token to another token** > ( use input parameter swapInAmount ) > ( use input parameter minimumSwapOutAmount ) > ( skip if swapAmount == 0 ) ::: 3 :::danger **stake control** increase liquidity with NonfungiblePositionManager or MasterChef (NonfungiblePositionManager need to call refundETH when deposit with BNB) ::: 3-1 token pair include WBNB :::info **Verification** > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) **Increase Liquidity** > **Increase liquidity by masterChefV3 if isStaking == true** > ( approve token to masterChefV3 or send BNB in msg.value ) > ( increaseLiquidity to masterChefV3 ) > > **Increase liquidity by NonfungiblePositionManager if isStaking == false** > ( approve token to NonfungiblePositionManager or send BNB in msg.value ) > ( increaseLiquidity to NonfungiblePositionManager ) > ( call refundETH after increaseliquidity ) **Send Dust Token Back to User** > **Transfer dust BEP20 token and BNB to user** > ( safeTransfer BEP20 token & transfer BNB in msg.value ) > ( skip if dust token == 0 ) ::: 3-2 token pair not include WBNB :::info **Verification** > **Verify token0 and token1 is not WBNB** > ( token0Address != WBNB && token1Address != WBNB ) **Increase Liquidity** > **Increase liquidity by masterChefV3 if isStaking == true** > ( approve both token to masterChefV3 ) > ( increaseLiquidity to masterChefV3 ) > > **Increase liquidity by NonfungiblePositionManager if isStaking == false** > ( approve both token to NonfungiblePositionManager ) > ( increaseLiquidity to NonfungiblePositionManager ) **Send Dust Token Back to User** > **Transfer dust token0 and token1 to user** > ( safeTransfer BEP20 token ) > ( skip if dust token == 0 ) ::: 4 common :::info **Share Management** > **Update userShare & totalUserShare** > ( share to liquidity unit = total share / total liquidity ) > ( despoit share = increased liquidity * share to liquidity unit ) **Mint Tracker Token** > **Mint tracker token to user** > ( mint tracker token in increased share amount to user ) **User List Management** > **Add user into list if it's first deposit** > ( if isInUserList[userAddress] == false, > set isInUserList[userAddress] = true, > update userIndex and push userAddress into userList ) ::: **Return** :::success uint256 **increasedToken0Amount** uint256 **increasedToken1Amount** uint256 **sendBackToken0Amount** uint256 **sendBackToken1Amount** ::: **Events** :::success ``` IncreaseLiquidity ( uint256 indexed liquidityNftId, address indexed userAddress, uint128 increasedLiquidity, uint256 increasedShare, uint256 increasedToken0Amount, uint256 increasedToken1Amount, uint256 sendBackToken0Amount, uint256 sendBackToken1Amount ) ``` ::: --- ### User Withdraw Liquidity **Related Variables** :::warning **Constant Variable** blackHole, nonfungiblePositionManager, masterChefV3, farm **Liquidity-NFT-related Variable** liquidityNftId **Pool-related Variable** token0Address, token1Address **Tracker-Token-related Variable** trackerTokenAddress **User-Management-related Variable** userList, userIndex, isInUserList **User-Share-Management-related Variable** userShare, totalUserShare **Reward-Management-related Variable** rewardToken0Amount, rewardToken1Amount **Earn-Loop-Control-related Variable** isEarning **Stake-Control-related Variable** isStaking ::: **Function Parameters** :::info address **userAddress** uint256 **withdrawShares** ::: Frontend preparation ``` user needs to approve tracker token to strategy contract in withdrawShares amount ``` **Steps** :::danger **stake control** collect & decreaseLiquidity with NonfungiblePositionManager or MasterChef ::: :::info **Parameter Verification** > **Verify userAddress is valid** > ( userAddress != address(0) ) > > **Verify withdrawShares is more than 0** > ( withdrawShares > 0 ) **Verification** > **Verify caller is farm contract** > ( msg.sender == farm ) > > **Verify is not earning** > ( isEarning == false ) > > **Verify user is in user list** > ( isInUserList[userAddress] == true ) > > **Verify user has sufficient share** > ( userShare[userAddress] >= withdrawShares ) > > **Verify user has sufficient tracker token allowance** > ( allowance[userAddress, strategy contract] >= withdrawShares ) **Transfer User's Tracker Token to Black Hole** > **Transfer user's tracker token to black hole** > ( transfer user's tracker token to black hole in withdrawShares amount ) **Calculate Decreased Liquidity** > **Calculate "decreasedLiquidityAmount" from decreased share** > ( share to liquidity unit = total share / total liquidity ) > ( withdraw liquidity = decreased share / share to liquidity unit ) **Share Management** > **Update userShare & totalUserShare** > ( decrease share in withdrawShares amount ) **User List Management** > **Remove user from userList if userShare is 0** > ( if userShare[userAddress] == 0, > set isInUserList[userAddress] = false, > remove userAddress from userList and update userList length ) **Collect Token0 and Token1 Reward** > **Collect trade reward by masterChefV3 if isStaking == true** > ( masterChefV3 collect maximum reward and set recipient as strategy contract ) > > **Collect trade reward by NonfungiblePositionManager if isStaking == false** > ( NonfungiblePositionManager collect maximum reward and set recipient as strategy contract ) > > **Update amount of reward token amount** > ( update rewardToken0Amount and rewardToken1Amount ) > ( skip if amount of token is 0 ) **Decrease Liquidity** > **Decrease liquidity by masterChefV3 if isStaking == true** > ( masterChefV3 decreaseLiquidity decrease "decreasedLiquidityAmount" ) > > **Decrease liquidity by NonfungiblePositionManager if isStaking == false** > ( NonfungiblePositionManager decreaseLiquidity decrease "decreasedLiquidityAmount" ) **Collect and Send Decreased Liquidity to User** > **Collect and send token of "decreasedLiquidityAmount" to user** > **by masterChefV3 if isStaking == true** > ( masterChefV3 collect maximum token and set recipient as userAddress ) > > **Collect and send token of "decreasedLiquidityAmount" to user** > **by NonfungiblePositionManager if isStaking == false** > ( NonfungiblePositionManager collect maximum token and set recipient as userAddress ) ::: **Return** :::success uint256 **userReceivedToken0Amount** uint256 **userReceivedToken1Amount** ::: **Events** :::success ``` DecreaseLiquidity ( uint256 indexed liquidityNftId, address indexed userAddress, uint128 decreasedLiquidity, uint256 decreasedShare, uint256 decreasedToken0Amount, uint256 decreasedToken1Amount ) ``` ::: --- ### Operator/Backend Earn Reward **Related Variables** :::warning **Constant Variable** usdt, cake, blackHole, masterChefV3, controller, zap, buybackDenominator, fundManagerProfitDenominator **Liquidity-NFT-related Variable** liquidityNftId **Pool-related Variable** token0Address, token1Address **User-Management-related Variable** userList **User-Share-Management-related Variable** userShare, totalUserShare **Reward-Management-related Variable** rewardToken0Amount, rewardToken1Amount, rewardCakeAmount, rewardUsdtAmount **User-Reward-Management-related Variable** userUsdtReward, totalUserUsdtReward **Buyback-related Variable** buyBackToken*, buyBackNumerator*, usdtToBuyBackTokenPath* **Fund-Manager-related Variable** fundManagers* **Earn-Loop-Control-related Variable** earnLoopSegmentSize*, earnLoopDistributedAmount, earnLoopStartIndex, isEarning **Stake-Control-related Variable** isStaking ::: ==**Earn Stage 0 (triggered by backend)**== :::danger **sandwich attack prevention** move collect reward process from stage 1 to stage 0 **stake control** collect trade reward with NonfungiblePositionManager or MasterChef collect staking reward with MasterChef only when isStaking == true ::: **Steps** :::info **Verification** > **Verify caller is controller contract** > ( msg.sender == controller ) **Collect Token0 and Token1 Reward** > **Collect trade reward by masterChefV3 if isStaking == true** > ( masterChefV3 collect maximum reward and set recipient as strategy contract ) > > **Collect trade reward by NonfungiblePositionManager if isStaking == false** > ( NonfungiblePositionManager collect maximum reward and set recipient as strategy contract ) > > **Update amount of reward token 0 & token 1** > ( rewardToken0Amount += collectedRewardToken0Amount ) > ( rewardToken1Amount += collectedRewardToken1Amount ) **Collect Cake Reward** > **Collect staking reward by masterChefV3 if isStaking == true** > ( masterChefV3 harvest reward and set recipient as strategy contract ) > > **Update amount of reward cake** > ( rewardCakeAmount += collectedRewardCakeAmount ) ::: **Events** :::success ``` CollectRewards ( uint256 indexed liquidityNftId, uint256 rewardToken0Amount, uint256 rewardToken1Amount, uint256 rewardCakeAmount, uint256 rewardUsdtAmount ) ``` ::: ==**Earn Stage 1 (triggered by backend)**== :::danger **sandwich attack prevention** * remove collect reward process * add minimum swap out amount input parameter * minimumToken0SwapOutAmount * minimumToken1SwapOutAmount * minimumCakeSwapOutAmount * minimumBuybackSwapOutAmount * remove internal minimumSwapOutAmount calculation (use input parameter minimumSwapOutAmount when swap) ::: **Function Parameters** :::info uint256 **minimumToken0SwapOutAmount** uint256 **minimumToken1SwapOutAmount** uint256 **minimumCakeSwapOutAmount** uint256 **minimumBuybackSwapOutAmount** ::: **Steps** :::info **Variable Verification** > **Verify related variables are well set up** > ( buyBackToken != address(0) && fundManagerAddress in fundManagers != address(0) ) **Verification** > **Verify caller is controller contract** > ( msg.sender == controller ) > > **Verify is not earning** > ( isEarning == false ) **Swap Available Reward Tokens to USDT** > **Swap all the available trade reward and stake reward to USDT** > ( swap rewardToken0Amount with minimumToken0SwapOutAmount ) > ( swap rewardToken1Amount with minimumToken1SwapOutAmount) > ( swap rewardCakeAmount with minimumCakeSwapOutAmount ) > ( record swapped USDT result in function variable ) > > **Update dust token** > ( set rewardToken0Amount = 0, rewardToken1Amount = 0, rewardCakeAmount = 0 ) **Send USDT to Fund Manager** > **Distribute part of USDT to fundManager** > ( each fund manager get USDT amount as below ) ``` ( rewardUsdtAmount + swappedUSDTResult ) * fundManagerProfitNumerator / fundManagerProfitDenominator ``` > ( record fundManager used USDT amount in function variable ) > ( skip if fundManagerProfitNumerator in fundermanagers == 0 ) **Buyback RXD Token** > **Buyback RXD by part of USDT** > ( buyback RXD using USDT amount as below ) ``` ( rewardUsdtAmount + swappedUSDTResult ) * buyBackNumerator / buybackDenominator ``` > ( record buyback RXD used USDT amount in function variable ) > ( skip if buyBackNumerator == 0 ) > > **Swap USDT to RXD and send RXD to black hole contract** > ( user pancakeswap V2 to swap ) > ( swap with minimumBuybackSwapOutAmount ) > ( set swap recipient as blackhole ) > ( skip if buyBackNumerator == 0 ) **Update USDT Reward Token Amount** > **Update rewardUsdtAmount** ``` rewardUsdtAmount = rewardUsdtAmount + swappedUsdtResult - fundManagerUsedUsdt - buybackRxdUsedUsdt ``` **Set Earning Status** > **Set earn flag** > ( set isEarning = true ) ::: **Events** :::success ``` EarnStart ( uint256 indexed liquidityNftId, uint256 rewardUsdtAmount, uint256 fundManagerUsedUsdt, uint256 buybackRxdUsedUsdt, uint256 buybackRxdAmount ) ``` ::: ==**Earn Stage 2 (triggered by backend)**== trigger continously until isEarning == false **Steps** 1 distribute USDT to user by loop segment :::info **Variable Verification** > **Verify related variable is well set up** > ( earnLoopSegmentSize != 0 ) **Verification** > **Verify caller is controller contract** > ( msg.sender == controller ) > > **Verify is earning** > ( isEarning == true ) **Earn By Segment** > **Calculate the end index of this segment** > ( if earnLoopStartIndex + earnLoopSegmentSize > userListLength, > endIndexInSegment = userListLength ) > ( if earnLoopStartIndex + earnLoopSegmentSize <= userListLength, > endIndexInSegment = earnLoopStartIndex + earnLoopSegmentSize ) > > **Distribute reward by looping through the users** > ( userUsdtRewards[userAddress] += rewardUsdtAmount * userShare / totalUserShare ) **Update Earn Result For This Segment** > **Update total user usdt reward** > ( totalUserUsdtReward += totalDistributionInSegment ) ::: 2-1 Earn not complete in this segment :::info **Verification** > **Verify having remaining user for distribution** > ( endIndexInSegment < userListLength ) **Update Earn Loop Control Indicator** > **Update earn loop distributed USDT amount and Next Segment Start Index** > ( earnLoopDistributedAmount += totalDistributionInSegment > & earnLoopStartIndex = endIndexInSegment ) ::: **Events** :::success ``` Earning ( uint256 indexed liquidityNftId, uint256 distributedUsdtAmountInSegment, uint256 earnLoopSegmentStartIndex, uint256 earnLoopSegmentEndIndex, uint256 earnLoopSegmentSize, uint256 userListLength ) ``` ::: 2-2 Earn complete in this segment :::info **Verification** > **Verify having no remaining user for distribution** > ( endIndexInSegment == userListLength ) **Record Dust USDT Reward** > **Update rewardUsdtAmount** > ( rewardUsdtAmount -= (earnLoopDistributedAmount + totalDistributionInSegment) ) **Initialize Earn Loop Control Indicator** > **Initialize earnLoopDistributedAmount & earnLoopStartIndex** > ( set earnLoopDistributedAmount = 0, earnLoopStartIndex = 0 ) **Set Earning Status** > **Set earn flag** > ( set isEarning = false ) ::: **Events** :::success ``` EarnEnd ( uint256 indexed liquidityNftId, uint256 distributedUsdtAmountInSegment, uint256 earnLoopSegmentStartIndex, uint256 earnLoopSegmentEndIndex, uint256 earnLoopSegmentSize, uint256 userListLength, uint256 dustRewardUsdtAmount ) ``` ::: :::spoiler *Earn Policy* #### **Policy 1** 100BUSD,100DAI,100cake sent to the strategy contract. All will be converted to USDT at the strategy. Some USDT sent to fund managers. Some USDT buyback RXD tokens. The remaining USDT distribute to user according to share ratio. Users can withdraw their USDT with the "claim" function. #### **Policy 2** [Next Version] 100BUSD,100DAI,100cake sent to the strategy contract. All will be converted to USDT at the strategy. Some USDT sent to fund managers. Some USDT buyback RXD tokens. Convert remaining USDT to token0,token1 Add it back to liquidity ::: --- ### User Claim Reward **Related Variables** :::warning **Constant Variable** usdt, farm **User-Reward-Management-related Variable** userUsdtReward, totalUserUsdtReward ::: **Function Parameters** :::info address **userAddress** ::: **Steps** :::info **Parameter Verification** > **Verify userAddress is valid** > ( userAddress != address(0) ) **Verification** > **Verify caller is farm contract** > ( msg.sender == farm ) > > **Verify user has reward more than 0** > ( userUsdtReward[userAddress] > 0 ) **Claim Reward** > **Reset user reward** > ( set userUsdtReward[userAddress] = 0 ) > > **Transfer USDT reward to user** > ( safeTransfer(userAddress, userUsdtReward) ) **Update Total User USDT Reward** > **Update total user usdt reward** > ( totalUserUsdtReward -= claimedReward ) ::: **Events** :::success ``` ClaimReward ( uint256 indexed liquidityNftId, address indexed userAddress, uint256 claimedRewardAmount ) ``` ::: --- ### Operator/Backend Rescale **Related Variables** :::warning **Constant Variable** cake, nonfungiblePositionManager, masterChefV3, controller, swapRateCalculator, zap **Liquidity-NFT-related Variable** liquidityNftId, tickSpreadUpper*, tickSpreadLower*, tickSpacing **Pool-related Variable** poolAddress, poolFee, token0Address, token1Address **Reward-Management-related Variable** rewardToken0Amount, rewardToken1Amount, rewardCakeAmount **Rescale-related Variable** dustToken0Amount, dustToken1Amount **Stake-Control-related Variable** isStaking, stakeNext* ::: **Steps** :::danger **tick & tickLower Duplication Verification** add Verify newly created tickLower and tick are not duplicated **replace tickSpread with tickSpreadUpper, tickSpreadLower** * Verify tickSpreadUpper, tickSpreadLower is more than or equal to 0 * use tickSpreadUpper when tick break tickUpper * use tickSpreadLower when tick break tickLower **stake control** collect with NonfungiblePositionManager or MasterChef unstake only when isStaking == true stake new NFT only when stakeNext == true ::: :::info **Verification** > **Verify token price is out of NFT valid price range** > ( get tickLower & tickUpper by masterChefV3 userPositionInfos(liquidityNftId) ) > ( get current tick by poolAddress slot0 ) > ( tick <= `tickLower - tickSpacing` || tick >= `tickUpper + tickSpacing` ) > > **Verify caller is controller contract** > ( msg.sender == controller ) > > **Verify is not earning** > ( isEarning == false ) > > **Verify tickSpreadUpper, tickSpreadLower is more than or equal to 0** > ( tickSpreadUpper >= 0 && tickSpreadLower >= 0 ) > > **Verify newly created tickLower and tick are not duplicated** > ( !(tickLower > tick && tick % tickSpacing == 0) ) **Collect and Store Token0 and Token1 Reward** > **Collect trade reward by masterChefV3 if isStaking == true** > ( masterChefV3 collect maximum reward and set recipient as strategy contract ) > > **Collect trade reward by NonfungiblePositionManager if isStaking == false** > ( NonfungiblePositionManager collect maximum reward and set recipient as strategy contract ) > > **Update Token0 and Token1 Reward Record** > ( rewardToken0Amount += collectRewardToken0 , > rewardToken1Amount += collectRewardToken1 ) **Unstake NFT & Collect and Store Cake Reward** > **Unstake liquidity NFT by masterChefV3 if isStaking == true** > ( masterChefV3 withdraw function would unstake and harvest Cake reward ) > > **Update Cake Reward Record** > ( rewardCakeAmount += harvestedCake ) **Withdraw All The Liquidity** > **Get liquidity amount** > ( get liquidity by nonfungiblePositionManager positions(liquidityNftId) ) > > **Decrease all the liquidity** > ( nonfungiblePositionManager decreaseLiquidity decrease all the liquidity ) > > **Collect all the liquidity** > ( nonfungiblePositionManager collect maximum token and set recipient as strategy contract ) **Burn Old Liquidity NFT** > **Burn liquidity NFT** > ( nonfungiblePositionManager burn(liquidityNftId) ) **Mint New Liquidity NFT** > **Calculate available tokens** > ( availableToken0 = dustToken0Amount + collectedLiquidityToken0 ) > ( availableToken1 = dustToken1Amount + collectedLiquidityToken1 ) > > **Calculate tickUpper & tickLower** > ( if tick > tickUpper, tickSpread = tickSpreadUpper ) > ( if tick < tickLower, tickSpread = tickSpreadLower ) > ( if tickSpread == 0, tickDistance = tickSpacing ) > ( if tickSpread > 0, tickDistance = 2 * tickSpread * tickSpacing ) > ( if tick <= `tickLower - tickSpacing`, > tickLower = ceiling tick, tickUpper = tickLower + tickDistance ) > ( if tick >= `tickUpper + tickSpacing`, > tickUpper = floor tick, tickLower = tickUpper - tickDistance ) > > **Mint liquidity NFT** > ( approve NonfungiblePositionManager ) > ( put all the availableToken0 and availableToken1 to new liquidity NFT ) > > **Stake liquidity NFT if stakeNext == true** > ( if stakeNext == true, safeTransferFrom strategy to masterChefV3 ) **Update Dust Tokens** > **Update amount of dust tokens** > ( dustToken0Amount = availableToken0 - liquidityUsedToken0 ) > ( dustToken1Amount = availableToken1 - liquidityUsedToken1 ) **Update Liquidity NFT Id** > **Set Liquidity NFT Id** > ( set liquidityNftId = new liquidity NFT id ) ::: **Events** :::success ``` RescaleStart ( uint256 indexed burnedLiquidityNftId, uint256 collectedToken0Reward, uint256 collectedToken1Reward, uint256 harvestedCakeReward, uint256 dustToken0Amount, uint256 dustToken1Amount, bool swapToken0ToToken1, uint256 remainingSwapAmount ) ``` ``` RescaleEnd ( uint256 indexed liquidityNftId, bool swapToken0ToToken1, uint256 swapInAmount, uint256 swapOutAmount, uint256 swapTimeStamp, uint128 initialliquidity, uint256 initialFundToken0Amount, uint256 initialFundToken1Amount, uint256 dustToken0Amount, uint256 dustToken1Amount ) ``` ::: --- ### Operator/Backend Deposit Dust Token **Related Variables** :::warning **Constant Variable** wbnb, masterChefV3, controller, swapRateCalculator, zap **Liquidity-NFT-related Variable** liquidityNftId **Pool-related Variable** token0Address, token1Address **Earn-Loop-Control-related Variable** isEarning **Rescale-related Variable** dustToken0Amount, dustToken1Amount **Stake-Control-related Variable** isStaking ::: **Function Parameters** :::info bool **depositDustToken0** ::: **Steps** :::danger **tick & tickLower Duplication Verification** add Verify tick and tickLower are not duplicated ::: 1 verification :::info **Parameter Verification** > **Verify dustToken is more than 0** > ( if depositDustToken0 == true, dustToken0Amount > 0 > if depositDustToken0 == false, dustToken1Amount > 0 ) **Verification** > **Verify caller is controller contract** > ( msg.sender == controller ) > > **Verify is not earning** > ( isEarning == false ) > > **Verify tick and tickLower are not duplicated** > ( tickLower != tick ) ::: 2-1 dust token is WBNB :::info **Verification** > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) > > **Verify dust token is WBNB** > ( wbnb == dustToken ) **Calculate Swap Amount** > **Get "swapAmount" from swapRateCalculator** **Swap WBNB to BNB** > **Swap ("inputAmount"-"swapAmount") WBNB to BNB** **Swap WBNB to another token** > **Swap "swapAmount" WBNB to another token** ::: 2-2 dust token is BEP20-token & token pair include WBNB :::info **Verification** > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) > > **Verify dust token is not WBNB** > ( wbnb != dustToken ) **Calculate Swap Amount** > **Get "swapAmount" from swapRateCalculator** **Swap token to BNB** > **Swap "swapAmount" token to WBNB** > > **Swap "swapAmount" WBNB to BNB** ::: 2-3 dust token is BEP20-token & token pair not include WBNB :::info **Verification** > **Verify token0 and token1 is not WBNB** > ( token0Address != WBNB && token1Address != WBNB ) **Calculate Swap Amount** > **Get "swapAmount" from swapRateCalculator** **Swap token to another token** > **Swap "swapAmount" token to another token** ::: 3 :::danger **stake control** increase liquidity with NonfungiblePositionManager or MasterChef (NonfungiblePositionManager need to call refundETH when deposit with BNB) ::: 3-1 token pair include WBNB :::info **Verification** > **Verify token0 or token1 is WBNB** > ( token0Address == WBNB || token1Address == WBNB ) **Increase Liquidity** > **Increase liquidity by masterChefV3 if isStaking == true** > ( approve token to masterChefV3 or send BNB in msg.value ) > ( increaseLiquidity to masterChefV3 ) > > **Increase liquidity by NonfungiblePositionManager if isStaking == false** > ( approve token to NonfungiblePositionManager or send BNB in msg.value ) > ( increaseLiquidity to NonfungiblePositionManager ) > ( call refundETH after increaseliquidity ) **Update Dust Token** > **Update amount of dust tokens** > ( update dustToken0Amount & dustToken1Amount ) **Wrap Dust BNB** > **Wrap Dust BNB to WBNB** > ( WBNB deposit function, skip if dust BNB = 0 ) ::: 3-2 token pair not include WBNB :::info **Verification** > **Verify token0 and token1 is not WBNB** > ( token0Address != WBNB && token1Address != WBNB ) **Increase Liquidity** > **Increase liquidity by masterChefV3 if isStaking == true** > ( approve both token to masterChefV3 ) > ( increaseLiquidity to masterChefV3 ) > > **Increase liquidity by NonfungiblePositionManager if isStaking == false** > ( approve both token to NonfungiblePositionManager ) > ( increaseLiquidity to NonfungiblePositionManager ) **Update Dust Token** > **Update amount of dust tokens** > ( update dustToken0Amount & dustToken1Amount ) ::: **Return** :::success uint256 **increasedToken0Amount** uint256 **increasedToken1Amount** ::: **Events** :::success ``` DepositDustToken ( uint256 indexed liquidityNftId, uint128 increasedLiquidity, uint256 increasedToken0Amount, uint256 increasedToken1Amount, uint256 dustToken0Amount, uint256 dustToken1Amount ) ``` ::: --- ## Main Functions in MinimumSwapOutAmountCalculator Contract :::danger **sandwich attack prevention** add contract to support minimum swap out amount calculation ::: ### Minimum swap out amount for deposit **Input Parameters** :::info address **strategyAddress** address **inputToken** uint256 **inputAmount** ::: **Steps** :::info **Get swapInAmount** > SwapRateCalculator **calculateMaximumSwapAmountForSingleTokenLiquidityIncrease** > ( return **swapAmountWithTradeFee** ) **Get minimumSwapOutAmount** > Zap **getMinimumSwapOutAmount** > ( return **minimumSwapOutAmount** ) ::: **Return** :::success uint256 **swapInAmount** uint256 **minimumSwapOutAmount** ::: ### Minimum swap out amount for earn **Input Parameters** :::info address **strategyAddress** ::: **Steps** :::info **Get minimumToken0SwapOutAmount** > get Strategy **rewardToken0Amount** > Zap **getMinimumSwapOutAmount** (token0 to USDT) > ( if rewardToken0Amount == 0, minimumToken0SwapOutAmount = 0 ) > ( if token0 == USDT, minimumToken0SwapOutAmount = rewardToken0Amount ) **Get minimumToken1SwapOutAmount** > get Strategy **rewardToken1Amount** > Zap **getMinimumSwapOutAmount** (token1 to USDT) > ( if rewardToken1Amount == 0, minimumToken1SwapOutAmount = 0 ) > ( if token1 == USDT, minimumToken1SwapOutAmount = rewardToken1Amount ) **Get minimumCakeSwapOutAmount** > get Strategy **rewardCakeAmount** > Zap **getMinimumSwapOutAmount** (cake to USDT) > ( if rewardCakeAmount == 0, minimumCakeSwapOutAmount = 0 ) **Get minimumBuybackSwapOutAmount** > get Strategy **buyBackToken**, **buyBackNumerator**, **BUY_BACK_DENOMINATOR** > calculate estimated minimum buyback USDT amount ``` ( rewardUsdtAmount + swappedMinimumUSDTResult ) * buyBackNumerator / buybackDenominator ``` > ( if buyBackNumerator == 0 || buyBackUsdtAmount == 0, minimumBuybackSwapOutAmount = 0 ) > get estimate swap out amount > ( Pancakeswap Router V2 getAmountsOut ) ::: **Return** :::success uint256 **minimumToken0SwapOutAmount** uint256 **minimumToken1SwapOutAmount** uint256 **minimumCakeSwapOutAmount** uint256 **minimumBuybackSwapOutAmount** ::: --- ## Main Functions in SwapRateCalculator Contract ### Calculate Maximum Swap Amount for Single Token Liquidity Increase > embedded in **User Deposit Liquidity** flow > > When increasing liquidity, users can choose token 0 or token 1 to increase liquidity. > This calculator would take into account the **liquidity ratio**, **price ratio**, and **pool fee**. > It returns the **maximum swap amount for the input token** to the other token. **Input Parameters** :::info uint256 **liquidityNftId** address **inputToken** uint256 **inputAmount** ::: **Steps** :::info **Parameter Verification** > **Verify inputToken is valid** > ( inputToken != address(0) ) > > **Verify liquidityNftId & inputAmount is more than 0** > ( liquidityNftId > 0 && inputAmount > 0 ) **Verification** > **Verify inputToken is one of the pair token** > (inputToken == token0 || inputToken == token1) **Swap Amount Calculation** > **If NFT is out of range** > if inputToken is liquidity 0 token, return 0 > if inputToken is not liquidity 0 token, return inputAmount > > **If NFT is not out of range** > ( refer to formula explanation ) ::: **Return** :::success uint256 **swapAmountWithTradeFee** ::: :::spoiler *Formula Explanation* #### ==[Token Price Calculation]== [Original formula (without decimal precision)] ``` (token1 * (10^decimal1)) / (token0 * (10^decimal0)) = (sqrtPriceX96 / (2^96))^2 tokenPrice = token1 / token0 = (sqrtPriceX96 / (2^96))^2 * (10^decimal0) / (10^decimal1) ``` [Formula with decimal precision & decimal adjustment] ``` tokenPriceWithDecimalAdj = tokenPrice * (10^decimalPrecision) = (sqrtPriceX96 / (2^96))^2 * (10^decimalPrecision) * (10^decimal0) / (10^decimal1) = (sqrtPriceX96 * (10^decimalPrecision) / (2^96))^2 * (10^decimal0) / ((10^decimal1) * (10^decimalPrecision)) = (sqrtPriceX96 * (10^decimalPrecision) / (2^96))^2 / (10^(decimalPrecision + decimal1 - decimal0)) decimalAdj = decimalPrecision + decimal1 - decimal0 scaledPriceX96 = sqrtPriceX96 * (10^decimalPrecision) / (2^96) ``` #### ==[In Range Liquidity NFT Liquidity Ratio Calculation]== [Original formula (without decimal precision)] ``` tickUpper -> sqrtRatioBX96 tickLower -> sqrtRatioAX96 tick -> sqrtPriceX96 (token1 * (10^decimal1)) / (token0 * (10^decimal0)) = (sqrtPriceX96 * sqrtRatioBX96 * (sqrtPriceX96 - sqrtRatioAX96)) / ((2^192) * (sqrtRatioBX96 - sqrtPriceX96)) liquidityRatio = token1 / token0 = (sqrtPriceX96 * sqrtRatioBX96 * (sqrtPriceX96 - sqrtRatioAX96) * (10^decimal0)) / ((2^192) * (sqrtRatioBX96-sqrtPriceX96) * (10^decimal1)) ``` [Formula with decimal precision & decimal adjustment] ``` liquidityRatioWithDecimalAdj = liquidityRatio * (10^decimalPrecision) = (sqrtPriceX96 * sqrtRatioBX96 * (sqrtPriceX96 - sqrtRatioAX96) * (10^decimal0) * (10^decimalPrecision)) / ((2^192) * (sqrtRatioBX96 - sqrtPriceX96) * (10^decimal1)) = (sqrtPriceX96 * (10^decimalPrecision) / (2^96)) * (sqrtPriceBX96 * (10^decimalPrecision) / (2^96)) * (sqrtPriceX96 - sqrtRatioAX96) * (10^decimal0) / ((sqrtRatioBX96 - sqrtPriceX96) * (10^decimal1) * (10^decimalPrecision)) = (sqrtPriceX96 * (10^decimalPrecision) / (2^96)) * (sqrtPriceBX96 * (10^decimalPrecision) / (2^96)) * (sqrtPriceX96 - sqrtRatioAX96) / ((sqrtRatioBX96 - sqrtPriceX96) * (10^(decimalPrecision + decimal1 - decimal0))) decimalAdj = decimalPrecision + decimal1 - decimal0 scaledPriceX96 = sqrtPriceX96 * (10^decimalPrecision) / (2^96) scaledPriceBX96 = sqrtPriceBX96 * (10^decimalPrecision) / (2^96) ``` #### ==[Input Token0 Swap Amount Calculation]== [Basic formula] ``` {token0/token1 liquiduty ratio} = (inputAmount0 - token0SwapAmount - token0SwapAmount*txfee) / (token0SwapAmount * {token1/token0 price ratio}) ``` [Calculation] ``` {token0/token1 liquiduty ratio} * {token1/token0 price ratio} = {token1/token0 price ratio} / {token1/token0 liquiduty ratio} = (inputAmount0 - token0SwapAmount - token0SwapAmount*txfee) / (token0SwapAmount) = (inputAmount0/token0SwapAmount) - (1 + txfee) ({token1/token0 price ratio} / {token1/token0 liquiduty ratio}) + (1 + txfee) = (inputAmount0/token0SwapAmount) token0SwapAmount = (inputAmount0) / (({token1/token0 price ratio} / {token1/token0 liquiduty ratio}) + (1 + txfee)) token0SwapAmountWithTradeFee = token0SwapAmount * (1 + txfee) ``` #### ==[Input Token1 Swap Amount Calculation]== [Basic formula] ``` {token1/token0 liquiduty ratio} = (inputAmount1 - token1SwapAmount - token1SwapAmount*txfee) / (token1SwapAmount * {token0/token1 price ratio}) ``` [Calculation] ``` {token1/token0 liquiduty ratio} * {token0/token1 price ratio} = {token1/token0 liquiduty ratio} / {token1/token0 price ratio} = (inputAmount1 - token1SwapAmount - token1SwapAmount*txfee) / (token1SwapAmount) = (inputAmount1/token1SwapAmount) - (1 + txfee) ({token1/token0 liquiduty ratio} / {token1/token0 price ratio}) + (1 + txfee) = (inputAmount1/token1SwapAmount) token1SwapAmount = (inputAmount1) / (({token1/token0 liquiduty ratio} / {token1/token0 price ratio}) + (1 + txfee)) token1SwapAmountWithTradeFee = token1SwapAmount * (1 + txfee) ``` ::: --- ## Pre-defined Token and Path in Zap Contract ### Input Token | no. | token | address | |:---:|:-----:|:-------------------------------------------------------------------------------------------------------------------- | | 1 | BNB | native token | | 2 | WBNB | [0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c](https://bscscan.com/address/0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c) | | 3 | USDT | [0x55d398326f99059fF775485246999027B3197955](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | | 4 | BTCB | [0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c](https://bscscan.com/address/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c) | | 5 | BUSD | [0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56](https://bscscan.com/address/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56) | | 6 | CAKE | [0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82](https://bscscan.com/address/0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82) | | 7 | ETH | [0x2170Ed0880ac9A755fd29B2688956BD959F933F8](https://bscscan.com/address/0x2170Ed0880ac9A755fd29B2688956BD959F933F8) | | 8 | XRP | [0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE](https://bscscan.com/address/0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE) | | 9 | ADA | [0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47](https://bscscan.com/address/0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47) | | 10 | DOT | [0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402](https://bscscan.com/address/0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402) | | 11 | USDC | [0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | --- ### Output Token | no. | token | address | |:---:|:-----:|:-------------------------------------------------------------------------------------------------------------------- | | 1 | BNB | native token | | 2 | WBNB | [0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c](https://bscscan.com/address/0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c) | | 3 | USDT | [0x55d398326f99059fF775485246999027B3197955](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | | 4 | BTCB | [0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c](https://bscscan.com/address/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c) | | 5 | BUSD | [0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56](https://bscscan.com/address/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56) | | 6 | USDC | [0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | | 7 | ETH | [0x2170Ed0880ac9A755fd29B2688956BD959F933F8](https://bscscan.com/address/0x2170Ed0880ac9A755fd29B2688956BD959F933F8) | --- ### Swap Path & Fee Definition > reference: [(Zap) Token path research V3](https://hackmd.io/@JOYHSU/HyI26hw_n) #### **Single Swap** 1 Stable coin swap | no. | input token | output token | trade fee | |:---:|:-----------:|:------------:|:---------:| | 1 | USDT | BUSD | 0.01% | | 2 | BUSD | USDT | 0.01% | | 3 | BUSD | USDC | 0.01% | | 4 | USDC | BUSD | 0.01% | 2 Stable coin & Major coin swap | no. | input token | output token | trade fee | |:---:|:-----------:|:------------:|:---------:| | 1 | WBNB | USDT | 0.05% | | 2 | WBNB | BUSD | 0.05% | | 3 | USDT | WBNB | 0.05% | | 4 | USDT | BTCB | 0.05% | | 5 | USDT | USDC | 0.05% | | 6 | BTCB | USDT | 0.05% | | 7 | BTCB | BUSD | 0.05% | | 8 | ETH | USDC | 0.05% | | 9 | BUSD | WBNB | 0.05% | | 10 | BUSD | BTCB | 0.05% | | 11 | USDC | USDT | 0.05% | | 12 | USDC | ETH | 0.05% | 3 Others (0.25%) | no. | input token | output token | trade fee | |:---:|:-----------:|:------------:|:---------:| | 1 | WBNB | BTCB | 0.25% | | 2 | WBNB | ETH | 0.25% | | 3 | CAKE | WBNB | 0.25% | | 4 | CAKE | USDT | 0.25% | | 5 | CAKE | BUSD | 0.25% | | 6 | BTCB | WBNB | 0.25% | | 7 | BTCB | ETH | 0.25% | | 8 | ETH | WBNB | 0.25% | | 9 | ETH | BTCB | 0.25% | | 10 | XRP | WBNB | 0.25% | | 11 | ADA | WBNB | 0.25% | | 12 | DOT | WBNB | 0.25% | #### **Multiple Swap** | no. | input token | output token | swap path 1 | fee 1 | swap path 2 | fee 2 | swap path 3 | fee 3 | |:---:|:-----------:|:------------:|:------------ |:-----:|:------------ |:-----:|:------------ |:----- | | 1 | WBNB | USDC | WBNB -> BUSD | 0.05% | BUSD -> USDC | 0.01% | | | | 2 | CAKE | BTCB | CAKE -> USDT | 0.25% | USDT -> BTCB | 0.05% | | | | 3 | CAKE | USDC | CAKE -> BUSD | 0.25% | BUSD -> USDC | 0.01% | | | | 4 | CAKE | ETH | CAKE -> WBNB | 0.25% | WBNB -> ETH | 0.25% | | | | 5 | USDT | ETH | USDT -> USDC | 0.05% | USDC -> ETH | 0.05% | | | | 6 | BTCB | USDC | BTCB -> BUSD | 0.05% | BUSD -> USDC | 0.01% | | | | 7 | ETH | USDT | ETH -> USDC | 0.05% | USDC -> USDT | 0.05% | | | | 8 | ETH | BUSD | ETH -> USDC | 0.05% | USDC -> BUSD | 0.01% | | | | 9 | BUSD | ETH | BUSD -> USDC | 0.01% | USDC -> ETH | 0.05% | | | | 10 | XRP | USDT | XRP -> WBNB | 0.25% | WBNB -> USDT | 0.05% | | | | 11 | XRP | BTCB | XRP -> WBNB | 0.25% | WBNB -> BTCB | 0.25% | | | | 12 | XRP | BUSD | XRP -> WBNB | 0.25% | WBNB -> BUSD | 0.05% | | | | 13 | XRP | USDC | XRP -> WBNB | 0.25% | WBNB -> BUSD | 0.05% | BUSD -> USDC | 0.01% | | 14 | XRP | ETH | XRP -> WBNB | 0.25% | WBNB -> ETH | 0.25% | | | | 15 | ADA | USDT | ADA -> WBNB | 0.25% | WBNB -> USDT | 0.05% | | | | 16 | ADA | BTCB | ADA -> WBNB | 0.25% | WBNB -> BTCB | 0.25% | | | | 17 | ADA | BUSD | ADA -> WBNB | 0.25% | WBNB -> BUSD | 0.05% | | | | 18 | ADA | USDC | ADA -> WBNB | 0.25% | WBNB -> BUSD | 0.05% | BUSD -> USDC | 0.01% | | 19 | ADA | ETH | ADA -> WBNB | 0.25% | WBNB -> ETH | 0.25% | | | | 20 | DOT | USDT | DOT -> WBNB | 0.25% | WBNB -> USDT | 0.05% | | | | 21 | DOT | BTCB | DOT -> WBNB | 0.25% | WBNB -> BTCB | 0.25% | | | | 22 | DOT | BUSD | DOT -> WBNB | 0.25% | WBNB -> BUSD | 0.05% | | | | 23 | DOT | USDC | DOT -> WBNB | 0.25% | WBNB -> BUSD | 0.05% | BUSD -> USDC | 0.01% | | 24 | DOT | ETH | DOT -> WBNB | 0.25% | WBNB -> ETH | 0.25% | | | | 25 | USDC | WBNB | USDC -> BUSD | 0.01% | BUSD -> WBNB | 0.05% | | | | 26 | USDC | BTCB | USDC -> BUSD | 0.01% | BUSD -> BTCB | 0.05% | | | --- ## Main Functions in Zap Contract ### Variables :::warning **Constant Variable** | Variable | Address | |:---------------- |:------------------------------------------ | | smartRouter | 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4 | | pancakeV3Factory | 0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865 | | Variable | uint24 | |:---------------------------- |:--------- | | slippageToleranceDenominator | 1,000,000 | | swapTradeFeeDenominator | 1,000,000 | ::: :::warning ``` variable with * means operator adjustable ``` **Swap-relatd Variable** | Variable | unit | Fixed | Explanation | |:--------------------------- |:------------------------------------------------- |:-----:|:---------------------------------------------- | | slippageToleranceNumerator* | uint24 | X | slippage tolerance numerator | | swapPath* | mapping(address => mapping(address => address[])) | X | inputToken, outputToken, swapPath | | swapTradeFeeNumerator* | mapping(address => mapping(address => uint24)) | X | inputToken, outputToken, swapTradeFeeNumerator | ::: --- ### Get Swap Info **Function Parameters** :::info address **inputToken** address **outputToken** ::: **Step** :::info **Parameter Verification** > **Verify inputToken & outputToken is valid** > ( inputToken != address(0) && outputToken != address(0) ) **Verification** > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) **Query Swap Data** > **Condition 1** > If Swap Path Length is 2 & Swap Trade Fee is available, > it's single swap path > > **Condition 2** > If Swap Path Length is more than 2 & Swap Trade Fee are available, > it's multiple swap path > > **Condition 3** > If Swap Path Length is less than 2 or Swap Trade Fee is not available, > it's path not defined ::: **Return** :::success bool **isPathDefined** address[] **swapPathArray** uint24[] **swapTradeFeeArray** ::: * if **isPathDefined == false**, it'll return empty swapPath & empty swapTradeFee or swapPath & at least a 0 swapTradeFee * if **isPathDefined == true** and it's **single swap path**, it'll return 2 address in swapPath & 1 fee in swapTradeFee * if **isPathDefined == true** and it's **multiple swap path**, it'll return more than 2 address in swapPath & more than 1 fee in swapTradeFee --- ### Set Swap Trade Fee Numerator **Function Parameters** :::info address **inputToken** address **outputToken** uint24 **swapTradeFee** ::: **Steps** :::info **Parameter Verification** > **Verify inputToken & outputToken is valid** > ( inputToken != address(0) && outputToken != address(0) ) > > **Verify swapTradeFee is more than 0** > ( swapTradeFee > 0 ) **Verification** > **Verify caller is valid** > ( only owner can modify ) > > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify pool is exist** > ( pancakeV3Factory getPool != address(0) ) **Update Trade Fee** > **Update swapTradeFeeNumerator** > ( swapTradeFeeNumerator[inputToken][outputToken] = swapTradeFee ) ::: **Events** :::success ``` UpdateSwapTradeFee ( address indexed inputToken, address indexed outputToken, uint24 swapTradeFee ) ``` ::: --- ### Set Swap Path **Function Parameters** :::info address **inputToken** address **outputToken** address[] **newSwapPath** ::: **Steps** :::info **Parameter Verification** > **Verify inputToken & outputToken is valid** > ( inputToken != address(0) && outputToken != address(0) ) > > **Verify element in newSwapPath is valid** > ( element in newSwapPath != address(0) ) **Verification** > **Verify caller is valid** > ( only owner can modify ) > > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify input path is valid swap path** > ( newSwapPath.length >= 2 ) > > **Verify first token in newSwapPath is inputToken** > > **Verify last token in newSwapPath is outputToken** > > **Verify each swap's fee is defined** > ( swapTradeFee for each swap != 0 ) **Update Swap Path** > **Delet existing swapPath** > ( delete swapPath[inputToken][outputToken] ) > > **Set new swapPath** > ( swapPath[inputToken][outputToken] = newSwapPath ) ::: **Events** :::success ``` UpdateSwapPath ( address indexed inputToken, address indexed outputToken, address[] newSwapPath ) ``` ::: --- ### Get Token Exchange Rate *only available for token pair that having existing pancakeswap V3 pool* **Function Parameters** :::info address **inputToken** address **outputToken** ::: **Steps** :::info **Parameter Verification** > **Verify inputToken & outputToken is valid** > ( inputToken != address(0) && outputToken != address(0) ) **Verification** > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify swap trade fee is defined** > ( swapTradeFee[inputToken][outputToken] != 0 ) > > **Verify pool is exist** > ( pancakeV3Factory getPool != address(0) ) **Query Pool Date** > **Get sqrtPriceX96** > ( PancakeV3Pool slot0 get sqrtPriceX96 ) > > **Get Token Decimal** > ( ERC20 decimal get decimal0, decimal1 ) **Calculate Token Price** > **Calculate token price with 18 decimal precision** > ( reference: [uniswap-v3-math-primer](https://blog.uniswap.org/uniswap-v3-math-primer) ) ::: **Return** :::success address **token0** address **token1** uint256 **tokenPriceWith18Decimals** ::: return value = (token1 / token0) with 18 decimal precision --- ### Get Minimum Swap Out Amount **Function Parameters** :::info address **inputToken** address **outputToken** uint256 **inputAmount** ::: **Steps** :::info **Parameter Verification** > **Verify inputToken & outputToken is valid** > ( inputToken != address(0) && outputToken != address(0) ) > > **Verify inputAmount is more than 0** > ( inputAmount > 0 ) **Variable Verification** > **Verify related variables are well set up** > ( slippageToleranceNumerator > 0 ) **Verification** > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify swap info is defined** > ( getSwapInfo(inputToken, outputToken), isPathDefined == true ) **Get Swap Path Length** > **Get swap path length as loop end index** > ( getSwapInfo(inputToken, outputToken), swapPath.length ) **Input Token Decimal Adjustment** > **Adjust input token decimal from original to 18 decimal** **Loop Calculate Swap Out Amount Without Slippage** > **Get token exchange rate, token0 and token1 in pool** > ( getTokenExcahnegRate(inputToken, outputToken) ) > > **Deduct trade fee** > ( Really inputAmount is > inputAmount * > (swapTradeFeeDenominator-swapTradeFeeNumerator) / > swapTradeFeeDenominator ) > ( Really inputAmount for trade fee 0.01% is inputAmount * 99.99% ) > ( Really inputAmount for trade fee 0.05% is inputAmount * 99.95% ) > ( Really inputAmount for trade fee 0.25% is inputAmount * 99.75% ) > > **Get swap out amount without slippage** > ( tokenPrice = token1 / token0 ) > ( if inputToken == token0, > swapOutAmount is inputAmount * tokenPrice ) > ( if inputToken == token1, > swapOutAmount is inputAmount / tokenPrice ) **Output Token Decimal Adjustment** > **Adjust output token decimal from 18 to original decimal** **Calculate Price With Slippage** > **Calculate price include slippage tolerance** > ( Really outputAmount is > outputAmount * > (slippageToleranceDenominator-slippageToleranceNumerator) / > slippageToleranceDenominator ) > ( Really outputAmount for slippage tolerance 0.5% is outputAmount * 99.5% ) ::: **Return** :::success uint256 **minimumSwapOutAmount** ::: --- ### Get Estimate Swap Out Amount **Function Parameters** :::info address **inputToken** address **outputToken** uint256 **inputAmount** ::: **Steps** :::info **Parameter Verification** > **Verify inputToken & outputToken is valid** > ( inputToken != address(0) && outputToken != address(0) ) > > **Verify inputAmount is more than 0** > ( inputAmount > 0 ) **Variable Verification** > **Verify related variables are well set up** > ( slippageToleranceNumerator > 0 ) **Verification** > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify swap info is defined** > ( getSwapInfo(inputToken, outputToken), isPathDefined == true ) **Get Swap Path Length** > **Get swap path length as loop end index** > ( getSwapInfo(inputToken, outputToken), swapPath.length ) **Input Token Decimal Adjustment** > **Adjust input token decimal from original to 18 decimal** **Loop Calculate Swap Out Amount Without Slippage** > **Get token exchange rate, token0 and token1 in pool** > ( getTokenExcahnegRate(inputToken, outputToken) ) > > **Deduct trade fee** > ( Really inputAmount is > inputAmount * > (swapTradeFeeDenominator-swapTradeFeeNumerator) / > swapTradeFeeDenominator ) > ( Really inputAmount for trade fee 0.01% is inputAmount * 99.99% ) > ( Really inputAmount for trade fee 0.05% is inputAmount * 99.95% ) > ( Really inputAmount for trade fee 0.25% is inputAmount * 99.75% ) > > **Get swap out amount without slippage** > ( tokenPrice = token1 / token0 ) > ( if inputToken == token0, > swapOutAmount is inputAmount * tokenPrice ) > ( if inputToken == token1, > swapOutAmount is inputAmount / tokenPrice ) **Output Token Decimal Adjustment** > **Adjust output token decimal from 18 to original decimal** ::: **Return** :::success uint256 **estimateSwapOutAmount** ::: --- ### Swap Token **Function Parameters** :::info bool **isBNB** address **inputToken** address **outputToken** uint256 **inputAmount** address **recipient** ::: Caller preparation ``` if isBNB == true, user needs to transfer BNB to zap contract if isBNB == false, caller need to approve inputToken to zap contract in inputAmount amount ``` **Steps** 1 common :::info **Parameter Verification** > **Verify inputToken & outputToken & recipient is valid** > ( inputToken != address(0) && outputToken != address(0) & recipient != address(0) ) > > **Verify inputAmount is more than 0** > ( inputAmount > 0 ) **Verification** > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify swap info is defined** > ( getSwapInfo(inputToken, outputToken), isPathDefined == true ) > > **Verify sufficient inputAmount** > ( if isBNB == true, > verify inputToken == WBNB& msg.value == inputAmount, > if isBNB == false, > verify caller inputToken allowance >= inputAmount ) **Prepare Swap Token** > **Prepare swap token** > ( if isBNB == true, > wrap BNB to WBNB by WBNB deposit function > if isBNB == false, > safeTransferFrom caller to zap contract ) **Approve SmartRouter** > **Approve inputToken to SmartRouter in inputAmount amount** > ( approve(smart router, inputAmount) ) **Get Minimum Swap Out Amount** > **Get minimum slippage tolerance swap out amount** > ( getMinimumSwapOutAmount(inputToken, outputToken, inputAmount) ) ::: 2-1 single swap :::info **Verification** > **Verify swapPath length is 2** > ( getSwapInfo(inputToken, outputToken), swapPath.length == 2 ) **Swap token by smart router** > **Swap by exactInputSingle function** > ( amountOutMinimum is minimumSwapOutAmount ) ::: **Events** :::success ``` SingleSwap ( address indexed recipient, bool isBNB, address inputToken, uint256 inputAmount, address outputToken, uint256 outputAmount, address[] swapPath, uint24[] swapTradeFee ) ``` ::: 2-2 multi swap :::info **Verification** > **Verify swapPath Length is more than 2** > ( getSwapInfo(inputToken, outputToken), swapPath.length > 2 ) **Encode Multi Swap Path** > **Encode multi swap path by abi.encodePacked** > ( reference: [Uniswap V3 Swap Examples](https://solidity-by-example.org/defi/uniswap-v3-swap/) ) **Swap token by smart router** > **Swap by exactInput function** > ( amountOutMinimum is minimumSwapOutAmount ) ::: **Events** :::success ``` MultiSwap ( address indexed recipient, bool isBNB, address inputToken, uint256 inputAmount, address outputToken, uint256 outputAmount, address[] swapPath, uint24[] swapTradeFee ) ``` ::: **Return** :::success uint256 **outputAmount** ::: --- ### Swap Token With Minimum Output :::danger **sandwich attack prevention** * add parameter uint256 **minimumSwapOutAmount** * remove internal calculation minimum swap out amount remove **Get Minimum Swap Out Amount** ::: **Function Parameters** :::info bool **isBNB** address **inputToken** address **outputToken** uint256 **inputAmount** uint256 **minimumSwapOutAmount** address **recipient** ::: Caller preparation ``` if isBNB == true, user needs to transfer BNB to zap contract if isBNB == false, caller need to approve inputToken to zap contract in inputAmount amount ``` **Steps** 1 common :::info **Parameter Verification** > **Verify inputToken & outputToken & recipient is valid** > ( inputToken != address(0) && outputToken != address(0) & recipient != address(0) ) > > **Verify inputAmount is more than 0** > ( inputAmount > 0 ) **Verification** > **Verify inputToken is not outputToken** > ( inputToken != outputToken ) > > **Verify swap info is defined** > ( getSwapInfo(inputToken, outputToken), isPathDefined == true ) > > **Verify sufficient inputAmount** > ( if isBNB == true, > verify inputToken == WBNB& msg.value == inputAmount, > if isBNB == false, > verify caller inputToken allowance >= inputAmount ) **Prepare Swap Token** > **Prepare swap token** > ( if isBNB == true, > wrap BNB to WBNB by WBNB deposit function > if isBNB == false, > safeTransferFrom caller to zap contract ) **Approve SmartRouter** > **Approve inputToken to SmartRouter in inputAmount amount** > ( approve(smart router, inputAmount) ) ::: 2-1 single swap :::info **Verification** > **Verify swapPath length is 2** > ( getSwapInfo(inputToken, outputToken), swapPath.length == 2 ) **Swap token by smart router** > **Swap by exactInputSingle function** > ( amountOutMinimum is minimumSwapOutAmount ) ::: **Events** :::success ``` SingleSwap ( address indexed recipient, bool isBNB, address inputToken, uint256 inputAmount, address outputToken, uint256 outputAmount, address[] swapPath, uint24[] swapTradeFee ) ``` ::: 2-2 multi swap :::info **Verification** > **Verify swapPath Length is more than 2** > ( getSwapInfo(inputToken, outputToken), swapPath.length > 2 ) **Encode Multi Swap Path** > **Encode multi swap path by abi.encodePacked** > ( reference: [Uniswap V3 Swap Examples](https://solidity-by-example.org/defi/uniswap-v3-swap/) ) **Swap token by smart router** > **Swap by exactInput function** > ( amountOutMinimum is minimumSwapOutAmount ) ::: **Events** :::success ``` MultiSwap ( address indexed recipient, bool isBNB, address inputToken, uint256 inputAmount, address outputToken, uint256 outputAmount, address[] swapPath, uint24[] swapTradeFee ) ``` ::: **Return** :::success uint256 **outputAmount** ::: --- ## Main Functions for BNB & WBNB Swap WBNB contract: [0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c](https://bscscan.com/address/0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c) WBNB testnet contract: [0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd](https://testnet.bscscan.com/address/0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd) ### Swap BNB to WBNB interact with WBNB contract deposit function directly (with msg.value) ``` function deposit() public payable { balanceOf[msg.sender] += msg.value; Deposit(msg.sender, msg.value); } ``` --- ### Swap WBNB to BNB interact with WBNB contract withdraw function directly ``` function withdraw(uint wad) public { require(balanceOf[msg.sender] >= wad); balanceOf[msg.sender] -= wad; msg.sender.transfer(wad); Withdrawal(msg.sender, wad); } ```