Challenge #8 - Puppet - insolvable test condition
t-gm opened this issue · comments
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.
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));
});
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.