# Safemoon代币的通缩模型

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

---

代币通缩模型最早起源 meme币Pig 发扬光大在safemoon ，有早期用户0.2eth变6700万美元的神话。

道理比较简单，就是币随交易，越来越少，从而导致代币越来越有价值。 而体现合约中，查阅后却没有一个很好的解释，趁着最近在看合约代码，试图做一些浅显的理解： 我以一个实现通缩的Token catgirl 为例

1、原理介绍
======

首先先明确两个uint类型私有变量：\_tTotal 和 \_rTotal

*   \_tTotal 就是 我们发行的总币量
    
*   \_rTotal 则是 一个内部币总量 通过减少该币数量 进行通缩分红 使得账号上代币数量提升
    

其中 \_rTotal >> \_tTotal

    uint256 private constant MAX = ~uint256(0);	
    //对外展示的币总量，为100000 * 10**12 * 10**9，
    uint256 private _tTotal = 100000 * 10**12 * 10**9;  	
    //是内部实际的币总量，为2^256-(2^256%_tTotal)
    uint256 private _rTotal = (MAX - (MAX % _tTotal));	
    

如果你对银行业务比较了解，你应该知道一个名词叫做**存款准备金率**。其核心就是 **中央银行要求的存款准备金占其存款总额的比例就是存款准备金率**（deposit-reserve ratio）。

\_rTotal and \_tTotal类似于 银行存款，但是相反的是。这里 \_ rTotal 为中央银行的总代币供应量， \_tTotal是你口袋中+存在银行里的代币数量。rTotal大于\_tTotal，为什么会大于，下面会将，现在只需要记住\_rTotal和\_tTotal的类比概念即可。 函数 \_getRate() 则表示\_rTotal/\_tTotal，类似于上面的**存款准备金率**概念。

核心原理：**就是在\_tTotal 除扣除手续费外总量不变的情况下，\_rTotal每次交易扣除2倍手续费来进行通缩，从而提升所有持有相同t币数量的t币价值。**

下面我根据下图来进行讲解

