Skip to content

Commit 1b3a22c

Browse files
Merge pull request #1121 from adeniran19-maker/feature/contract-reentrancy-events-errors-flash-swap
fix(contract): normalize error codes, transient reentrancy guard, event schema, and flash swap bounds
2 parents ace59ff + c2da2f2 commit 1b3a22c

331 files changed

Lines changed: 96174 additions & 44809 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend-abi-registry/errorCodes.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* @fileoverview Canonical SoroTask error codes and human-readable message decoder.
3+
* Maps categorized on-chain Soroban contracterror discriminants into human-readable error descriptions.
4+
* @module backend-abi-registry/errorCodes
5+
*/
6+
7+
const ERROR_CODES = {
8+
// ── 100..199: Authorization & Role-Based Access ──────────────────────────────
9+
100: { name: 'Unauthorized', category: 'Auth', message: 'Caller is not authorized to perform this action' },
10+
101: { name: 'UnauthorizedSlasher', category: 'Auth', message: 'Caller is not authorized to slash keeper' },
11+
102: { name: 'OperatorAlreadySet', category: 'Auth', message: 'Operator address has already been set' },
12+
103: { name: 'NotInitialized', category: 'Auth', message: 'Contract has not been initialized' },
13+
104: { name: 'AlreadyInitialized', category: 'Auth', message: 'Contract has already been initialized' },
14+
105: { name: 'FeatureDisabled', category: 'Auth', message: 'Requested protocol feature is disabled' },
15+
106: { name: 'InsufficientDelegation', category: 'Auth', message: 'Keeper delegation amount is insufficient' },
16+
107: { name: 'InvalidCommissionRate', category: 'Auth', message: 'Commission rate exceeds allowable maximum' },
17+
18+
// ── 200..299: Task Lifecycle & Validation ────────────────────────────────────
19+
200: { name: 'InvalidInterval', category: 'TaskValidation', message: 'Task execution interval is invalid' },
20+
201: { name: 'TaskPaused', category: 'TaskValidation', message: 'Task execution is currently paused' },
21+
202: { name: 'TaskAlreadyPaused', category: 'TaskValidation', message: 'Task is already in a paused state' },
22+
203: { name: 'TaskAlreadyActive', category: 'TaskValidation', message: 'Task is already in an active state' },
23+
204: { name: 'TaskNotFound', category: 'TaskValidation', message: 'Specified task ID does not exist' },
24+
205: { name: 'DuplicateTask', category: 'TaskValidation', message: 'Duplicate task registration detected' },
25+
206: { name: 'InvalidPayload', category: 'TaskValidation', message: 'Task payload or arguments are malformed' },
26+
207: { name: 'ArgsTooMany', category: 'TaskValidation', message: 'Number of task arguments exceeds limit' },
27+
208: { name: 'ArgsTooLarge', category: 'TaskValidation', message: 'Total size of task arguments exceeds limit' },
28+
209: { name: 'BountyBelowMinimum', category: 'TaskValidation', message: 'Task execution bounty is below required minimum' },
29+
210: { name: 'InvalidBounty', category: 'TaskValidation', message: 'Task execution bounty parameters are invalid' },
30+
211: { name: 'InvalidUpgradeVersion', category: 'TaskValidation', message: 'Target WASM upgrade version is invalid' },
31+
212: { name: 'InvalidInsurancePolicy', category: 'TaskValidation', message: 'Insurance policy parameters are invalid' },
32+
33+
// ── 300..399: Execution, Dependency & Reentrancy ─────────────────────────────
34+
300: { name: 'ReentrantCall', category: 'Execution', message: 'Reentrancy guard triggered on recursive entry' },
35+
301: { name: 'SelfDependency', category: 'Execution', message: 'Task cannot depend on itself' },
36+
302: { name: 'DependencyNotFound', category: 'Execution', message: 'Required task dependency not found' },
37+
303: { name: 'CircularDependency', category: 'Execution', message: 'Circular dependency cycle detected' },
38+
304: { name: 'DependencyBlocked', category: 'Execution', message: 'Task execution blocked by unresolved dependencies' },
39+
305: { name: 'DependencyLimitExceeded', category: 'Execution', message: 'Maximum dependency count exceeded' },
40+
306: { name: 'DependencyDepthExceeded', category: 'Execution', message: 'Maximum dependency tree depth exceeded' },
41+
307: { name: 'KeeperStakeTooLow', category: 'Execution', message: 'Keeper active stake is below minimum threshold' },
42+
308: { name: 'EmptyBundle', category: 'Execution', message: 'Task execution bundle contains zero steps' },
43+
309: { name: 'BundleTooLarge', category: 'Execution', message: 'Task bundle size exceeds block limit' },
44+
310: { name: 'BundleStepFailed', category: 'Execution', message: 'One or more bundle execution steps failed' },
45+
311: { name: 'BlockExecutionLimitReached', category: 'Execution', message: 'Per-block task execution cap reached' },
46+
312: { name: 'DecryptionFailed', category: 'Execution', message: 'Encrypted task parameter decryption failed' },
47+
313: { name: 'OptimisticClaimPending', category: 'Execution', message: 'Optimistic challenge claim is currently pending' },
48+
314: { name: 'NoOptimisticClaim', category: 'Execution', message: 'No active optimistic challenge claim found' },
49+
315: { name: 'ChallengeWindowClosed', category: 'Execution', message: 'Optimistic challenge window has expired' },
50+
316: { name: 'ChallengeWindowActive', category: 'Execution', message: 'Challenge window is still active' },
51+
317: { name: 'FraudProofInvalid', category: 'Execution', message: 'Supplied optimistic fraud proof is invalid' },
52+
53+
// ── 400..499: Oracles, VRF & ZK Verifier ─────────────────────────────────────
54+
400: { name: 'OracleNotSet', category: 'Oracle', message: 'Required price or data oracle is not configured' },
55+
401: { name: 'OracleRequestFailed', category: 'Oracle', message: 'Oracle data request failed or reverted' },
56+
402: { name: 'OracleInvalidResponse', category: 'Oracle', message: 'Oracle returned invalid or unparseable data' },
57+
403: { name: 'OracleTimeout', category: 'Oracle', message: 'Oracle response exceeded timeout window' },
58+
404: { name: 'OracleUnsupportedProvider', category: 'Oracle', message: 'Specified oracle provider is not supported' },
59+
405: { name: 'VrfOracleNotSet', category: 'Oracle', message: 'VRF randomness oracle is not configured' },
60+
406: { name: 'InvalidVrfRequest', category: 'Oracle', message: 'VRF randomness request parameters are invalid' },
61+
407: { name: 'VrfRequestFailed', category: 'Oracle', message: 'VRF randomness fulfillment request failed' },
62+
408: { name: 'VrfAlreadyFulfilled', category: 'Oracle', message: 'VRF request has already been fulfilled' },
63+
409: { name: 'InvalidZkProof', category: 'Oracle', message: 'Zero-knowledge verification proof is invalid' },
64+
410: { name: 'InvalidVdfProof', category: 'Oracle', message: 'Verifiable delay function proof is invalid' },
65+
66+
// ── 500..599: Yield, Flash Swaps & Treasury ──────────────────────────────────
67+
500: { name: 'InsufficientBalance', category: 'Treasury', message: 'Contract or task escrow balance is insufficient' },
68+
501: { name: 'YieldStrategyNotInitialized', category: 'Treasury', message: 'Yield strategy adapter has not been initialized' },
69+
502: { name: 'InvalidYieldStrategy', category: 'Treasury', message: 'Yield strategy configuration is invalid' },
70+
503: { name: 'YieldHarvestFailed', category: 'Treasury', message: 'Harvesting yield from external protocol failed' },
71+
504: { name: 'InsufficientYield', category: 'Treasury', message: 'Yield generated is below expected threshold' },
72+
505: { name: 'FlashSwapFailed', category: 'Treasury', message: 'Flash swap callback execution failed' },
73+
506: { name: 'InsufficientFlashProfit', category: 'Treasury', message: 'Flash swap did not generate required minimum profit' },
74+
507: { name: 'InvalidSlippage', category: 'Treasury', message: 'Slippage parameter exceeds maximum allowed tolerance' },
75+
76+
// ── 600..699: Volatility & Circuit Breakers ──────────────────────────────────
77+
600: { name: 'VolatilityExceeded', category: 'Volatility', message: 'Asset volatility exceeds allowed tolerance limit' },
78+
601: { name: 'VolatilityCircuitBreakerTripped', category: 'Volatility', message: 'Volatility circuit breaker tripped; execution paused' },
79+
602: { name: 'VolatilityTimelockActive', category: 'Volatility', message: 'Volatility timelock is active; cannot execute until window expires' },
80+
};
81+
82+
/**
83+
* Decodes an on-chain numeric error code into a human-readable error description.
84+
*
85+
* @param {number} code - On-chain error discriminant.
86+
* @returns {{ code: number, name: string, category: string, message: string }} Error metadata.
87+
*/
88+
function decodeErrorCode(code) {
89+
const info = ERROR_CODES[code];
90+
if (info) {
91+
return { code, ...info };
92+
}
93+
return {
94+
code,
95+
name: 'UnknownError',
96+
category: 'Unknown',
97+
message: `Unknown contract error code: ${code}`,
98+
};
99+
}
100+
101+
module.exports = {
102+
ERROR_CODES,
103+
decodeErrorCode,
104+
};

