IFO英文全称是Initial Farm Offering,用户在pancake进行CAKE-BNB挖矿获得的LP Token,用户使用LP Token可以在IFO合约中进行新发布币种的认购。
代码在:initial-farm-offering/IFO.sol at 1d452d71d5956693467605e76b04a1f6c06c8344 · pancakeswap/initial-farm-offering · GitHub
pragma solidity 0.6.12;
import '@pancakeswap/pancake-swap-lib/contracts/math/SafeMath.sol';
import '@pancakeswap/pancake-swap-lib/contracts/token/BEP20/IBEP20.sol';
import '@pancakeswap/pancake-swap-lib/contracts/token/BEP20/SafeBEP20.sol';
import '@pancakeswap/pancake-swap-lib/contracts/utils/ReentrancyGuard.sol';
contract IFO is ReentrancyGuard {
using SafeMath for uint256;
using SafeBEP20 for IBEP20;
// Info of each user.
struct UserInfo {
uint256 amount; // How many tokens the user has provided.
bool claimed; // default false
}
// admin address
address public adminAddress;
// The raising token
IBEP20 public lpToken;
// The offering token
IBEP20 public offeringToken;
// The block number when IFO starts
uint256 public startBlock;
// The block number when IFO ends
uint256 public endBlock;
// total amount of raising tokens need to be raised
uint256 public raisingAmount;
// total amount of offeringToken that will offer
uint256 public offeringAmount;
// total amount of raising tokens that have already raised
uint256 public totalAmount;
// address => amount
mapping (address => UserInfo) public userInfo;
// participators
address[] public addressList;
event Deposit(address indexed user, uint256 amount);
event Harvest(address indexed user, uint256 offeringAmount, uint256 excessAmount);
constructor(
IBEP20 _lpToken,
IBEP20 _offeringToken,
uint256 _startBlock,
uint256 _endBlock,
uint256 _offeringAmount,
uint256 _raisingAmount,
address _adminAddress
) public {
lpToken = _lpToken;
offeringToken = _offeringToken;
startBlock = _startBlock;
endBlock = _endBlock;
offeringAmount = _offeringAmount;
raisingAmount= _raisingAmount;
totalAmount = 0;
adminAddress = _adminAddress;
}
modifier onlyAdmin() {
require(msg.sender == adminAddress, "admin: wut?");
_;
}
function setOfferingAmount(uint256 _offerAmount) public onlyAdmin {
require (block.number < startBlock, 'no');
offeringAmount = _offerAmount;
}
function setRaisingAmount(uint256 _raisingAmount) public onlyAdmin {
require (block.number < startBlock, 'no');
raisingAmount= _raisingAmount;
}
function deposit(uint256 _amount) public {
require (block.number > startBlock && block.number < endBlock, 'not ifo time');
require (_amount > 0, 'need _amount > 0');
lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
if (userInfo[msg.sender].amount == 0) {
addressList.push(address(msg.sender));
}
userInfo[msg.sender].amount = userInfo[msg.sender].amount.add(_amount);
totalAmount = totalAmount.add(_amount);
emit Deposit(msg.sender, _amount);
}
function harvest() public nonReentrant {
require (block.number > endBlock, 'not harvest time');
require (userInfo[msg.sender].amount > 0, 'have you participated?');
require (!userInfo[msg.sender].claimed, 'nothing to harvest');
uint256 offeringTokenAmount = getOfferingAmount(msg.sender);
uint256 refundingTokenAmount = getRefundingAmount(msg.sender);
offeringToken.safeTransfer(address(msg.sender), offeringTokenAmount);
if (refundingTokenAmount > 0) {
lpToken.safeTransfer(address(msg.sender), refundingTokenAmount);
}
userInfo[msg.sender].claimed = true;
emit Harvest(msg.sender, offeringTokenAmount, refundingTokenAmount);
}
function hasHarvest(address _user) external view returns(bool) {
return userInfo[_user].claimed;
}
// allocation 100000 means 0.1(10%), 1 meanss 0.000001(0.0001%), 1000000 means 1(100%)
function getUserAllocation(address _user) public view returns(uint256) {
return userInfo[_user].amount.mul(1e12).div(totalAmount).div(1e6);
}
// get the amount of IFO token you will get
function getOfferingAmount(address _user) public view returns(uint256) {
if (totalAmount > raisingAmount) {
uint256 allocation = getUserAllocation(_user);
return offeringAmount.mul(allocation).div(1e6);
}
else {
// userInfo[_user] / (raisingAmount / offeringAmount)
return userInfo[_user].amount.mul(offeringAmount).div(raisingAmount);
}
}
// get the amount of lp token you will be refunded
function getRefundingAmount(address _user) public view returns(uint256) {
if (totalAmount <= raisingAmount) {
return 0;
}
uint256 allocation = getUserAllocation(_user);
uint256 payAmount = raisingAmount.mul(allocation).div(1e6);
return userInfo[_user].amount.sub(payAmount);
}
function getAddressListLength() external view returns(uint256) {
return addressList.length;
}
function finalWithdraw(uint256 _lpAmount, uint256 _offerAmount) public onlyAdmin {
require (_lpAmount < lpToken.balanceOf(address(this)), 'not enough token 0');
require (_offerAmount < offeringToken.balanceOf(address(this)), 'not enough token 1');
lpToken.safeTransfer(address(msg.sender), _lpAmount);
offeringToken.safeTransfer(address(msg.sender), _offerAmount);
}
}
1、数据结构
// Info of each user.
struct UserInfo {
uint256 amount; // How many tokens the user has provided.
bool claimed; // default false
}
// admin address
address public adminAddress;
// The raising token
IBEP20 public lpToken;
// The offering token
IBEP20 public offeringToken;
// The block number when IFO starts
uint256 public startBlock;
// The block number when IFO ends
uint256 public endBlock;
// total amount of raising tokens need to be raised
uint256 public raisingAmount;
// total amount of offeringToken that will offer
uint256 public offeringAmount;
// total amount of raising tokens that have already raised
uint256 public totalAmount;
// address => amount
mapping (address => UserInfo) public userInfo;
// participators
address[] public addressList;
UserInfo结构体保存认购用户信息
adminAddress是管理员地址
lpToken是交易对的流动性代币合约
offeringToken是IFO中发行的新币的合约
startBlock是IFO开始区块
endBlock是IFO结束区块
raisingAmount是本次IFO募集的LP的目标值
offeringAmount是本次IFO发行新代币的总数量
totalAmount是本次募集的LP的实际值
userInfo保存每个参与的用户信息,是个映射
addressList保存每个参与的用户地址,是个数组
2、募集LP
function deposit(uint256 _amount) public {
require (block.number > startBlock && block.number < endBlock, 'not ifo time');
require (_amount > 0, 'need _amount > 0');
lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
if (userInfo[msg.sender].amount == 0) {
addressList.push(address(msg.sender));
}
userInfo[msg.sender].amount = userInfo[msg.sender].amount.add(_amount);
totalAmount = totalAmount.add(_amount);
emit Deposit(msg.sender, _amount);
}
首先募集期必须在startBlock和endBlock之间,然后投资的LP数量必须大于0.
然后把用户的LP转到当前合约,保存用户地址到addressList,保存用户信息到userInfo,增加totalAmount数量。
3、收获新发行代币
function harvest() public nonReentrant {
require (block.number > endBlock, 'not harvest time');
require (userInfo[msg.sender].amount > 0, 'have you participated?');
require (!userInfo[msg.sender].claimed, 'nothing to harvest');
uint256 offeringTokenAmount = getOfferingAmount(msg.sender);
uint256 refundingTokenAmount = getRefundingAmount(msg.sender);
offeringToken.safeTransfer(address(msg.sender), offeringTokenAmount);
if (refundingTokenAmount > 0) {
lpToken.safeTransfer(address(msg.sender), refundingTokenAmount);
}
userInfo[msg.sender].claimed = true;
emit Harvest(msg.sender, offeringTokenAmount, refundingTokenAmount);
}
function getOfferingAmount(address _user) public view returns(uint256) {
if (totalAmount > raisingAmount) {
uint256 allocation = getUserAllocation(_user);
return offeringAmount.mul(allocation).div(1e6);
}
else {
// userInfo[_user] / (raisingAmount / offeringAmount)
return userInfo[_user].amount.mul(offeringAmount).div(raisingAmount);
}
}
// get the amount of lp token you will be refunded
function getRefundingAmount(address _user) public view returns(uint256) {
if (totalAmount <= raisingAmount) {
return 0;
}
uint256 allocation = getUserAllocation(_user);
uint256 payAmount = raisingAmount.mul(allocation).div(1e6);
return userInfo[_user].amount.sub(payAmount);
}
function getUserAllocation(address _user) public view returns(uint256) {
return userInfo[_user].amount.mul(1e12).div(totalAmount).div(1e6);
}
首先必须在募集期结束才能执行,然后需要判断用户实际投资的数量以及是否已经收获过。
先通过getOfferingAmount方法计算实际用户可以收获的新代币的数量,这里有两种情况,一种是总量超过了募集目标,这时候按照用户投资数量占总量的比例分配总的offeringAmount:
userInfo[_user].amount = 100
raisingAmount = 10000
totalAmount = 20000
offeringTokenAmount = 10000
实际用户获得的新代币:10000*100*1e12/20000/1e6/1e6 = 50
如果总量没达到募集目标,按照用户投资数量占募集目标的比例进行分配。
userInfo[_user].amount = 100
raisingAmount = 10000
totalAmount = 9000
offeringTokenAmount = 10000
实际用户获得的新代币:10000*100/10000 = 100
可以看到,第一种情况offeringTokenAmount其实是全部被分配完的,用户的LP代币没有完全使用完,而第二种情况offeringTokenAmount只分配了一部分,而用户的LP代币完全使用完了。
最后把用户得到的新代币转给用户,针对第一种情况,会把剩下的LP代币转还给用户。
4、管理员方法
modifier onlyAdmin() {
require(msg.sender == adminAddress, "admin: wut?");
_;
}
function setOfferingAmount(uint256 _offerAmount) public onlyAdmin {
require (block.number < startBlock, 'no');
offeringAmount = _offerAmount;
}
function setRaisingAmount(uint256 _raisingAmount) public onlyAdmin {
require (block.number < startBlock, 'no');
raisingAmount= _raisingAmount;
}
function finalWithdraw(uint256 _lpAmount, uint256 _offerAmount) public onlyAdmin {
require (_lpAmount < lpToken.balanceOf(address(this)), 'not enough token 0');
require (_offerAmount < offeringToken.balanceOf(address(this)), 'not enough token 1');
lpToken.safeTransfer(address(msg.sender), _lpAmount);
offeringToken.safeTransfer(address(msg.sender), _offerAmount);
}
setOfferingAmount:设置目标代币总数,只有未开始的时候可以设置
setRaisingAmount: 设置募集目标,只有未开始可以设置
finalWithdraw:将多余的LP和新代币转给开发者
5、风险点
主要风险点在管理员方法,特别是finalWithdraw,如果黑客控制了管理员账户,可以在IFO期间,将lpToken和offeringToken全部转移到自己的账户。
?
|