Safemoon代币的通缩模型

代币通缩模型最早起源 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币价值。

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

在这里插入图片描述
在这里插入图片描述
  • 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://reflect-contract-doc.netlify.app/
https://ethereum.stackexchange.com/questions/98622/binance-smart-chain-tokens-what-are-ttotal-rtotal-tsupply-rsupply-rowned-t

Subscribe to berwinYes
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.