# Safemoon代币的通缩模型 **Published by:** [berwinYes](https://paragraph.com/@coolberwin/) **Published on:** 2022-07-04 **URL:** https://paragraph.com/@coolberwin/safemoon ## Content 代币通缩模型最早起源 meme币Pig 发扬光大在safemoon ,有早期用户0.2eth变6700万美元的神话。 道理比较简单,就是币随交易,越来越少,从而导致代币越来越有价值。 而体现合约中,查阅后却没有一个很好的解释,趁着最近在看合约代码,试图做一些浅显的理解: 我以一个实现通缩的Token catgirl 为例1、原理介绍首先先明确两个uint类型私有变量:_tTotal 和 _rTotal_tTotal 就是 我们发行的总币量_rTotal 则是 一个内部币总量 通过减少该币数量 进行通缩分红 使得账号上代币数量提升其中 _rTotal >> _tTotaluint256 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 - rfeebalanceOf(地址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 和扣除总费用rFeefunction _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 ## Publication Information - [berwinYes](https://paragraph.com/@coolberwin/): Publication homepage - [All Posts](https://paragraph.com/@coolberwin/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@coolberwin): Subscribe to updates - [Twitter](https://twitter.com/0xcoolberwin): Follow on Twitter