![在这里插入图片描述](https://storage.googleapis.com/papyrus_images/8ce69a91914ac661f14b479ad6c9f1995fd7d339dd313eab2b3bb093675b39b8.png)

在这里插入图片描述

*   tTotal 初始化为 10000 外部显示币总数量
    
*   rTotal 初始化为 127366483000000 = MAX-(MAX%tTotal) 注 MAX = uint256最大数值表示
    
*   rate rTotal/tTotal 的值
    
*   mapping (address => uint256) rOwned 内部一个拥有r币地址=> r数量的映射
    
*   mapping (address => uint256) tAmount 内部一个拥有t币地址=> r数量的映射
    
*   tAmount 一个地址想购买t币的数量
    
*   FeeRate 只要转账就将转账数量的FeeRate 直接燃烧销毁 这里我们取10%
    
*   rTransferAmount 转账对方收到的r币 = rAmount - rfee
    
*   balanceOf(地址X) 地址X的外部显示t币余额
    

**地址A 代表发币地址 初始tTotal = 10000** **内部： tTotal\[A\] = 10000 rTotal\[A\] = 127366483000000 外部显示：balanceOf(A) = 10000**

### 交易一

    此刻
    rTotal = 127366483000000
    tTotal = 10000
    rate = rTotal / tTotal = 12736648300
    假设用户A向用户B转账100个 t币  tAmount = 100
    故：
    rAmount = tAmount * rate = 1273664830000
    tfee = 10%*tAmount = 10
    rfee = tfee *rate = 127366483000
    rTransferAmount = rAmount -  rfee = 1146298347000
    
    交易结束后rTotal再销毁 一份rfee 并且记录等值的tfree 
    调用safemoon中的reflectFee() 方法 从而改变rate
    

此时:

    rate = rTotal / tTotal = 12736648300
    rTotal = rTotal-rfee(转账消耗) - rfee(系统销毁r币) = 127111750034000
    rOwned[A] -= rAmount  = 127366483000000 - 1273664830000 = 126092818170000
    rOwned[B] += rTransferAmount （扣除税）= 1146298347000
    tOwned[A] -= 10000-100 = 9900
    tOwned[B] += rTransferAmount/rate = 90
    

**balanceOf(A) = rOwned\[A\] /rate = 9900 balanceOf(B) = rOwned\[B\] /rate = 90**

### 交易二

    此刻
    rTotal = 127111750034000
    tTotal = 9990
    
    rate = rTotal / tTotal = 12723898902.302301  此时 小于第一次交易的rate 
    
    假设用户A向用户C转账100个 t币  tAmount = 100
    故：
    rAmount = tAmount * rate = 1272389890230
    tfee = 10%*tAmount = 10
    rfee = tfee *rate = 127238989023
    rTransferAmount = rAmount -  rfee = 1145150901207.207
    此时:
    rOwned[A] -= rAmount 
    rOwned[C] += rTransferAmount （扣除税）
    tOwned[A] -= tAmount 
    tOwned[C] += tAmount - tfee
    
    交易结束后rTotal再销毁 一份rfee 并且记录等值的tfree 
    调用safemoon中的reflectFee() 方法 从而改变rate
    

此时指标:

    rate = rTotal / tTotal = 12723898902.302301 
    rTotal = rTotal-rfee(转账消耗) - rfee(系统销毁r币) = 126857272055954
    rOwned[A] -= rAmount = 126092818170000 - 1272389890230 = 124820428279770
    rOwned[C] += rTransferAmount （扣除税）= 1145150901207.207
    tOwned[A] -= 9900-100 = 9800
    tOwned[C] += rTransferAmount/rate = 90
    

**balanceOf(A) = rOwned\[A\] /rate = 9809.919839679378 balanceOf(B) = rOwned\[B\] /rate =90.09018036072145 balanceOf(C) = rOwned\[C\] /rate = 90**

balanceOf() 函数是由 rOwned\[\] 和rate 决定，和tOwned\[\]没有关系。

**可以发现此时 B账户的r币没变，但是balanceOf 得到数量余额变多了，这就是通缩燃烧r币导致的代币变多，从而价值提升。**

2、代码介绍
------

第一步 设置变量

    // 用户内部持有的实际币数量，可以看成是每个用户拥有的盘子数量
    mapping (address => uint256) private _rOwned;	
    // 只用于非分红用户的转账，可以看成是一个帮助类
    mapping (address => uint256) private _tOwned;	
    // 类似于ERC20的allowance，指用户授权某些账户的可使用额度
    mapping (address => mapping (address => uint256)) private _allowances;	
    // 账户白名单，用来判断是否需要转账手续费
    mapping (address => bool) private _isExcludedFromFee;	
    // 账户通缩名单标志位，用来判断是否参与通缩分红 true代表排除通缩之外  false 代表进行通缩奖励 默认全员通缩
    mapping (address => bool) private _isExcluded; 
    // 巨鲸黑名单标志位 购买超过总量的1%
    mapping (address => bool) public _isExcludedFromAntiWhale; 
    // 拥有token标志位
    mapping (address => bool) private _AddressExists; 
    // 地址数组
    address[] private _addressList; 
    // 不计入通缩address记录 只有_isExcluded[address]=true的 才加入
    address[] private _excluded; 
    
    uint256 private constant MAX = ~uint256(0);	
    //对外展示的币总量，为100000 * 10**12 * 10**9，可以看出是总的蛋糕    
    uint256 private _tTotal = 100000 * 10**12 * 10**9;  	
    //是内部实际的币总量，为2^256-(2^256%_tTotal)，是一个很大的数，可以看出是盘子的数量
    uint256 private _rTotal = (MAX - (MAX % _tTotal));	
    // 收取的手续费，可以看成是打碎了多少盘子，但是不影响总蛋糕_tTotal
    uint256 private _tFeeTotal;	
    // 代币名称
    string private _name = "CatGirl";	
    // 代币符号
    string private _symbol = "CATGIRL";
    // 代币精度
    uint8 private _decimals = 9;	
    // 转账收取的手续费，这部分手续费会直接销毁，进而导致_rTotal减少，也就是总量的通缩。
    uint256 public _taxFee = 4;
    // 上一次设置的手续费，是个历史记录
    uint256 private _previousTaxFee = _taxFee;	
    // 转账收取的流动性手续费，这部分手续费会添加到uniswap的交易对里
    uint256 public _liquidityFee = 1;	
    // 上一次设置的手续费，是个历史记录
    uint256 private _previousLiquidityFee = _liquidityFee;	
    // 彩票税
    uint256 public _lottoFee = 2;
    uint256 private _previousLottoFee = _lottoFee;
    // 开发者税
    uint256 public _devFee = 1;
    uint256 private _previousDevFee = _devFee;
    // uniswap的路由器，用于添加流动性
    IUniswapV2Router02 public uniswapV2Router;	
    // 在uniswap的创建的catgirl-bnb交易对
    address public uniswapV2Pair;	
    // 用于lockTheSwap这个modifier，用来加锁的
    bool inSwapAndLiquify;	
    bool inLotteryDraw;	 
    

代码核心四个方法：

#### 方法一：

*   \_getRate 获取当前的rSupply/tSupply 的 比率
    

    function _getRate() private view returns(uint256) {	
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();	
        return rSupply.div(tSupply);	
    }	
    function _getCurrentSupply() private view returns(uint256, uint256) {	
        uint256 rSupply = _rTotal;	
        uint256 tSupply = _tTotal;      	
        for (uint256 i = 0; i < _excluded.length; i++) {	
            if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply) return (_rTotal, _tTotal);	
            rSupply = rSupply.sub(_rOwned[_excluded[i]]);	
            tSupply = tSupply.sub(_tOwned[_excluded[i]]);	
        }	
        if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal);	
        return (rSupply, tSupply);	
    

