The test has Alice send exactly 1 wei less than the fee while holding WETH allowance: she receives her SNOW, is charged the full 5 WETH, and additionally loses ~5 ETH that stays trapped in the contract.
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {Snow} from "../src/Snow.sol";
import {DeploySnow} from "../script/DeploySnow.s.sol";
import {MockWETH} from "../src/mock/MockWETH.sol";
contract SnowFarmingYPagoTest is Test {
Snow snow;
DeploySnow deployer;
MockWETH weth;
uint256 FEE;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
address carol = makeAddr("carol");
address atacante = makeAddr("atacante");
function setUp() public {
deployer = new DeploySnow();
snow = deployer.run();
weth = deployer.weth();
FEE = deployer.FEE();
}
function test_elCooldownEsGlobalYBloqueaAlRestoDeUsuarios() public {
vm.prank(alice);
snow.earnSnow();
assertEq(snow.balanceOf(alice), 1, "alice farmea");
vm.prank(bob);
vm.expectRevert();
snow.earnSnow();
vm.prank(carol);
vm.expectRevert();
snow.earnSnow();
assertEq(snow.balanceOf(bob), 0, "bob bloqueado por la accion de otro");
assertEq(snow.balanceOf(carol), 0, "carol tambien");
}
function test_unAtacantePuedeCongelarElFarmingIndefinidamente() public {
vm.deal(atacante, 100 ether);
for (uint256 semana = 1; semana <= 4; semana++) {
vm.warp(block.timestamp + 1 weeks + 1);
vm.prank(atacante);
snow.buySnow{value: FEE}(1);
vm.prank(alice);
vm.expectRevert();
snow.earnSnow();
}
assertEq(snow.balanceOf(alice), 0, "alice nunca consigue farmear");
console2.log("coste del bloqueo por semana (wei):", FEE);
}
function test_elEthEnviadoQueNoCuadraSePierde() public {
uint256 precio = FEE;
weth.mint(alice, precio);
vm.deal(alice, precio);
uint256 ethAntes = alice.balance;
vm.startPrank(alice);
weth.approve(address(snow), precio);
snow.buySnow{value: precio - 1}(1);
vm.stopPrank();
assertEq(snow.balanceOf(alice), 1, "recibe su SNOW");
assertEq(weth.balanceOf(alice), 0, "ha pagado el precio COMPLETO en WETH");
assertEq(alice.balance, ethAntes - (precio - 1), "y encima ha perdido el ETH enviado");
assertEq(address(snow).balance, precio - 1, "el ETH se queda atrapado en el contrato");
console2.log("pagado en WETH:", precio);
console2.log("ETH perdido ademas:", precio - 1);
}
function test_sePagaElPrecioEnteroPorUnWeiDeToken() public {
assertEq(snow.decimals(), 18, "el token declara 18 decimales");
vm.deal(alice, FEE);
vm.prank(alice);
snow.buySnow{value: FEE}(1);
assertEq(snow.balanceOf(alice), 1, "recibe 1 WEI de SNOW, no 1 SNOW");
assertLt(snow.balanceOf(alice), 1e18, "muy lejos de un token entero");
console2.log("ETH pagado: ", FEE);
console2.log("SNOW recibido (wei): ", snow.balanceOf(alice));
console2.log("hacen falta 1e18 wei para 1 SNOW entero -> coste real: FEE * 1e18");
}
}
Do not use a strict equality to pick the payment path, and refund any excess ETH instead of retaining it:
function buySnow(uint256 amount) external payable canFarmSnow {
uint256 price = s_buyFee * amount;
- if (msg.value == price) { _mint(msg.sender, amount); }
- else { i_weth.safeTransferFrom(msg.sender, address(this), price); _mint(msg.sender, amount); }
+ if (msg.value > 0) {
+ if (msg.value < price) revert S__InsufficientPayment();
+ if (msg.value > price) { (bool ok,) = payable(msg.sender).call{value: msg.value - price}(""); require(ok); }
+ } else {
+ i_weth.safeTransferFrom(msg.sender, address(this), price);
+ }
+ _mint(msg.sender, amount);
}