合约源码:
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 地址。
关于UniswapV2Pair字节码的计算逻辑
bytes memory bytecode = type(UniswapV2Pair).creationCode;
这一行代码在 Solidity 中用于获取 UniswapV2Pair 合约的创建字节码(也就是部署合约时使用的字节码)。
详细解释
type(UniswapV2Pair):
- 这是一个 Solidity 中的特殊关键字,用于获取某个合约类型的相关信息。
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 并执行,用来生成该合约的实例。
内容:
- 包含合约的构造函数代码(如果有的话),以及合约的初始化代码。
- 包含所有初始化的静态数据和逻辑。
- 在部署时,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 变量的相关逻辑。
不包含构造函数,因为构造函数在部署时已经执行完毕。
内联汇编创建pair合约:
在 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 - 一个随机或特定的 32 字节的值。这个值与
bytecode共同决定了新合约的地址。不同的salt会生成不同的合约地址,即使bytecode相同。