Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Challenge #8 - Puppet - insolvable test condition #35

Closed
t-gm opened this issue Mar 4, 2023 · 6 comments
Closed

Challenge #8 - Puppet - insolvable test condition #35

t-gm opened this issue Mar 4, 2023 · 6 comments

Comments

@t-gm
Copy link

t-gm commented Mar 4, 2023

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.

@nzmpi
Copy link

nzmpi commented Mar 7, 2023

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

@t-gm t-gm closed this as completed Mar 7, 2023
@RealJohnnyTime
Copy link

RealJohnnyTime commented Sep 6, 2023

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));

});

@t-gm
Copy link
Author

t-gm commented Sep 6, 2023

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

@RealJohnnyTime
Copy link

Got it, cool! :)

@agent3bood
Copy link

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.

@letonchanh
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants