如果你还不熟悉Uniswap[4],它是一个去中心化的交易所(DEX),依靠外部流动性提供者将代币添加到流动池配对中,用户可以直接交易这些代币。
由于它在以太坊上运行,可以交易的是以太坊 ERC-20 代币。每种代币都有自己的智能合约和流动资金池。Uniswap--作为完全的去中心化--对哪些代币可以添加没有限制。如果一个代币对还没有流动池合约存在,任何人都可以 Uniswap 的工厂创建一个,任何人都可以向池子提供流动性。每笔交易有 0.3%的费用给流动性提供者作为奖励。
代币的价格是由池中的流动性决定的。例如,如果一个用户用TOKEN2购买TOKEN1,池中TOKEN1的供应将减少,而TOKEN2的供应将增加,TOKEN1的价格将增加。同样地,如果一个用户正在出售TOKEN1,TOKEN1的价格将下降。因此,代币价格总是反映了供需关系。
当然,用户不一定是人,也可以是一个智能合约。这使得可以将 Uniswap 添加到我们自己的合约中,为我们合约的用户增加额外的支付选项。Uniswap 使这个过程非常方便,请看下面的整合方法。
Uniswap UI
之前有一篇文章[5]讨论了 Uniswap v2 的新内容,现在让我们看看 Uniswap v3 的新内容。
Uniswap 如此受欢迎的原因之一可能是将它们整合到自己的智能合约中的非常简单。比方说,你有一个系统,用户用 DAI 支付。有了 Uniswap,只需几行代码,你就可以增加他们也可以用 ETH 支付的选项。ETH 可以在实际逻辑之前自动转换为 DAI。它看起来像这样:
function pay(uint paymentAmountInDai) public payable {
if (msg.value > 0) {
convertEthToExactDai(paymentAmountInDai);
} else {
require(daiToken.transferFrom(msg.sender, address(this), paymentAmountInDai);
}
// do something with that DAI
...
}
在你的函数的开头做一个简单的检查就足够了。现在,对于convertEthToExactDai
函数,它将看起来像这样的东西。
function convertEthToExactDai(uint256 daiAmount) external payable {
require(daiAmount > 0, "Must pass non 0 DAI amount");
require(msg.value > 0, "Must pass non 0 ETH amount");
uint256 deadline = block.timestamp + 15; // using 'now' for convenience, for mainnet pass deadline from frontend!
address tokenIn = WETH9;
address tokenOut = multiDaiKovan;
uint24 fee = 3000;
address recipient = msg.sender;
uint256 amountOut = daiAmount;
uint256 amountInMaximum = msg.value;
uint160 sqrtPriceLimitX96 = 0;
ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams(
tokenIn,
tokenOut,
fee,
recipient,
deadline,
amountOut,
amountInMaximum,
sqrtPriceLimitX96
);
uniswapRouter.exactOutputSingle{ value: msg.value }(params);
uniswapRouter.refundETH();
// refund leftover ETH to user
(bool success,) = msg.sender.call{ value: address(this).balance }("");
require(success, "refund failed");
}
这里有几件事情需要解读。
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564)
为任何主网或测试网实例化它。接口代码可以在这里[9]找到。receive() payable external {}
。deadline
参数控制交易有效期。确保从你的前端传递这个 UNIX 时间戳,不要在合约内使用now
。我们现在遇到的一个问题是,当用户调用支付函数并想用 ETH 支付时,不知道他需要多少 ETH。我们可以使用quoteExactOutputSingle[11]函数来精确计算:
function getEstimatedETHforDAI(uint daiAmount) external payable returns (uint256) {
address tokenIn = WETH9;
address tokenOut = multiDaiKovan;
uint24 fee = 500;
uint160 sqrtPriceLimitX96 = 0;
return quoter.quoteExactOutputSingle(
tokenIn,
tokenOut,
fee,
daiAmount,
sqrtPriceLimitX96
);
}
但是请注意,我们没有把它声明为视图函数,但是不要在链上调用这个函数。尽管它可以作为一个视图函数来调用的,但它会采用非视图方式(底层)来获得计算结果。由于 Solidity 的特性,所以这里也不可能将它本身声明为一个视图函数,仅能使用场景如 Web3 的 call()[12] 功能来读取前端的结果。
现在我们可以在前端调用getEstimatedETHforDAI
。为了确保我们发送了足够的 ETH,并且交易不会被退回,我们可以将估计的 ETH 数量增加一点。
const requiredEth = (await myContract.getEstimatedETHforDAI(daiAmount).call())[0];
const sendEth = requiredEth * 1.1;
在这种情况下,你可以使用exactInput[13]和exactOutput[14]函数,它以path
为参数。这个路径是代币地址的字节编码数据(为了 Gas 效率而编码)。
任何兑换都需要有一个开始和结束的路径。虽然在 Uniswap 中,你可以有代币 1 到代币 2 的兑换,但不一定能保证这样一个池子真的存在。但是,只要你能找到一条路径,你仍然可以交易它们,例如,Token1 → Token2 → WETH → Token3。在这种情况下,你仍然可以用 Token1 换 Token3,只是比直接兑换要多花一点 gas。
在下边你可以看到[Uniswap 示例代码](https://soliditydeveloper.com/path frontend: https://github.com/Uniswap/uniswap-v3-periphery/blob/9ca9575d09b0b8d985cc4d9a0f689f7a4470ecb7/test/shared/path.ts "Uniswap 示例代码"),了解如何在前端计算这个路径:
function encodePath(tokenAddresses, fees) {
const FEE_SIZE = 3
if (path.length != fees.length + 1) {
throw new Error('path/fee lengths do not match')
}
let encoded = '0x'
for (let i = 0; i < fees.length; i++) {
// 20 byte encoding of the address
encoded += path[i].slice(2)
// 3 byte encoding of the fee
encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0')
}
// encode the final token
encoded += path[path.length - 1].slice(2)
return encoded.toLowerCase()
}
这里有一个完全可用的例子,你可以直接在 Remix 上使用。它允许你用 ETH 交易Multi-collaterized Kovan DAI[15],它还包括exactOutputSingle[16]的替代方案,即exactInputSingle[17],允许你用 ETH 换取多少 DAI,你就能得到多少。
// SPDX-License-Identifier: MIT
pragma solidity =0.7.6;
pragma abicoder v2;
import "https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol";
import "https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/IQuoter.sol";
interface IUniswapRouter is ISwapRouter {
function refundETH() external payable;
}
contract Uniswap3 {
IUniswapRouter public constant uniswapRouter = IUniswapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
IQuoter public constant quoter = IQuoter(0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6);
address private constant multiDaiKovan = 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa;
address private constant WETH9 = 0xd0A1E359811322d97991E03f863a0C30C2cF029C;
function convertExactEthToDai() external payable {
require(msg.value > 0, "Must pass non 0 ETH amount");
uint256 deadline = block.timestamp + 15; // using 'now' for convenience, for mainnet pass deadline from frontend!
address tokenIn = WETH9;
address tokenOut = multiDaiKovan;
uint24 fee = 3000;
address recipient = msg.sender;
uint256 amountIn = msg.value;
uint256 amountOutMinimum = 1;
uint160 sqrtPriceLimitX96 = 0;
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams(
tokenIn,
tokenOut,
fee,
recipient,
deadline,
amountIn,
amountOutMinimum,
sqrtPriceLimitX96
);
uniswapRouter.exactInputSingle{ value: msg.value }(params);
uniswapRouter.refundETH();
// refund leftover ETH to user
(bool success,) = msg.sender.call{ value: address(this).balance }("");
require(success, "refund failed");
}
function convertEthToExactDai(uint256 daiAmount) external payable {
require(daiAmount > 0, "Must pass non 0 DAI amount");
require(msg.value > 0, "Must pass non 0 ETH amount");
uint256 deadline = block.timestamp + 15; // using 'now' for convenience, for mainnet pass deadline from frontend!
address tokenIn = WETH9;
address tokenOut = multiDaiKovan;
uint24 fee = 3000;
address recipient = msg.sender;
uint256 amountOut = daiAmount;
uint256 amountInMaximum = msg.value;
uint160 sqrtPriceLimitX96 = 0;
ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams(
tokenIn,
tokenOut,
fee,
recipient,
deadline,
amountOut,
amountInMaximum,
sqrtPriceLimitX96
);
uniswapRouter.exactOutputSingle{ value: msg.value }(params);
uniswapRouter.refundETH();
// refund leftover ETH to user
(bool success,) = msg.sender.call{ value: address(this).balance }("");
require(success, "refund failed");
}
// do not used on-chain, gas inefficient!
function getEstimatedETHforDAI(uint daiAmount) external payable returns (uint256) {
address tokenIn = WETH9;
address tokenOut = multiDaiKovan;
uint24 fee = 3000;
uint160 sqrtPriceLimitX96 = 0;
return quoter.quoteExactOutputSingle(
tokenIn,
tokenOut,
fee,
daiAmount,
sqrtPriceLimitX96
);
}
// important to receive ETH
receive() payable external {}
}
一旦你执行这些函数并在 Etherscan 中查看它们,区别就会立即变得很明显。这里我们是用 exactOutput 进行交易。我们提供 1 个 ETH,希望收到 100 个 DAI 作为回报。任何多余的 ETH 都会退还给我们。
以准确的DAI购买
而下面,我们正在使用 exactInput 进行交易。我们提供 1 个 ETH,并希望得到多少 DAI,而这恰好是 196 个 DAI。
用精确的ETH购买
请注意,如果你困惑为什么价格会如此不同,这是测试网的一个小池子,第一个交易严重影响了池子里的价格。没有多少人在测试网中进行套利交易 :)
本翻译由 Cell Network[18] 赞助支持。
来源:https://soliditydeveloper.com/uniswap3
[1]
登链翻译计划: https://github.com/lbc-team/Pioneer
[2]
翻译小组: https://learnblockchain.cn/people/412
[3]
Tiny 熊: https://learnblockchain.cn/people/15
[4]
Uniswap: https://uniswap.exchange/
[5]
一篇文章: https://soliditydeveloper.com/uniswap2
[6]
v3自2021年5月5日起在主网上运行: https://uniswap.org/blog/launch-uniswap-v3/
[7]
文件: https://docs.uniswap.org/
[8]
白皮书: https://uniswap.org/whitepaper-v3.pdf
[9]
这里: https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol
[10]
exactOutputSingle: https://docs.uniswap.org/reference/periphery/interfaces/ISwapRouter#exactoutputsingle
[11]
quoteExactOutputSingle: https://docs.uniswap.org/reference/periphery/interfaces/IQuoter#quoteexactoutputsingle
[12]
call(): https://web3js.readthedocs.io/en/v1.3.4/web3-eth-contract.html#methods-mymethod-call
[13]
exactInput: https://docs.uniswap.org/reference/periphery/interfaces/ISwapRouter#exactinput
[14]
exactOutput: https://docs.uniswap.org/reference/periphery/interfaces/ISwapRouter#exactoutput
[15]
Multi-collaterized Kovan DAI: https://oasis.app/borrow?network=kovan
[16]
exactOutputSingle: https://docs.uniswap.org/reference/periphery/interfaces/ISwapRouter#exactoutputsingle
[17]
exactInputSingle: https://docs.uniswap.org/reference/periphery/interfaces/ISwapRouter#exactinputsingle
[18]
Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain