MAIEV
by thesecretlab
MAIEV
by thesecretlab
Audit Report evmbench-20260220-140146
Styled to match thesecretlab visual direction and issued by MAIEV.
PASS
Launch Signal: Clear for this audit scope
No findings detected in this run. Evidence package is clean for current scope.
No findings detected in this run. Evidence package is clean for current scope.
Start Here (60-Second Read)
This section explains what passed, what failed, and where to drill down.
What This Run Means
- Scope: 6 contracts reviewed across 6 labeled systems.
- Risk: No findings detected in this run. Evidence package is clean for current scope.
- Coverage: Suite coverage file not available.
- Evidence: Raw logs, digests, and source snapshots are attached in Diagnostics.
How To Read Colors
PASS: Safe in scope
WARN: Review before launch
FAIL: Blocker
Step 1
Check launch signal and findings count.
Step 2
Open Findings tab for exact risk details.
Step 3
Use Diagnostics for proof logs and artifacts.
Findings
0
Critical
0
High
0
Suite Gaps
0
Timeline Events
17
Contracts Reviewed
6
Economic Overlay (thesecretlab)
Latest coherence + flywheel + VM/privacy runs linked into this audit archive.
| Track | Latest Run | Status | Key Metrics | Artifacts |
|---|---|---|---|---|
| Economic Coherence | econ-20260220-142514 | FAILED | Checks 6/9, Reference Trade 100 VAI @ 5.034% | json | md |
| Flywheel Runtime | flywheel-20260220-145840 | PASS | Checks 10/10, Findings 2, Critical 0 | json | md |
| VM Privacy | vmprivacy-20260220-155511 | PASS | Checks 5/5, Findings 0, Mechanisms 5/5 | json | md |
Execution Timeline
Polling every 5s
| # | Polled At | Status Trace |
|---|---|---|
| 1 | 2/20/2026 2:01:46 PM | 0s -> queued |
| 2 | 2/20/2026 2:01:51 PM | 5s -> running |
| 3 | 2/20/2026 2:01:56 PM | 10s -> running |
| 4 | 2/20/2026 2:02:01 PM | 15s -> running |
| 5 | 2/20/2026 2:02:06 PM | 20s -> running |
| 6 | 2/20/2026 2:02:11 PM | 25s -> running |
| 7 | 2/20/2026 2:02:16 PM | 30s -> running |
| 8 | 2/20/2026 2:02:21 PM | 35s -> running |
| 9 | 2/20/2026 2:02:26 PM | 40s -> running |
| 10 | 2/20/2026 2:02:31 PM | 45s -> running |
| 11 | 2/20/2026 2:02:36 PM | 50s -> running |
| 12 | 2/20/2026 2:02:41 PM | 55s -> running |
| 13 | 2/20/2026 2:02:46 PM | 60s -> running |
| 14 | 2/20/2026 2:02:51 PM | 65s -> running |
| 15 | 2/20/2026 2:02:56 PM | 70s -> running |
| 16 | 2/20/2026 2:03:01 PM | 75s -> running |
| 17 | 2/20/2026 2:03:06 PM | 80s -> succeeded |
VAI/DAI Full Suite Coverage
Missing Components: 0 / 0
| # | Contract | Component | Presence | Notes |
|---|---|---|---|---|
| No VAI/DAI suite coverage file found for this run. | ||||
Contract Coverage Labels
Label Groups: 6
| # | Contract | Label | Audit Status |
|---|---|---|---|
| 1 | VeilKeep3r.sol |
EVM Keep3r | PASS |
| 2 | VeilLiquidityIntentGateway.sol |
EVM Intent Gateway (Liquidity) | PASS |
| 3 | VeilOrderIntentGateway.sol |
EVM Intent Gateway (Order) | PASS |
| 4 | VeilTreasury.sol |
EVM Treasury | PASS |
| 5 | VeilUniV2Dex.sol |
EVM UniV2 LP/DEX | PASS |
| 6 | VeilVAI.sol |
EVM VAI | PASS |
Findings Breakdown
Contract Scope: C:\Users\Josh\hypersdk\examples\veilvm\companion-evm\contracts
| # | Title | Severity | Location | Details |
|---|---|---|---|---|
| No vulnerabilities reported. This run is clean against the current audit prompt and contract scope. | ||||
Contract Integrity Proof
SHA256 + line/byte counts of audited source
| # | Contract | Label | Lines | Bytes | SHA256 |
|---|---|---|---|---|---|
| 1 | VeilKeep3r.sol |
EVM Keep3r | 119 | 4834 | b6efc0da28...e5c731c5 |
| 2 | VeilLiquidityIntentGateway.sol |
EVM Intent Gateway (Liquidity) | 270 | 9254 | ee7f0427b1...a5274f65 |
| 3 | VeilOrderIntentGateway.sol |
EVM Intent Gateway (Order) | 199 | 7000 | 252c4021ef...a02b11df |
| 4 | VeilTreasury.sol |
EVM Treasury | 79 | 3266 | 66da611912...af9839fc |
| 5 | VeilUniV2Dex.sol |
EVM UniV2 LP/DEX | 359 | 15432 | 29ca83a48e...1db35a6e |
| 6 | VeilVAI.sol |
EVM VAI | 125 | 4391 | f1544b9e16...7d3a0592 |
Code Under Audit (With Commentary)
Exact source snapshot with reviewer focus notes
VeilKeep3r.sol
EVM Keep3r
raw .sol
Commentary: Check keeper registration, permissions, and job execution gating. | Validate reward accounting to prevent inflation or double-claim patterns. | Review anti-spam and slashing/penalty logic consistency.
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.23;
3 |
4 | interface IERC20LikeKeep3r {
5 | function balanceOf(address account) external view returns (uint256);
6 | function transfer(address to, uint256 value) external returns (bool);
7 | function transferFrom(address from, address to, uint256 value) external returns (bool);
8 | }
9 |
10 | /// @notice Fast companion EVM Keep3r-style registry and payout rail.
11 | /// @dev Lightweight fork surface for VEIL local/ops workflows.
12 | contract VeilKeep3r {
13 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
14 | event JobSet(address indexed job, bool enabled);
15 | event MinimumBondSet(uint256 previousMinimumBond, uint256 nextMinimumBond);
16 | event Bonded(address indexed keeper, uint256 amount, uint256 bondedTotal);
17 | event Unbonded(address indexed keeper, uint256 amount, uint256 bondedTotal);
18 | event Funded(address indexed from, uint256 amount);
19 | event Worked(address indexed job, address indexed keeper, uint256 amount);
20 | event Slashed(address indexed keeper, address indexed recipient, uint256 amount);
21 |
22 | error Keep3rZeroAddress();
23 | error Keep3rOnlyOwner();
24 | error Keep3rOnlyJob();
25 | error Keep3rInvalidAmount();
26 | error Keep3rTransferFailed();
27 | error Keep3rInsufficientBond();
28 | error Keep3rNotActiveKeeper();
29 | error Keep3rInsufficientCredits();
30 |
31 | IERC20LikeKeep3r public immutable VEIL;
32 | address public owner;
33 | uint256 public minimumBond;
34 | uint256 public totalBonded;
35 |
36 | mapping(address => bool) public jobs;
37 | mapping(address => uint256) public keeperBond;
38 |
39 | modifier onlyOwner() {
40 | if (msg.sender != owner) revert Keep3rOnlyOwner();
41 | _;
42 | }
43 |
44 | modifier onlyJob() {
45 | if (!jobs[msg.sender]) revert Keep3rOnlyJob();
46 | _;
47 | }
48 |
49 | constructor(address veilToken_, uint256 minimumBond_, address owner_) {
50 | if (veilToken_ == address(0) || owner_ == address(0)) revert Keep3rZeroAddress();
51 | VEIL = IERC20LikeKeep3r(veilToken_);
52 | minimumBond = minimumBond_;
53 | owner = owner_;
54 | emit OwnershipTransferred(address(0), owner_);
55 | }
56 |
57 | function transferOwnership(address nextOwner) external onlyOwner {
58 | if (nextOwner == address(0)) revert Keep3rZeroAddress();
59 | emit OwnershipTransferred(owner, nextOwner);
60 | owner = nextOwner;
61 | }
62 |
63 | function setJob(address job, bool enabled) external onlyOwner {
64 | if (job == address(0)) revert Keep3rZeroAddress();
65 | jobs[job] = enabled;
66 | emit JobSet(job, enabled);
67 | }
68 |
69 | function setMinimumBond(uint256 nextMinimumBond) external onlyOwner {
70 | uint256 previous = minimumBond;
71 | minimumBond = nextMinimumBond;
72 | emit MinimumBondSet(previous, nextMinimumBond);
73 | }
74 |
75 | function isKeeper(address keeper) public view returns (bool) {
76 | return keeperBond[keeper] >= minimumBond;
77 | }
78 |
79 | function availableCredits() public view returns (uint256) {
80 | uint256 bal = VEIL.balanceOf(address(this));
81 | if (bal <= totalBonded) {
82 | return 0;
83 | }
84 | return bal - totalBonded;
85 | }
86 |
87 | function fund(uint256 amount) external {
88 | if (amount == 0) revert Keep3rInvalidAmount();
89 | if (!VEIL.transferFrom(msg.sender, address(this), amount)) {
90 | revert Keep3rTransferFailed();
91 | }
92 | emit Funded(msg.sender, amount);
93 | }
94 |
95 | function bond(uint256 amount) external {
96 | if (amount == 0) revert Keep3rInvalidAmount();
97 | if (!VEIL.transferFrom(msg.sender, address(this), amount)) {
98 | revert Keep3rTransferFailed();
99 | }
100 | keeperBond[msg.sender] += amount;
101 | totalBonded += amount;
102 | emit Bonded(msg.sender, amount, keeperBond[msg.sender]);
103 | }
104 |
105 | function unbond(uint256 amount) external {
106 | if (amount == 0) revert Keep3rInvalidAmount();
107 | uint256 bonded = keeperBond[msg.sender];
108 | if (bonded < amount) revert Keep3rInsufficientBond();
109 | unchecked {
110 | keeperBond[msg.sender] = bonded - amount;
111 | totalBonded -= amount;
112 | }
113 | if (!VEIL.transfer(msg.sender, amount)) revert Keep3rTransferFailed();
114 | emit Unbonded(msg.sender, amount, keeperBond[msg.sender]);
115 | }
116 |
117 | function worked(address keeper, uint256 amount) external onlyJob {
118 | if (amount == 0) revert Keep3rInvalidAmount();
119 | if (!isKeeper(keeper)) revert Keep3rNotActiveKeeper();
120 | if (availableCredits() < amount) revert Keep3rInsufficientCredits();
121 | if (!VEIL.transfer(keeper, amount)) revert Keep3rTransferFailed();
122 | emit Worked(msg.sender, keeper, amount);
123 | }
124 |
125 | function slash(address keeper, uint256 amount, address recipient) external onlyOwner {
126 | if (recipient == address(0)) revert Keep3rZeroAddress();
127 | if (amount == 0) revert Keep3rInvalidAmount();
128 | uint256 bonded = keeperBond[keeper];
129 | if (bonded < amount) revert Keep3rInsufficientBond();
130 | unchecked {
131 | keeperBond[keeper] = bonded - amount;
132 | totalBonded -= amount;
133 | }
134 | if (!VEIL.transfer(recipient, amount)) revert Keep3rTransferFailed();
135 | emit Slashed(keeper, recipient, amount);
136 | }
137 | }
138 |
VeilLiquidityIntentGateway.sol
EVM Intent Gateway (Liquidity)
raw .sol
Commentary: Review liquidity intent constraints and anti-replay protections. | Validate authorization on liquidity movement and execution entrypoints. | Check failure modes around partial fills and state rollbacks.
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.23;
3 |
4 | /// @notice EVM-side intent gateway for VEIL UniV2 liquidity actions.
5 | /// Relayers consume submitted intents and execute them through the VEIL router.
6 | contract VeilLiquidityIntentGateway {
7 | uint8 public constant OP_CREATE_POOL = 0;
8 | uint8 public constant OP_ADD_LIQUIDITY = 1;
9 | uint8 public constant OP_REMOVE_LIQUIDITY = 2;
10 | uint8 public constant OP_SWAP_EXACT_IN = 3;
11 |
12 | uint8 public constant ASSET_VEIL = 0;
13 | uint8 public constant ASSET_VAI = 1;
14 |
15 | uint16 public constant MIN_POOL_FEE_BIPS = 1;
16 | uint16 public constant MAX_POOL_FEE_BIPS = 1000;
17 |
18 | enum IntentState {
19 | NONE,
20 | SUBMITTED,
21 | EXECUTED,
22 | CANCELLED
23 | }
24 |
25 | struct LiquidityIntent {
26 | address trader;
27 | uint8 operation;
28 | uint8 asset0;
29 | uint8 asset1;
30 | uint16 feeBips;
31 | uint64 amount0;
32 | uint64 amount1;
33 | uint64 minLP;
34 | uint64 lpAmount;
35 | uint64 minAmount0;
36 | uint64 minAmount1;
37 | uint64 amountIn;
38 | uint64 minAmountOut;
39 | uint64 nonce;
40 | IntentState state;
41 | }
42 |
43 | error Unauthorized();
44 | error InvalidAddress();
45 | error InvalidOperation(uint8 operation);
46 | error InvalidAsset(uint8 asset);
47 | error InvalidAssetPair(uint8 asset0, uint8 asset1);
48 | error InvalidAmount();
49 | error InvalidFeeBips(uint16 feeBips);
50 | error IntentAlreadyExists(bytes32 intentId);
51 | error IntentNotFound(bytes32 intentId);
52 | error IntentNotSubmitted(bytes32 intentId);
53 | error IntentNotOwned(bytes32 intentId, address expectedOwner, address sender);
54 |
55 | event OwnerTransferred(address indexed previousOwner, address indexed newOwner);
56 | event RelayExecutorSet(address indexed previousRelay, address indexed newRelay);
57 |
58 | event LiquidityIntentSubmitted(
59 | bytes32 indexed intentId,
60 | address indexed trader,
61 | uint8 indexed operation,
62 | uint8 asset0,
63 | uint8 asset1,
64 | uint16 feeBips,
65 | uint64 amount0,
66 | uint64 amount1,
67 | uint64 minLP,
68 | uint64 lpAmount,
69 | uint64 minAmount0,
70 | uint64 minAmount1,
71 | uint64 amountIn,
72 | uint64 minAmountOut,
73 | uint64 nonce
74 | );
75 | event LiquidityIntentExecuted(bytes32 indexed intentId, bytes32 indexed veilTxHash, address indexed executor);
76 | event LiquidityIntentCancelled(bytes32 indexed intentId, address indexed trader);
77 |
78 | address public owner;
79 | address public relayExecutor;
80 |
81 | mapping(address => uint64) public nonces;
82 | mapping(bytes32 => LiquidityIntent) private intents;
83 |
84 | modifier onlyOwner() {
85 | if (msg.sender != owner) {
86 | revert Unauthorized();
87 | }
88 | _;
89 | }
90 |
91 | modifier onlyRelay() {
92 | if (msg.sender != relayExecutor) {
93 | revert Unauthorized();
94 | }
95 | _;
96 | }
97 |
98 | constructor(address initialOwner, address initialRelayExecutor) {
99 | owner = initialOwner == address(0) ? msg.sender : initialOwner;
100 | relayExecutor = initialRelayExecutor;
101 | emit OwnerTransferred(address(0), owner);
102 | emit RelayExecutorSet(address(0), initialRelayExecutor);
103 | }
104 |
105 | function transferOwnership(address newOwner) external onlyOwner {
106 | if (newOwner == address(0)) {
107 | revert InvalidAddress();
108 | }
109 | emit OwnerTransferred(owner, newOwner);
110 | owner = newOwner;
111 | }
112 |
113 | function setRelayExecutor(address newRelayExecutor) external onlyOwner {
114 | emit RelayExecutorSet(relayExecutor, newRelayExecutor);
115 | relayExecutor = newRelayExecutor;
116 | }
117 |
118 | function submitCreatePoolIntent(uint8 asset0, uint8 asset1, uint16 feeBips) external returns (bytes32 intentId) {
119 | _validateAssetPair(asset0, asset1);
120 | if (feeBips < MIN_POOL_FEE_BIPS || feeBips > MAX_POOL_FEE_BIPS) {
121 | revert InvalidFeeBips(feeBips);
122 | }
123 | intentId = _storeIntent(OP_CREATE_POOL, asset0, asset1, feeBips, 0, 0, 0, 0, 0, 0, 0, 0);
124 | }
125 |
126 | function submitAddLiquidityIntent(
127 | uint8 asset0,
128 | uint8 asset1,
129 | uint64 amount0,
130 | uint64 amount1,
131 | uint64 minLP
132 | ) external returns (bytes32 intentId) {
133 | _validateAssetPair(asset0, asset1);
134 | if (amount0 == 0 || amount1 == 0) {
135 | revert InvalidAmount();
136 | }
137 | intentId = _storeIntent(OP_ADD_LIQUIDITY, asset0, asset1, 0, amount0, amount1, minLP, 0, 0, 0, 0, 0);
138 | }
139 |
140 | function submitRemoveLiquidityIntent(
141 | uint8 asset0,
142 | uint8 asset1,
143 | uint64 lpAmount,
144 | uint64 minAmount0,
145 | uint64 minAmount1
146 | ) external returns (bytes32 intentId) {
147 | _validateAssetPair(asset0, asset1);
148 | if (lpAmount == 0) {
149 | revert InvalidAmount();
150 | }
151 | intentId = _storeIntent(OP_REMOVE_LIQUIDITY, asset0, asset1, 0, 0, 0, 0, lpAmount, minAmount0, minAmount1, 0, 0);
152 | }
153 |
154 | function submitSwapExactInIntent(
155 | uint8 assetIn,
156 | uint8 assetOut,
157 | uint64 amountIn,
158 | uint64 minAmountOut
159 | ) external returns (bytes32 intentId) {
160 | _validateAssetPair(assetIn, assetOut);
161 | if (amountIn == 0) {
162 | revert InvalidAmount();
163 | }
164 | intentId = _storeIntent(OP_SWAP_EXACT_IN, assetIn, assetOut, 0, 0, 0, 0, 0, 0, 0, amountIn, minAmountOut);
165 | }
166 |
167 | function markIntentExecuted(bytes32 intentId, bytes32 veilTxHash) external onlyRelay {
168 | LiquidityIntent storage intent = intents[intentId];
169 | if (intent.state == IntentState.NONE) {
170 | revert IntentNotFound(intentId);
171 | }
172 | if (intent.state != IntentState.SUBMITTED) {
173 | revert IntentNotSubmitted(intentId);
174 | }
175 | intent.state = IntentState.EXECUTED;
176 | emit LiquidityIntentExecuted(intentId, veilTxHash, msg.sender);
177 | }
178 |
179 | function cancelIntent(bytes32 intentId) external {
180 | LiquidityIntent storage intent = intents[intentId];
181 | if (intent.state == IntentState.NONE) {
182 | revert IntentNotFound(intentId);
183 | }
184 | if (intent.trader != msg.sender) {
185 | revert IntentNotOwned(intentId, intent.trader, msg.sender);
186 | }
187 | if (intent.state != IntentState.SUBMITTED) {
188 | revert IntentNotSubmitted(intentId);
189 | }
190 | intent.state = IntentState.CANCELLED;
191 | emit LiquidityIntentCancelled(intentId, msg.sender);
192 | }
193 |
194 | function getIntent(bytes32 intentId) external view returns (LiquidityIntent memory) {
195 | LiquidityIntent memory intent = intents[intentId];
196 | if (intent.state == IntentState.NONE) {
197 | revert IntentNotFound(intentId);
198 | }
199 | return intent;
200 | }
201 |
202 | function _storeIntent(
203 | uint8 operation,
204 | uint8 asset0,
205 | uint8 asset1,
206 | uint16 feeBips,
207 | uint64 amount0,
208 | uint64 amount1,
209 | uint64 minLP,
210 | uint64 lpAmount,
211 | uint64 minAmount0,
212 | uint64 minAmount1,
213 | uint64 amountIn,
214 | uint64 minAmountOut
215 | ) internal returns (bytes32 intentId) {
216 | if (operation > OP_SWAP_EXACT_IN) {
217 | revert InvalidOperation(operation);
218 | }
219 |
220 | uint64 nonce = nonces[msg.sender];
221 | nonces[msg.sender] = nonce + 1;
222 | intentId = keccak256(
223 | abi.encode(
224 | block.chainid,
225 | address(this),
226 | msg.sender,
227 | operation,
228 | asset0,
229 | asset1,
230 | feeBips,
231 | amount0,
232 | amount1,
233 | minLP,
234 | lpAmount,
235 | minAmount0,
236 | minAmount1,
237 | amountIn,
238 | minAmountOut,
239 | nonce
240 | )
241 | );
242 |
243 | if (intents[intentId].state != IntentState.NONE) {
244 | revert IntentAlreadyExists(intentId);
245 | }
246 |
247 | intents[intentId] = LiquidityIntent({
248 | trader: msg.sender,
249 | operation: operation,
250 | asset0: asset0,
251 | asset1: asset1,
252 | feeBips: feeBips,
253 | amount0: amount0,
254 | amount1: amount1,
255 | minLP: minLP,
256 | lpAmount: lpAmount,
257 | minAmount0: minAmount0,
258 | minAmount1: minAmount1,
259 | amountIn: amountIn,
260 | minAmountOut: minAmountOut,
261 | nonce: nonce,
262 | state: IntentState.SUBMITTED
263 | });
264 |
265 | emit LiquidityIntentSubmitted(
266 | intentId,
267 | msg.sender,
268 | operation,
269 | asset0,
270 | asset1,
271 | feeBips,
272 | amount0,
273 | amount1,
274 | minLP,
275 | lpAmount,
276 | minAmount0,
277 | minAmount1,
278 | amountIn,
279 | minAmountOut,
280 | nonce
281 | );
282 | }
283 |
284 | function _validateAssetPair(uint8 asset0, uint8 asset1) internal pure {
285 | if (!_isSupportedAsset(asset0)) {
286 | revert InvalidAsset(asset0);
287 | }
288 | if (!_isSupportedAsset(asset1)) {
289 | revert InvalidAsset(asset1);
290 | }
291 | if (asset0 == asset1) {
292 | revert InvalidAssetPair(asset0, asset1);
293 | }
294 | }
295 |
296 | function _isSupportedAsset(uint8 asset) internal pure returns (bool) {
297 | return asset == ASSET_VEIL || asset == ASSET_VAI;
298 | }
299 | }
300 |
VeilOrderIntentGateway.sol
EVM Intent Gateway (Order)
raw .sol
Commentary: Review order intent validation and replay-protection semantics. | Check signature/domain separation and nonce handling. | Validate settlement path authorization and failure handling.
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.23;
3 |
4 | /// @notice Lightweight EVM entrypoint that captures user order intents and
5 | /// emits deterministic events for the VEIL relayer.
6 | contract VeilOrderIntentGateway {
7 | uint8 public constant SIDE_BUY = 0;
8 | uint8 public constant SIDE_SELL = 1;
9 | uint8 public constant OUTCOME_YES = 0;
10 | uint8 public constant OUTCOME_NO = 1;
11 | uint8 public constant MARKET_TYPE_VEIL_NATIVE = 0;
12 | uint8 public constant MARKET_TYPE_POLYGON_NATIVE = 1;
13 | uint16 public constant POLYGON_ROUTING_FEE_BPS = 3; // 0.03%
14 |
15 | enum IntentState {
16 | NONE,
17 | SUBMITTED,
18 | EXECUTED,
19 | CANCELLED
20 | }
21 |
22 | struct Intent {
23 | address trader;
24 | bytes32 marketKey;
25 | uint64 amountUsdE6;
26 | uint16 routingFeeBps;
27 | uint8 side;
28 | uint8 outcome;
29 | uint8 marketType;
30 | uint64 nonce;
31 | IntentState state;
32 | }
33 |
34 | error Unauthorized();
35 | error InvalidAddress();
36 | error InvalidSide(uint8 side);
37 | error InvalidOutcome(uint8 outcome);
38 | error InvalidMarketType(uint8 marketType);
39 | error InvalidAmount();
40 | error InvalidRoutingFee(uint16 provided, uint16 requiredFloor, uint8 marketType);
41 | error IntentAlreadyExists(bytes32 intentId);
42 | error IntentNotFound(bytes32 intentId);
43 | error IntentNotSubmitted(bytes32 intentId);
44 | error IntentNotOwned(bytes32 intentId, address expectedOwner, address sender);
45 |
46 | event OwnerTransferred(address indexed previousOwner, address indexed newOwner);
47 | event RelayExecutorSet(address indexed previousRelay, address indexed newRelay);
48 | event IntentSubmitted(
49 | bytes32 indexed intentId,
50 | address indexed trader,
51 | bytes32 indexed marketKey,
52 | uint8 side,
53 | uint8 outcome,
54 | uint64 amountUsdE6,
55 | uint8 marketType,
56 | uint16 routingFeeBps,
57 | uint64 nonce
58 | );
59 | event IntentExecuted(bytes32 indexed intentId, bytes32 indexed veilTxHash, address indexed executor);
60 | event IntentCancelled(bytes32 indexed intentId, address indexed trader);
61 |
62 | address public owner;
63 | address public relayExecutor;
64 |
65 | mapping(address => uint64) public nonces;
66 | mapping(bytes32 => Intent) private intents;
67 |
68 | modifier onlyOwner() {
69 | if (msg.sender != owner) {
70 | revert Unauthorized();
71 | }
72 | _;
73 | }
74 |
75 | modifier onlyRelay() {
76 | if (msg.sender != relayExecutor) {
77 | revert Unauthorized();
78 | }
79 | _;
80 | }
81 |
82 | constructor(address initialOwner, address initialRelayExecutor) {
83 | owner = initialOwner == address(0) ? msg.sender : initialOwner;
84 | relayExecutor = initialRelayExecutor;
85 | emit OwnerTransferred(address(0), owner);
86 | emit RelayExecutorSet(address(0), initialRelayExecutor);
87 | }
88 |
89 | function transferOwnership(address newOwner) external onlyOwner {
90 | if (newOwner == address(0)) {
91 | revert InvalidAddress();
92 | }
93 | emit OwnerTransferred(owner, newOwner);
94 | owner = newOwner;
95 | }
96 |
97 | function setRelayExecutor(address newRelayExecutor) external onlyOwner {
98 | emit RelayExecutorSet(relayExecutor, newRelayExecutor);
99 | relayExecutor = newRelayExecutor;
100 | }
101 |
102 | function submitIntent(
103 | bytes32 marketKey,
104 | uint8 side,
105 | uint8 outcome,
106 | uint64 amountUsdE6,
107 | uint8 marketType,
108 | uint16 routingFeeBps
109 | ) external returns (bytes32 intentId) {
110 | if (side > SIDE_SELL) {
111 | revert InvalidSide(side);
112 | }
113 | if (outcome > OUTCOME_NO) {
114 | revert InvalidOutcome(outcome);
115 | }
116 | if (marketType > MARKET_TYPE_POLYGON_NATIVE) {
117 | revert InvalidMarketType(marketType);
118 | }
119 | if (amountUsdE6 == 0) {
120 | revert InvalidAmount();
121 | }
122 |
123 | uint16 normalizedRoutingFeeBps = normalizeRoutingFee(marketType, routingFeeBps);
124 | uint64 nonce = nonces[msg.sender];
125 | nonces[msg.sender] = nonce + 1;
126 |
127 | intentId = keccak256(
128 | abi.encode(
129 | block.chainid,
130 | address(this),
131 | msg.sender,
132 | marketKey,
133 | side,
134 | outcome,
135 | amountUsdE6,
136 | marketType,
137 | normalizedRoutingFeeBps,
138 | nonce
139 | )
140 | );
141 |
142 | if (intents[intentId].state != IntentState.NONE) {
143 | revert IntentAlreadyExists(intentId);
144 | }
145 |
146 | intents[intentId] = Intent({
147 | trader: msg.sender,
148 | marketKey: marketKey,
149 | amountUsdE6: amountUsdE6,
150 | routingFeeBps: normalizedRoutingFeeBps,
151 | side: side,
152 | outcome: outcome,
153 | marketType: marketType,
154 | nonce: nonce,
155 | state: IntentState.SUBMITTED
156 | });
157 |
158 | emit IntentSubmitted(
159 | intentId,
160 | msg.sender,
161 | marketKey,
162 | side,
163 | outcome,
164 | amountUsdE6,
165 | marketType,
166 | normalizedRoutingFeeBps,
167 | nonce
168 | );
169 | }
170 |
171 | function markIntentExecuted(bytes32 intentId, bytes32 veilTxHash) external onlyRelay {
172 | Intent storage intent = intents[intentId];
173 | if (intent.state == IntentState.NONE) {
174 | revert IntentNotFound(intentId);
175 | }
176 | if (intent.state != IntentState.SUBMITTED) {
177 | revert IntentNotSubmitted(intentId);
178 | }
179 | intent.state = IntentState.EXECUTED;
180 | emit IntentExecuted(intentId, veilTxHash, msg.sender);
181 | }
182 |
183 | function cancelIntent(bytes32 intentId) external {
184 | Intent storage intent = intents[intentId];
185 | if (intent.state == IntentState.NONE) {
186 | revert IntentNotFound(intentId);
187 | }
188 | if (intent.trader != msg.sender) {
189 | revert IntentNotOwned(intentId, intent.trader, msg.sender);
190 | }
191 | if (intent.state != IntentState.SUBMITTED) {
192 | revert IntentNotSubmitted(intentId);
193 | }
194 | intent.state = IntentState.CANCELLED;
195 | emit IntentCancelled(intentId, msg.sender);
196 | }
197 |
198 | function getIntent(bytes32 intentId) external view returns (Intent memory) {
199 | Intent memory intent = intents[intentId];
200 | if (intent.state == IntentState.NONE) {
201 | revert IntentNotFound(intentId);
202 | }
203 | return intent;
204 | }
205 |
206 | function normalizeRoutingFee(uint8 marketType, uint16 requestedFeeBps) public pure returns (uint16) {
207 | if (marketType == MARKET_TYPE_POLYGON_NATIVE) {
208 | if (requestedFeeBps == 0) {
209 | return POLYGON_ROUTING_FEE_BPS;
210 | }
211 | if (requestedFeeBps < POLYGON_ROUTING_FEE_BPS) {
212 | revert InvalidRoutingFee(requestedFeeBps, POLYGON_ROUTING_FEE_BPS, marketType);
213 | }
214 | return requestedFeeBps;
215 | }
216 |
217 | if (requestedFeeBps != 0) {
218 | revert InvalidRoutingFee(requestedFeeBps, 0, marketType);
219 | }
220 | return 0;
221 | }
222 | }
223 |
VeilTreasury.sol
EVM Treasury
raw .sol
Commentary: Validate treasury withdrawal authorization and role scoping. | Review value-moving methods for reentrancy and external-call risk. | Confirm emergency and pause controls are explicit and auditable.
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.23;
3 |
4 | interface IERC20LikeTreasury {
5 | function transfer(address to, uint256 value) external returns (bool);
6 | function transferFrom(address from, address to, uint256 value) external returns (bool);
7 | }
8 |
9 | interface IVeilVAITreasury {
10 | function mint(address to, uint256 amount) external;
11 | function burnFrom(address from, uint256 amount) external;
12 | }
13 |
14 | /// @notice Companion EVM treasury for VEIL/wVEIL + VAI operations.
15 | /// @dev Owner-controlled for now; designed for fast local recovery workflows.
16 | contract VeilTreasury {
17 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
18 | event Keep3rSet(address indexed previousKeep3r, address indexed nextKeep3r);
19 | event VeilDeposited(address indexed from, uint256 amount);
20 | event Keep3rFunded(address indexed keep3r, uint256 amount);
21 | event VAIMinted(address indexed to, uint256 amount);
22 | event VAIBurned(address indexed from, uint256 amount);
23 | event TokenWithdrawn(address indexed token, address indexed to, uint256 amount);
24 |
25 | error TreasuryZeroAddress();
26 | error TreasuryOnlyOwner();
27 | error TreasuryKeep3rUnset();
28 | error TreasuryTokenTransferFailed();
29 |
30 | address public owner;
31 | address public keep3r;
32 | IERC20LikeTreasury public immutable VEIL;
33 | IVeilVAITreasury public immutable VAI;
34 |
35 | modifier onlyOwner() {
36 | if (msg.sender != owner) revert TreasuryOnlyOwner();
37 | _;
38 | }
39 |
40 | constructor(address veilToken_, address vaiToken_, address owner_) {
41 | if (veilToken_ == address(0) || vaiToken_ == address(0) || owner_ == address(0)) {
42 | revert TreasuryZeroAddress();
43 | }
44 | VEIL = IERC20LikeTreasury(veilToken_);
45 | VAI = IVeilVAITreasury(vaiToken_);
46 | owner = owner_;
47 | emit OwnershipTransferred(address(0), owner_);
48 | }
49 |
50 | function transferOwnership(address nextOwner) external onlyOwner {
51 | if (nextOwner == address(0)) revert TreasuryZeroAddress();
52 | emit OwnershipTransferred(owner, nextOwner);
53 | owner = nextOwner;
54 | }
55 |
56 | function setKeep3r(address nextKeep3r) external onlyOwner {
57 | if (nextKeep3r == address(0)) revert TreasuryZeroAddress();
58 | emit Keep3rSet(keep3r, nextKeep3r);
59 | keep3r = nextKeep3r;
60 | }
61 |
62 | function depositVeil(uint256 amount) external {
63 | if (!VEIL.transferFrom(msg.sender, address(this), amount)) {
64 | revert TreasuryTokenTransferFailed();
65 | }
66 | emit VeilDeposited(msg.sender, amount);
67 | }
68 |
69 | function fundKeep3r(uint256 amount) external onlyOwner {
70 | address target = keep3r;
71 | if (target == address(0)) revert TreasuryKeep3rUnset();
72 | if (!VEIL.transfer(target, amount)) revert TreasuryTokenTransferFailed();
73 | emit Keep3rFunded(target, amount);
74 | }
75 |
76 | function mintVAI(address to, uint256 amount) external onlyOwner {
77 | VAI.mint(to, amount);
78 | emit VAIMinted(to, amount);
79 | }
80 |
81 | function burnVAIFrom(address from, uint256 amount) external onlyOwner {
82 | VAI.burnFrom(from, amount);
83 | emit VAIBurned(from, amount);
84 | }
85 |
86 | function withdrawToken(address token, address to, uint256 amount) external onlyOwner {
87 | if (token == address(0) || to == address(0)) revert TreasuryZeroAddress();
88 | if (!IERC20LikeTreasury(token).transfer(to, amount)) {
89 | revert TreasuryTokenTransferFailed();
90 | }
91 | emit TokenWithdrawn(token, to, amount);
92 | }
93 | }
94 |
VeilUniV2Dex.sol
EVM UniV2 LP/DEX
raw .sol
Commentary: Validate pool and swap math assumptions and fee accounting. | Check LP mint/burn paths for reserve invariant safety. | Review token transfer ordering and external call protections.
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.23;
3 |
4 | interface IERC20MiniUniV2 {
5 | function totalSupply() external view returns (uint256);
6 | function balanceOf(address account) external view returns (uint256);
7 | function allowance(address owner, address spender) external view returns (uint256);
8 | function approve(address spender, uint256 amount) external returns (bool);
9 | function transfer(address to, uint256 amount) external returns (bool);
10 | function transferFrom(address from, address to, uint256 amount) external returns (bool);
11 | }
12 |
13 | library VeilUniV2Math {
14 | function min(uint256 a, uint256 b) internal pure returns (uint256) {
15 | return a < b ? a : b;
16 | }
17 |
18 | function sqrt(uint256 y) internal pure returns (uint256 z) {
19 | if (y > 3) {
20 | z = y;
21 | uint256 x = y / 2 + 1;
22 | while (x < z) {
23 | z = x;
24 | x = (y / x + x) / 2;
25 | }
26 | } else if (y != 0) {
27 | z = 1;
28 | }
29 | }
30 | }
31 |
32 | /// @notice Minimal Uniswap V2-style pair for VEIL companion EVM rails.
33 | contract VeilUniV2Pair {
34 | string public constant name = "VEIL LP";
35 | string public constant symbol = "VLP";
36 | uint8 public constant decimals = 18;
37 | uint256 public constant MINIMUM_LIQUIDITY = 1_000;
38 |
39 | address public immutable factory;
40 | address public immutable token0;
41 | address public immutable token1;
42 |
43 | uint112 private reserve0;
44 | uint112 private reserve1;
45 | uint32 private blockTimestampLast;
46 |
47 | uint256 public totalSupply;
48 | mapping(address => uint256) public balanceOf;
49 | mapping(address => mapping(address => uint256)) public allowance;
50 |
51 | event Approval(address indexed owner, address indexed spender, uint256 value);
52 | event Transfer(address indexed from, address indexed to, uint256 value);
53 | event Mint(address indexed sender, uint256 amount0, uint256 amount1);
54 | event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
55 | event Swap(
56 | address indexed sender,
57 | uint256 amount0In,
58 | uint256 amount1In,
59 | uint256 amount0Out,
60 | uint256 amount1Out,
61 | address indexed to
62 | );
63 | event Sync(uint112 reserve0, uint112 reserve1);
64 |
65 | error UniV2BadConfig();
66 | error UniV2InsufficientLiquidityMinted();
67 | error UniV2InsufficientLiquidityBurned();
68 | error UniV2InsufficientOutputAmount();
69 | error UniV2InsufficientLiquidity();
70 | error UniV2InvalidTo();
71 | error UniV2TransferFailed();
72 | error UniV2KInvariant();
73 |
74 | constructor(address token0_, address token1_) {
75 | if (token0_ == token1_ || token0_ == address(0) || token1_ == address(0)) {
76 | revert UniV2BadConfig();
77 | }
78 | factory = msg.sender;
79 | token0 = token0_;
80 | token1 = token1_;
81 | }
82 |
83 | function getReserves() external view returns (uint112, uint112, uint32) {
84 | return (reserve0, reserve1, blockTimestampLast);
85 | }
86 |
87 | function approve(address spender, uint256 value) external returns (bool) {
88 | allowance[msg.sender][spender] = value;
89 | emit Approval(msg.sender, spender, value);
90 | return true;
91 | }
92 |
93 | function transfer(address to, uint256 value) external returns (bool) {
94 | return transferFrom(msg.sender, to, value);
95 | }
96 |
97 | function transferFrom(address from, address to, uint256 value) public returns (bool) {
98 | if (to == address(0)) revert UniV2InvalidTo();
99 | if (from != msg.sender) {
100 | uint256 allowed = allowance[from][msg.sender];
101 | if (allowed != type(uint256).max) {
102 | require(allowed >= value, "UniV2/insufficient-allowance");
103 | unchecked {
104 | allowance[from][msg.sender] = allowed - value;
105 | }
106 | emit Approval(from, msg.sender, allowance[from][msg.sender]);
107 | }
108 | }
109 | require(balanceOf[from] >= value, "UniV2/insufficient-balance");
110 | unchecked {
111 | balanceOf[from] -= value;
112 | balanceOf[to] += value;
113 | }
114 | emit Transfer(from, to, value);
115 | return true;
116 | }
117 |
118 | function mint(address to) external returns (uint256 liquidity) {
119 | (uint112 r0, uint112 r1,) = (reserve0, reserve1, blockTimestampLast);
120 | uint256 balance0 = IERC20MiniUniV2(token0).balanceOf(address(this));
121 | uint256 balance1 = IERC20MiniUniV2(token1).balanceOf(address(this));
122 | uint256 amount0 = balance0 - r0;
123 | uint256 amount1 = balance1 - r1;
124 |
125 | uint256 _totalSupply = totalSupply;
126 | if (_totalSupply == 0) {
127 | liquidity = VeilUniV2Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
128 | _mint(address(0), MINIMUM_LIQUIDITY);
129 | } else {
130 | liquidity = VeilUniV2Math.min((amount0 * _totalSupply) / r0, (amount1 * _totalSupply) / r1);
131 | }
132 |
133 | if (liquidity == 0) revert UniV2InsufficientLiquidityMinted();
134 | _mint(to, liquidity);
135 | _update(balance0, balance1);
136 | emit Mint(msg.sender, amount0, amount1);
137 | }
138 |
139 | function burn(address to) external returns (uint256 amount0, uint256 amount1) {
140 | if (to == address(0)) revert UniV2InvalidTo();
141 | address _token0 = token0;
142 | address _token1 = token1;
143 | uint256 balance0 = IERC20MiniUniV2(_token0).balanceOf(address(this));
144 | uint256 balance1 = IERC20MiniUniV2(_token1).balanceOf(address(this));
145 | uint256 liquidity = balanceOf[address(this)];
146 |
147 | uint256 _totalSupply = totalSupply;
148 | amount0 = (liquidity * balance0) / _totalSupply;
149 | amount1 = (liquidity * balance1) / _totalSupply;
150 | if (amount0 == 0 || amount1 == 0) revert UniV2InsufficientLiquidityBurned();
151 |
152 | _burn(address(this), liquidity);
153 | _safeTransfer(_token0, to, amount0);
154 | _safeTransfer(_token1, to, amount1);
155 |
156 | balance0 = IERC20MiniUniV2(_token0).balanceOf(address(this));
157 | balance1 = IERC20MiniUniV2(_token1).balanceOf(address(this));
158 |
159 | _update(balance0, balance1);
160 | emit Burn(msg.sender, amount0, amount1, to);
161 | }
162 |
163 | function swap(uint256 amount0Out, uint256 amount1Out, address to) external {
164 | if (amount0Out == 0 && amount1Out == 0) revert UniV2InsufficientOutputAmount();
165 | (uint112 r0, uint112 r1,) = (reserve0, reserve1, blockTimestampLast);
166 | if (amount0Out >= r0 || amount1Out >= r1) revert UniV2InsufficientLiquidity();
167 | if (to == token0 || to == token1 || to == address(0)) revert UniV2InvalidTo();
168 |
169 | if (amount0Out > 0) _safeTransfer(token0, to, amount0Out);
170 | if (amount1Out > 0) _safeTransfer(token1, to, amount1Out);
171 |
172 | uint256 balance0 = IERC20MiniUniV2(token0).balanceOf(address(this));
173 | uint256 balance1 = IERC20MiniUniV2(token1).balanceOf(address(this));
174 | uint256 amount0In = balance0 > (r0 - amount0Out) ? balance0 - (r0 - amount0Out) : 0;
175 | uint256 amount1In = balance1 > (r1 - amount1Out) ? balance1 - (r1 - amount1Out) : 0;
176 | if (amount0In == 0 && amount1In == 0) revert UniV2InsufficientOutputAmount();
177 |
178 | // 30 bps fee: x * 997 / 1000.
179 | uint256 balance0Adjusted = (balance0 * 1000) - (amount0In * 3);
180 | uint256 balance1Adjusted = (balance1 * 1000) - (amount1In * 3);
181 | if (balance0Adjusted * balance1Adjusted < uint256(r0) * uint256(r1) * 1_000_000) {
182 | revert UniV2KInvariant();
183 | }
184 |
185 | _update(balance0, balance1);
186 | emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
187 | }
188 |
189 | function sync() external {
190 | _update(
191 | IERC20MiniUniV2(token0).balanceOf(address(this)),
192 | IERC20MiniUniV2(token1).balanceOf(address(this))
193 | );
194 | }
195 |
196 | function _safeTransfer(address token, address to, uint256 value) private {
197 | (bool ok, bytes memory data) =
198 | token.call(abi.encodeWithSelector(IERC20MiniUniV2.transfer.selector, to, value));
199 | if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) {
200 | revert UniV2TransferFailed();
201 | }
202 | }
203 |
204 | function _update(uint256 balance0, uint256 balance1) private {
205 | reserve0 = uint112(balance0);
206 | reserve1 = uint112(balance1);
207 | blockTimestampLast = uint32(block.timestamp);
208 | emit Sync(reserve0, reserve1);
209 | }
210 |
211 | function _mint(address to, uint256 value) private {
212 | totalSupply += value;
213 | unchecked {
214 | balanceOf[to] += value;
215 | }
216 | emit Transfer(address(0), to, value);
217 | }
218 |
219 | function _burn(address from, uint256 value) private {
220 | unchecked {
221 | balanceOf[from] -= value;
222 | totalSupply -= value;
223 | }
224 | emit Transfer(from, address(0), value);
225 | }
226 | }
227 |
228 | contract VeilUniV2Factory {
229 | mapping(address => mapping(address => address)) public getPair;
230 | address[] public allPairs;
231 |
232 | event PairCreated(address indexed token0, address indexed token1, address pair, uint256 totalPairs);
233 |
234 | error UniV2IdenticalAddresses();
235 | error UniV2ZeroAddress();
236 | error UniV2PairExists();
237 |
238 | function allPairsLength() external view returns (uint256) {
239 | return allPairs.length;
240 | }
241 |
242 | function createPair(address tokenA, address tokenB) external returns (address pair) {
243 | if (tokenA == tokenB) revert UniV2IdenticalAddresses();
244 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
245 | if (token0 == address(0)) revert UniV2ZeroAddress();
246 | if (getPair[token0][token1] != address(0)) revert UniV2PairExists();
247 |
248 | pair = address(new VeilUniV2Pair(token0, token1));
249 | getPair[token0][token1] = pair;
250 | getPair[token1][token0] = pair;
251 | allPairs.push(pair);
252 |
253 | emit PairCreated(token0, token1, pair, allPairs.length);
254 | }
255 | }
256 |
257 | contract VeilUniV2Router {
258 | address public immutable factory;
259 |
260 | error UniV2InvalidPath();
261 | error UniV2PairMissing();
262 | error UniV2InsufficientA();
263 | error UniV2InsufficientB();
264 | error UniV2InsufficientOut();
265 | error UniV2TransferFailed();
266 |
267 | constructor(address factory_) {
268 | require(factory_ != address(0), "UniV2Router/bad-factory");
269 | factory = factory_;
270 | }
271 |
272 | function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) public pure returns (uint256 amountB) {
273 | require(amountA > 0, "UniV2Router/insufficient-amount");
274 | require(reserveA > 0 && reserveB > 0, "UniV2Router/insufficient-liquidity");
275 | amountB = (amountA * reserveB) / reserveA;
276 | }
277 |
278 | function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
279 | public
280 | pure
281 | returns (uint256 amountOut)
282 | {
283 | require(amountIn > 0, "UniV2Router/insufficient-input");
284 | require(reserveIn > 0 && reserveOut > 0, "UniV2Router/insufficient-liquidity");
285 | uint256 amountInWithFee = amountIn * 997;
286 | amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);
287 | }
288 |
289 | function getAmountsOut(uint256 amountIn, address[] memory path) public view returns (uint256[] memory amounts) {
290 | if (path.length < 2) revert UniV2InvalidPath();
291 | amounts = new uint256[](path.length);
292 | amounts[0] = amountIn;
293 | for (uint256 i = 0; i < path.length - 1; i++) {
294 | (uint256 reserveIn, uint256 reserveOut) = getReserves(path[i], path[i + 1]);
295 | amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
296 | }
297 | }
298 |
299 | function getReserves(address tokenA, address tokenB) public view returns (uint256 reserveA, uint256 reserveB) {
300 | address pair = VeilUniV2Factory(factory).getPair(tokenA, tokenB);
301 | if (pair == address(0)) revert UniV2PairMissing();
302 | (address token0,) = sortTokens(tokenA, tokenB);
303 | (uint112 reserve0, uint112 reserve1,) = VeilUniV2Pair(pair).getReserves();
304 | (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
305 | }
306 |
307 | function addLiquidity(
308 | address tokenA,
309 | address tokenB,
310 | uint256 amountADesired,
311 | uint256 amountBDesired,
312 | uint256 amountAMin,
313 | uint256 amountBMin,
314 | address to
315 | ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
316 | address pair = VeilUniV2Factory(factory).getPair(tokenA, tokenB);
317 | if (pair == address(0)) {
318 | pair = VeilUniV2Factory(factory).createPair(tokenA, tokenB);
319 | }
320 |
321 | (uint256 reserveA, uint256 reserveB) = _tryGetReserves(tokenA, tokenB);
322 | if (reserveA == 0 && reserveB == 0) {
323 | (amountA, amountB) = (amountADesired, amountBDesired);
324 | } else {
325 | uint256 amountBOptimal = quote(amountADesired, reserveA, reserveB);
326 | if (amountBOptimal <= amountBDesired) {
327 | if (amountBOptimal < amountBMin) revert UniV2InsufficientB();
328 | (amountA, amountB) = (amountADesired, amountBOptimal);
329 | } else {
330 | uint256 amountAOptimal = quote(amountBDesired, reserveB, reserveA);
331 | if (amountAOptimal < amountAMin) revert UniV2InsufficientA();
332 | (amountA, amountB) = (amountAOptimal, amountBDesired);
333 | }
334 | }
335 |
336 | _safeTransferFrom(tokenA, msg.sender, pair, amountA);
337 | _safeTransferFrom(tokenB, msg.sender, pair, amountB);
338 | liquidity = VeilUniV2Pair(pair).mint(to);
339 | }
340 |
341 | function removeLiquidity(
342 | address tokenA,
343 | address tokenB,
344 | uint256 liquidity,
345 | uint256 amountAMin,
346 | uint256 amountBMin,
347 | address to
348 | ) external returns (uint256 amountA, uint256 amountB) {
349 | address pair = VeilUniV2Factory(factory).getPair(tokenA, tokenB);
350 | if (pair == address(0)) revert UniV2PairMissing();
351 | _safeTransferFrom(pair, msg.sender, pair, liquidity);
352 | (uint256 amount0, uint256 amount1) = VeilUniV2Pair(pair).burn(to);
353 | (address token0,) = sortTokens(tokenA, tokenB);
354 | (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
355 | if (amountA < amountAMin) revert UniV2InsufficientA();
356 | if (amountB < amountBMin) revert UniV2InsufficientB();
357 | }
358 |
359 | function swapExactTokensForTokens(
360 | uint256 amountIn,
361 | uint256 amountOutMin,
362 | address[] calldata path,
363 | address to
364 | ) external returns (uint256[] memory amounts) {
365 | amounts = getAmountsOut(amountIn, path);
366 | if (amounts[amounts.length - 1] < amountOutMin) revert UniV2InsufficientOut();
367 | address firstPair = VeilUniV2Factory(factory).getPair(path[0], path[1]);
368 | if (firstPair == address(0)) revert UniV2PairMissing();
369 | _safeTransferFrom(path[0], msg.sender, firstPair, amounts[0]);
370 | _swap(amounts, path, to);
371 | }
372 |
373 | function _swap(uint256[] memory amounts, address[] calldata path, address to_) internal {
374 | for (uint256 i = 0; i < path.length - 1; i++) {
375 | (address input, address output) = (path[i], path[i + 1]);
376 | address pair = VeilUniV2Factory(factory).getPair(input, output);
377 | if (pair == address(0)) revert UniV2PairMissing();
378 | (address token0,) = sortTokens(input, output);
379 | uint256 amountOut = amounts[i + 1];
380 | (uint256 amount0Out, uint256 amount1Out) =
381 | input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0));
382 | address to = i < path.length - 2
383 | ? VeilUniV2Factory(factory).getPair(output, path[i + 2])
384 | : to_;
385 | VeilUniV2Pair(pair).swap(amount0Out, amount1Out, to);
386 | }
387 | }
388 |
389 | function sortTokens(address tokenA, address tokenB) public pure returns (address token0, address token1) {
390 | require(tokenA != tokenB, "UniV2Router/identical");
391 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
392 | require(token0 != address(0), "UniV2Router/zero");
393 | }
394 |
395 | function _tryGetReserves(address tokenA, address tokenB)
396 | internal
397 | view
398 | returns (uint256 reserveA, uint256 reserveB)
399 | {
400 | address pair = VeilUniV2Factory(factory).getPair(tokenA, tokenB);
401 | if (pair == address(0)) return (0, 0);
402 | (address token0,) = sortTokens(tokenA, tokenB);
403 | (uint112 reserve0, uint112 reserve1,) = VeilUniV2Pair(pair).getReserves();
404 | (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
405 | }
406 |
407 | function _safeTransferFrom(address token, address from, address to, uint256 value) private {
408 | (bool ok, bytes memory data) =
409 | token.call(abi.encodeWithSelector(IERC20MiniUniV2.transferFrom.selector, from, to, value));
410 | if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) {
411 | revert UniV2TransferFailed();
412 | }
413 | }
414 | }
415 |
VeilVAI.sol
EVM VAI
raw .sol
Commentary: Verify mint and burn authority boundaries and any privileged issuer paths. | Check ERC-20 transfer and allowance flows for standard edge-case safety. | Confirm administrative controls cannot bypass intended supply policy.
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.23;
3 |
4 | /// @notice Minimal VAI token for companion EVM rails.
5 | /// @dev Owner sets minter (treasury); minter mints, holders can burn.
6 | contract VeilVAI {
7 | string public constant name = "VEIL USD";
8 | string public constant symbol = "VAI";
9 | uint8 public constant decimals = 18;
10 |
11 | event Transfer(address indexed from, address indexed to, uint256 value);
12 | event Approval(address indexed owner, address indexed spender, uint256 value);
13 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
14 | event MinterSet(address indexed previousMinter, address indexed nextMinter);
15 |
16 | error VAIZeroAddress();
17 | error VAIOnlyOwner();
18 | error VAIOnlyMinter();
19 | error VAIInsufficientBalance();
20 | error VAIInsufficientAllowance();
21 |
22 | address public owner;
23 | address public minter;
24 |
25 | uint256 public totalSupply;
26 | mapping(address => uint256) public balanceOf;
27 | mapping(address => mapping(address => uint256)) public allowance;
28 |
29 | modifier onlyOwner() {
30 | if (msg.sender != owner) revert VAIOnlyOwner();
31 | _;
32 | }
33 |
34 | modifier onlyMinter() {
35 | if (msg.sender != minter) revert VAIOnlyMinter();
36 | _;
37 | }
38 |
39 | constructor(
40 | address owner_,
41 | address minter_,
42 | address initialRecipient_,
43 | uint256 initialSupply_
44 | ) {
45 | if (owner_ == address(0) || minter_ == address(0)) revert VAIZeroAddress();
46 | owner = owner_;
47 | minter = minter_;
48 | emit OwnershipTransferred(address(0), owner_);
49 |
50 | if (initialSupply_ > 0) {
51 | address recipient = initialRecipient_ == address(0) ? owner_ : initialRecipient_;
52 | _mint(recipient, initialSupply_);
53 | }
54 | }
55 |
56 | function transferOwnership(address nextOwner) external onlyOwner {
57 | if (nextOwner == address(0)) revert VAIZeroAddress();
58 | emit OwnershipTransferred(owner, nextOwner);
59 | owner = nextOwner;
60 | }
61 |
62 | function setMinter(address nextMinter) external onlyOwner {
63 | if (nextMinter == address(0)) revert VAIZeroAddress();
64 | emit MinterSet(minter, nextMinter);
65 | minter = nextMinter;
66 | }
67 |
68 | function approve(address spender, uint256 amount) external returns (bool) {
69 | allowance[msg.sender][spender] = amount;
70 | emit Approval(msg.sender, spender, amount);
71 | return true;
72 | }
73 |
74 | function transfer(address to, uint256 amount) external returns (bool) {
75 | _transfer(msg.sender, to, amount);
76 | return true;
77 | }
78 |
79 | function transferFrom(address from, address to, uint256 amount) external returns (bool) {
80 | if (from != msg.sender) {
81 | uint256 allowed = allowance[from][msg.sender];
82 | if (allowed != type(uint256).max) {
83 | if (allowed < amount) revert VAIInsufficientAllowance();
84 | unchecked {
85 | allowance[from][msg.sender] = allowed - amount;
86 | }
87 | emit Approval(from, msg.sender, allowance[from][msg.sender]);
88 | }
89 | }
90 | _transfer(from, to, amount);
91 | return true;
92 | }
93 |
94 | function mint(address to, uint256 amount) external onlyMinter {
95 | _mint(to, amount);
96 | }
97 |
98 | function burn(uint256 amount) external {
99 | _burn(msg.sender, amount);
100 | }
101 |
102 | function burnFrom(address from, uint256 amount) external {
103 | if (from != msg.sender) {
104 | uint256 allowed = allowance[from][msg.sender];
105 | if (allowed != type(uint256).max) {
106 | if (allowed < amount) revert VAIInsufficientAllowance();
107 | unchecked {
108 | allowance[from][msg.sender] = allowed - amount;
109 | }
110 | emit Approval(from, msg.sender, allowance[from][msg.sender]);
111 | }
112 | }
113 | _burn(from, amount);
114 | }
115 |
116 | function _transfer(address from, address to, uint256 amount) internal {
117 | if (to == address(0)) revert VAIZeroAddress();
118 | uint256 fromBal = balanceOf[from];
119 | if (fromBal < amount) revert VAIInsufficientBalance();
120 | unchecked {
121 | balanceOf[from] = fromBal - amount;
122 | balanceOf[to] += amount;
123 | }
124 | emit Transfer(from, to, amount);
125 | }
126 |
127 | function _mint(address to, uint256 amount) internal {
128 | if (to == address(0)) revert VAIZeroAddress();
129 | totalSupply += amount;
130 | unchecked {
131 | balanceOf[to] += amount;
132 | }
133 | emit Transfer(address(0), to, amount);
134 | }
135 |
136 | function _burn(address from, uint256 amount) internal {
137 | uint256 fromBal = balanceOf[from];
138 | if (fromBal < amount) revert VAIInsufficientBalance();
139 | unchecked {
140 | balanceOf[from] = fromBal - amount;
141 | totalSupply -= amount;
142 | }
143 | emit Transfer(from, address(0), amount);
144 | }
145 | }
146 |
Diagnostics Package
13 files captured
| # | Artifact | Bytes | Type |
|---|---|---|---|
| 1 | docker-logs/backend-backend-1.log | 123 | log |
| 2 | docker-logs/backend-instancer-1.log | 151 | log |
| 3 | docker-logs/backend-resultsvc-1.log | 2252 | log |
| 4 | docker-logs/evmbench-worker-a7f4fc4f-5c8a-4db1-b56d-5f8aadaace09.log | 173 | log |
| 5 | docker-logs/oai-proxy.log | 205741 | log |
| 6 | docker-logs/secretsvc.log | 3704 | log |
| 7 | index.html | 15933 | html |
| 8 | manifest.json | 4994 | json |
| 9 | README.md | 675 | md |
| 10 | worker/agent.log | 56220 | log |
| 11 | worker/codex_login.log | 53 | log |
| 12 | worker/model_map.json | 102 | json |
| 13 | worker/runner.log | 0 | log |
Recent Audit Runs
| Run | Status | Findings | Created |
|---|---|---|---|
| evmbench-20260220-141106 | PASS | 0 | 2/20/2026 2:11:07 PM |
| evmbench-20260220-140348 | PASS | 0 | 2/20/2026 2:03:49 PM |
| evmbench-20260220-140146 | PASS | 0 | 2/20/2026 2:01:46 PM |
| evmbench-20260220-133630 | PASS | 0 | 2/20/2026 1:36:31 PM |
| evmbench-20260220-133433 | FAILED | 0 | 2/20/2026 1:34:34 PM |
| evmbench-20260220-133143 | FAILED | 0 | 2/20/2026 1:31:44 PM |
| evmbench-20260220-133031 | FAILED | 0 | 2/20/2026 1:30:32 PM |
| evmbench-20260220-132918 | FAILED | 0 | 2/20/2026 1:29:19 PM |