backend-abi-registry/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const parser = require('./parser');
22
const registry = require('./registry');
33
const errorHandler = require('./errorHandler');
4+
const errorCodes = require('./errorCodes');
45
const Monitor = require('./monitor');
56
const { AbiCache } = require('./abiCache');
67

@@ -30,6 +31,14 @@ class ABIRegistryService {
3031
return errorHandler;
3132
}
3233

34+
getErrorCodes() {
35+
return errorCodes;
36+
}
37+
38+
decodeErrorCode(code) {
39+
return errorCodes.decodeErrorCode(code);
40+
}
41+
3342
async getABI(contractId, wasmHash, fetchABI) {
3443
const cached = await this.cache.getPersistent(contractId, wasmHash);
3544
if (cached) return cached;

backend-abi-registry/tests/abi-registry.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,22 @@ describe('ABIRegistryService', () => {
240240
expect(abiRegistryService.getRegistry()).toBe(registry);
241241
expect(abiRegistryService.getErrorHandler()).toBe(errorHandler);
242242
});
243+
244+
it('should decode canonical error codes correctly', () => {
245+
const decoded100 = abiRegistryService.decodeErrorCode(100);
246+
expect(decoded100.name).toBe('Unauthorized');
247+
expect(decoded100.category).toBe('Auth');
248+
249+
const decoded300 = abiRegistryService.decodeErrorCode(300);
250+
expect(decoded300.name).toBe('ReentrantCall');
251+
expect(decoded300.category).toBe('Execution');
252+
253+
const decoded507 = abiRegistryService.decodeErrorCode(507);
254+
expect(decoded507.name).toBe('InvalidSlippage');
255+
expect(decoded507.category).toBe('Treasury');
256+
257+
const decodedUnknown = abiRegistryService.decodeErrorCode(9999);
258+
expect(decodedUnknown.name).toBe('UnknownError');
259+
});
243260
});
244261
});

contract/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[workspace]
2+
13
[package]
24
name = "soro_task_contract"
35
version = "0.1.0"

contract/src/events.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ pub struct FeeDiscountTierUpdatedEvent {
168168
pub old_tier: u32,
169169
pub new_tier: u32,
170170
pub total_executions: u64,
171+
pub timestamp: u64,
172+
}
173+
171174
/// Event payload for oracle volatility breaches
172175
#[contracttype]
173176
#[derive(Clone, Debug)]
@@ -236,8 +239,8 @@ impl EventLogger {
236239
};
237240

238241
let topics = (
239-
Symbol::new(env, "sorotask"),
240-
Symbol::new(env, "state_change"),
242+
Symbol::new(env, "Task"),
243+
Symbol::new(env, "StateChange"),
241244
task_id,
242245
);
243246
env.events().publish(topics, event_data);
@@ -272,8 +275,8 @@ impl EventLogger {
272275
};
273276

274277
let topics = (
275-
Symbol::new(env, "sorotask"),
276-
Symbol::new(env, "execution"),
278+
Symbol::new(env, "Task"),
279+
Symbol::new(env, "Executed"),
277280
task_id,
278281
);
279282
env.events().publish(topics, event_data);
@@ -301,8 +304,8 @@ impl EventLogger {
301304
};
302305

303306
let topics = (
304-
Symbol::new(env, "sorotask"),
305-
Symbol::new(env, "exec_step"),
307+
Symbol::new(env, "Task"),
308+
Symbol::new(env, "StepExecuted"),
306309
task_id,
307310
);
308311
env.events().publish(topics, event_data);
@@ -328,8 +331,8 @@ impl EventLogger {
328331
};
329332

330333
let topics = (
331-
Symbol::new(env, "sorotask"),
332-
Symbol::new(env, "access_log"),
334+
Symbol::new(env, "Auth"),
335+
Symbol::new(env, "Access"),
333336
actor,
334337
);
335338
env.events().publish(topics, event_data);
@@ -352,8 +355,8 @@ impl EventLogger {
352355
};
353356

354357
let topics = (
355-
Symbol::new(env, "sorotask"),
356-
Symbol::new(env, "task_invalidated"),
358+
Symbol::new(env, "Task"),
359+
Symbol::new(env, "Invalidated"),
357360
task_id,
358361
);
359362
env.events().publish(topics, event_data);
@@ -376,8 +379,8 @@ impl EventLogger {
376379
};
377380

378381
let topics = (
379-
Symbol::new(env, "sorotask"),
380-
Symbol::new(env, "rate_limit_exceeded"),
382+
Symbol::new(env, "Task"),
383+
Symbol::new(env, "RateLimited"),
381384
task_id,
382385
);
383386
env.events().publish(topics, event_data);
@@ -399,8 +402,8 @@ impl EventLogger {
399402
};
400403

401404
let topics = (
402-
Symbol::new(env, "sorotask"),
403-
Symbol::new(env, "encrypted_params_registered"),
405+
Symbol::new(env, "Task"),
406+
Symbol::new(env, "EncryptedParams"),
404407
task_id,
405408
);
406409
env.events().publish(topics, event_data);
@@ -418,17 +421,17 @@ impl EventLogger {
418421
let timestamp = env.ledger().timestamp();
419422
let event_data = DelegationPoolEvent {
420423
delegator,
421-
keeper,
424+
keeper: keeper.clone(),
422425
amount,
423426
commission_rate,
424427
action: action.clone(),
425428
timestamp,
426429
};
427430

428431
let topics = (
429-
Symbol::new(env, "sorotask"),
430-
Symbol::new(env, "delegation_pool"),
432+
Symbol::new(env, "Stake"),
431433
action,
434+
keeper,
432435
);
433436
env.events().publish(topics, event_data);
434437
}

0 commit comments

Comments
 (0)