# meme币的彩票模型和流动性自动添加模型

By [berwinYes](https://paragraph.com/@coolberwin) · 2022-07-04

---

这篇文章从合约代买的角度带大家了解一下 去年土狗风靡一时的**彩票模型**和**自动流动性添加模型**。

1、彩票模型：
=======

核心每次交易的部分手续费进入彩票池，当彩票池中金额达到开奖标准，则随机在持有者中抽取一个钱包地址，转入奖金，当然，持有者必须符合持有一定代币数量，否则将本期奖金打入开发者钱包。

找了个之前买的meme币 catgirl [0x79eBC9A2ce02277A4b5b3A768b1C0A4ed75Bd936](https://bscscan.com/address/0x79ebc9a2ce02277a4b5b3a768b1c0a4ed75bd936#code) 来解读一下彩票的代码：

1.1 定义变量
--------

    mapping (address => uint256) private _rOwned;	// address=> 持币数量 映射
    
    address[] private _addressList; // 存所有地址
    
    address payable private _devWallet; // 开发者钱包
    address private _lottoPotAddress; //抽奖地址
    address private _lottoWalletAddress; // 抽奖钱包地址
    uint256 public _lastLottoWinnerAmount; // 最后赢家奖金金额
    uint256 public _totalLottoPrize;       // 总奖金金额 
    uint public _lottoDrawCount = 0;       // 计数
    
    uint256 public lotteryThreshold = 10 * 10**12 * 10**9;	 // 开奖阈值
    
    bool inSwapAndLiquify;	// 是否已经进入流动性添加环节标志  默认为false
    bool inLotteryDraw;	  // 是否已经进入开奖环节标志    默认为false
    
    bool public swapAndLiquifyEnabled = true;	// 是否开启 自动加入流动性方法 标志
    bool public lottoEnabled = true;          // 是否开启 彩票抽奖方法 标志
    bool public _shouldSwapToBNB = false;
    

1.2 方法调用
--------

合约 \_transfer()方法中出现

    function _transfer(	
        address from,	
        address to,	
        uint256 amount	
    ) private {	
        .......
        
         // 查看 彩票奖池里的代币余额
         uint256 lottoBalance = balanceOf(_lottoPotAddress);
         // 彩票奖池中余额是否大于开奖阈值
         bool overMinLottoBalance = lottoBalance >= lotteryThreshold;
         if (
             overMinLottoBalance && !inSwapAndLiquify && !inLotteryDraw && lottoEnabled
         ) {
             drawLotto(lottoBalance);
         }
        ......
    }
    

开奖程序是否启动设置函数

    function setLottoEnabled(bool enabled) public onlyOwner() {
        lottoEnabled = enabled;
    }
    

彩票抽奖方法主方法：

    event DrawLotto(uint256 amount, uint _lottoDrawCount);
    
    
    // 函数修改器  修改进入流程标志
    modifier lockTheLottery {	
        inLotteryDraw = true;	
        _;	
        inLotteryDraw = false;	
    }
    
    function lotterize() private view returns(address) {
        uint256 randomNumber = random().mod(_addressList.length);
        
        uint256 ownedAmount = _rOwned[_addressList[randomNumber]];
        // 如果随机的address 持有代币数量不足 直接打入开发者钱包
        if (ownedAmount >= _minLottoBalance) {
            return _addressList[randomNumber];
        }
        return _devWallet;
    }
    
    function drawLotto(uint256 amount) private lockTheLottery {
        _lottoWalletAddress = lotterize();
        // 此时将amount 转入 中奖者 地址 
        // 并且由于函数修改器 将inLotteryDraw = true 故再次转账时跳过抽奖环节
        _transfer(_lottoPotAddress, _lottoWalletAddress, amount);
        _lastLottoWinnerAmount = amount;
        // 总发出奖金
        _totalLottoPrize = _totalLottoPrize.add(amount);
        // 总开奖次数
        ++_lottoDrawCount;
        // 链上上报事件
        emit DrawLotto(amount, _lottoDrawCount);
    }
        
    

2、流动性自动添加模型
===========

流动性自动添加模型就是在每笔交易中收取一部分手续费，将其累计，当到达一定的金额，自动拿出积累的一半token换入 eth 组成\[eth, token\]交易对，注入流动池。这样会保证池子一直充盈，不会被大户交易吸干。

如果看懂了上面的彩票模型，那么流动性自动添加也是一个套路

    event SwapAndLiquify(	
            uint256 tokensSwapped,	
            uint256 ethReceived,	
            uint256 tokensIntoLiqudity	
        );
    function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {	
        swapAndLiquifyEnabled = _enabled;	
        emit SwapAndLiquifyEnabledUpdated(_enabled);	
    }	
    

每次调用\_transfer()方法，进行一次流动性添加判断

    function _transfer(	
        address from,	
        address to,	
        uint256 amount	
    ) private {	
            ....
        // 查看 合约地址内的代币余额  每笔交易的手续费中一部分存在address(this) 中作为流动性储备
        uint256 contractTokenBalance = balanceOf(address(this));	
        // 查看 是否触发最大转账金额 默认_maxTxAmount 为代币对外总数量  10000 * 10**12 *10*9
        if(contractTokenBalance >= _maxTxAmount)	
        {	
            contractTokenBalance = _maxTxAmount;	
        }	
        // 持有数量是否 超过转移数量就加入流动性阈值
        // numTokensSellToAddToLiquidity 初始为 50 * 10**12 *10**9
        bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity;	
        if (	
            overMinTokenBalance && !inSwapAndLiquify &&	from != uniswapV2Pair && swapAndLiquifyEnabled	
        ) {	
            contractTokenBalance = numTokensSellToAddToLiquidity;	
            //add liquidity
            // 流动性添加	
            swapAndLiquify(contractTokenBalance);	
        }
        ....
        }
    

进入swapAndLiquify() 方法

    // 函数修改器 修改 流动性添加是否在进行的标志
    modifier lockTheSwap {	
        inSwapAndLiquify = true;	
        _;	
        inSwapAndLiquify = false;	
    }
    function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {	
        // split the contract balance into halves	
        // contractTokenBalance 的一半换成 eth
        uint256 half = contractTokenBalance.div(2);	
        uint256 otherHalf = contractTokenBalance.sub(half);	
        // capture the contract's current ETH balance.	
        // this is so that we can capture exactly the amount of ETH that the	
        // swap creates, and not make the liquidity event include any ETH that	
        // has been manually sent to the contract	
        // 查看初始时 address(this)中的eth数量
        uint256 initialBalance = address(this).balance;	
        // swap tokens for ETH
        // 换eth操作	
        swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered	
        // how much ETH did we just swap into?
        // 换到了多少eth	
        uint256 newBalance = address(this).balance.sub(initialBalance);	
        // add liquidity to uniswap	
        // 一半contractTokenBalance  和 一半contractTokenBalance换的eth 添加进流动性池
        addLiquidity(otherHalf, newBalance);	
        // 报链上事件
        emit SwapAndLiquify(half, newBalance, otherHalf);	
    }	
    function swapTokensForEth(uint256 tokenAmount) private {	
        // generate the uniswap pair path of token -> weth	
        address[] memory path = new addressUnsupported embed;	
        path[0] = address(this);	
        path[1] = uniswapV2Router.WETH();	
        _approve(address(this), address(uniswapV2Router), tokenAmount);	
        // make the swap	
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(	
            tokenAmount,	
            0, // accept any amount of ETH	
            path,	
            address(this),	
            block.timestamp	
        );	
    }	
    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {	
        // approve token transfer to cover all possible scenarios	
        _approve(address(this), address(uniswapV2Router), tokenAmount);	
        // add the liquidity	
        uniswapV2Router.addLiquidityETH{value: ethAmount}(	
            address(this),	
            tokenAmount,	
            0, // slippage is unavoidable	
            0, // slippage is unavoidable	
            owner(),	
            block.timestamp	
        );	
    }

---

*Originally published on [berwinYes](https://paragraph.com/@coolberwin/meme)*
