We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在前面的两篇文章中,我们已经已经实现了Exchange合约的所有核心机制,包括定价功能、交换、LP代币和费用。看起来已经比较完善了,但是还缺少了一部分:工厂合约。本篇文章,我们就来实现它。
事实上工厂合约充当的角色是Exchange合约的注册表:每个新部署的Exchange合约都在工厂中注册。这是一个重要的机制:任何Exchange合约都可以通过查询注册表找到。通过注册表,当用户尝试将一个Token代币交换为另一个Token代币(而不是以太币)的时候,Exchange合约可以通过工厂合约找到其他的Exchange合约。
工厂合约提供的另一个比较实用的功能是能够方便的部署Exchange合约而无需处理代码、节点、部署脚本和任何其他开发工具。
Factory实现了一个功能,允许用户只需调用该函数即可创建和部署Exchange合约。所以,今天我们还将学习一个合约如何部署另一个合约。
Uniswap 只有一份工厂合约,因此只有一份 Uniswap 交易对的注册表。然而,没有什么可以阻止其他用户部署自己的工厂,甚至是未在官方工厂注册的Exchange合约。虽然这是可能的,但这样的交易所不会被 Uniswap 认可,也无法通过官方网站使用它们来交换代币。
让我们看看代码如何实现:
工厂合约本质上是一个注册表,我们需要一个数据结构来存储Exchange合约,这应该是一个地址到地址的映射,它允许通过代币查找另外一个代币(1 个交换只能交换 1 个代币,还记得吗?)。
pragma solidity ^0.8.24; import "./Exchange.sol"; contract Factory { mapping(address => address) public tokenToExchange; ...
接下来是 createExchange 函数,它允许通过Token地址来创建和部署Exchange合约:
createExchange
function createExchange(address _tokenAddress) public returns (address) { require(_tokenAddress != address(0), "invalid token address"); require( tokenToExchange[_tokenAddress] == address(0), "exchange already exists" ); Exchange exchange = new Exchange(_tokenAddress); tokenToExchange[_tokenAddress] = address(exchange); return address(exchange); }
让我们来做个简单的梳理:
这里有两项检查:
1、第一个确保令牌地址不是零地址( 0x0000000000000000000000000000000000000000 )。
0x0000000000000000000000000000000000000000
2、下一步确保Token合约还没有被添加到注册表中(默认地址值为零地址)。我们的想法是,我们不希望同一个Token代币有不同的Exchange合约,因为我们不希望流动性分散在多个Exchange合约中。它应该更好地集中在一个Exchange合约,以减少滑点并提供更好的汇率。
Exchange exchange = new Exchange(_tokenAddress);
创建一个新的 Exchange 合约实例,并将 _tokenAddress 传递给该合约的构造函数。
Exchange
_tokenAddress
我们使用提供的代币地址实例化 Exchange,这就是我们之前需要导入“Exchange.sol”的原因。这种实例化类似于OOP语言中的类实例化,但是,在Solidity中, new 操作符实际上会部署一个合约。返回的值具有Exchange合约的类型,并且每个合约都可以转换为地址 - 这就是我们在下一行中所做的,以获取新交易所的地址并将其保存到注册表中。
new
tokenToExchange[_tokenAddress] = address(exchange); return address(exchange);
将新创建的 Exchange 合约地址存储在映射 tokenToExchange 中,以便以后可以通过代币地址查找到对应的兑换合约。返回新创建的 Exchange 合约的地址。
tokenToExchange
要完成合约,我们只需要再实现一个函数 - getExchange ,这将允许我们通过另一个合约的接口查询注册表:
getExchange
function getExchange(address _tokenAddress) public view returns (address) { return tokenToExchange[_tokenAddress]; }
工厂就这样了!这真的很简单。
接下来,我们需要改进Exchange合约,以便它可以使用工厂来执行代币到代币的交换。
首先,我们需要将Exchange合约和Factory做关联,因为每个Exchange合约都需要知道Factory合约的地址,并且我们并不期望硬编码,硬编码缺乏灵活性。如果想要将Exchange合约和Factory做关联,我们需要添加一个新的状态变量来存储工厂合约的地址,并且需要在构造函数中完成。
contract Exchange is ERC20 { address public tokenAddress; address public factoryAddress; // <--- new line constructor(address _token) ERC20("Suniswap-V1", "SUNI-V1") { require(_token != address(0), "invalid token address"); tokenAddress = _token; factoryAddress = msg.sender; // <--- new line } ... }
就是这样。现在已准备好进行Token代币到另一个Token代币的交换。让我们来实现它。
当我们有两个通过注册表链接的Exchange合约时,我们如何将一个Token代币交换为另一个Token代币呢?也许是这样的:
1、将一个标准ERC20 Token和以太币进行兑换;
2、不要将以太币发送给用户,而是要将目标Token的地址通过映射找出来。
3、如果Exchange合约存在,用以太币发送到对应的合约兑换为那个对应的Token代币。
4、将兑换出来的Token代币发送给用户。
这个想法看起来不错,我们尝试实现这个想法,我们会创建一个函数名字就叫做tokenToTokenSwap
// Exchange.sol function tokenToTokenSwap( uint256 _tokensSold, uint256 _minTokensBought, address _tokenAddress ) public { ...
该函数接受三个参数:要出售的代币数量、要交换的最小代币数量、要交换已售代币的代币地址。
我们首先检查用户提供的代币地址是否存在兑换。如果没有,就会抛出错误。
address exchangeAddress = IFactory(factoryAddress).getExchange( _tokenAddress ); require( exchangeAddress != address(this) && exchangeAddress != address(0), "invalid exchange address" );
我们使用 IFactory ,它是工厂合约的接口。与其他合约(或 OOP 中的类)交互时使用接口是一个很好的实践。然而,接口不允许访问状态变量,这就是我们在工厂合约中实现 getExchange 函数的原因——这样我们就可以通过接口调用合约中的函数从而拿到状态变量。
IFactory
interface IFactory { function getExchange(address _tokenAddress) external returns (address); }
接下来,我们使用当前的Exchange合约将Token代币交换为以太币,将用户的代币转移到Exchange合约。
uint256 tokenReserve = getReserve(); uint256 ethBought = getAmount( _tokensSold, tokenReserve, address(this).balance ); IERC20(tokenAddress).transferFrom( msg.sender, address(this), _tokensSold );
该函数的最后一步是使用其他Exchange合约将以太币交换为代币:
IExchange(exchangeAddress).ethToTokenSwap{value: ethBought}( _minTokensBought );
我们就完成了!
实际上并非如此。你能看出问题吗?让我们看一下 etherToTokenSwap 的最后一行:
etherToTokenSwap
IERC20(tokenAddress).transfer(msg.sender, tokensBought);
它将购买的代币发送到 msg.sender 。在 Solidity 中, msg.sender 是动态的,而不是静态的,它指向发起当前调用的人(或者在合约的情况下是什么)。当用户调用合约函数时,它会指向用户的地址。但是当一个合约调用另一个合约时, msg.sender 就是调用合约的地址!
msg.sender
因此, tokenToTokenSwap 会将代币发送到第一个Exchange合约的地址!但这不是问题,因为我们可以调用 ERC20(_tokenAddress).transfer(...) 将这些Token发送给用户。然而,有一个 getter 解决方案:让我们节省一些 Gas 并将代币直接发送给用户。为此,我们需要将 etherToTokenSwap 函数拆分为两个函数:
tokenToTokenSwap
ERC20(_tokenAddress).transfer(...)
function ethToToken(uint256 _minTokens, address recipient) private { uint256 tokenReserve = getReserve(); uint256 tokensBought = getAmount( msg.value, address(this).balance - msg.value, tokenReserve ); require(tokensBought >= _minTokens, "insufficient output amount"); IERC20(tokenAddress).transfer(recipient, tokensBought); } function ethToTokenSwap(uint256 _minTokens) public payable { ethToToken(_minTokens, msg.sender); }
ethToToken 是私有函数,所有 ethToTokenSwap 都用于执行此操作,只有一个区别:它需要一个Token接收者的地址,这使我们可以灵活地选择要将Token发送给谁。 ethToTokenSwap 现在只是 ethToToken 的包装函数,始终将 msg.sender 作为接收者传递。
ethToToken
ethToTokenSwap
现在,我们需要另一个函数来将Token发送给自定义接收者。我们可以使用 ethToToken 来实现这一点,但让我们将其保留为私有且nopayable的。
function ethToTokenTransfer(uint256 _minTokens, address _recipient) public payable { ethToToken(_minTokens, _recipient); }
这只是 ethToTokenSwap 的副本,允许将Token发送给自定接收者。我们现在可以在 tokenToTokenSwap 函数中使用它:
... IExchange(exchangeAddress).ethToTokenTransfer{value: ethBought}( _minTokensBought, msg.sender ); }
我们将向发起交换的人发送Token代币。现在,我们完成了!
我们自己的 Uniswap V1 现已完成。如果您对如何改进有任何想法,请尝试一下!例如,交易所中有一个函数可以计算代币互换中代币的输出量。如果您在理解某些东西的工作原理时遇到任何问题,请随时检查测试。我已经介绍了所有功能,包括 tokenToTokenSwap 。
下次我们将开始学习Uniswap V2。虽然它们基本上是相同的东西,相同的集合或核心原则,但它提供了一些新的强大功能。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
开篇介绍
在前面的两篇文章中,我们已经已经实现了Exchange合约的所有核心机制,包括定价功能、交换、LP代币和费用。看起来已经比较完善了,但是还缺少了一部分:工厂合约。本篇文章,我们就来实现它。
工厂合约的作用是什么
事实上工厂合约充当的角色是Exchange合约的注册表:每个新部署的Exchange合约都在工厂中注册。这是一个重要的机制:任何Exchange合约都可以通过查询注册表找到。通过注册表,当用户尝试将一个Token代币交换为另一个Token代币(而不是以太币)的时候,Exchange合约可以通过工厂合约找到其他的Exchange合约。
工厂合约提供的另一个比较实用的功能是能够方便的部署Exchange合约而无需处理代码、节点、部署脚本和任何其他开发工具。
Factory实现了一个功能,允许用户只需调用该函数即可创建和部署Exchange合约。所以,今天我们还将学习一个合约如何部署另一个合约。
Uniswap 只有一份工厂合约,因此只有一份 Uniswap 交易对的注册表。然而,没有什么可以阻止其他用户部署自己的工厂,甚至是未在官方工厂注册的Exchange合约。虽然这是可能的,但这样的交易所不会被 Uniswap 认可,也无法通过官方网站使用它们来交换代币。
让我们看看代码如何实现:
工厂合约的实现
工厂合约本质上是一个注册表,我们需要一个数据结构来存储Exchange合约,这应该是一个地址到地址的映射,它允许通过代币查找另外一个代币(1 个交换只能交换 1 个代币,还记得吗?)。
接下来是
createExchange
函数,它允许通过Token地址来创建和部署Exchange合约:让我们来做个简单的梳理:
这里有两项检查:
1、第一个确保令牌地址不是零地址(
0x0000000000000000000000000000000000000000
)。2、下一步确保Token合约还没有被添加到注册表中(默认地址值为零地址)。我们的想法是,我们不希望同一个Token代币有不同的Exchange合约,因为我们不希望流动性分散在多个Exchange合约中。它应该更好地集中在一个Exchange合约,以减少滑点并提供更好的汇率。
创建一个新的
Exchange
合约实例,并将_tokenAddress
传递给该合约的构造函数。我们使用提供的代币地址实例化 Exchange,这就是我们之前需要导入“Exchange.sol”的原因。这种实例化类似于OOP语言中的类实例化,但是,在Solidity中,
new
操作符实际上会部署一个合约。返回的值具有Exchange合约的类型,并且每个合约都可以转换为地址 - 这就是我们在下一行中所做的,以获取新交易所的地址并将其保存到注册表中。将新创建的
Exchange
合约地址存储在映射tokenToExchange
中,以便以后可以通过代币地址查找到对应的兑换合约。返回新创建的Exchange
合约的地址。要完成合约,我们只需要再实现一个函数 -
getExchange
,这将允许我们通过另一个合约的接口查询注册表:工厂就这样了!这真的很简单。
接下来,我们需要改进Exchange合约,以便它可以使用工厂来执行代币到代币的交换。
将Exchange合约和Factory合约做关联
首先,我们需要将Exchange合约和Factory做关联,因为每个Exchange合约都需要知道Factory合约的地址,并且我们并不期望硬编码,硬编码缺乏灵活性。如果想要将Exchange合约和Factory做关联,我们需要添加一个新的状态变量来存储工厂合约的地址,并且需要在构造函数中完成。
就是这样。现在已准备好进行Token代币到另一个Token代币的交换。让我们来实现它。
代币到代币互换
当我们有两个通过注册表链接的Exchange合约时,我们如何将一个Token代币交换为另一个Token代币呢?也许是这样的:
1、将一个标准ERC20 Token和以太币进行兑换;
2、不要将以太币发送给用户,而是要将目标Token的地址通过映射找出来。
3、如果Exchange合约存在,用以太币发送到对应的合约兑换为那个对应的Token代币。
4、将兑换出来的Token代币发送给用户。
这个想法看起来不错,我们尝试实现这个想法,我们会创建一个函数名字就叫做tokenToTokenSwap
该函数接受三个参数:要出售的代币数量、要交换的最小代币数量、要交换已售代币的代币地址。
我们首先检查用户提供的代币地址是否存在兑换。如果没有,就会抛出错误。
我们使用
IFactory
,它是工厂合约的接口。与其他合约(或 OOP 中的类)交互时使用接口是一个很好的实践。然而,接口不允许访问状态变量,这就是我们在工厂合约中实现getExchange
函数的原因——这样我们就可以通过接口调用合约中的函数从而拿到状态变量。接下来,我们使用当前的Exchange合约将Token代币交换为以太币,将用户的代币转移到Exchange合约。
该函数的最后一步是使用其他Exchange合约将以太币交换为代币:
我们就完成了!
实际上并非如此。你能看出问题吗?让我们看一下
etherToTokenSwap
的最后一行:它将购买的代币发送到
msg.sender
。在 Solidity 中,msg.sender
是动态的,而不是静态的,它指向发起当前调用的人(或者在合约的情况下是什么)。当用户调用合约函数时,它会指向用户的地址。但是当一个合约调用另一个合约时,msg.sender
就是调用合约的地址!因此,
tokenToTokenSwap
会将代币发送到第一个Exchange合约的地址!但这不是问题,因为我们可以调用ERC20(_tokenAddress).transfer(...)
将这些Token发送给用户。然而,有一个 getter 解决方案:让我们节省一些 Gas 并将代币直接发送给用户。为此,我们需要将etherToTokenSwap
函数拆分为两个函数:ethToToken
是私有函数,所有ethToTokenSwap
都用于执行此操作,只有一个区别:它需要一个Token接收者的地址,这使我们可以灵活地选择要将Token发送给谁。ethToTokenSwap
现在只是ethToToken
的包装函数,始终将msg.sender
作为接收者传递。现在,我们需要另一个函数来将Token发送给自定义接收者。我们可以使用
ethToToken
来实现这一点,但让我们将其保留为私有且nopayable的。这只是
ethToTokenSwap
的副本,允许将Token发送给自定接收者。我们现在可以在tokenToTokenSwap
函数中使用它:我们将向发起交换的人发送Token代币。现在,我们完成了!
总结:
我们自己的 Uniswap V1 现已完成。如果您对如何改进有任何想法,请尝试一下!例如,交易所中有一个函数可以计算代币互换中代币的输出量。如果您在理解某些东西的工作原理时遇到任何问题,请随时检查测试。我已经介绍了所有功能,包括
tokenToTokenSwap
。下次我们将开始学习Uniswap V2。虽然它们基本上是相同的东西,相同的集合或核心原则,但它提供了一些新的强大功能。
The text was updated successfully, but these errors were encountered: