协议简介
SushiSwap是一个分叉自Uniswap的去中心化交易协议,它在交易模式上延续了Uniswap的核心设计——AMM(自动做市商)模型,但与Uniswap不同之处在于SushiSwap增加了经济奖励模型,SushiSwap交易手续费为0.3%,其中0.25%直接分给发给流动性提供,0.05%买成SUSHI并分配给Sushi代币持有者(Uniswap是通过开关模式决定是否将0.05%的手续费给开发者团队),Sushi在每次分发时会预留10%给项目未来开发迭代及安全审计等。
项目地址
项目地址:https://github.com/sushiswap/sushiswap
源码结构
SushiSwap协议源码结构如下,在后面的源码分析阶段我们主要对时间锁定合约和SushiSwap文件内容进行分析,UniswapV2协议部分不再深入,如需了解可参考之前的UniswapV2协议分析文章:
源码分析
接下来我们将对SushiSwap关键文件进行分析,下面是其扮演的角色:
-
SushiToken:代币合同,带有投票功能
-
MasterChef:将LPsTokens存入SUSHI fram
-
SushiMaker:收集交易费,转换为SUSHI并发送到SushiBar
-
SushiBar:抵押SUSHI以获得更多SUSHI
-
Migrator:把MasterChef LP从Uniswap迁移到SushiSwap
-
GovernorAlpha+Timelock:来自Compound的治理功能
-
UniswapV2:UniswapV2合约,进行了少量修改以迁移合约
Timelock
Timelock合约会对智能合约执行的任何更新都有48小时的时间锁定,在函数退出timelock之后,可以用5个团队多重签名中的3个来执行,下图是SushiSwap合约中timelock的记录信息:
https://app.sushi.com/governance
下面的代码是SushiSwap官方提供的timelock源码:
// SPDX-License-Identifier: MIT
// COPIED FROM https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol
// Copyright 2020 Compound Labs, Inc.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \\\"AS IS\\\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Ctrl+f for XXX to see all the modifications.
// XXX: pragma solidity ^0.5.16;
pragma solidity 0.6.12;
// XXX: import \\\"./SafeMath.sol\\\";
import \\\"@openzeppelin/contracts/math/SafeMath.sol\\\";
contract Timelock {
using SafeMath for uint;
event NewAdmin(address indexed newAdmin);
event NewPendingAdmin(address indexed newPendingAdmin);
event NewDelay(uint indexed newDelay);
event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
uint public constant GRACE_PERIOD = 14 days;
uint public constant MINIMUM_DELAY = 2 days;
uint public constant MAXIMUM_DELAY = 30 days;
address public admin;
address public pendingAdmin;
uint public delay;
bool public admin_initialized;
mapping (bytes32 => bool) public queuedTransactions;
constructor(address admin_, uint delay_) public {
require(delay_ >= MINIMUM_DELAY, \\\"Timelock::constructor: Delay must exceed minimum delay.\\\");
require(delay_ <= MAXIMUM_DELAY, \\\"Timelock::constructor: Delay must not exceed maximum delay.\\\");
admin = admin_;
delay = delay_;
admin_initialized = false;
}
// XXX: function() external payable { }
receive() external payable { }
function setDelay(uint delay_) public {
require(msg.sender == address(this), \\\"Timelock::setDelay: Call must come from Timelock.\\\");
require(delay_ >= MINIMUM_DELAY, \\\"Timelock::setDelay: Delay must exceed minimum delay.\\\");
require(delay_ <= MAXIMUM_DELAY, \\\"Timelock::setDelay: Delay must not exceed maximum delay.\\\");
delay = delay_;
emit NewDelay(delay);
}
function acceptAdmin() public {
require(msg.sender == pendingAdmin, \\\"Timelock::acceptAdmin: Call must come from pendingAdmin.\\\");
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
function setPendingAdmin(address pendingAdmin_) public {
// allows one time setting of admin for deployment purposes
if (admin_initialized) {
require(msg.sender == address(this), \\\"Timelock::setPendingAdmin: Call must come from Timelock.\\\");
} else {
require(msg.sender == admin, \\\"Timelock::setPendingAdmin: First call must come from admin.\\\");
admin_initialized = true;
}
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
require(msg.sender == admin, \\\"Timelock::queueTransaction: Call must come from admin.\\\");
require(eta >= getBlockTimestamp().add(delay), \\\"Timelock::queueTransaction: Estimated execution block must satisfy delay.\\\");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
require(msg.sender == admin, \\\"Timelock::cancelTransaction: Call must come from admin.\\\");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
require(msg.sender == admin, \\\"Timelock::executeTransaction: Call must come from admin.\\\");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], \\\"Timelock::executeTransaction: Transaction hasn\\\'t been queued.\\\");
require(getBlockTimestamp() >= eta, \\\"Timelock::executeTransaction: Transaction hasn\\\'t surpassed time lock.\\\");
require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), \\\"Timelock::executeTransaction: Transaction is stale.\\\");
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
// solium-disable-next-line security/no-call-value
(bool success, bytes memory returnData) = target.call.value(value)(callData);
require(success, \\\"Timelock::executeTransaction: Transaction execution reverted.\\\");
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
function getBlockTimestamp() internal view returns (uint) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
}
在合约的开头之处首先定义了一系列的事件:
//更新Admin
event NewAdmin(address indexed newAdmin);
//新增Admin到Admin预备队列
event NewPendingAdmin(address indexed newPendingAdmin);
//新设延迟时间
event NewDelay(uint indexed newDelay);
//取消交易
event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
//执行交易
event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
//添加交易对交易队列
event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
之后定义了最小延迟时间和最大延迟时间以及宽限期:
uint public constant GRACE_PERIOD = 14 days; //宽限期:14天
uint public constant MINIMUM_DELAY = 2 days; //最短延迟时间
uint public constant MAXIMUM_DELAY = 30 days; //最长延迟时间
之后声明后续会使用到的全局变量:
address public admin; //admin地址
address public pendingAdmin; //pendingAdmin地址
uint public delay; //延迟时间
bool public admin_initialized; //admin地址是否初始化
mapping (bytes32 => bool) public queuedTransactions; //使用mapping存储交易队列,键值对为bytes32=>bool(交易字节与是否在队列布尔值)
之后在构造函数中进行初始化操作,要求delay延迟介于最小延迟和最大延迟之间,之后初始化admin地址,并将admin_initialized设为false:
constructor(address admin_, uint delay_) public {
require(delay_ >= MINIMUM_DELAY, \\\"Timelock::constructor: Delay must exceed minimum delay.\\\");
require(delay_ <= MAXIMUM_DELAY, \\\"Timelock::constructor: Delay must not exceed maximum delay.\\\");
admin = admin_;
delay = delay_;
admin_initialized = false;
}
之后的receive函数用于接受外部账户地址和合约地址向当前合约地址转移代币:
// XXX: function() external payable { }
receive() external payable { }
setDelay函数用于更新延迟,函数要求函数的调用者为当前合约地址自身,同时要求延迟处于最短延迟和最大延迟之间,之后通过emit触发事件:
function setDelay(uint delay_) public {
require(msg.sender == address(this), \\\"Timelock::setDelay: Call must come from Timelock.\\\");
require(delay_ >= MINIMUM_DELAY, \\\"Timelock::setDelay: Delay must exceed minimum delay.\\\");
require(delay_ <= MAXIMUM_DELAY, \\\"Timelock::setDelay: Delay must not exceed maximum delay.\\\");
delay = delay_;
emit NewDelay(delay);
}
acceptAdmin函数用于更新admin地址,该函数要求函数调用者为admin队列中的地址,之后要求更新当前admin并且将admin对了中的地址移除,之后通过emit触发事件:
function acceptAdmin() public {
require(msg.sender == pendingAdmin, \\\"Timelock::acceptAdmin: Call must come from pendingAdmin.\\\");
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
setPendingAdmin函数用于设置队列中的admin地址,首先会检查admin_initialized是否为true(在构造函数中虽然对admin地址进行了初始化,但是admin_initialized依旧为false,未做任何更新),如果为false则进入else,之后检查当前函数调用者是否为admin地址,如果是则更新admin_initialized,将其设置为true,之后更新pendingAdmin,当第二次调用setPendingAdmin时,此时的admin_initialized已为true,所以会要求调用者为当前合约地址,这也是为什么SushiSwap官方表示的\\”已将管理权限交由时间锁定(timelock)合约\\”的原因所在:
function setPendingAdmin(address pendingAdmin_) public {
// allows one time setting of admin for deployment purposes
if (admin_initialized) {
require(msg.sender == address(this), \\\"Timelock::setPendingAdmin: Call must come from Timelock.\\\");
} else {
require(msg.sender == admin, \\\"Timelock::setPendingAdmin: First call must come from admin.\\\");
admin_initialized = true;
}
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
queueTransaction用于交易添加到交易队列中,该函数首先会检索函数调用者是否为admin地址,之后检索是否满足延迟要求,之后计算交易hash,之后将该交易添加到交易队列中,并通过emit触发事件:
function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
require(msg.sender == admin, \\\"Timelock::queueTransaction: Call must come from admin.\\\");
require(eta >= getBlockTimestamp().add(delay), \\\"Timelock::queueTransaction: Estimated execution block must satisfy delay.\\\");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
cancelTransaction函数用于取消交易队列中的交易,首先会检索当前函数的调用者是否为合约的admin地址,之后计算一次交易hash,然后从交易队列中移除交易,之后通过emit触发事件:
function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
require(msg.sender == admin, \\\"Timelock::cancelTransaction: Call must come from admin.\\\");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
executeTransaction函数用于执行交易,该函数首先检索当前函数的调用者是否为admin地址,之后计算一次交易hash,之后检索是否满足延迟条件,之后从交易队列中移除交易,然后通过call调用执行交易,最后通过emit触发事件:
function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
require(msg.sender == admin, \\\"Timelock::executeTransaction: Call must come from admin.\\\");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], \\\"Timelock::executeTransaction: Transaction hasn\\\'t been queued.\\\");
require(getBlockTimestamp() >= eta, \\\"Timelock::executeTransaction: Transaction hasn\\\'t surpassed time lock.\\\");
require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), \\\"Timelock::executeTransaction: Transaction is stale.\\\");
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
// solium-disable-next-line security/no-call-value
(bool success, bytes memory returnData) = target.call.value(value)(callData);
require(success, \\\"Timelock::executeTransaction: Transaction execution reverted.\\\");
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
getBlockTimestamp函数的作用很简单,只是获取当前区块时间戳:
function getBlockTimestamp() internal view returns (uint) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
SushiToken
SushiToken是代币合约,带有投票功能,下面是官方提供的源码:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";
import \\\"@openzeppelin/contracts/access/Ownable.sol\\\";
// SushiToken with Governance.
contract SushiToken is ERC20(\\\"SushiToken\\\", \\\"SUSHI\\\"), Ownable {
/// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef).
function mint(address _to, uint256 _amount) public onlyOwner {
_mint(_to, _amount);
_moveDelegates(address(0), _delegates[_to], _amount);
}
// Copied and modified from YAM code:
// https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol
// https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol
// Which is copied and modified from COMPOUND:
// https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol
/// @notice A record of each accounts delegate
mapping (address => address) internal _delegates;
/// @notice A checkpoint for marking number of votes from a given block
struct Checkpoint {
uint32 fromBlock;
uint256 votes;
}
/// @notice A record of votes checkpoints for each account, by index
mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;
/// @notice The number of checkpoints for each account
mapping (address => uint32) public numCheckpoints;
/// @notice The EIP-712 typehash for the contract\\\'s domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256(\\\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\\\");
/// @notice The EIP-712 typehash for the delegation struct used by the contract
bytes32 public constant DELEGATION_TYPEHASH = keccak256(\\\"Delegation(address delegatee,uint256 nonce,uint256 expiry)\\\");
/// @notice A record of states for signing / validating signatures
mapping (address => uint) public nonces;
/// @notice An event thats emitted when an account changes its delegate
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
/// @notice An event thats emitted when a delegate account\\\'s vote balance changes
event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance);
/**
* @notice Delegate votes from `msg.sender` to `delegatee`
* @param delegator The address to get delegatee for
*/
function delegates(address delegator)
external
view
returns (address)
{
return _delegates[delegator];
}
/**
* @notice Delegate votes from `msg.sender` to `delegatee`
* @param delegatee The address to delegate votes to
*/
function delegate(address delegatee) external {
return _delegate(msg.sender, delegatee);
}
/**
* @notice Delegates votes from signatory to `delegatee`
* @param delegatee The address to delegate votes to
* @param nonce The contract state required to match the signature
* @param expiry The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/
function delegateBySig(
address delegatee,
uint nonce,
uint expiry,
uint8 v,
bytes32 r,
bytes32 s
)
external
{
bytes32 domainSeparator = keccak256(
abi.encode(
DOMAIN_TYPEHASH,
keccak256(bytes(name())),
getChainId(),
address(this)
)
);
bytes32 structHash = keccak256(
abi.encode(
DELEGATION_TYPEHASH,
delegatee,
nonce,
expiry
)
);
bytes32 digest = keccak256(
abi.encodePacked(
\\\"\\\\x19\\\\x01\\\",
domainSeparator,
structHash
)
);
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), \\\"SUSHI::delegateBySig: invalid signature\\\");
require(nonce == nonces[signatory]++, \\\"SUSHI::delegateBySig: invalid nonce\\\");
require(now <= expiry, \\\"SUSHI::delegateBySig: signature expired\\\");
return _delegate(signatory, delegatee);
}
/**
* @notice Gets the current votes balance for `account`
* @param account The address to get votes balance
* @return The number of current votes for `account`
*/
function getCurrentVotes(address account)
external
view
returns (uint256)
{
uint32 nCheckpoints = numCheckpoints[account];
return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
}
/**
* @notice Determine the prior number of votes for an account as of a block number
* @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
* @param account The address of the account to check
* @param blockNumber The block number to get the vote balance at
* @return The number of votes the account had as of the given block
*/
function getPriorVotes(address account, uint blockNumber)
external
view
returns (uint256)
{
require(blockNumber < block.number, \\\"SUSHI::getPriorVotes: not yet determined\\\");
uint32 nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
return checkpoints[account][nCheckpoints - 1].votes;
}
// Next check implicit zero balance
if (checkpoints[account][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = checkpoints[account][center];
if (cp.fromBlock == blockNumber) {
return cp.votes;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return checkpoints[account][lower].votes;
}
function _delegate(address delegator, address delegatee)
internal
{
address currentDelegate = _delegates[delegator];
uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled);
_delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
}
function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {
// decrease old representative
uint32 srcRepNum = numCheckpoints[srcRep];
uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
uint256 srcRepNew = srcRepOld.sub(amount);
_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
}
if (dstRep != address(0)) {
// increase new representative
uint32 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
uint256 dstRepNew = dstRepOld.add(amount);
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
}
}
}
function _writeCheckpoint(
address delegatee,
uint32 nCheckpoints,
uint256 oldVotes,
uint256 newVotes
)
internal
{
uint32 blockNumber = safe32(block.number, \\\"SUSHI::_writeCheckpoint: block number exceeds 32 bits\\\");
if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
} else {
checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
numCheckpoints[delegatee] = nCheckpoints + 1;
}
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
}
function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {
require(n < 2**32, errorMessage);
return uint32(n);
}
function getChainId() internal pure returns (uint) {
uint256 chainId;
assembly { chainId := chainid() }
return chainId;
}
}
该函数继承自ERC20(这是一种新颖的写法,直接使用ERC-20的构造函数进行对当前合约初始化)和Ownable:
contract SushiToken is ERC20(\\\"SushiToken\\\", \\\"SUSHI\\\"), Ownable {
之后的mint函数用于增发代币,该函数需要传递两个参数:
-
to:接受新增代币的地址
-
_amount:新增代币的数量
该函数由onlyOwner修饰器修饰,需要合约的owner调用,之后调用UniswapV2的mint函数进行铸币操作:
function mint(address _to, uint256 _amount) public onlyOwner {
_mint(_to, _amount);
_moveDelegates(address(0), _delegates[_to], _amount);
}
// Ownable.sol
// M1 - M5: OK
// C1 - C21: OK
modifier onlyOwner() {
require(msg.sender == owner, \\\"Ownable: caller is not the owner\\\");
_;
}
// UniswapV2ERC20.sol
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
之后调用_moveDelegates转移委托,在这里我们以之前传入的参数为例进行分析直接进入到最后一个if语句中,之后根据dstRep来检索账户的检查站点(标记某一特定区块的票数的检查站点),当账户的检查站点数大于0,检索上一个检查检查点数的投票数量并将其赋值给srcRepOld,如果没有检测点数则设置为0,之后使用原票数加上要转移的资产数量,作为新的票数(也可以说增发的token与票数相关,1 token代表1票),之后调用_writeCheckpoint更新检查站点:
/// @notice A checkpoint for marking number of votes from a given block
struct Checkpoint {
uint32 fromBlock;
uint256 votes;
}
/// @notice A record of votes checkpoints for each account, by index
mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;
/// @notice The number of checkpoints for each account
mapping (address => uint32) public numCheckpoints;
function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {
// decrease old representative
uint32 srcRepNum = numCheckpoints[srcRep];
uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
uint256 srcRepNew = srcRepOld.sub(amount);
_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
}
if (dstRep != address(0)) {
// increase new representative
uint32 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
uint256 dstRepNew = dstRepOld.add(amount);
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
}
}
}
_writeCheckpoint代码如下所示,这里的参数说明如下:
-
delegatee:接受token地址
-
nCheckpoints:原检查站点数
-
oldVotes:旧投票数量
-
newVotes:新投票数量
之后获取当前区块数,之后检查原检查站站点数是否大于0,接受token地址对应的前一个检查点数的fromBlock是否与当前区块数一致,如果一致则更新接受token地址对应的前一个检查点数的投票,否则更新接受token地址的当前检查站点的投票,同时将检查站点数加1,之后通过emit触发事件,总体来看铸币的时候也会伴随着投票的记录与分发:
function _writeCheckpoint(
address delegatee,
uint32 nCheckpoints,
uint256 oldVotes,
uint256 newVotes
)
internal
{
uint32 blockNumber = safe32(block.number, \\\"SUSHI::_writeCheckpoint: block number exceeds 32 bits\\\");
if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
} else {
checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
numCheckpoints[delegatee] = nCheckpoints + 1;
}
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
}
function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {
require(n < 2**32, errorMessage);
return uint32(n);
}
下面是定义的一些全局变量,其中有部分上面已经介绍到了:
/// @notice A record of votes checkpoints for each account, by index
// 按索引列出的每个帐户的投票检查点记录
mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;
/// @notice The number of checkpoints for each account
// 每个帐户的检查点数
mapping (address => uint32) public numCheckpoints;
/// @notice The EIP-712 typehash for the contract\\\'s domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256(\\\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\\\");
/// @notice The EIP-712 typehash for the delegation struct used by the contract
bytes32 public constant DELEGATION_TYPEHASH = keccak256(\\\"Delegation(address delegatee,uint256 nonce,uint256 expiry)\\\");
/// @notice A record of states for signing / validating signatures
// 签名/验证签名的状态记录
mapping (address => uint) public nonces;
之后是两个事件:
/// @notice An event thats emitted when an account changes its delegate
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
/// @notice An event thats emitted when a delegate account\\\'s vote balance changes
event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance);
之后的delegates函数用于将函数调用者的票投给delegator:
/**
* @notice Delegate votes from `msg.sender` to `delegatee`
* @param delegator The address to get delegatee for
*/
function delegates(address delegator)
external
view
returns (address)
{
return _delegates[delegator];
}
delegatee函数也用于将函数调用者的票投递给指定的地址,和上面不同的是该函数会调用_moveDelegates用于转移委托:
/**
* @notice Delegate votes from `msg.sender` to `delegatee`
* @param delegatee The address to delegate votes to
*/
function delegate(address delegatee) external {
return _delegate(msg.sender, delegatee);
}
function _delegate(address delegator, address delegatee)
internal
{
address currentDelegate = _delegates[delegator];
uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled);
_delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
}
从签字人到被授权人的代表投票:
-
delegatee:将投票委托给的地址
-
nonce:匹配签名所需的合同状态
-
expiry:过期签名过期的时间
-
v:签名的恢复字节
-
r:ECDSA签名对的r一半
-
s:是ECDSA签名对的一半
/**
* @notice Delegates votes from signatory to `delegatee`
* @param delegatee The address to delegate votes to
* @param nonce The contract state required to match the signature
* @param expiry The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/
function delegateBySig(
address delegatee,
uint nonce,
uint expiry,
uint8 v,
bytes32 r,
bytes32 s
)
external
{
bytes32 domainSeparator = keccak256(
abi.encode(
DOMAIN_TYPEHASH,
keccak256(bytes(name())),
getChainId(),
address(this)
)
);
bytes32 structHash = keccak256(
abi.encode(
DELEGATION_TYPEHASH,
delegatee,
nonce,
expiry
)
);
bytes32 digest = keccak256(
abi.encodePacked(
\\\"\\\\x19\\\\x01\\\",
domainSeparator,
structHash
)
);
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), \\\"SUSHI::delegateBySig: invalid signature\\\");
require(nonce == nonces[signatory]++, \\\"SUSHI::delegateBySig: invalid nonce\\\");
require(now <= expiry, \\\"SUSHI::delegateBySig: signature expired\\\");
return _delegate(signatory, delegatee);
}
getCurrentVotes用于获取当前的投票:
/**
* @notice Gets the current votes balance for `account`
* @param account The address to get votes balance
* @return The number of current votes for `account`
*/
function getCurrentVotes(address account)
external
view
returns (uint256)
{
uint32 nCheckpoints = numCheckpoints[account];
return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
}
getPriorVotes函数用于确定从一个区块编号开始的帐户的优先票数:
/**
* @notice Determine the prior number of votes for an account as of a block number
* @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
* @param account The address of the account to check
* @param blockNumber The block number to get the vote balance at
* @return The number of votes the account had as of the given block
*/
function getPriorVotes(address account, uint blockNumber)
external
view
returns (uint256)
{
require(blockNumber < block.number, \\\"SUSHI::getPriorVotes: not yet determined\\\");
uint32 nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
return checkpoints[account][nCheckpoints - 1].votes;
}
// Next check implicit zero balance
if (checkpoints[account][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = checkpoints[account][center];
if (cp.fromBlock == blockNumber) {
return cp.votes;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return checkpoints[account][lower].votes;
}
getChainId函数用于检索当前的getChainId:
function getChainId() internal pure returns (uint) {
uint256 chainId;
assembly { chainId := chainid() }
return chainId;
}
MasterChef
MasterChef合约的主要左右是将LPsTokens存入SUSHI fram,下面是官方提供的合约源码:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";
import \\\"@openzeppelin/contracts/token/ERC20/SafeERC20.sol\\\";
import \\\"@openzeppelin/contracts/utils/EnumerableSet.sol\\\";
import \\\"@openzeppelin/contracts/math/SafeMath.sol\\\";
import \\\"@openzeppelin/contracts/access/Ownable.sol\\\";
import \\\"./SushiToken.sol\\\";
interface IMigratorChef {
// Perform LP token migration from legacy UniswapV2 to SushiSwap.
// Take the current LP token address and return the new LP token address.
// Migrator should have full access to the caller\\\'s LP token.
// Return the new LP token address.
//
// XXX Migrator must have allowance access to UniswapV2 LP tokens.
// SushiSwap must mint EXACTLY the same amount of SushiSwap LP tokens or
// else something bad will happen. Traditional UniswapV2 does not
// do that so be careful!
function migrate(IERC20 token) external returns (IERC20);
}
// MasterChef is the master of Sushi. He can make Sushi and he is a fair guy.
//
// Note that it\\\'s ownable and the owner wields tremendous power. The ownership
// will be transferred to a governance smart contract once SUSHI is sufficiently
// distributed and the community can show to govern itself.
//
// Have fun reading it. Hopefully it\\\'s bug-free. God bless.
contract MasterChef is Ownable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// Info of each user.
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
//
// We do some fancy math here. Basically, any point in time, the amount of SUSHIs
// entitled to a user but is pending to be distributed is:
//
// pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
//
// Whenever a user deposits or withdraws LP tokens to a pool. Here\\\'s what happens:
// 1. The pool\\\'s `accSushiPerShare` (and `lastRewardBlock`) gets updated.
// 2. User receives the pending reward sent to his/her address.
// 3. User\\\'s `amount` gets updated.
// 4. User\\\'s `rewardDebt` gets updated.
}
// Info of each pool.
struct PoolInfo {
IERC20 lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.
uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.
uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.
}
// The SUSHI TOKEN!
SushiToken public sushi;
// Dev address.
address public devaddr;
// Block number when bonus SUSHI period ends.
uint256 public bonusEndBlock;
// SUSHI tokens created per block.
uint256 public sushiPerBlock;
// Bonus muliplier for early sushi makers.
uint256 public constant BONUS_MULTIPLIER = 10;
// The migrator contract. It has a lot of power. Can only be set through governance (owner).
IMigratorChef public migrator;
// Info of each pool.
PoolInfo[] public poolInfo;
// Info of each user that stakes LP tokens.
mapping(uint256 => mapping(address => UserInfo)) public userInfo;
// Total allocation poitns. Must be the sum of all allocation points in all pools.
uint256 public totalAllocPoint = 0;
// The block number when SUSHI mining starts.
uint256 public startBlock;
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event EmergencyWithdraw(
address indexed user,
uint256 indexed pid,
uint256 amount
);
constructor(
SushiToken _sushi,
address _devaddr,
uint256 _sushiPerBlock,
uint256 _startBlock,
uint256 _bonusEndBlock
) public {
sushi = _sushi;
devaddr = _devaddr;
sushiPerBlock = _sushiPerBlock;
bonusEndBlock = _bonusEndBlock;
startBlock = _startBlock;
}
function poolLength() external view returns (uint256) {
return poolInfo.length;
}
// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
function add(
uint256 _allocPoint,
IERC20 _lpToken,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
uint256 lastRewardBlock =
block.number > startBlock ? block.number : startBlock;
totalAllocPoint = totalAllocPoint.add(_allocPoint);
poolInfo.push(
PoolInfo({
lpToken: _lpToken,
allocPoint: _allocPoint,
lastRewardBlock: lastRewardBlock,
accSushiPerShare: 0
})
);
}
// Update the given pool\\\'s SUSHI allocation point. Can only be called by the owner.
function set(
uint256 _pid,
uint256 _allocPoint,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(
_allocPoint
);
poolInfo[_pid].allocPoint = _allocPoint;
}
// Set the migrator contract. Can only be called by the owner.
function setMigrator(IMigratorChef _migrator) public onlyOwner {
migrator = _migrator;
}
// Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
function migrate(uint256 _pid) public {
require(address(migrator) != address(0), \\\"migrate: no migrator\\\");
PoolInfo storage pool = poolInfo[_pid];
IERC20 lpToken = pool.lpToken;
uint256 bal = lpToken.balanceOf(address(this));
lpToken.safeApprove(address(migrator), bal);
IERC20 newLpToken = migrator.migrate(lpToken);
require(bal == newLpToken.balanceOf(address(this)), \\\"migrate: bad\\\");
pool.lpToken = newLpToken;
}
// Return reward multiplier over the given _from to _to block.
function getMultiplier(uint256 _from, uint256 _to)
public
view
returns (uint256)
{
if (_to <= bonusEndBlock) {
return _to.sub(_from).mul(BONUS_MULTIPLIER);
} else if (_from >= bonusEndBlock) {
return _to.sub(_from);
} else {
return
bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(
_to.sub(bonusEndBlock)
);
}
}
// View function to see pending SUSHIs on frontend.
function pendingSushi(uint256 _pid, address _user)
external
view
returns (uint256)
{
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accSushiPerShare = pool.accSushiPerShare;
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (block.number > pool.lastRewardBlock && lpSupply != 0) {
uint256 multiplier =
getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
accSushiPerShare = accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
}
return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);
}
// Update reward vairables for all pools. Be careful of gas spending!
function massUpdatePools() public {
uint256 length = poolInfo.length;
for (uint256 pid = 0; pid < length; ++pid) {
updatePool(pid);
}
}
// Update reward variables of the given pool to be up-to-date.
function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (lpSupply == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
sushi.mint(devaddr, sushiReward.div(10));
sushi.mint(address(this), sushiReward);
pool.accSushiPerShare = pool.accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
pool.lastRewardBlock = block.number;
}
// Deposit LP tokens to MasterChef for SUSHI allocation.
function deposit(uint256 _pid, uint256 _amount) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
if (user.amount > 0) {
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
}
pool.lpToken.safeTransferFrom(
address(msg.sender),
address(this),
_amount
);
user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
emit Deposit(msg.sender, _pid, _amount);
}
// Withdraw LP tokens from MasterChef.
function withdraw(uint256 _pid, uint256 _amount) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount >= _amount, \\\"withdraw: not good\\\");
updatePool(_pid);
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
user.amount = user.amount.sub(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
pool.lpToken.safeTransfer(address(msg.sender), _amount);
emit Withdraw(msg.sender, _pid, _amount);
}
// Withdraw without caring about rewards. EMERGENCY ONLY.
function emergencyWithdraw(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
emit EmergencyWithdraw(msg.sender, _pid, user.amount);
user.amount = 0;
user.rewardDebt = 0;
}
// Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.
function safeSushiTransfer(address _to, uint256 _amount) internal {
uint256 sushiBal = sushi.balanceOf(address(this));
if (_amount > sushiBal) {
sushi.transfer(_to, sushiBal);
} else {
sushi.transfer(_to, _amount);
}
}
// Update dev address by the previous dev.
function dev(address _devaddr) public {
require(msg.sender == devaddr, \\\"dev: wut?\\\");
devaddr = _devaddr;
}
}
这里的定义的结构体UserInfo用于存储用户的信息:
-
amount:用户所持有的LP tokens的数量
-
rewardDebt:用户计算奖励时的一个先前负债
这里的rewardDebt也可以理解为负债,因为每个人在抵押的时候池子当中的奖励可能已经开始发放或者累积了,即accSushiPerShare数值在增加变化,每个用户抵押后不能把之前累计的奖励也都发给他,因为在他抵押前他是不能产生奖励的,所以就有了一个负债,要从总奖励中扣除整个池子在他抵押前产生的奖励,从他抵押后开始计算,这也是用户奖励计算的方式来源:
pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
同时可以从注释信息中看到用户在进行存取LP tokens时,池子中的accSushiPerShare、lastRewardBlock、rewardDebt等信息都在变化:
// Info of each user.
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
//
// We do some fancy math here. Basically, any point in time, the amount of SUSHIs
// entitled to a user but is pending to be distributed is:
//
// pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
//
// Whenever a user deposits or withdraws LP tokens to a pool. Here\\\'s what happens:
// 1. The pool\\\'s `accSushiPerShare` (and `lastRewardBlock`) gets updated.
// 2. User receives the pending reward sent to his/her address.
// 3. User\\\'s `amount` gets updated.
// 4. User\\\'s `rewardDebt` gets updated.
}
这里定义的结构体PoolInfo用于存储池子信息:
-
lpToken:LP Token合约地址
-
allocPoint:池子从单个区块中分配SUSHIs的权重
-
lastRewardBlock:SUSHIs分发的最近一个区块的编号
-
accSushiPerShare:计算抵押挖矿时单个LP代币可以获得的对应奖励数量
// Info of each pool.
struct PoolInfo {
IERC20 lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.
uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.
uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.
}
之后声明了后续用到的全局变量:
// The SUSHI TOKEN!
SushiToken public sushi; //SUSHI Token合约地址
// Dev address.
address public devaddr; //生产环境地址
// Block number when bonus SUSHI period ends.
uint256 public bonusEndBlock; //奖励SUSHI结束时区块编号
// SUSHI tokens created per block.
uint256 public sushiPerBlock; //一个区块可以产生的SUSHI数量
// Bonus muliplier for early sushi makers.
uint256 public constant BONUS_MULTIPLIER = 10; //早期sushi奖励加倍倍数
// The migrator contract. It has a lot of power. Can only be set through governance (owner).
IMigratorChef public migrator; //migrator合约地址
// Info of each pool.
PoolInfo[] public poolInfo; //用于存储矿池信息的数组
// Info of each user that stakes LP tokens.
mapping(uint256 => mapping(address => UserInfo)) public userInfo; //用户抵押的LP Tokens的信息
// Total allocation poitns. Must be the sum of all allocation points in all pools.
uint256 public totalAllocPoint = 0; //总分配权重(必须是所有池子分配权重之和)
// The block number when SUSHI mining starts.
uint256 public startBlock; //开始挖矿的气势区块
之和定义了相关事件:
//存储
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
//提取
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
//紧急提款
event EmergencyWithdraw(
address indexed user,
uint256 indexed pid,
uint256 amount
);
之后的构造函数进行初始化操作:
constructor(
SushiToken _sushi,
address _devaddr,
uint256 _sushiPerBlock,
uint256 _startBlock,
uint256 _bonusEndBlock
) public {
sushi = _sushi;
devaddr = _devaddr;
sushiPerBlock = _sushiPerBlock;
bonusEndBlock = _bonusEndBlock;
startBlock = _startBlock;
}
poolLength用于返回当前池子个数:
function poolLength() external view returns (uint256) {
return poolInfo.length;
}
add函数用于添加池子,该函数只能由合约的owner调用,且需要传递三个参数:
-
_allocPoint:分配权重(_allocPoint/totalAllocPoint为当前池子单个区块中分配SUSHIs的总数)
-
_lpToken:LP Tokens合约地址
-
_withUpdate:是否更新池子
在这里首先会检查_withUpdate的布尔值,如果为true则更新一次池子,如果为false,则检索当前最后奖励区块数,之后更新总分配点数,之后将新建的池子信息进行存储:
// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
function add(
uint256 _allocPoint,
IERC20 _lpToken,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
uint256 lastRewardBlock =
block.number > startBlock ? block.number : startBlock;
totalAllocPoint = totalAllocPoint.add(_allocPoint);
poolInfo.push(
PoolInfo({
lpToken: _lpToken,
allocPoint: _allocPoint,
lastRewardBlock: lastRewardBlock,
accSushiPerShare: 0
})
);
}
set函数用于更新池子中的SUSHI分配点数,该函数只能由合约的owner调用,在这里需要传递三个参数:
-
_pid:池子的ID序列
-
_allocPoint:分配权重(_allocPoint/totalAllocPoint为当前池子单个区块中分配SUSHIs的总数)
-
_withUpdate:是否更新
之后检查是否更新,如果为true则更新一次池子,如果为false,则通过pid检索对应的池子的分配点数,之后更新分配点数:
// Update the given pool\\\'s SUSHI allocation point. Can only be called by the owner.
function set(
uint256 _pid,
uint256 _allocPoint,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(
_allocPoint
);
poolInfo[_pid].allocPoint = _allocPoint;
}
setMigrator函数只能由合约的owner调用,用于设置migrate合约的地址:
// Set the migrator contract. Can only be called by the owner.
function setMigrator(IMigratorChef _migrator) public onlyOwner {
migrator = _migrator;
}
migrate函数用于将LP token迁移到另一个LP合约中,在这里会首先检查migrator地址是否为空,之后根据pid参数来检索对应的池子信息,之后检索LpToken合约地址,之后查询LpToken合约中的当前合约地址所持有的LP Token数量,之后调用LpToken合约的safeApprove函数将当前合约地址所持有的LP Tokens操作权限授权给migrator合约地址,之后调用migrator合约的migrator函数进行迁移,之后检查LpToken合约中当前合约地址账户所对应的Lp tokens是否等于当前newLpToken合约中当前合约账户地址所持有的LP Tokens数量,最后更新池子信息中的lpToken地址
// Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
function migrate(uint256 _pid) public {
require(address(migrator) != address(0), \\\"migrate: no migrator\\\");
PoolInfo storage pool = poolInfo[_pid];
IERC20 lpToken = pool.lpToken;
uint256 bal = lpToken.balanceOf(address(this));
lpToken.safeApprove(address(migrator), bal);
IERC20 newLpToken = migrator.migrate(lpToken);
require(bal == newLpToken.balanceOf(address(this)), \\\"migrate: bad\\\");
pool.lpToken = newLpToken;
}
下面我们对migrate的migrate方法进行分析一下,看看具体的迁移过程,这里首先会从上面传入lpToken合约的地址作为orig参数的值,这里的相关检查不再赘述(其中chef、notBeforeBlock、oldFactory接在Migrator合约的构造函数中被初始化,不予展开讨论),之后使用token0与token1存储旧LpToken中交易对的地址信息,之后根据token0和token1检索当前是否存在对应的交易对,如果不存在则新建,之后使用lp存储LpToken合约中函数调用者的Lp Tokens数量,如果此时的lp为0,则直接返回pair,如果lp不为0,则将lp赋值给desiredLiquidity,之后调用LpToken合约中的transferFrom函数转移lp到LpToken合约地址中,之后调用LpToken合约地址的burn函数通过燃烧流动性代币的方式来提取相应的两种资产,并相应的减少交易对的流动性,最后通过调用当前交易对的mint函数在用户提供流动性时(提供一定比例的两种ERC-20代币到交易对)增加流动性代币给流动性提供者最后完成LP的迁移,需要注意的是必须是符合UniswapV2协议:
// Migrator
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import \\\"./uniswapv2/interfaces/IUniswapV2Pair.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Factory.sol\\\";
contract Migrator {
address public chef;
address public oldFactory;
IUniswapV2Factory public factory;
uint256 public notBeforeBlock;
uint256 public desiredLiquidity = uint256(-1);
constructor(
address _chef,
address _oldFactory,
IUniswapV2Factory _factory,
uint256 _notBeforeBlock
) public {
chef = _chef;
oldFactory = _oldFactory;
factory = _factory;
notBeforeBlock = _notBeforeBlock;
}
function migrate(IUniswapV2Pair orig) public returns (IUniswapV2Pair) {
require(msg.sender == chef, \\\"not from master chef\\\");
require(block.number >= notBeforeBlock, \\\"too early to migrate\\\");
require(orig.factory() == oldFactory, \\\"not from old factory\\\");
address token0 = orig.token0();
address token1 = orig.token1();
IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1));
if (pair == IUniswapV2Pair(address(0))) {
pair = IUniswapV2Pair(factory.createPair(token0, token1));
}
uint256 lp = orig.balanceOf(msg.sender);
if (lp == 0) return pair;
desiredLiquidity = lp;
orig.transferFrom(msg.sender, address(orig), lp);
orig.burn(address(pair));
pair.mint(msg.sender);
desiredLiquidity = uint256(-1);
return pair;
}
}
getMultiplier函数用于获取奖励乘数,首先会检查to地址是否小于SUSHI一轮结束后的区块数,如果是则计算to和from之间的差值,之后乘以BONUS_MULTIPLIER并返回,如果to大于,SUSHI一轮结束后的区块数,之后检查from是否大于SUSHI一轮结束后的区块数,如果是则计算to与from之间的差值,之后返回,如果form小于SUSHI一轮结束后的区块数,to大于SUSHI一轮结束后的区块数,则计算from与to之间的差值,之后乘以BONUS_MULTIPLIER,然后加上to减去SUSHI一轮结束后的区块数:
// Return reward multiplier over the given _from to _to block.
function getMultiplier(uint256 _from, uint256 _to)
public
view
returns (uint256)
{
if (_to <= bonusEndBlock) {
return _to.sub(_from).mul(BONUS_MULTIPLIER);
} else if (_from >= bonusEndBlock) {
return _to.sub(_from);
} else {
return
bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(
_to.sub(bonusEndBlock)
);
}
}
pendingSushi函数用于检索队列中的SUSHI,该函数首先会根据pid来检索对应池子,之后根据pid和user检索对应池子中用户信息(结构体信息存储),之后检索池子中的accSushiPerShare,之后使用lpSupply存储LpToken合约中当前合约地址所是有的Lp总量,之后检查当前区块数是否大于池子中最后一个奖励区块以及lpSupply是否为0,之后调用之前的getMultiplier来计算奖励乘数,之后使用奖励乘积乘以一个区块可以产生的SUSHI Tokens个数,之后乘以池子的分配权重(池子的分配点数除以总分配点数),之后更新accSushiPerShare(使用accSushiPerShare加上sushiReward之后乘以1e12并除以lpSupply),之后使用用户所持有的LpTokens数量乘以accSushiPerShare然后除以1e12,然后减去用户负债:
// View function to see pending SUSHIs on frontend.
function pendingSushi(uint256 _pid, address _user)
external
view
returns (uint256)
{
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accSushiPerShare = pool.accSushiPerShare;
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (block.number > pool.lastRewardBlock && lpSupply != 0) {
uint256 multiplier =
getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
accSushiPerShare = accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
}
return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);
}
massUpdatePools用于更新奖励池,在这里首先获取奖励池的个数,之后从第一个奖励池开始更新,更新方式是直接调用updatePool函数:
// Update reward vairables for all pools. Be careful of gas spending!
function massUpdatePools() public {
uint256 length = poolInfo.length;
for (uint256 pid = 0; pid < length; ++pid) {
updatePool(pid);
}
}
updatePool函数是更新奖励池的具体实现,在这里首先会根据pid来检索对应的池子信息,之后检查当前区块编号是否小于池子最近一个奖励区块,如果是则直接return,如果大于最近一个奖励区块则检索LpTokens合约地址中当前合约地址所对应的LP总量,如果LP总量为0,则更新最后一个奖励区块为当前区块数,如果LP不为0,则调用getMultiplier来计算奖励乘数,之后计算sushiReward,之后调用sushi合约的mint函数增发sushiReward.div(10)数量的数量的SUSHI,这也是文章开头所说的\\”Sushi在每次分发时会预留10%给项目未来开发迭代及安全审计等\\”,之后向当前合约地址增发sushiReward数量的SUSHI,最后更新accSushiPerShare,以及最近一次奖励区块。
// Update reward variables of the given pool to be up-to-date.
function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (lpSupply == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
sushi.mint(devaddr, sushiReward.div(10));
sushi.mint(address(this), sushiReward);
pool.accSushiPerShare = pool.accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
pool.lastRewardBlock = block.number;
}
deposit函数用于将LP token质押给MasterChef以便于SUSHI分配,该函数首先会根据pid检索对应的池子,之后再根据pid和msg.sender检索池子中用户的相关信息,之后调用updatePool更新一次池子,之后检索用户所持的LP token是否大于0,如果大于0则计算一次pending期间的奖励,具体计算方式是使用用户所持有的LP token数量乘以池子中的accSushiPerShare,之后除以1e12,并减去用户的奖励负债,之后调用safeSushiTransfer发送pending数量的SUSHI到msg.sender,之后再调用lpToken合约的safeTransferFrom函数将msg.sender地址中_amount数量的LpToken发送到当前合约中,之后更新用户所持有的LpToken总量,以及用户的负债奖励,之后通过emit触发事件:
// Deposit LP tokens to MasterChef for SUSHI allocation.
function deposit(uint256 _pid, uint256 _amount) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
if (user.amount > 0) {
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
}
pool.lpToken.safeTransferFrom(
address(msg.sender),
address(this),
_amount
);
user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
emit Deposit(msg.sender, _pid, _amount);
}
withdraw函数用于从MasterChef中提取Lp Token,该函数首先会根据pid来检索对应的池子,之后根据pid和msg.sender来检索对应的user信息,之后检查user所持有的LP tokens是否大于要提取的Lp tokens数量,之后更新一次池子,然后计算一次pending(队列期间获得的奖励,因为在withdraw和deposits期间可能还有其他用户在存取,导致accSushiPerShare等变化,在这个阶段也会产生奖励),之后调用safeSushiTransfer发送对应数量的SUSHI到msg.sender,之后更新用户所持有的LPTokens数量,之后更新奖励负债,之后调用lpToken合约的safeTransfer函数先msg.sender地址发送amout数量的LP tokens,之后调用emit触发事件。
// Withdraw LP tokens from MasterChef.
function withdraw(uint256 _pid, uint256 _amount) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount >= _amount, \\\"withdraw: not good\\\");
updatePool(_pid);
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
user.amount = user.amount.sub(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
pool.lpToken.safeTransfer(address(msg.sender), _amount);
emit Withdraw(msg.sender, _pid, _amount);
}
emergencyWithdraw用于紧急提款,首先会根据pid检索对应的池子,之后根据pid以及msg.sender地址检索用户的信息,之后调用LpToken合约的Safetransfer函数先msg.sender地址发送user.amount数量的LP tokens,之后通过emit触发事件,并更新用户所持有的LP tokens的数量以及奖励负债。
// Withdraw without caring about rewards. EMERGENCY ONLY.
function emergencyWithdraw(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
emit EmergencyWithdraw(msg.sender, _pid, user.amount);
user.amount = 0;
user.rewardDebt = 0;
}
safeSushiTransfer函数就是一个简单的SUSHI转账函数,这里不再赘述:
// Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.
function safeSushiTransfer(address _to, uint256 _amount) internal {
uint256 sushiBal = sushi.balanceOf(address(this));
if (_amount > sushiBal) {
sushi.transfer(_to, sushiBal);
} else {
sushi.transfer(_to, _amount);
}
}
MasterChefV2
MasterChefV2与MasterChef类似,这里就不再赘述了~
Migrator
Migrator合约的作用主要就是在两个支持UniswapV2协议的池子中实现LP的迁移操作,由于之前已对这一部分的代码做过纤细的解释,这里就不再赘述了,如果有问题,可以往前面翻翻看,下面是Migrator的源码:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import \\\"./uniswapv2/interfaces/IUniswapV2Pair.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Factory.sol\\\";
contract Migrator {
address public chef;
address public oldFactory;
IUniswapV2Factory public factory;
uint256 public notBeforeBlock;
uint256 public desiredLiquidity = uint256(-1);
constructor(
address _chef,
address _oldFactory,
IUniswapV2Factory _factory,
uint256 _notBeforeBlock
) public {
chef = _chef;
oldFactory = _oldFactory;
factory = _factory;
notBeforeBlock = _notBeforeBlock;
}
function migrate(IUniswapV2Pair orig) public returns (IUniswapV2Pair) {
require(msg.sender == chef, \\\"not from master chef\\\");
require(block.number >= notBeforeBlock, \\\"too early to migrate\\\");
require(orig.factory() == oldFactory, \\\"not from old factory\\\");
address token0 = orig.token0();
address token1 = orig.token1();
IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1));
if (pair == IUniswapV2Pair(address(0))) {
pair = IUniswapV2Pair(factory.createPair(token0, token1));
}
uint256 lp = orig.balanceOf(msg.sender);
if (lp == 0) return pair;
desiredLiquidity = lp;
orig.transferFrom(msg.sender, address(orig), lp);
orig.burn(address(pair));
pair.mint(msg.sender);
desiredLiquidity = uint256(-1);
return pair;
}
}
Ownable
Ownerable合约算是一个辅助性扩展合约吧,主要涉及到owner的初始化以及owner权限的转移等,较为简单,这里不再赘述,下面是源码:
// SPDX-License-Identifier: MIT
// Audit on 5-Jan-2021 by Keno and BoringCrypto
// P1 - P3: OK
pragma solidity 0.6.12;
// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
// Edited by BoringCrypto
// T1 - T4: OK
contract OwnableData {
// V1 - V5: OK
address public owner;
// V1 - V5: OK
address public pendingOwner;
}
// T1 - T4: OK
contract Ownable is OwnableData {
// E1: OK
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor () internal {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
// F1 - F9: OK
// C1 - C21: OK
function transferOwnership(address newOwner, bool direct, bool renounce) public onlyOwner {
if (direct) {
// Checks
require(newOwner != address(0) || renounce, \\\"Ownable: zero address\\\");
// Effects
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
} else {
// Effects
pendingOwner = newOwner;
}
}
// F1 - F9: OK
// C1 - C21: OK
function claimOwnership() public {
address _pendingOwner = pendingOwner;
// Checks
require(msg.sender == _pendingOwner, \\\"Ownable: caller != pending owner\\\");
// Effects
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
pendingOwner = address(0);
}
// M1 - M5: OK
// C1 - C21: OK
modifier onlyOwner() {
require(msg.sender == owner, \\\"Ownable: caller is not the owner\\\");
_;
}
}
SushiBar
SushiBar合约的主要作用是抵押SUSHI以获得更多SUSHI,用户可以先抵押Sushi,然后获得xSushi作为回报,然后将其放到xSushi池中,当用户在SushiSwap交易所进行交易时,将收取0.3%的费用,此费用的0.05%以LP令牌的形式添加到SushiBar池中,当奖励合同被调用时(每天最少一次),所有LP代币都将以Sushi的价格出售(在SushiSwap Exchange上), 然后将新购买的SUSHI按比例分配给池中的xSushi持有者,这意味着他们的xSushi现在价值更高。目前,直到提款,您才能看到增加的金额,它最初是1个SUSHI= 1个xSushi,但是就像LP代币一样,xSushi的价格会随着时间的变化而变化,这取决于池中有多少SUSHI奖励,下面是官方源码:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";
import \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";
import \\\"@openzeppelin/contracts/math/SafeMath.sol\\\";
// SushiBar is the coolest bar in town. You come in with some Sushi, and leave with more! The longer you stay, the more Sushi you get.
//
// This contract handles swapping to and from xSushi, SushiSwap\\\'s staking token.
contract SushiBar is ERC20(\\\"SushiBar\\\", \\\"xSUSHI\\\"){
using SafeMath for uint256;
IERC20 public sushi;
// Define the Sushi token contract
constructor(IERC20 _sushi) public {
sushi = _sushi;
}
// Enter the bar. Pay some SUSHIs. Earn some shares.
// Locks Sushi and mints xSushi
function enter(uint256 _amount) public {
// Gets the amount of Sushi locked in the contract
uint256 totalSushi = sushi.balanceOf(address(this));
// Gets the amount of xSushi in existence
uint256 totalShares = totalSupply();
// If no xSushi exists, mint it 1:1 to the amount put in
if (totalShares == 0 || totalSushi == 0) {
_mint(msg.sender, _amount);
}
// Calculate and mint the amount of xSushi the Sushi is worth. The ratio will change overtime, as xSushi is burned/minted and Sushi deposited + gained from fees / withdrawn.
else {
uint256 what = _amount.mul(totalShares).div(totalSushi);
_mint(msg.sender, what);
}
// Lock the Sushi in the contract
sushi.transferFrom(msg.sender, address(this), _amount);
}
// Leave the bar. Claim back your SUSHIs.
// Unlocks the staked + gained Sushi and burns xSushi
function leave(uint256 _share) public {
// Gets the amount of xSushi in existence
uint256 totalShares = totalSupply();
// Calculates the amount of Sushi the xSushi is worth
uint256 what = _share.mul(sushi.balanceOf(address(this))).div(totalShares);
_burn(msg.sender, _share);
sushi.transfer(msg.sender, what);
}
}
当前合约继承自ERC20合约,并进行了一次初始化操作
contract SushiBar is ERC20(\\\"SushiBar\\\", \\\"xSUSHI\\\"){
之后在构造函数中初始化了SUSHI合约的地址
// Define the Sushi token contract
constructor(IERC20 _sushi) public {
sushi = _sushi;
}
这里的enter函数用于锁定sushi并铸造Xsushi,在这里首先会检查当前合约中的sushi总量,之后计算一次xSushi总量,如果l以上两者有任意一个的总量为0,则调用_mint函数按照1:1的比例铸币,如果两者都不为0则计算需要增发的Xsushi的总量,并调用mint进行增发Xsushi,之后调用sushi的transferfrom函数向当前合约地址注入sushi:
// Enter the bar. Pay some SUSHIs. Earn some shares.
// Locks Sushi and mints xSushi
function enter(uint256 _amount) public {
// Gets the amount of Sushi locked in the contract
uint256 totalSushi = sushi.balanceOf(address(this));
// Gets the amount of xSushi in existence
uint256 totalShares = totalSupply();
// If no xSushi exists, mint it 1:1 to the amount put in
if (totalShares == 0 || totalSushi == 0) {
_mint(msg.sender, _amount);
}
// Calculate and mint the amount of xSushi the Sushi is worth. The ratio will change overtime, as xSushi is burned/minted and Sushi deposited + gained from fees / withdrawn.
else {
uint256 what = _amount.mul(totalShares).div(totalSushi);
_mint(msg.sender, what);
}
// Lock the Sushi in the contract
sushi.transferFrom(msg.sender, address(this), _amount);
}
这里的leave函数用于解锁和提取Sushi并销毁Xsushi,和上面的函数功能截然相反,在这里首先计算xSushi总量,之后计算当前的xSushi可以值多少的Sushi,之后销毁xSushi并提取Sushi
// Leave the bar. Claim back your SUSHIs.
// Unlocks the staked + gained Sushi and burns xSushi
function leave(uint256 _share) public {
// Gets the amount of xSushi in existence
uint256 totalShares = totalSupply();
// Calculates the amount of Sushi the xSushi is worth
uint256 what = _share.mul(sushi.balanceOf(address(this))).div(totalShares);
_burn(msg.sender, _share);
sushi.transfer(msg.sender, what);
}
SushiMaker
SushiMaker合约的作用转换Token为SUSHI并发送到SushiBar,官方提供的源码如下所示:
// SPDX-License-Identifier: MIT
// P1 - P3: OK
pragma solidity 0.6.12;
import \\\"./libraries/SafeMath.sol\\\";
import \\\"./libraries/SafeERC20.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2ERC20.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Pair.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Factory.sol\\\";
import \\\"./Ownable.sol\\\";
// SushiMaker is MasterChef\\\'s left hand and kinda a wizard. He can cook up Sushi from pretty much anything!
// This contract handles \\\"serving up\\\" rewards for xSushi holders by trading tokens collected from fees for Sushi.
// T1 - T4: OK
contract SushiMaker is Ownable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// V1 - V5: OK
IUniswapV2Factory public immutable factory;
//0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac
// V1 - V5: OK
address public immutable bar;
//0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272
// V1 - V5: OK
address private immutable sushi;
//0x6B3595068778DD592e39A122f4f5a5cF09C90fE2
// V1 - V5: OK
address private immutable weth;
//0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
// V1 - V5: OK
mapping(address => address) internal _bridges;
// E1: OK
event LogBridgeSet(address indexed token, address indexed bridge);
// E1: OK
event LogConvert(
address indexed server,
address indexed token0,
address indexed token1,
uint256 amount0,
uint256 amount1,
uint256 amountSUSHI
);
constructor(
address _factory,
address _bar,
address _sushi,
address _weth
) public {
factory = IUniswapV2Factory(_factory);
bar = _bar;
sushi = _sushi;
weth = _weth;
}
// F1 - F10: OK
// C1 - C24: OK
function bridgeFor(address token) public view returns (address bridge) {
bridge = _bridges[token];
if (bridge == address(0)) {
bridge = weth;
}
}
// F1 - F10: OK
// C1 - C24: OK
function setBridge(address token, address bridge) external onlyOwner {
// Checks
require(
token != sushi && token != weth && token != bridge,
\\\"SushiMaker: Invalid bridge\\\"
);
// Effects
_bridges[token] = bridge;
emit LogBridgeSet(token, bridge);
}
// M1 - M5: OK
// C1 - C24: OK
// C6: It\\\'s not a fool proof solution, but it prevents flash loans, so here it\\\'s ok to use tx.origin
modifier onlyEOA() {
// Try to make flash-loan exploit harder to do by only allowing externally owned addresses.
require(msg.sender == tx.origin, \\\"SushiMaker: must use EOA\\\");
_;
}
// F1 - F10: OK
// F3: _convert is separate to save gas by only checking the \\\'onlyEOA\\\' modifier once in case of convertMultiple
// F6: There is an exploit to add lots of SUSHI to the bar, run convert, then remove the SUSHI again.
// As the size of the SushiBar has grown, this requires large amounts of funds and isn\\\'t super profitable anymore
// The onlyEOA modifier prevents this being done with a flash loan.
// C1 - C24: OK
function convert(address token0, address token1) external onlyEOA() {
_convert(token0, token1);
}
// F1 - F10: OK, see convert
// C1 - C24: OK
// C3: Loop is under control of the caller
function convertMultiple(
address[] calldata token0,
address[] calldata token1
) external onlyEOA() {
// TODO: This can be optimized a fair bit, but this is safer and simpler for now
uint256 len = token0.length;
for (uint256 i = 0; i < len; i++) {
_convert(token0[i], token1[i]);
}
}
// F1 - F10: OK
// C1- C24: OK
function _convert(address token0, address token1) internal {
// Interactions
// S1 - S4: OK
IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1));
require(address(pair) != address(0), \\\"SushiMaker: Invalid pair\\\");
// balanceOf: S1 - S4: OK
// transfer: X1 - X5: OK
IERC20(address(pair)).safeTransfer(
address(pair),
pair.balanceOf(address(this))
);
// X1 - X5: OK
(uint256 amount0, uint256 amount1) = pair.burn(address(this));
if (token0 != pair.token0()) {
(amount0, amount1) = (amount1, amount0);
}
emit LogConvert(
msg.sender,
token0,
token1,
amount0,
amount1,
_convertStep(token0, token1, amount0, amount1)
);
}
// F1 - F10: OK
// C1 - C24: OK
// All safeTransfer, _swap, _toSUSHI, _convertStep: X1 - X5: OK
function _convertStep(
address token0,
address token1,
uint256 amount0,
uint256 amount1
) internal returns (uint256 sushiOut) {
// Interactions
if (token0 == token1) {
uint256 amount = amount0.add(amount1);
if (token0 == sushi) {
IERC20(sushi).safeTransfer(bar, amount);
sushiOut = amount;
} else if (token0 == weth) {
sushiOut = _toSUSHI(weth, amount);
} else {
address bridge = bridgeFor(token0);
amount = _swap(token0, bridge, amount, address(this));
sushiOut = _convertStep(bridge, bridge, amount, 0);
}
} else if (token0 == sushi) {
// eg. SUSHI - ETH
IERC20(sushi).safeTransfer(bar, amount0);
sushiOut = _toSUSHI(token1, amount1).add(amount0);
} else if (token1 == sushi) {
// eg. USDT - SUSHI
IERC20(sushi).safeTransfer(bar, amount1);
sushiOut = _toSUSHI(token0, amount0).add(amount1);
} else if (token0 == weth) {
// eg. ETH - USDC
sushiOut = _toSUSHI(
weth,
_swap(token1, weth, amount1, address(this)).add(amount0)
);
} else if (token1 == weth) {
// eg. USDT - ETH
sushiOut = _toSUSHI(
weth,
_swap(token0, weth, amount0, address(this)).add(amount1)
);
} else {
// eg. MIC - USDT
address bridge0 = bridgeFor(token0);
address bridge1 = bridgeFor(token1);
if (bridge0 == token1) {
// eg. MIC - USDT - and bridgeFor(MIC) = USDT
sushiOut = _convertStep(
bridge0,
token1,
_swap(token0, bridge0, amount0, address(this)),
amount1
);
} else if (bridge1 == token0) {
// eg. WBTC - DSD - and bridgeFor(DSD) = WBTC
sushiOut = _convertStep(
token0,
bridge1,
amount0,
_swap(token1, bridge1, amount1, address(this))
);
} else {
sushiOut = _convertStep(
bridge0,
bridge1, // eg. USDT - DSD - and bridgeFor(DSD) = WBTC
_swap(token0, bridge0, amount0, address(this)),
_swap(token1, bridge1, amount1, address(this))
);
}
}
}
// F1 - F10: OK
// C1 - C24: OK
// All safeTransfer, swap: X1 - X5: OK
function _swap(
address fromToken,
address toToken,
uint256 amountIn,
address to
) internal returns (uint256 amountOut) {
// Checks
// X1 - X5: OK
IUniswapV2Pair pair =
IUniswapV2Pair(factory.getPair(fromToken, toToken));
require(address(pair) != address(0), \\\"SushiMaker: Cannot convert\\\");
// Interactions
// X1 - X5: OK
(uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
uint256 amountInWithFee = amountIn.mul(997);
if (fromToken == pair.token0()) {
amountOut =
amountIn.mul(997).mul(reserve1) /
reserve0.mul(1000).add(amountInWithFee);
IERC20(fromToken).safeTransfer(address(pair), amountIn);
pair.swap(0, amountOut, to, new bytes(0));
// TODO: Add maximum slippage?
} else {
amountOut =
amountIn.mul(997).mul(reserve0) /
reserve1.mul(1000).add(amountInWithFee);
IERC20(fromToken).safeTransfer(address(pair), amountIn);
pair.swap(amountOut, 0, to, new bytes(0));
// TODO: Add maximum slippage?
}
}
// F1 - F10: OK
// C1 - C24: OK
function _toSUSHI(address token, uint256 amountIn)
internal
returns (uint256 amountOut)
{
// X1 - X5: OK
amountOut = _swap(token, sushi, amountIn, bar);
}
}
这里的构造函数直接初始化了UniswapV2工厂合约的地址、bar合约的地址、SUSHI合约的地址、WETH合约的地址:
constructor(
address _factory,
address _bar,
address _sushi,
address _weth
) public {
factory = IUniswapV2Factory(_factory);
bar = _bar;
sushi = _sushi;
weth = _weth;
}
bridgeFor函数用于检索换取某种token的桥梁,如果未设置默认是WETH:
// F1 - F10: OK
// C1 - C24: OK
function bridgeFor(address token) public view returns (address bridge) {
bridge = _bridges[token];
if (bridge == address(0)) {
bridge = weth;
}
}
setBridge用于设置换取某种token的桥梁:
// F1 - F10: OK
// C1 - C24: OK
function setBridge(address token, address bridge) external onlyOwner {
// Checks
require(
token != sushi && token != weth && token != bridge,
\\\"SushiMaker: Invalid bridge\\\"
);
// Effects
_bridges[token] = bridge;
emit LogBridgeSet(token, bridge);
}
必须使用外部账户调用:
// M1 - M5: OK
// C1 - C24: OK
// C6: It\\\'s not a fool proof solution, but it prevents flash loans, so here it\\\'s ok to use tx.origin
modifier onlyEOA() {
// Try to make flash-loan exploit harder to do by only allowing externally owned addresses.
require(msg.sender == tx.origin, \\\"SushiMaker: must use EOA\\\");
_;
}
两种代币之间的转换:
// F1 - F10: OK
// F3: _convert is separate to save gas by only checking the \\\'onlyEOA\\\' modifier once in case of convertMultiple
// F6: There is an exploit to add lots of SUSHI to the bar, run convert, then remove the SUSHI again.
// As the size of the SushiBar has grown, this requires large amounts of funds and isn\\\'t super profitable anymore
// The onlyEOA modifier prevents this being done with a flash loan.
// C1 - C24: OK
function convert(address token0, address token1) external onlyEOA() {
_convert(token0, token1);
}
// F1 - F10: OK
// C1- C24: OK
function _convert(address token0, address token1) internal {
// Interactions
// S1 - S4: OK
IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1));
require(address(pair) != address(0), \\\"SushiMaker: Invalid pair\\\");
// balanceOf: S1 - S4: OK
// transfer: X1 - X5: OK
IERC20(address(pair)).safeTransfer(
address(pair),
pair.balanceOf(address(this))
);
// X1 - X5: OK
(uint256 amount0, uint256 amount1) = pair.burn(address(this));
if (token0 != pair.token0()) {
(amount0, amount1) = (amount1, amount0);
}
emit LogConvert(
msg.sender,
token0,
token1,
amount0,
amount1,
_convertStep(token0, token1, amount0, amount1)
);
}
// F1 - F10: OK
// C1 - C24: OK
// All safeTransfer, _swap, _toSUSHI, _convertStep: X1 - X5: OK
function _convertStep(
address token0,
address token1,
uint256 amount0,
uint256 amount1
) internal returns (uint256 sushiOut) {
// Interactions
if (token0 == token1) {
uint256 amount = amount0.add(amount1);
if (token0 == sushi) {
IERC20(sushi).safeTransfer(bar, amount);
sushiOut = amount;
} else if (token0 == weth) {
sushiOut = _toSUSHI(weth, amount);
} else {
address bridge = bridgeFor(token0);
amount = _swap(token0, bridge, amount, address(this));
sushiOut = _convertStep(bridge, bridge, amount, 0);
}
} else if (token0 == sushi) {
// eg. SUSHI - ETH
IERC20(sushi).safeTransfer(bar, amount0);
sushiOut = _toSUSHI(token1, amount1).add(amount0);
} else if (token1 == sushi) {
// eg. USDT - SUSHI
IERC20(sushi).safeTransfer(bar, amount1);
sushiOut = _toSUSHI(token0, amount0).add(amount1);
} else if (token0 == weth) {
// eg. ETH - USDC
sushiOut = _toSUSHI(
weth,
_swap(token1, weth, amount1, address(this)).add(amount0)
);
} else if (token1 == weth) {
// eg. USDT - ETH
sushiOut = _toSUSHI(
weth,
_swap(token0, weth, amount0, address(this)).add(amount1)
);
} else {
// eg. MIC - USDT
address bridge0 = bridgeFor(token0);
address bridge1 = bridgeFor(token1);
if (bridge0 == token1) {
// eg. MIC - USDT - and bridgeFor(MIC) = USDT
sushiOut = _convertStep(
bridge0,
token1,
_swap(token0, bridge0, amount0, address(this)),
amount1
);
} else if (bridge1 == token0) {
// eg. WBTC - DSD - and bridgeFor(DSD) = WBTC
sushiOut = _convertStep(
token0,
bridge1,
amount0,
_swap(token1, bridge1, amount1, address(this))
);
} else {
sushiOut = _convertStep(
bridge0,
bridge1, // eg. USDT - DSD - and bridgeFor(DSD) = WBTC
_swap(token0, bridge0, amount0, address(this)),
_swap(token1, bridge1, amount1, address(this))
);
}
}
}
// F1 - F10: OK
// C1 - C24: OK
// All safeTransfer, swap: X1 - X5: OK
function _swap(
address fromToken,
address toToken,
uint256 amountIn,
address to
) internal returns (uint256 amountOut) {
// Checks
// X1 - X5: OK
IUniswapV2Pair pair =
IUniswapV2Pair(factory.getPair(fromToken, toToken));
require(address(pair) != address(0), \\\"SushiMaker: Cannot convert\\\");
// Interactions
// X1 - X5: OK
(uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
uint256 amountInWithFee = amountIn.mul(997);
if (fromToken == pair.token0()) {
amountOut =
amountIn.mul(997).mul(reserve1) /
reserve0.mul(1000).add(amountInWithFee);
IERC20(fromToken).safeTransfer(address(pair), amountIn);
pair.swap(0, amountOut, to, new bytes(0));
// TODO: Add maximum slippage?
} else {
amountOut =
amountIn.mul(997).mul(reserve0) /
reserve1.mul(1000).add(amountInWithFee);
IERC20(fromToken).safeTransfer(address(pair), amountIn);
pair.swap(amountOut, 0, to, new bytes(0));
// TODO: Add maximum slippage?
}
}
// F1 - F10: OK
// C1 - C24: OK
function _toSUSHI(address token, uint256 amountIn)
internal
returns (uint256 amountOut)
{
// X1 - X5: OK
amountOut = _swap(token, sushi, amountIn, bar);
}
SushiRoll
SushiRoll帮助您将现有Uniswap LP令牌迁移到SushiSwap LP令牌,官方源码如下所示:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";
import \\\"@openzeppelin/contracts/token/ERC20/SafeERC20.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Pair.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Router01.sol\\\";
import \\\"./uniswapv2/interfaces/IUniswapV2Factory.sol\\\";
import \\\"./uniswapv2/libraries/UniswapV2Library.sol\\\";
// SushiRoll helps your migrate your existing Uniswap LP tokens to SushiSwap LP ones
contract SushiRoll {
using SafeERC20 for IERC20;
IUniswapV2Router01 public oldRouter;
IUniswapV2Router01 public router;
constructor(IUniswapV2Router01 _oldRouter, IUniswapV2Router01 _router) public {
oldRouter = _oldRouter;
router = _router;
}
function migrateWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public {
IUniswapV2Pair pair = IUniswapV2Pair(pairForOldRouter(tokenA, tokenB));
pair.permit(msg.sender, address(this), liquidity, deadline, v, r, s);
migrate(tokenA, tokenB, liquidity, amountAMin, amountBMin, deadline);
}
// msg.sender should have approved \\\'liquidity\\\' amount of LP token of \\\'tokenA\\\' and \\\'tokenB\\\'
function migrate(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline
) public {
require(deadline >= block.timestamp, \\\'SushiSwap: EXPIRED\\\');
// Remove liquidity from the old router with permit
(uint256 amountA, uint256 amountB) = removeLiquidity(
tokenA,
tokenB,
liquidity,
amountAMin,
amountBMin,
deadline
);
// Add liquidity to the new router
(uint256 pooledAmountA, uint256 pooledAmountB) = addLiquidity(tokenA, tokenB, amountA, amountB);
// Send remaining tokens to msg.sender
if (amountA > pooledAmountA) {
IERC20(tokenA).safeTransfer(msg.sender, amountA - pooledAmountA);
}
if (amountB > pooledAmountB) {
IERC20(tokenB).safeTransfer(msg.sender, amountB - pooledAmountB);
}
}
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline
) internal returns (uint256 amountA, uint256 amountB) {
IUniswapV2Pair pair = IUniswapV2Pair(pairForOldRouter(tokenA, tokenB));
pair.transferFrom(msg.sender, address(pair), liquidity);
(uint256 amount0, uint256 amount1) = pair.burn(address(this));
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, \\\'SushiRoll: INSUFFICIENT_A_AMOUNT\\\');
require(amountB >= amountBMin, \\\'SushiRoll: INSUFFICIENT_B_AMOUNT\\\');
}
// calculates the CREATE2 address for a pair without making any external calls
function pairForOldRouter(address tokenA, address tokenB) internal view returns (address pair) {
(address token0, address token1) = UniswapV2Library.sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex\\\'ff\\\',
oldRouter.factory(),
keccak256(abi.encodePacked(token0, token1)),
hex\\\'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f\\\' // init code hash
))));
}
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired
) internal returns (uint amountA, uint amountB) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired);
address pair = UniswapV2Library.pairFor(router.factory(), tokenA, tokenB);
IERC20(tokenA).safeTransfer(pair, amountA);
IERC20(tokenB).safeTransfer(pair, amountB);
IUniswapV2Pair(pair).mint(msg.sender);
}
function _addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired
) internal returns (uint256 amountA, uint256 amountB) {
// create the pair if it doesn\\\'t exist yet
IUniswapV2Factory factory = IUniswapV2Factory(router.factory());
if (factory.getPair(tokenA, tokenB) == address(0)) {
factory.createPair(tokenA, tokenB);
}
(uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(address(factory), tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint256 amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint256 amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
}
构造函数初始化了旧UniswapV2Router地址和新UniswapV2Router地址:
constructor(IUniswapV2Router01 _oldRouter, IUniswapV2Router01 _router) public {
oldRouter = _oldRouter;
router = _router;
}
migrateWithPermit函数用于检索交易对是否存在,然后调用交易对合约的permit函数进行授权操作,最后调用migrate进行迁移:
function migrateWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public {
IUniswapV2Pair pair = IUniswapV2Pair(pairForOldRouter(tokenA, tokenB));
pair.permit(msg.sender, address(this), liquidity, deadline, v, r, s);
migrate(tokenA, tokenB, liquidity, amountAMin, amountBMin, deadline);
}
// msg.sender should have approved \\\'liquidity\\\' amount of LP token of \\\'tokenA\\\' and \\\'tokenB\\\'
function migrate(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline
) public {
require(deadline >= block.timestamp, \\\'SushiSwap: EXPIRED\\\');
// Remove liquidity from the old router with permit
(uint256 amountA, uint256 amountB) = removeLiquidity(
tokenA,
tokenB,
liquidity,
amountAMin,
amountBMin,
deadline
);
// Add liquidity to the new router
(uint256 pooledAmountA, uint256 pooledAmountB) = addLiquidity(tokenA, tokenB, amountA, amountB);
// Send remaining tokens to msg.sender
if (amountA > pooledAmountA) {
IERC20(tokenA).safeTransfer(msg.sender, amountA - pooledAmountA);
}
if (amountB > pooledAmountB) {
IERC20(tokenB).safeTransfer(msg.sender, amountB - pooledAmountB);
}
}
removeLiquidity用于移除流动性,之后将要燃烧的流动性代币转回交易对合约,之后调用交易对的burn函数燃烧掉转进去的流动性代币,然后提取相应的两种代币给接收者,之后进行排序,之后进行赋值操作,然后检查是否提取的对应代币是否大于最小的提取代币数量:
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline
) internal returns (uint256 amountA, uint256 amountB) {
IUniswapV2Pair pair = IUniswapV2Pair(pairForOldRouter(tokenA, tokenB));
pair.transferFrom(msg.sender, address(pair), liquidity);
(uint256 amount0, uint256 amount1) = pair.burn(address(this));
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, \\\'SushiRoll: INSUFFICIENT_A_AMOUNT\\\');
require(amountB >= amountBMin, \\\'SushiRoll: INSUFFICIENT_B_AMOUNT\\\');
}
// UniswapV2Pair.sol
// this low-level function should be called from a contract which performs important safety checks
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
uint balance0 = IERC20Uniswap(_token0).balanceOf(address(this));
uint balance1 = IERC20Uniswap(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, \\\'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED\\\');
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20Uniswap(_token0).balanceOf(address(this));
balance1 = IERC20Uniswap(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
pairForOldRouter用于在不进行任何外部调用的情况下计算对的CREATE2地址
// calculates the CREATE2 address for a pair without making any external calls
function pairForOldRouter(address tokenA, address tokenB) internal view returns (address pair) {
(address token0, address token1) = UniswapV2Library.sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex\\\'ff\\\',
oldRouter.factory(),
keccak256(abi.encodePacked(token0, token1)),
hex\\\'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f\\\' // init code hash
))));
addLiquidity用于增加流动性:
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired
) internal returns (uint amountA, uint amountB) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired);
address pair = UniswapV2Library.pairFor(router.factory(), tokenA, tokenB);
IERC20(tokenA).safeTransfer(pair, amountA);
IERC20(tokenB).safeTransfer(pair, amountB);
IUniswapV2Pair(pair).mint(msg.sender);
}
function _addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired
) internal returns (uint256 amountA, uint256 amountB) {
// create the pair if it doesn\\\'t exist yet
IUniswapV2Factory factory = IUniswapV2Factory(router.factory());
if (factory.getPair(tokenA, tokenB) == address(0)) {
factory.createPair(tokenA, tokenB);
}
(uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(address(factory), tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint256 amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint256 amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
}
安全问题
在这里我们要对上面的SushiSwap合约中的两个安全问题进行简要分析:
条件竞争
漏洞函数:emergencyWithdraw
漏洞代码:
// Withdraw without caring about rewards. EMERGENCY ONLY.
function emergencyWithdraw(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
emit EmergencyWithdraw(msg.sender, _pid, user.amount);
user.amount = 0;
user.rewardDebt = 0;
}
漏洞描述:如上面的代码所述,这里的emergencyWithdraw函数用于紧急提款,但是资产的更新位于转账之后,导致存在条件竞争关系。
解决方案:正确的写法理应如下
function emergencyWithdraw(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
uint256 amount = user.amount;
user.amount = 0;
user.rewardDebt = 0;
pool.lpToken.safeTransfer(address(msg.sender), amount);
emit EmergencyWithdraw(msg.sender, _pid, amount);
}
重入攻击
漏洞函数:setMigrator+migrate
漏洞代码:
// Set the migrator contract. Can only be called by the owner.
function setMigrator(IMigratorChef _migrator) public onlyOwner {
migrator = _migrator;
}
// Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
function migrate(uint256 _pid) public {
require(address(migrator) != address(0), \\\"migrate: no migrator\\\");
PoolInfo storage pool = poolInfo[_pid];
IERC20 lpToken = pool.lpToken;
uint256 bal = lpToken.balanceOf(address(this));
lpToken.safeApprove(address(migrator), bal);
IERC20 newLpToken = migrator.migrate(lpToken);
require(bal == newLpToken.balanceOf(address(this)), \\\"migrate: bad\\\");
pool.lpToken = newLpToken;
}
漏洞描述:在上面的代码中可以看到合约的owner可以设定migrator,当migrator的值被确定后migrator.migrate(lpToken)也就可以被随之确定,由于migrate的方法是通过IMigratorChef的接口来进行调用的,因此在调用的时候,migrate的方法中的逻辑代码会根据migrator值的不同而变化,此时如果智能合约的owner将migrator的值指向一个包含恶意migrate方法代码的智能合约,那么该拥有者可以进行任何其想进行的恶意操作,甚至可能取空账户内所有的代币,同时在migrator.migrate(lpToken)这一行代码执行结束后,合约拥有者也可以利用重入攻击漏洞,再次重新执行从migrate方法或者其他智能合约方法,进行恶意操作。
解决方案:合理设置owner的权限
文末小结
SushiSwap是在UniswapV2协议的基础上进行了拓展,因为其紧急奖励模型更加偏向于用户利益,所以它相较与UniswapV2更容易吸引更多用户参与质押来提供流动性,同时本篇文章也揭示了权限问题、编码逻辑设计所暴露的安全风险问题。
参考链接
https://app.sushi.com/
https://docs.sushi.com/
https://help.sushidocs.com/
https://sushiswapchef.medium.com
原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34351.html