Skip to content

Commit

Permalink
[OTE-139] implement GET /compliance/screen/{address} endpoint (#1087)
Browse files Browse the repository at this point in the history
  • Loading branch information
dydxwill authored Feb 26, 2024
1 parent 48e582b commit b47972f
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ export default class ComplianceStatusModel extends Model {

address!: string;

status!: string;
status!: ComplianceStatus;

reason?: string;
reason?: ComplianceReason;

createdAt!: string;

Expand Down
5 changes: 3 additions & 2 deletions indexer/packages/postgres/src/types/db-model-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Big from 'big.js';

import { CandleResolution } from './candle-types';
import { ComplianceReason, ComplianceStatus } from './compliance-status-types';
import { FillType, Liquidity } from './fill-types';
import {
OrderSide, OrderStatus, OrderType, TimeInForce,
Expand Down Expand Up @@ -225,8 +226,8 @@ export interface ComplianceDataFromDatabase {

export interface ComplianceStatusFromDatabase {
address: string;
status: string;
reason?: string;
status: ComplianceStatus;
reason?: ComplianceReason;
createdAt: IsoString;
updatedAt: IsoString;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
ComplianceStatus,
dbHelpers,
testMocks,
testConstants,
ComplianceReason,
ComplianceStatusTable,
ComplianceStatusFromDatabase,
} from '@dydxprotocol-indexer/postgres';
import { getIpAddr } from '../../../../src/lib/utils';
import { sendRequest } from '../../../helpers/helpers';
import { RequestMethod } from '../../../../src/types';
import { stats } from '@dydxprotocol-indexer/base';
import { redis } from '@dydxprotocol-indexer/redis';
import { ratelimitRedis } from '../../../../src/caches/rate-limiters';
import { ComplianceControllerHelper } from '../../../../src/controllers/api/v4/compliance-controller';

jest.mock('../../../../src/lib/utils', () => ({
...jest.requireActual('../../../../src/lib/utils'),
getIpAddr: jest.fn(),
}));

describe('ComplianceV2Controller', () => {
const ipAddr: string = '192.168.1.1';

const ipAddrMock: jest.Mock = (getIpAddr as unknown as jest.Mock);

beforeAll(async () => {
await dbHelpers.migrate();
jest.spyOn(stats, 'increment');
jest.spyOn(stats, 'timing');
});

afterAll(async () => {
await dbHelpers.teardown();
});

describe('GET', () => {
beforeEach(async () => {
ipAddrMock.mockReturnValue(ipAddr);
await testMocks.seedData();
});

afterEach(async () => {
await redis.deleteAllAsync(ratelimitRedis.client);
await dbHelpers.clearData();
jest.clearAllMocks();
});

it('should return COMPLIANT for a non-restricted, non-dydx address', async () => {
jest.spyOn(ComplianceControllerHelper.prototype, 'screen').mockImplementation(() => {
return Promise.resolve({
restricted: false,
});
});

const response: any = await sendRequest({
type: RequestMethod.GET,
path: '/v4/compliance/screen/0x123',
});
expect(response.body.status).toEqual(ComplianceStatus.COMPLIANT);
});

it('should return BLOCKED/COMPLIANCE_PROVIDER for a restricted, non-dydx address', async () => {
jest.spyOn(ComplianceControllerHelper.prototype, 'screen').mockImplementation(() => {
return Promise.resolve({
restricted: true,
});
});

const response: any = await sendRequest({
type: RequestMethod.GET,
path: '/v4/compliance/screen/0x123',
});
expect(response.body.status).toEqual(ComplianceStatus.BLOCKED);
expect(response.body.reason).toEqual(ComplianceReason.COMPLIANCE_PROVIDER);
});

it('should return BLOCKED & upsert for a restricted, dydx address without existing compliance status',
async () => {
jest.spyOn(ComplianceControllerHelper.prototype, 'screen').mockImplementation(() => {
return Promise.resolve({
restricted: true,
});
});

let data: ComplianceStatusFromDatabase[] = await ComplianceStatusTable.findAll({}, [], {});
expect(data).toHaveLength(0);

const response: any = await sendRequest({
type: RequestMethod.GET,
path: `/v4/compliance/screen/${testConstants.defaultAddress}`,
});
expect(response.body.status).toEqual(ComplianceStatus.BLOCKED);
expect(response.body.reason).toEqual(ComplianceReason.COMPLIANCE_PROVIDER);
data = await ComplianceStatusTable.findAll({}, [], {});
expect(data).toHaveLength(1);
expect(data[0]).toEqual(expect.objectContaining({
address: testConstants.defaultAddress,
status: ComplianceStatus.BLOCKED,
reason: ComplianceReason.COMPLIANCE_PROVIDER,
}));
});

it('should return CLOSE_ONLY & update for a restricted, dydx address with existing compliance status',
async () => {
jest.spyOn(ComplianceControllerHelper.prototype, 'screen').mockImplementation(() => {
return Promise.resolve({
restricted: true,
});
});

await ComplianceStatusTable.create({
address: testConstants.defaultAddress,
status: ComplianceStatus.FIRST_STRIKE,
});
let data: ComplianceStatusFromDatabase[] = await ComplianceStatusTable.findAll({}, [], {});
expect(data).toHaveLength(1);

const response: any = await sendRequest({
type: RequestMethod.GET,
path: `/v4/compliance/screen/${testConstants.defaultAddress}`,
});
expect(response.body.status).toEqual(ComplianceStatus.CLOSE_ONLY);
expect(response.body.reason).toEqual(ComplianceReason.COMPLIANCE_PROVIDER);
data = await ComplianceStatusTable.findAll({}, [], {});
expect(data).toHaveLength(1);
expect(data[0]).toEqual(expect.objectContaining({
address: testConstants.defaultAddress,
status: ComplianceStatus.CLOSE_ONLY,
reason: ComplianceReason.COMPLIANCE_PROVIDER,
}));
});

it('should return COMPLIANT for a non-restricted, dydx address', async () => {
jest.spyOn(ComplianceControllerHelper.prototype, 'screen').mockImplementation(() => {
return Promise.resolve({
restricted: false,
});
});

const response: any = await sendRequest({
type: RequestMethod.GET,
path: `/v4/compliance/screen/${testConstants.defaultAddress}`,
});
expect(response.body.status).toEqual(ComplianceStatus.COMPLIANT);
});
});
});
93 changes: 84 additions & 9 deletions indexer/services/comlink/public/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,9 +444,7 @@ headers = {
'Accept': 'application/json'
}

r = requests.get('https://dydx-testnet.imperator.co/v4/screen', params={
'address': 'string'
}, headers = headers)
r = requests.get('https://dydx-testnet.imperator.co/v4/compliance/screen/{address}', headers = headers)

print(r.json())

Expand All @@ -458,7 +456,7 @@ const headers = {
'Accept':'application/json'
};

fetch('https://dydx-testnet.imperator.co/v4/screen?address=string',
fetch('https://dydx-testnet.imperator.co/v4/compliance/screen/{address}',
{
method: 'GET',

Expand All @@ -472,30 +470,30 @@ fetch('https://dydx-testnet.imperator.co/v4/screen?address=string',

```

`GET /screen`
`GET /compliance/screen/{address}`

### Parameters

|Name|In|Type|Required|Description|
|---|---|---|---|---|
|address|query|string|true|none|
|address|path|string|true|none|

> Example responses
> 200 Response
```json
{
"restricted": true,
"reason": "string"
"status": "COMPLIANT",
"reason": "MANUAL"
}
```

### Responses

|Status|Meaning|Description|Schema|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[ComplianceResponse](#schemacomplianceresponse)|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[ComplianceV2Response](#schemacompliancev2response)|

<aside class="success">
This operation does not require authentication
Expand Down Expand Up @@ -2409,6 +2407,83 @@ This operation does not require authentication
|restricted|boolean|true|none|none|
|reason|string|false|none|none|

## ComplianceStatus

<a id="schemacompliancestatus"></a>
<a id="schema_ComplianceStatus"></a>
<a id="tocScompliancestatus"></a>
<a id="tocscompliancestatus"></a>

```json
"COMPLIANT"

```

### Properties

|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
|*anonymous*|string|false|none|none|

#### Enumerated Values

|Property|Value|
|---|---|
|*anonymous*|COMPLIANT|
|*anonymous*|FIRST_STRIKE|
|*anonymous*|CLOSE_ONLY|
|*anonymous*|BLOCKED|

## ComplianceReason

<a id="schemacompliancereason"></a>
<a id="schema_ComplianceReason"></a>
<a id="tocScompliancereason"></a>
<a id="tocscompliancereason"></a>

```json
"MANUAL"

```

### Properties

|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
|*anonymous*|string|false|none|none|

#### Enumerated Values

|Property|Value|
|---|---|
|*anonymous*|MANUAL|
|*anonymous*|US_GEO|
|*anonymous*|CA_GEO|
|*anonymous*|SANCTIONED_GEO|
|*anonymous*|COMPLIANCE_PROVIDER|

## ComplianceV2Response

<a id="schemacompliancev2response"></a>
<a id="schema_ComplianceV2Response"></a>
<a id="tocScompliancev2response"></a>
<a id="tocscompliancev2response"></a>

```json
{
"status": "COMPLIANT",
"reason": "MANUAL"
}

```

### Properties

|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
|status|[ComplianceStatus](#schemacompliancestatus)|true|none|none|
|reason|[ComplianceReason](#schemacompliancereason)|false|none|none|

## OrderSide

<a id="schemaorderside"></a>
Expand Down
62 changes: 62 additions & 0 deletions indexer/services/comlink/public/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,40 @@
"type": "object",
"additionalProperties": false
},
"ComplianceStatus": {
"enum": [
"COMPLIANT",
"FIRST_STRIKE",
"CLOSE_ONLY",
"BLOCKED"
],
"type": "string"
},
"ComplianceReason": {
"enum": [
"MANUAL",
"US_GEO",
"CA_GEO",
"SANCTIONED_GEO",
"COMPLIANCE_PROVIDER"
],
"type": "string"
},
"ComplianceV2Response": {
"properties": {
"status": {
"$ref": "#/components/schemas/ComplianceStatus"
},
"reason": {
"$ref": "#/components/schemas/ComplianceReason"
}
},
"required": [
"status"
],
"type": "object",
"additionalProperties": false
},
"OrderSide": {
"enum": [
"BUY",
Expand Down Expand Up @@ -1295,6 +1329,34 @@
]
}
},
"/compliance/screen/{address}": {
"get": {
"operationId": "Screen",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComplianceV2Response"
}
}
}
}
},
"security": [],
"parameters": [
{
"in": "path",
"name": "address",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
"/fills": {
"get": {
"operationId": "GetFills",
Expand Down
Loading

0 comments on commit b47972f

Please sign in to comment.