make a swap using pancake smart router in solidity contract

overview

In this article i gonna show you how to make a swap using pancakeswap smart router in solidity contract.Here wo go. In this example i swap BUSD token to CAKE.Here is the address of each token:

soldity

Let’s go dive into the deep.Firstly we need set a swap amount, eg:0.01 BUSD, an wed need a min out amount as well.The last parameter is path which means the path of swap tokens.When we swap busd to cake we can find a pool consist of busd and cake。So the path is simple.For some other tokens maybe the path is a litte bit longer.We have to use two or three or more pools to get the token we want. In the pancakeswap front-end page , it returns a path before we make the swap. In the solidity code we have to do it ourself.We have to get the optimal path before we make the swap.Let’s dive into the pancakeswap smart router code.

1
2
3
4
5
interface PancakeRouter {
function swapExactTokensForTokens(uint256 amountIn,uint256 amountOutMin,address[] calldata path,address to,uint256 deadline) external;
function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256 amountIn,uint256 amountOutMin,address[] calldata path,address to,uint256 deadline) external;
function factory() external pure returns (address);
}

There are so many swap function we can use to help use swap tokens. In this demo we use swapExactTokensForTokens. As you see there is a function named swapExactTokensForTokensSupportingFeeOnTransferTokens. We need to know what’s the difference between this two functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
// transfer token from sender address to pool[0]
TransferHelper.safeTransferFrom(
path[0], msg.sender, PancakeLibrary.pairFor(factory, path[0], path[1]), amountIn
);

// the balance of des token that receiver hold.
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);

// make the sawp.
_swapSupportingFeeOnTransferTokens(path, to);

// make sure the out token is bigger than the set amountOutMin value.
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'PancakeRouter: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
// get the out amount using PancakeLibrary via amountIn.
amounts = PancakeLibrary.getAmountsOut(factory, amountIn, path);

// after calculate all result make sure the out amount is bigger than amountOutMin.
require(amounts[amounts.length - 1] >= amountOutMin, 'PancakeRouter: INSUFFICIENT_OUTPUT_AMOUNT');

//transfer code.
TransferHelper.safeTransferFrom(
path[0], msg.sender, PancakeLibrary.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}

After compare the above two functions we know the difference. In the swapExactTokensForTokens function the exact transfer amount is set by PancakeLibrary getAmountsOut. And in this article we use swapExactTokensForTokensSupportingFeeOnTransferTokens.
Here is the entire code from github gist : code

deploy our code

Deploy code:

1
2
3
4
5
6
7
8
9
10
11
12
13
const hre = require("hardhat");

async function main() {
const Swap = await hre.ethers.getContractFactory("PancakeSwap");
const swap = await Swap.deploy();
await swap.deployed();
console.log(swap.address);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
1
npx hardhat run script/scriptName.js --network YOUR_NETWORK_NAME.

test swap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const hre = require("hardhat");

async function main() {
const Bundle = await hre.ethers.getContractFactory("PancakeSwap");
const bundle = Bundle.attach(YOUR_DEPLOYED_CONTRACT_ADDRESS);
const amountIn = hre.ethers.utils.parseEther("0.01");
const amountOutMin = 0;
const tokenSwapPath = [TOKEN1_ADDRESS, TOKEN2_ADDRESS];
let ret = await bundle.SimpleSwap(amountIn, amountOutMin, tokenSwapPath);
console.log(ret);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

pancakeswap-reserve-calculate

pancakeswap 如何通过 reserve 计算 amountOut 金额

无常损失

当你成了一个 AMM 的 LP(流动性提供者)的时候,那你的收益部分就来自于无常损失。
同时无常损失也不是永久性的,价格经过短暂的下跌之后又重新恢复。为什么会在短时间内恢复呢?正式通过一个个搬砖的人监控不同池子中的价格差价进行套利操作后使得不同池子中的价格达到一个平衡。无常损失的核心公式:

$$
K = A * B
$$

假设我们的池子中有两种货币 token0 和 token1,A 代表 token0 在池子中的剩余数量,B 代表 token1 在池子中的剩余数量,而 K 的值在池子创建的时候就固定了。并且在流动性变化的过程中 K 的值不是完全相等的,而是会有细微的偏差但是不会太大。
当价格发生变化的时候,价格我们兑换数量 amountIn 的 token0,为了使得 K 的值是相等的会得到一下的公式

$$
K = (A+amountIn)(B-amountOut)
$$

$$
amountOut = B - K/(A+amountIn)
$$

举个例子

下面我们拿一个 pancakeswap 的池子来实际算一下这个 amountOut(我们预估能得到的金额)。
为了测试方便我们拿了 bsc 测试网的池子,包含了 BUSD(0xab1a4d4f1d656d2450692d237fdd6c7f9146e814)和 WBNB(0xae13d989dac2f0debff460ac112a837c89baa7cd)两种货币。
pool address
通过 contract 的 ABI 可以看到相关的几个参数:

  • kLast
  • token0
  • token1
  • getReserves
    我们也可以直接通过 UI 界面找到相关的参数
    alt "ABI"

代码实现

这里我通过 python 读取了相关池子中的参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from web3 import Web3
import json

# init w3 provider
# w3 = Web3(Web3.HTTPProvider("https://bsc-dataseed.binance.org"))
w3 = Web3(Web3.HTTPProvider(TESTNET_PROVIDER_ADDRESS_HERE))


def fetchPool(poolAddress):
with open("./abi/pancake.json", 'r') as json_file:
contract = w3.eth.contract(
address=poolAddress, abi=json.load(json_file))
reserves = contract.functions.getReserves().call()
# arr [reserve0, reserve1, timestamp]
return reserves

# 费用
fee = 0.0025

arr = fetchPool("0xa96818CA65B57bEc2155Ba5c81a70151f63300CD")
# token0 剩余数量
r0 = arr[0]
# token1 剩余数量
r1 = arr[1]
# K的值
k = r0 * r1
amountIn = 10**16
price1 = r1 - k/(r0+amountIn*(1-fee))
print(price1/10**18)
# output
# 606895316154

alt "ABI"
对于 pancakeswap 而言我们最终的输入价格 amountIn 要减掉一个 fee,pancakeswap UI 上预估的价格和计算的价格是对的上的。