可以看到用的是

*   rSupply = \_rTotal - 所有被排除通缩账号的r数量
    
*   tSupply = \_tTotal - 所有被排除通缩账号的t数量
    

#### 方法二：

*   \_getRValues 根据转移的 tAmount 计算 对应的rAmount和扣除各种费用的rTransferAmount 和扣除总费用rFee
    

    function _getRValues(TData memory _data) private pure returns (uint256, uint256, uint256) {	
        uint256 rAmount = _data.tAmount.mul(_data.currentRate);	
        uint256 rFee = _data.tFee.mul(_data.currentRate);	
        uint256 rLiquidity = _data.tLiquidity.mul(_data.currentRate);	
        uint256 rLotto = _data.tLotto.mul(_data.currentRate);
    uint256 rDev = _data.tDev.mul(_data.currentRate);
        uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity).sub(rLotto).sub(rDev);	
        return (rAmount, rTransferAmount, rFee);	
    

*   得到结果 rAmount = tAmount\* currentRate;
    

#### 方法三：

*   \_reflectFee(rFee, tFee); 主动通缩r币总量 并将对应的tFee计数保存
    

    receive() external payable {}	
    function _reflectFee(uint256 rFee, uint256 tFee) private {	
        _rTotal = _rTotal.sub(rFee);	
        _tFeeTotal = _tFeeTotal.add(tFee);	
    }	
    

每次交易都会调用该方法，通缩减少rFee个r币 \_rTotal = \_rTotal.sub(rFee);

#### 方法四：

    function balanceOf(address account) public view override returns (uint256) {	  
    // 如果account在非通缩分红名单中 直接返回_tOwned[account] 否则进行通缩计算新值
        if (_isExcluded[account]) return _tOwned[account];	
        return tokenFromReflection(_rOwned[account]);	
    }	
    function tokenFromReflection(uint256 rAmount) public view returns(uint256) {	
        require(rAmount <= _rTotal, "Amount must be less than total reflections");	
        uint256 currentRate =  _getRate();	
        return rAmount.div(currentRate);	
    }	
    

balanceOf(address) = \_rOwned\[account\].div(currentRate)

### 参考

[https://its201.com/article/zgsdzczh/116706584](https://its201.com/article/zgsdzczh/116706584) [https://reflect-contract-doc.netlify.app/](https://reflect-contract-doc.netlify.app/) [https://ethereum.stackexchange.com/questions/98622/binance-smart-chain-tokens-what-are-ttotal-rtotal-tsupply-rsupply-rowned-t](https://ethereum.stackexchange.com/questions/98622/binance-smart-chain-tokens-what-are-ttotal-rtotal-tsupply-rsupply-rowned-t)

---

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