下面是简要的代码摘要:
Factory.sol
contract SwapFactory is ISwapFactory {
address[] public allPairs;
mapping(address tokenA => mapping(address tokenB => address pair)) public override getPair;
constructor(){
}
function allPairsLength() external view returns (uint) {
return allPairs.length;
}
function createPair(address tokenA, address tokenB) external override returns (address pair) {
require(tokenA != tokenB, "Identical addresses");
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); // 对代币地址进行排序,以保证在映射中存储时的一致性
require(token0 != address(0), "Zero address");
require(getPair[token0][token1] == address(0), "Pair exists");
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
// 调用新创建的流动性池合约的 initialize 函数,以设置代币对
// 如果是内联汇编的方式创建的pair对象,需要进行转换
SwapPair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair;
allPairs.push(pair);
emit SwapPairCreated(token0, token1, pair);
}
}
Pair.sol
contract SwapPair is ISwapPair, SwapToken {
uint256 public constant MINIMUM_LIQUIDITY = 10**3;
address public factory;
address public token0;
address public token1;
uint112 private reserve0;
uint112 private reserve1;
uint32 private blockTimestampLast;
bool private unlock = true;
modifier lock {
require(unlock, "locked!");
unlock = false;
_;
unlock = true;
}
constructor() {
factory = msg.sender;
}
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, "Unauthorized");
token0 = _token0;
token1 = _token1;
}
// 返回的是最近一次更新(无论是 mint添加流动性、burn移除流动性,还是swap发生交易)之后的代币储备量
function getReserves() public view override returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
// 更新流动性池中的代币储备(reserves)
function _update(uint balance0, uint balance1) private {
require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "Overflow"); // type(uint112) / 1e18 = 5192296858534827
// 更新池中的代币储备量
// balance0 和 balance1 是函数的输入参数,表示当前池中 token0 和 token1 的实际余额
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = uint32(block.timestamp % 2**32); // // 将时间戳转换为 32 位整数,以避免溢出
}
// 用户向流动性池中提供代币并获得流动性凭证(liquidity tokens)
// to:表示流动性凭证的接收者,通常是添加流动性的一方
// lock 修饰符:防止重入攻击,这是一种确保在执行完当前函数之前,不允许再次调用该函数的机制
function mint(address to) external lock override returns (uint liquidity) {
// 此时调用 getReserves() 获取的储备量对应于:在当前流动性添加操作mint之前的代币数量,尚未包含当前交易中的新代币注入。
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
// 当前合约地址上持有的 token0 和 token1 的余额。等于 流动性提供者新注入的代币数量 加上 现有储备的总和
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
// 流动性提供者注入的代币量:通过当前合约代币余额减去储备余额,得到了新增的代币量
uint amount0 = balance0 - _reserve0;
uint amount1 = balance1 - _reserve1;
uint _totalSupply = totalSupply;
// 第一次流动性注入,通过恒定乘积公式计算 sqrt(amount0 * amount1) 计算流动性代币数量
// 初始流动性代币中会永久锁定一部分 (MINIMUM_LIQUIDITY) 作为安全机制,这些代币被铸造给 address(0),确保流动池永远有一部分不会被提走
if (_totalSupply == 0) {
liquidity = MathTools.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(1), MINIMUM_LIQUIDITY);
} else {
// 对于后续的流动性提供者,流动性代币按比例分配
// 最终取这两个值的最小值,以确保按比例添加的代币数量是平衡的
liquidity = MathTools.min((amount0 * _totalSupply) / _reserve0, (amount1 * _totalSupply) / _reserve1);
}
// 用户注入的代币数量过少,不足以铸造出流动性代币
require(liquidity > 0, "Insufficient liquidity");
// 使用 _mint 函数将计算好的流动性代币铸造给流动性提供者的地址 to
_mint(to, liquidity);
// balance0 和 balance1 是流动池的新代币余额
// _reserve0 和 _reserve1 是旧的储备。该函数会将新的储备值更新为当前的余额,确保储备数据一致性
_update(balance0, balance1);
emit Mint(msg.sender, amount0, amount1);
}
SwapToken.sol
contract SwapToken is IERC20 {
string public constant name = 'Uniswap V2';
string public constant symbol = 'UNI-V2';
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
constructor() {}
function _mint(address to, uint value) internal {
totalSupply = totalSupply + value;
balanceOf[to] = balanceOf[to] + value;
emit Transfer(address(0), to, value);
}
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from] - value;
totalSupply = totalSupply - value;
emit Transfer(from, address(0), value);
}
function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from] - value;
balanceOf[to] = balanceOf[to] + value;
emit Transfer(from, to, value);
}
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] > 0) {
allowance[from][msg.sender] = allowance[from][msg.sender] - value;
}
_transfer(from, to, value);
return true;
}
}
Router.sol
contract SwapRouter is ISwapRouter {
address public immutable override factory;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, "Expired");
_;
}
modifier hasExpired(uint256 _deadline) {
require(block.timestamp <= _deadline, "expired");
_;
}
constructor(address _factory) {
factory = _factory;
}
// 基于 tokenA 和 tokenB 的储量 reserveA 和 reserveB 以及用户意向的 amountADesired 和 amountBDesired
// 计算出满足 Δx / Δy = x / y,即满足比例关系的 tokenA 和 tokenB 的实际数量 amountA 和 amountB
// 因为用户意向的 amountADesired 和 amountBDesired 并不一定满足 x / y 的比例关系
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired, // 用户希望添加的 tokenA 数量
uint amountBDesired, // 用户希望添加的 tokenB 数量
uint amountAMin, // 最小接受的 tokenA 数量(保护机制)
uint amountBMin // 最小接受的 tokenB 数量(保护机制)
) private returns (uint amountA, uint amountB) {
if (ISwapFactory(factory).getPair(tokenA, tokenB) == address(0)) {
ISwapFactory(factory).createPair(tokenA, tokenB);
}
// (uint reserveA, uint reserveB,) = ISwapPair(ISwapFactory(factory).getPair(tokenA, tokenB)).getReserves();
(uint256 reserveA, uint256 reserveB) = SwapTools.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
// 根据当前储备量,计算最优的 amountBOptimal 和 amountAOptimal,以确保添加流动性时保持比例
// 根据用户想要传入的 amountA,计算此时按照 x / y = Δx / Δy 比例算出的 amountB 的数量
// 在首次计算时,amountA 尚未确定,必须先用 amountADesired
uint amountBOptimal = SwapTools.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
// 如果计算出的 amountBOptimal 小于或等于用户希望添加的 amountBDesired。
// 也就是说用户提供的 tokenB 的数量是足够的,能够完全消耗掉 tokenA
require(amountBOptimal >= amountBMin, "Insufficient B amount"); // 检查是否大于等于 amountBMin,确保用户可以接受的最小数量
(amountA, amountB) = (amountADesired, amountBOptimal); // tokenA 完全被消耗,但是 tokenB 数量过多,只消耗了 amountBOptimal
} else {
// 如果 amountBOptimal 超过了用户的 amountBDesired
// 也就是说此时提供的 tokenB 的数量不够,tokenA的数量过多
uint amountAOptimal = SwapTools.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, "Insufficient A amount");
(amountA, amountB) = (amountAOptimal, amountBDesired); // tokenA 数量过多,只消耗了 amountAOptimal,tokenB 完全被消耗了
}
}
}
// 下面这个函数是用户进行调用的,因此 msg.sender = 用户
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
// 计算出 满足 Δx / Δy = x / y 关系的 tokenA 和 tokenB 的数量
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = ISwapFactory(factory).getPair(tokenA, tokenB);
// 把对应数量的 token 转到 pair 合约
IERC20(tokenA).transferFrom(msg.sender, pair, amountA);
IERC20(tokenB).transferFrom(msg.sender, pair, amountB);
// 获得 流动性LP Token
liquidity = ISwapPair(pair).mint(to);
}
}
按照正常的调用逻辑:用户调用Router中的 addLiquidity ,然后Pair合约对象给to地址发送LP Token。
下面是在 remix 中调用 addLiquidity 后面的日志信息:
from 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
to SwapRouter.addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256) 0x870f80823772b3Ef098844A852dDfBeec1061776
gas 3251666 gas
transaction cost 2787735 gas
execution cost 2784491 gas
input 0xe8e...9d160
output 0x0000000000000000000000000000000000000000000000008ac7230489e800000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000002be2aac7077d57db
decoded input {
"address tokenA": "0x51A0dfea63768e7827e9AAA532314910648F3eD2",
"address tokenB": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358",
"uint256 amountADesired": "10000000000000000000",
"uint256 amountBDesired": "1000000000000000000",
"uint256 amountAMin": "9950000000000000000",
"uint256 amountBMin": "995000000000000000",
"address to": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"uint256 deadline": "1799999840"
}
decoded output {
"0": "uint256: amountA 10000000000000000000",
"1": "uint256: amountB 1000000000000000000",
"2": "uint256: liquidity 3162277660168378331"
}
logs [
{
"from": "0x7Dd7622649035B7460DF4c94D6dDE5c6Abc84e95",
"topic": "0x43751f02e1e73fa4388fefe59462d9de97fd7b221544e34cc60fca29faf9d7df",
"event": "SwapPairCreated",
"args": {
"0": "0x51A0dfea63768e7827e9AAA532314910648F3eD2",
"1": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358",
"2": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF",
"token0": "0x51A0dfea63768e7827e9AAA532314910648F3eD2",
"token1": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358",
"pair": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF"
}
},
{
"from": "0x51A0dfea63768e7827e9AAA532314910648F3eD2",
"topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"event": "Transfer",
"args": {
"0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"1": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF",
"2": "10000000000000000000",
"from": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"to": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", // pair
"value": "10000000000000000000"
}
},
{
"from": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358",
"topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"event": "Transfer",
"args": {
"0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"1": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF",
"2": "1000000000000000000",
"from": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"to": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", // pair
"value": "1000000000000000000"
}
},
{
"from": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF",
"topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"event": "Transfer",
"args": {
"0": "0x0000000000000000000000000000000000000000",
"1": "0x0000000000000000000000000000000000000001",
"2": "1000",
"from": "0x0000000000000000000000000000000000000000",
"to": "0x0000000000000000000000000000000000000001",
"value": "1000"
}
},
{
"from": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF",
"topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"event": "Transfer",
"args": {
"0": "0x0000000000000000000000000000000000000000",
"1": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"2": "3162277660168378331",
"from": "0x0000000000000000000000000000000000000000",
"to": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"value": "3162277660168378331"
}
},
{
"from": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF",
"topic": "0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f",
"event": "Mint",
"args": {
"0": "0x870f80823772b3Ef098844A852dDfBeec1061776",
"1": "10000000000000000000",
"2": "1000000000000000000",
"sender": "0x870f80823772b3Ef098844A852dDfBeec1061776", // router
"amount0": "10000000000000000000",
"amount1": "1000000000000000000"
}
}
]
很明显看到,是触发了Mint的事件的。
但是在remix控制面版中,查看相关信息如下:
感觉所有的状态都没有设置成功。