Skip to content

Commit 5356ad7

Browse files
authored
Merge branch 'main' into fix/issue-5-oracle-secret-key-masking
2 parents 114091b + 50a7249 commit 5356ad7

25 files changed

Lines changed: 968 additions & 161 deletions

api/src/__tests__/validation.test.ts

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,96 @@
11
import request from 'supertest';
22
import app from '../app';
33

4+
const VALID_ADDRESS = 'GDZZJ3UPZZCKY5DBH6ZGMPMRORRBG4ECIORASBUAXPPNCL4SYRHNLYU2';
5+
46
describe('Validation Middleware', () => {
57
describe('Prepare Validation (GET /api/lending/prepare/:operation)', () => {
68
it('should reject empty userAddress', async () => {
79
const response = await request(app)
810
.get('/api/lending/prepare/deposit')
9-
.send({ amount: '1000000' });
11+
.query({ amount: '1000000' });
12+
13+
expect(response.status).toBe(400);
14+
expect(response.body.error).toBeDefined();
15+
expect(response.body.error).toContain('User address is required');
16+
});
17+
18+
it('should reject invalid Stellar public key', async () => {
19+
const response = await request(app).get('/api/lending/prepare/deposit').query({
20+
userAddress: 'invalid-address',
21+
assetAddress: 'G...',
22+
amount: '100',
23+
});
24+
25+
expect(response.status).toBe(400);
26+
expect(response.body.error).toBeDefined();
27+
expect(response.body.error).toContain('Invalid Stellar address');
28+
});
29+
30+
it('should reject Stellar address with wrong prefix', async () => {
31+
const response = await request(app).get('/api/lending/prepare/deposit').query({
32+
userAddress: 'S...', // Secret key prefix
33+
assetAddress: 'G...',
34+
amount: '100',
35+
});
36+
37+
expect(response.status).toBe(400);
38+
expect(response.body.error).toBeDefined();
39+
expect(response.body.error).toContain('Invalid Stellar address');
40+
});
41+
42+
it('should accept valid Stellar public key', async () => {
43+
const response = await request(app).get('/api/lending/prepare/deposit').query({
44+
userAddress: VALID_ADDRESS,
45+
assetAddress: 'G...',
46+
amount: '100',
47+
});
48+
49+
expect(response.status).not.toBe(400);
50+
});
51+
52+
it('should reject missing amount', async () => {
53+
const response = await request(app).get('/api/lending/prepare/deposit').query({
54+
userAddress: VALID_ADDRESS,
55+
assetAddress: 'G...',
56+
});
1057

1158
expect(response.status).toBe(400);
59+
expect(response.body.error).toContain('Amount is required');
1260
});
1361

1462
it('should reject zero amount', async () => {
15-
const response = await request(app).get('/api/lending/prepare/deposit').send({
16-
userAddress: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
63+
const response = await request(app).get('/api/lending/prepare/deposit').query({
64+
userAddress: VALID_ADDRESS,
65+
assetAddress: 'G...',
1766
amount: '0',
1867
});
1968

2069
expect(response.status).toBe(400);
70+
expect(response.body.error).toBeDefined();
71+
expect(response.body.error).toContain('Amount must be greater than 0');
2172
});
2273

2374
it('should reject negative amount', async () => {
24-
const response = await request(app).get('/api/lending/prepare/deposit').send({
25-
userAddress: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
26-
amount: '-1000',
75+
const response = await request(app).get('/api/lending/prepare/deposit').query({
76+
userAddress: VALID_ADDRESS,
77+
assetAddress: 'G...',
78+
amount: '-1',
2779
});
2880

2981
expect(response.status).toBe(400);
82+
expect(response.body.error).toBeDefined();
83+
expect(response.body.error).toContain('Amount must be greater than 0');
3084
});
3185

3286
it('should reject invalid operation', async () => {
33-
const response = await request(app).get('/api/lending/prepare/invalid_op').send({
34-
userAddress: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
87+
const response = await request(app).get('/api/lending/prepare/invalid_op').query({
88+
userAddress: 'GDH7NBM22WUCYOLJJZ7ALN3QZ6W2G5YCHDP2YQWJZ76L2GZFPHSYZ4Y3',
3589
amount: '1000000',
3690
});
3791

3892
expect(response.status).toBe(400);
3993
});
40-
41-
it('should not require userSecret', async () => {
42-
// Sending userSecret should not cause a validation error (it's simply ignored)
43-
// The route should still validate normally without it
44-
const response = await request(app)
45-
.get('/api/lending/prepare/deposit')
46-
.send({ amount: '1000000' }); // missing userAddress — should still be 400
47-
48-
expect(response.status).toBe(400);
49-
});
5094
});
5195

5296
describe('Submit Validation (POST /api/lending/submit)', () => {

api/src/controllers/lending.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import logger from '../utils/logger';
66
export const prepare = async (req: Request, res: Response, next: NextFunction) => {
77
try {
88
const operation = req.params.operation as LendingOperation;
9-
const { userAddress, assetAddress, amount } = req.body;
9+
const { userAddress, assetAddress, amount } = { ...req.query, ...req.body } as any;
1010

1111
logger.info('Preparing unsigned transaction', { operation, userAddress, amount });
1212

api/src/middleware/validation.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { body, param, validationResult } from 'express-validator';
1+
import { body, param, validationResult, check } from 'express-validator';
22
import { Request, Response, NextFunction } from 'express';
33
import { ValidationError } from '../utils/errors';
4+
import { StrKey } from '@stellar/stellar-sdk';
45

56
const VALID_OPERATIONS = ['deposit', 'borrow', 'repay', 'withdraw'];
67

@@ -16,23 +17,29 @@ export const validateRequest = (req: Request, res: Response, next: NextFunction)
1617
next();
1718
};
1819

19-
const amountValidation = body('amount')
20-
.isString()
21-
.notEmpty()
22-
.withMessage('Amount is required')
23-
.custom((value) => {
24-
const num = BigInt(value);
25-
return num > 0n;
26-
})
27-
.withMessage('Amount must be greater than zero');
20+
export const amountValidation = [
21+
check('amount')
22+
.notEmpty()
23+
.withMessage('Amount is required')
24+
.isFloat({ min: 0.0000001 })
25+
.withMessage('Amount must be greater than 0'),
26+
];
2827

2928
export const prepareValidation = [
3029
param('operation')
3130
.isIn(VALID_OPERATIONS)
3231
.withMessage(`Operation must be one of: ${VALID_OPERATIONS.join(', ')}`),
33-
body('userAddress').isString().notEmpty().withMessage('User address is required'),
34-
amountValidation,
35-
body('assetAddress').optional().isString(),
32+
check('userAddress')
33+
.notEmpty()
34+
.withMessage('User address is required')
35+
.custom((value) => {
36+
if (!StrKey.isValidEd25519PublicKey(value)) {
37+
throw new Error('Invalid Stellar address');
38+
}
39+
return true;
40+
}),
41+
...amountValidation,
42+
check('assetAddress').optional().isString().notEmpty().withMessage('Asset address is required'),
3643
validateRequest,
3744
];
3845

stellar-lend/contracts/hello-world/src/errors.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ pub enum GovernanceError {
2828
ExecutionFailed = 122,
2929
InvalidMultisigConfig = 123,
3030
InsufficientApprovals = 124,
31-
RecoveryInProgress = 125,
32-
NoRecoveryInProgress = 126,
33-
InvalidGuardianConfig = 127,
34-
GuardianAlreadyExists = 128,
35-
GuardianNotFound = 129,
36-
MathOverflow = 130,
31+
InvalidProposalType = 125,
32+
GuardianAlreadyExists = 126,
33+
GuardianNotFound = 127,
34+
InvalidGuardianConfig = 128,
35+
RecoveryInProgress = 129,
36+
NoRecoveryInProgress = 130,
3737
Unauthorized = 131,
3838
AlreadyInitialized = 132,
3939
NotInitialized = 133,

0 commit comments

Comments
 (0)