Skip to content

Commit

Permalink
Merge pull request #123 from securesecrets/develop
Browse files Browse the repository at this point in the history
batch query splitting, derivatives queries
  • Loading branch information
AustinWoetzel authored Apr 4, 2024
2 parents 0e3fcaa + 4eeb514 commit a7cc649
Show file tree
Hide file tree
Showing 36 changed files with 2,529 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/fifty-shirts-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shadeprotocol/shadejs": patch
---

Batch query can split queries to avoid hitting query gas limits
5 changes: 5 additions & 0 deletions .changeset/healthy-hornets-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shadeprotocol/shadejs": minor
---

added apy calculations for derivatives and shade staking query interface
2 changes: 2 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default defineConfig({
{ text: 'Swap', link: '/queries/swap' },
{ text: 'Oracle', link: '/queries/oracle' },
{ text: 'stkd-SCRT', link: '/queries/derivativeScrt' },
{ text: 'Shade Staking', link: '/queries/shadeStaking' },
{ text: 'Batch Query', link: '/queries/batch-query' },
{ text: 'Snip20', link: '/queries/snip20' },
]
Expand All @@ -45,6 +46,7 @@ export default defineConfig({
items: [
{ text: 'Swap', link: '/calculations/swap' },
{ text: 'Routing', link: '/calculations/routing' },
{ text: 'Apy', link: '/calculations/apy' },
]
},
{
Expand Down
29 changes: 29 additions & 0 deletions docs/calculations/apy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# APY Calculations

This page demonstrates how to calculate outputs the shade derivative contracts

### stkd-SCRT APY
Calculates the apy expected for the stkd-SCRT token
```ts
/**
* Will calculate APY for the stkd secret derivative contract
*
* returns a number that is the decimal form of the percent APY
* @param lcdEndpoint is not optional due to the requirement of the secretChainQueries() function
*/
function calculateDerivativeScrtApy({
queryRouterContractAddress,
queryRouterCodeHash,
contractAddress,
codeHash,
lcdEndpoint,
chainId,
}: {
queryRouterContractAddress: string,
queryRouterCodeHash?: string,
contractAddress: string,
codeHash: string,
lcdEndpoint: string,
chainId?: string,
}): Promise<number>
```
2 changes: 2 additions & 0 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This page contains a list of deployed contracts.
| Batch Query Router | secret17gnlxnwux0szd7qhl90ym8lw22qvedjz4v09dm | 72a09535b77b76862f7b568baf1ddbe158a2e4bbd0f0879c69ada9b398e31c1f |
| Oracle | secret10n2xl5jmez6r9umtdrth78k0vwmce0l5m9f5dm | 32c4710842b97a526c243a68511b15f58d6e72a388af38a7221ff3244c754e91 |
| stkd-scrt | secret1k6u0cy4feepm6pehnz804zmwakuwdapm69tuc4 | f6be719b3c6feb498d3554ca0398eb6b7e7db262acb33f84a8f12106da6bbb09 |
| Shade Staking | secret1y6px5x7jzrk8hyvy67f06ytn8v0jwculypwxws | 2a1ae7fd2be82931cb11d0ce82b2e243507f2006074e2f316da661beb1abe3c3 |

::: tip
The ShadeSwap pairs contracts are accessible via the factory registered pairs query.
Expand All @@ -24,3 +25,4 @@ The ShadeSwap pairs contracts are accessible via the factory registered pairs qu
| Batch Query Router | secret10cxkxmspt44mp2wym6dcguk5wqqkm5a9ydw3du | 72a09535b77b76862f7b568baf1ddbe158a2e4bbd0f0879c69ada9b398e31c1f |
| Oracle | secret17z47r9u4nqytpdgvewxq4jqd965sfj2wpsnlak | 113c47c016667817b315dde03b4ee9774edf1fb293a7ea3f02d983c6b1fa1cf1 |
| stkd-scrt | secret14svk0x3sztxwta9kv9dv6fwzqlc26mmjfyypc2 | 680fbb3c8f8eb1c920da13d857daaedaa46ab8f9a8e26e892bb18a16985ec29e |
| Shade Staking | secret1f9ph34ydnnqg0rzs6h39ucswspllutp9c4ch4k | 5e1e1b0c2a8e2114d29725002be7206598ec68f4bdb28718b082fd84748d416f |
23 changes: 6 additions & 17 deletions docs/queries/batch-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,10 @@ This page demonstrates how to query using a smart contract batch query router. T

The batch query function is generalized to work with any contract queries. Miscellaneous ShadeJS services already implement the batch query router, for example the <a href="./swap.html#pairs-info" target="_blank">Pairs Info Query</a>

By default, the batch query will process all queries in a single
thread, i.e. using a single node to query. However, there are query
gas limitations controlled by the node provider that will throw errors when batch size is too large. When a batch size is provided as an input to the batch query, it will divide the batch into multi-threaded batches of batches of that size. There are certain batch sizes that have already been tested and are provided for specific ShadeJS services and recommended node settings, however your batch size will vary based on the type of query you are performing and your node provider settings.

## Performance
All of the following results are run with 500 total queries

| Batch Size | RPC Queries | Time | Success |
|------------|-------------|-------|---------|
| 1 | 500 | 5.56s | 26.6% |
| 5 | 100 | 6.51s | 100% |
| 10 | 50 | 9.24s | 100% |
| 25 | 20 | 7.27s | 100% |
| 50 | 10 | 8.78s | 100% |
| 100 | 5 | 7.72s | 100% |

::: warning
This testing was completed with a limited sample size and optimal batch size has
not yet determined. There is also an unknown variable of the quality of the infrastructure you would be using to perform these queries. It is encouraged to perform your own optimization testing when working with a batch query router.
:::
## Batch Query

**input**
Expand All @@ -41,12 +28,14 @@ async function batchQuery({
lcdEndpoint,
chainId,
queries,
batchSize, // defaults to all queries in single batch
}:{
contractAddress: string,
codeHash?: string,
lcdEndpoint?: string,
chainId?: string,
queries: BatchQueryParams[]
queries: BatchQueryParams[],
batchSize?: number,
}): Promise<BatchQueryParsedResponse>
```

Expand Down
46 changes: 46 additions & 0 deletions docs/queries/shadeStaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Shade Staking Examples

This page demonstrates how to query the shade staking contracts

## Staking Info

**input**

```ts
/**
* query the staking info from the shade staking contract
*/
async function queryShadeStakingOpportunity({
shadeStakingContractAddress,
shadeStakingCodeHash,
lcdEndpoint,
chainId,
}: {
shadeStakingContractAddress: string,
shadeStakingCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
}): Promise<StakingInfoServiceModel>
```

**output**

```ts
type StakingInfoServiceModel = {
stakeTokenAddress: string,
totalStakedRaw: string,
unbondingPeriod: number,
rewardPools: StakingRewardPoolServiceModel[],
}
// type references below
type StakingRewardPoolServiceModel = {
id: string,
amountRaw: string,
startDate: Date,
endDate: Date,
tokenAddress: string,
rateRaw: string,
}
```
1 change: 1 addition & 0 deletions src/contracts/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './snip20';
export * from './swap';
export * from './derivativeShd';
export * from './derivativeScrt';
export * from './shadeStaking';
10 changes: 10 additions & 0 deletions src/contracts/definitions/shadeStaking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {
test,
expect,
} from 'vitest';
import { msgQueryShadeStakingOpportunity } from './shadeStaking';

test('it tests the form of the query staking info msg', () => {
const output = { staking_info: {} };
expect(msgQueryShadeStakingOpportunity()).toStrictEqual(output);
});
8 changes: 8 additions & 0 deletions src/contracts/definitions/shadeStaking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* message for the getting staking opportunity info from the shade staking contract
*/
const msgQueryShadeStakingOpportunity = () => ({ staking_info: {} });

export {
msgQueryShadeStakingOpportunity,
};
124 changes: 123 additions & 1 deletion src/contracts/services/batchQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
vi,
beforeAll,
afterAll,
afterEach,
} from 'vitest';
import { of } from 'rxjs';
import batchPairConfigResponse from '~/test/mocks/batchQuery/batchPairConfigResponse.json';
Expand All @@ -12,10 +13,13 @@ import { BatchQueryParams } from '~/types/contracts/batchQuery/model';
import { msgBatchQuery } from '~/contracts/definitions/batchQuery';
import batchPricesWithError from '~/test/mocks/batchQuery/batchIndividualPricesWithErrorResponse.json';
import { batchPricesWithErrorParsed } from '~/test/mocks/batchQuery/batchPricesWithErrorParsed';
import { SecretNetworkClient } from 'secretjs';
import {
parseBatchQuery,
batchQuery$,
batchQuery,
batchQuerySingleBatch$,
divideSingleBatchIntoArrayOfMultipleBatches,
} from './batchQuery';

const sendSecretClientContractQuery$ = vi.hoisted(() => vi.fn());
Expand All @@ -38,6 +42,10 @@ afterAll(() => {
vi.clearAllMocks();
});

afterEach(() => {
vi.clearAllMocks();
});

test('it can parse the batch query success response', () => {
expect(parseBatchQuery(
batchPairConfigResponse,
Expand All @@ -50,13 +58,41 @@ test('it can parse the batch query mixed succes/error response', () => {
)).toStrictEqual(batchPricesWithErrorParsed);
});

test('it can call the batch query service', async () => {
test('it can call the single batch query service', async () => {
const input = {
contractAddress: 'CONTRACT_ADDRESS',
codeHash: 'CODE_HASH',
queries: ['BATCH_QUERY' as unknown as BatchQueryParams],
client: 'SECRET_CLIENT' as unknown as SecretNetworkClient,
};
// observables function
sendSecretClientContractQuery$.mockReturnValueOnce(of(batchPairConfigResponse));

let output;
batchQuerySingleBatch$(input).subscribe({
next: (response) => {
output = response;
},
});

expect(msgBatchQuery).toHaveBeenNthCalledWith(1, input.queries);
expect(output).toStrictEqual(batchPairConfigParsed);

// async/await function
sendSecretClientContractQuery$.mockReturnValueOnce(of(batchPairConfigResponse));
const response = await batchQuery(input);
expect(msgBatchQuery).toHaveBeenNthCalledWith(2, input.queries);
expect(response).toStrictEqual(batchPairConfigParsed);
});

test('it can call the multi-batch query service on a single batch', async () => {
const input = {
contractAddress: 'CONTRACT_ADDRESS',
codeHash: 'CODE_HASH',
lcdEndpoint: 'LCD_ENDPOINT',
chainId: 'CHAIN_ID',
queries: ['BATCH_QUERY' as unknown as BatchQueryParams],
// no batch param passed in, so it will process as a single batch
};
// observables function
sendSecretClientContractQuery$.mockReturnValueOnce(of(batchPairConfigResponse));
Expand All @@ -77,3 +113,89 @@ test('it can call the batch query service', async () => {
expect(msgBatchQuery).toHaveBeenNthCalledWith(2, input.queries);
expect(response).toStrictEqual(batchPairConfigParsed);
});

test('it can call the multi-batch query service for multiple batches', async () => {
const input = {
contractAddress: 'CONTRACT_ADDRESS',
codeHash: 'CODE_HASH',
lcdEndpoint: 'LCD_ENDPOINT',
chainId: 'CHAIN_ID',
queries: [
'BATCH_QUERY_1' as unknown as BatchQueryParams,
'BATCH_QUERY_2' as unknown as BatchQueryParams,
],
batchSize: 1,
};

// combined array of two of the outputs
const combinedOutput = batchPairConfigParsed.concat(batchPairConfigParsed);

// observables function
// provide two mocks of the same data
sendSecretClientContractQuery$
.mockReturnValueOnce(of(batchPairConfigResponse))
.mockReturnValueOnce(of(batchPairConfigResponse));

let output;
batchQuery$(input).subscribe({
next: (response) => {
output = response;
},
});

expect(msgBatchQuery).toHaveBeenNthCalledWith(1, [input.queries[0]]);
expect(msgBatchQuery).toHaveBeenNthCalledWith(2, [input.queries[1]]);

expect(output).toStrictEqual(combinedOutput);

// async/await function
sendSecretClientContractQuery$
.mockReturnValueOnce(of(batchPairConfigResponse))
.mockReturnValueOnce(of(batchPairConfigResponse));

const response = await batchQuery(input);
expect(msgBatchQuery).toHaveBeenNthCalledWith(3, [input.queries[0]]);
expect(msgBatchQuery).toHaveBeenNthCalledWith(4, [input.queries[1]]);
expect(response).toStrictEqual(combinedOutput);
});

test('it can divide a batch of queries into an array of multiple batches', () => {
const input1 = [
1 as unknown as BatchQueryParams,
2 as unknown as BatchQueryParams,
3 as unknown as BatchQueryParams,
4 as unknown as BatchQueryParams,
5 as unknown as BatchQueryParams,
6 as unknown as BatchQueryParams,
7 as unknown as BatchQueryParams,
8 as unknown as BatchQueryParams,
];

expect(divideSingleBatchIntoArrayOfMultipleBatches(input1, 2)).toStrictEqual([
[1, 2],
[3, 4],
[5, 6],
[7, 8],
]);

expect(divideSingleBatchIntoArrayOfMultipleBatches(input1, 3)).toStrictEqual([
[1, 2, 3],
[4, 5, 6],
[7, 8],
]);

expect(divideSingleBatchIntoArrayOfMultipleBatches(input1, 1)).toStrictEqual([
[1],
[2],
[3],
[4],
[5],
[6],
[7],
[8],
]);

expect(divideSingleBatchIntoArrayOfMultipleBatches(input1, 10)).toStrictEqual([
[1, 2, 3, 4, 5, 6, 7, 8],
]);
});
Loading

0 comments on commit a7cc649

Please sign in to comment.