这一篇文章,从源码角度梳理UniswapV2Factory的实现
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';
//uniswap工厂
contract UniswapV2Factory is IUniswapV2Factory {
address public feeTo; //收税地址
address public feeToSetter; //收税权限控制地址
//配对映射, 地址 => (地址 => 地址)
mapping(address => mapping(address => address)) public getPair;
//所有配对数组
address[] public allPairs;
//配对合约的Bytecode的hash
bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));
//事件:配对被创建
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
/**
* @dev 构造函数
* @param _feeToSetter 收税开关权限控制
*/
constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}
/**
* @dev 查询配对数组长度方法
*/
function allPairsLength() external view returns (uint) {
return allPairs.length;
}
/**
*
* @param tokenA TokenA
* @param tokenB TokenB
* @return pair 配对地址
* @dev 创建配对
*/
function createPair(address tokenA, address tokenB) external returns (address pair) {
//确认tokenA不等于tokenB
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
//将tokenA和tokenB进行大小排序,确保tokenA小于tokenB
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
//确认token0不等于0地址
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
//确认配对映射中不存在token0=>token1
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
//给bytecode变量赋值"UniswapV2Pair"合约的创建字节码
bytes memory bytecode = type(UniswapV2Pair).creationCode;
//将token0和token1打包后创建哈希
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
//内联汇编
//solium-disable-next-line
assembly {
//通过create2方法布署合约,并且加盐,返回地址到pair变量
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
//调用pair地址的合约中的"initialize"方法,传入变量token0,token1
IUniswapV2Pair(pair).initialize(token0, token1);
//配对映射中设置token0=>token1=pair
getPair[token0][token1] = pair;
//配对映射中设置token1=>token0=pair
getPair[token1][token0] = pair; // populate mapping in the reverse direction
//配对数组中推入pair地址
allPairs.push(pair);
//触发配对成功事件
emit PairCreated(token0, token1, pair, allPairs.length);
}
/**
* @dev 设置收税地址
* @param _feeTo 收税地址
*/
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}
/**
* @dev 收税权限控制
* @param _feeToSetter 收税权限控制
*/
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
}
这是 Uniswap V2 中的工厂合约 (UniswapV2Factory
) 的 Solidity 代码。这个合约负责创建和管理代币对(pair
),并且包含了一些关键的功能。
编译器已经被锁死了,使用的是0.5.16版本,这个版本中的solidity还会有溢出问题。这是相关技术背景。
UniswapV2Factory
仅仅继承了接口合约,继承接口合约的目的是为了保证要实现内部的所有方法,如果不实现其中的方法就会报错,接口相当于一种约束。
pragma solidity ^0.5.6;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
feeTo
:收取手续费的地址。feeToSetter
:控制 feeTo
地址的权限地址。getPair
:一个映射,用于存储两个代币之间的配对合约地址。allPairs
:一个数组,存储所有创建的配对合约地址。INIT_CODE_PAIR_HASH
:配对合约的字节码哈希值,用于通过 create2
函数部署合约。PairCreated
:每当一个新的代币对被创建时触发,记录了代币对的两个代币地址及其配对合约地址。constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}
构造函数接受一个 _feeToSetter
地址,用于初始化 feeToSetter
变量。可以在部署工厂合约的时候,传入一个初始化的部署参数,这个参数是一个地址类型,也就是_feeToSetter,然后_feeToSetter 被设置为管理员之后,可以设置feeTo
的地址,最终的手续费会被打到这个feeTo
地址上面。
allPairsLength
方法:
/**
* @dev 查询配对数组长度方法
*/
function allPairsLength() external view returns (uint) {
return allPairs.length;
}
这是一个view函数,返回 allPairs
数组的长度,即已经创建的配对数量。
createPair
方法:
/**
*
* @param tokenA TokenA
* @param tokenB TokenB
* @return pair 配对地址
* @dev 创建配对
*/
function createPair(address tokenA, address tokenB) external returns (address pair) {
//确认tokenA不等于tokenB
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
//将tokenA和tokenB进行大小排序,确保tokenA小于tokenB
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
//确认token0不等于0地址
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
//确认配对映射中不存在token0=>token1
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
//给bytecode变量赋值"UniswapV2Pair"合约的创建字节码
bytes memory bytecode = type(UniswapV2Pair).creationCode;
//将token0和token1打包后创建哈希
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
//内联汇编
//solium-disable-next-line
assembly {
//通过create2方法布署合约,并且加盐,返回地址到pair变量
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
//调用pair地址的合约中的"initialize"方法,传入变量token0,token1
IUniswapV2Pair(pair).initialize(token0, token1);
//配对映射中设置token0=>token1=pair
getPair[token0][token1] = pair;
//配对映射中设置token1=>token0=pair
getPair[token1][token0] = pair; // populate mapping in the reverse direction
//配对数组中推入pair地址
allPairs.push(pair);
//触发配对成功事件
emit PairCreated(token0, token1, pair, allPairs.length);
}
tokenA
和 tokenB
。逻辑梳理:
tokenA
和 tokenB
不相同。
按字典顺序将tokenA
和 tokenB
排序,确保 token0 < token1
, 因为是使用16进制表示,他们是可以进行比大小的。一定要将较小的token赋值给token0,稍微大的一个赋值给token1。token0
不是零地址。这里有个巧妙的计算,小的那个不是0地址,大的那个肯定也不是0地址。create2
创建新的 UniswapV2Pair
合约,并通过计算哈希值生成 salt
,确保创建的合约地址是唯一的。getPair
映射和 allPairs
数组中。PairCreated
事件。setFeeTo
方法:
/**
* @dev 设置收税地址
* @param _feeTo 收税地址
*/
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}
允许 feeToSetter
设置 feeTo
地址,控制手续费的接收地址。
setFeeToSetter
方法:
/**
* @dev 收税权限控制
* @param _feeToSetter 收税权限控制
*/
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
允许当前的 feeToSetter
地址设置新的 feeToSetter
地址。
bytes memory bytecode = type(UniswapV2Pair).creationCode;
这一行代码在 Solidity 中用于获取 UniswapV2Pair
合约的创建字节码(也就是部署合约时使用的字节码)。
type(UniswapV2Pair)
:
UniswapV2Pair
是合约的名称。type(ContractName)
,你可以访问与该合约相关的元信息,例如它的创建代码(creationCode
)和运行时代码(runtimeCode
)。creationCode
:
creationCode
是合约的创建字节码。这个字节码包含了合约的构造函数代码和所有初始的合约代码。当你部署一个合约时,以太坊虚拟机(EVM)会使用这个字节码来创建合约实例。creationCode
是部署合约时发送给 EVM 的代码,EVM 执行这个代码后,最终会生成并存储合约的运行时代码。bytes memory
:
bytes
是一个动态字节数组类型,用于存储任意长度的字节序列。memory
表示这个字节数组是在内存中分配的,而不是在区块链上存储(即不是 storage
)。当你编写 bytes memory bytecode = type(UniswapV2Pair).creationCode;
时,实质上你是在获取 UniswapV2Pair
合约的部署字节码,并将其存储在 bytecode
变量中。
在 createPair
函数中,Uniswap V2 工厂合约需要动态部署一个新的 UniswapV2Pair
合约实例。这就需要使用 create2
操作码,而 create2
需要提供合约的创建字节码和一个 salt
值(一个随机或特定的哈希值),以确保新合约地址的唯一性和可预测性。
具体的操作流程如下:
type(UniswapV2Pair).creationCode
获取 UniswapV2Pair
合约的创建字节码。create2
指令,并将获取的 bytecode
作为合约创建时的字节码输入。这确保了每次创建的 UniswapV2Pair
合约实例是正确的。bytes memory bytecode = type(UniswapV2Pair).creationCode;
是在合约中动态获取 UniswapV2Pair
合约的创建代码,以便稍后使用 create2
操作码来部署新合约。这种方法在工厂模式中非常常见,因为工厂合约通常需要根据输入参数动态创建多个合约实例。
creationCode
和 runtimeCode
区别是什么?creationCode
和 runtimeCode
是合约代码在以太坊虚拟机(EVM)中的两个不同阶段的字节码。它们分别对应合约部署和运行时的代码。让我们来详细解释它们的区别。
creationCode
(创建字节码)定义:creationCode
是合约的部署字节码。当你部署一个新的合约时,creationCode
被发送到 EVM 并执行,用来生成该合约的实例。
内容:
creationCode
,生成合约的 runtimeCode
,并将 runtimeCode
存储在合约地址下。使用场景:
creationCode
只在合约创建时使用,一旦合约部署完成,它不会再被使用。
获取方式:
type(ContractName).creationCode
可以获取某个合约的 creationCode
。runtimeCode
(运行时字节码)定义:runtimeCode
是合约在链上运行时的字节码。当外部账户或其他合约调用该合约时,EVM 执行的就是 runtimeCode
。
内容:
使用场景:
runtimeCode
是合约部署后存在于链上的代码,每次调用合约时,EVM 都会执行这部分代码。
获取方式:
在 Solidity 中并没有直接的方式获取 runtimeCode
,因为 runtimeCode
是部署后的代码。可以通过 EVM 调用 extcodesize
和 extcodecopy
指令来获取。
假设有一个简单的合约:
contract SimpleContract {
uint256 public value;
constructor(uint256 _value) public {
value = _value;
}
function setValue(uint256 _value) public {
value = _value;
}
}
creationCode
:
包含设置初始值 value
的构造函数代码。
包含部署完成后生成 runtimeCode
的逻辑。
runtimeCode
:
包含 setValue
方法和 value
变量的相关逻辑。
不包含构造函数,因为构造函数在部署时已经执行完毕。
在 createPair
方法中使用了内联汇编来调用 create2
指令,以部署新的合约。这是一种更低级别的操作,使得合约创建时可以通过 salt
来保证唯一的合约地址。
//内联汇编
//solium-disable-next-line
assembly {
//通过create2方法布署合约,并且加盐,返回地址到pair变量
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
create2
操作码有四个参数。它们分别是:
create2(value, bytecode, size, salt)
value
(指定为0
在这个例子中):
uint256
0
,表示不附带任何以太币。bytecode
:
uint256
(内存指针)create2
使用它来生成新合约。size
:
uint256
create2
应该从 bytecode
内存位置读取多少字节的数据。salt
:
bytes32
bytecode
共同决定了新合约的地址。不同的 salt
会生成不同的合约地址,即使 bytecode
相同。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!