theredguild / damn-vulnerable-defi

The smart contract security training ground for developers, security researchers and educators.

Home Page:https://damnvulnerabledefi.xyz

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Challenge #8 - Puppet - insolvable test condition

t-gm opened this issue · comments

commented

Hi,

I think in challenge #8 the first test condition

expect(await ethers.provider.getTransactionCount(player.address)).to.eq(1);

is not possible. The player needs to either approve spending or send their tokens for a contract before the actual exploit can be run (which then is consisting of multiple steps). So the minimum number of transactions the player can get away with is 2 here.

commented

You can solve it with only 1 transaction. It's doable

The only way I was able to solve it with 1 transaction is to create another signer to deploy the malicious contract and execute the attack.
If you use only player, you can't do it in less than 2 transactions since one of them should be approving the tokens from the player EOA account.

This is my JS code to solve it with one tx:

it('Execution', async function () {
    /** CODE YOUR SOLUTION HERE */

    [,,this.player2] = await ethers.getSigners();


    const AttackerContractFactory = await ethers.getContractFactory('AttackPuppet', this.player2);
    this.attackerContract = await AttackerContractFactory.deploy(
        token.address, uniswapExchange.address, lendingPool.address
    )

    token.connect(player).transfer(this.attackerContract.address, PLAYER_INITIAL_TOKEN_BALANCE);
    await this.attackerContract.attack({value: 11n * 10n ** 18n});
    await token.connect(this.player2).transfer(player.address, await token.balanceOf(this.player2.address));

});
commented

That is the same thing I thought originally, but you can actually do it in one transaction. I don't want to spoil the solution here, but for anyone searching later, look for how approval "can" work

Got it, cool! :)

So to solve this we can do, which works

      const attacker = await ethers.getSigner();
      // TODO I am deploying this as `attacker` whcih does not have any eth, why is this working?
      const playerContract = await (await ethers.getContractFactory('PupperPlayer', attacker))
        .deploy(attacker.address, player.address, lendingPool.address, token.address, uniswapExchange.address, {value: PLAYER_INITIAL_ETH_BALANCE});

      await token.connect(player).transfer(playerContract.address, PLAYER_INITIAL_TOKEN_BALANCE);
      await playerContract.connect(attacker).play();

But how does the attacker have eth without sending it from the player first. I think we still need two transactions.

@agent3bood ethers.getSigner() provides each account a default balance of 10000 ether. I am still thinking about how to complete the challenge in one transaction with the use of the player's balance.