Skip to content
THREATWIRE

Shared helper

src/test/basetest.sol

Shared base contract for the dataset's tests. It logs the attacker's balance before and after a test so that a reproduction's profit can be read. It contains no exploit logic, which is why we show it.

Code

Shown in full with its original header, as part of the open dataset (repository licence Apache-2.0, see NOTICE). This file's own SPDX header reads UNLICENSED. It declares interfaces and helpers only. 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/basetest.sol142 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "./tokenhelper.sol";
5import "forge-std/Test.sol";
6
7// Base contract for DeFi exploit POCs. Inherit from this instead of Test directly.
8// Provides before/after balance logging via the `balanceLog` modifier.
9//
10// Single-asset mode (default):
11// Set `fundingToken` to the token you profit in (address(0) = native ETH/chain coin).
12// The `balanceLog` modifier logs that one token before and after testExploit().
13//
14// Multi-asset mode:
15// Set `multiAssetLog = true` and populate `fundingTokens` with every token you want tracked.
16// The same `balanceLog` modifier will log all of them. No override needed.
17// Optionally set `attacker` to log a different address (e.g. a separate profit contract).
18// If `attacker` is left as address(0), it resolves to address(this).
19contract BaseTestWithBalanceLog is Test {
20 // Single-asset mode: the token to log profit in. address(0) = native coin.
21 address fundingToken = address(0);
22 // Multi-asset mode: full list of tokens to track (ERC-20 or address(0) for native).
23 address[] fundingTokens;
24 // Set to true to enable multi-asset logging via fundingTokens[].
25 bool multiAssetLog = false;
26 // Address whose balances are logged. Defaults to address(this) when left as address(0).
27 address attacker;
29 struct ChainInfo {
30 string name;
31 string symbol;
32 }
34 mapping(uint256 => ChainInfo) private chainIdToInfo;
36 constructor() {
37 chainIdToInfo[1] = ChainInfo("MAINNET", "ETH");
38 chainIdToInfo[238] = ChainInfo("BLAST", "ETH");
39 chainIdToInfo[10] = ChainInfo("OPTIMISM", "ETH");
40 chainIdToInfo[250] = ChainInfo("FANTOM", "FTM");
41 chainIdToInfo[42_161] = ChainInfo("ARBITRUM", "ETH");
42 chainIdToInfo[56] = ChainInfo("BSC", "BNB");
43 chainIdToInfo[1285] = ChainInfo("MOONRIVER", "MOVR");
44 chainIdToInfo[100] = ChainInfo("GNOSIS", "XDAI");
45 chainIdToInfo[43_114] = ChainInfo("AVALANCHE", "AVAX");
46 chainIdToInfo[137] = ChainInfo("POLYGON", "MATIC");
47 chainIdToInfo[42_220] = ChainInfo("CELO", "CELO");
48 chainIdToInfo[8453] = ChainInfo("BASE", "ETH");
49 chainIdToInfo[1329] = ChainInfo("SEI", "SEI");
50 }
52 function getChainSymbol(
53 uint256 chainId
54 ) internal view returns (string memory symbol) {
55 symbol = chainIdToInfo[chainId].symbol;
56 if (bytes(symbol).length == 0) symbol = "ETH";
57 }
59 function _getTokenData(
60 address token,
61 address account
62 ) internal returns (string memory symbol, uint256 balance, uint8 decimals) {
63 if (token == address(0)) {
64 symbol = getChainSymbol(block.chainid);
65 balance = account.balance;
66 decimals = 18;
67 } else {
68 symbol = TokenHelper.getTokenSymbol(token);
69 balance = TokenHelper.getTokenBalance(token, account);
70 decimals = TokenHelper.getTokenDecimals(token);
71 }
72 }
74 function _logTokenBalance(
75 address token,
76 address account,
77 string memory label
78 ) private {
79 (string memory symbol, uint256 balance, uint8 decimals) = _getTokenData(token, account);
80 emit log_named_decimal_uint(string(abi.encodePacked(label, " ", symbol, " Balance")), balance, decimals);
81 }
83 function _attacker() private view returns (address) {
84 return attacker == address(0) ? address(this) : attacker;
85 }
87 modifier balanceLog() virtual {
88 if (multiAssetLog) {
89 if (fundingToken == address(0)) vm.deal(_attacker(), 0);
90 _logMultiAssetBalances("Before exploit");
91 } else {
92 if (fundingToken == address(0)) vm.deal(_attacker(), 0);
93 _logTokenBalance(fundingToken, _attacker(), "Attacker Before exploit");
94 }
95 _;
96 if (multiAssetLog) {
97 _logMultiAssetBalances("After exploit");
98 } else {
99 _logTokenBalance(fundingToken, _attacker(), "Attacker After exploit");
100 }
101 }
103 modifier balanceLog2(address target) virtual {
104 if (fundingToken == address(0)) vm.deal(target, 0);
105 _logTokenBalance(fundingToken, target, string(abi.encodePacked("Attacker Before exploit")));
106 _;
107 _logTokenBalance(fundingToken, target, string(abi.encodePacked("Attacker After exploit")));
108 }
110 function logTokenBalance(
111 address token,
112 address account,
113 string memory label
114 ) internal {
115 _logTokenBalance(token, account, label);
116 }
118 function logMultipleTokenBalances(
119 address[] memory tokens,
120 address account,
121 string memory label
122 ) internal {
123 emit log_string(string(abi.encodePacked("=== ", label, " ===")));
124 for (uint256 i = 0; i < tokens.length; i++) {
125 _logTokenBalance(tokens[i], account, "");
126 }
127 }
129 function _addFundingToken(address token) internal {
130 fundingTokens.push(token);
131 }
133 function _addFundingTokens(address[] memory tokens) internal {
134 for (uint256 i = 0; i < tokens.length; i++) {
135 fundingTokens.push(tokens[i]);
136 }
137 }
139 function _logMultiAssetBalances(string memory label) internal {
140 logMultipleTokenBalances(fundingTokens, _attacker(), label);
141 }

Components and what they do

  • BaseTestWithBalanceLog: single and multi-asset balance logging
  • balanceLog and balanceLog2 modifiers
  • Chain id to native symbol table

Related files

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

File record

Name
basetest.sol
Path
src/test/basetest.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
3e52bd8025cb122dd7e70cbfd7498ad951c77b96
Language
Solidity
Size
142 lines
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/basetest.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 3e52bd8025cb122dd7e70cbfd7498ad951c77b96, retrieved 2026-10-19. Verbatim copy of the file including its SPDX header, read with git show. No edits. ThreatWire has never executed this file. Reference: source file at the pinned commit.