Skip to content

Commit

Permalink
feat(ant): add ANT read interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Atticus committed Mar 15, 2024
1 parent 1b6772e commit c941c96
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 22 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ services:
environment:
- LOG_LEVEL=debug
- PREFETCH_CONTRACTS=true
- PREFETCH_CONTRACT_IDS=_NctcA2sRy1-J4OmIQZbYFPM17piNcbdBPH2ncX2RL8
- PREFETCH_CONTRACT_IDS=['_NctcA2sRy1-J4OmIQZbYFPM17piNcbdBPH2ncX2RL8', 'UC2zwawQoTnh0TNd9mYLQS4wObBBeaOU5LPQTNETqA4']
- BOOTSTRAP_CONTRACTS=false
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/healthcheck']
Expand Down
45 changes: 45 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
ANTRecord,
ANTState,
ArIOState,
ArNSNameData,
EpochDistributionData,
Expand All @@ -27,6 +29,27 @@ export type BlockHeight = number;
export type SortKey = string;
export type WalletAddress = string;

// TODO: append this with other configuration options (e.g. local vs. remote evaluation)
export type ContractConfiguration =
| {
contract?: SmartWeaveContract<unknown>;
}
| {
contractTxId: string;
};

export function isContractConfiguration<T>(
config: ContractConfiguration,
): config is { contract: SmartWeaveContract<T> } {
return 'contract' in config;
}

export function isContractTxIdConfiguration(
config: ContractConfiguration,
): config is { contractTxId: string } {
return 'contractTxId' in config;
}

export type EvaluationOptions = {
evalTo?: { sortKey: SortKey } | { blockHeight: BlockHeight };
// TODO: any other evaluation constraints
Expand Down Expand Up @@ -102,6 +125,28 @@ export interface ArIOContract {
}: EvaluationParameters): Promise<EpochDistributionData>;
}

export interface ANTContract {
getState({ evaluationOptions }: EvaluationParameters): Promise<ANTState>;
getRecord({
domain,
evaluationOptions,
}: EvaluationParameters<{ domain: string }>): Promise<ANTRecord>;
getRecords({
evaluationOptions,
}: EvaluationParameters): Promise<Record<string, ANTRecord>>;
getOwner({ evaluationOptions }: EvaluationParameters): Promise<string>;
getControllers({ evaluationOptions }: EvaluationParameters): Promise<any[]>;

Check warning on line 138 in src/common.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 138 in src/common.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
getTicker({ evaluationOptions }: EvaluationParameters): Promise<any>;

Check warning on line 139 in src/common.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 139 in src/common.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
getName({ evaluationOptions }: EvaluationParameters): Promise<any>;

Check warning on line 140 in src/common.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 140 in src/common.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
getBalance({
address,
evaluationOptions,
}: EvaluationParameters<{ address: string }>): Promise<number>;
getBalances({
evaluationOptions,
}: EvaluationParameters): Promise<Record<string, number>>;
}

/* eslint-disable @typescript-eslint/no-explicit-any */
export interface Logger {
setLogLevel: (level: string) => void;
Expand Down
120 changes: 120 additions & 0 deletions src/common/ant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
ANTContract,
ANTRecord,
ANTState,
ContractConfiguration,
EvaluationOptions,
EvaluationParameters,
SmartWeaveContract,
isContractConfiguration,
isContractTxIdConfiguration,
} from '../types.js';
import { RemoteContract } from './contracts/remote-contract.js';

export class ANT implements ANTContract {
private contract: SmartWeaveContract<ANTState>;

constructor(config: ContractConfiguration) {
if (isContractConfiguration<ANTState>(config)) {
this.contract = config.contract;
} else if (isContractTxIdConfiguration(config)) {
this.contract = new RemoteContract<ANTState>({
contractTxId: config.contractTxId,
});
}
}

/**
* Returns the current state of the contract.
*/
async getState(params: EvaluationParameters): Promise<ANTState> {
const state = await this.contract.getContractState(params);
return state;
}

async getRecord({
domain,
evaluationOptions,
}: EvaluationParameters<{ domain: string }>): Promise<ANTRecord> {
const records = await this.getRecords({ evaluationOptions });

return records[domain];
}
async getRecords({
evaluationOptions,
}: {
evaluationOptions?: EvaluationOptions | Record<string, never> | undefined;
}): Promise<Record<string, ANTRecord>> {
const state = await this.contract.getContractState({ evaluationOptions });
return state.records;
}

async getOwner({
evaluationOptions,
}: {
evaluationOptions?: EvaluationOptions | Record<string, never> | undefined;
}): Promise<string> {
const state = await this.contract.getContractState({ evaluationOptions });
return state.owner;
}

async getControllers({
evaluationOptions,
}: {
evaluationOptions?: EvaluationOptions | Record<string, never> | undefined;
}): Promise<any[]> {

Check warning on line 81 in src/common/ant.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 81 in src/common/ant.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
const state = await this.contract.getContractState({ evaluationOptions });
return state.controllers;
}

async getName({
evaluationOptions,
}: {
evaluationOptions?: EvaluationOptions | Record<string, never> | undefined;
}): Promise<any> {

Check warning on line 90 in src/common/ant.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 90 in src/common/ant.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
const state = await this.contract.getContractState({ evaluationOptions });
return state.name;
}

async getTicker({
evaluationOptions,
}: {
evaluationOptions?: EvaluationOptions | Record<string, never> | undefined;
}): Promise<string> {
const state = await this.contract.getContractState({ evaluationOptions });
return state.ticker;
}

async getBalances({
evaluationOptions,
}: {
evaluationOptions?: EvaluationOptions | Record<string, never> | undefined;
}): Promise<Record<string, number>> {
const state = await this.contract.getContractState({ evaluationOptions });
return state.balances;
}

async getBalance({
address,
evaluationOptions,
}: EvaluationParameters<{ address: string }>): Promise<number> {
const balances = await this.getBalances({ evaluationOptions });
return balances[address];
}
}
24 changes: 3 additions & 21 deletions src/common/ar-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,18 @@ import {
ArIOContract,
ArIOState,
ArNSNameData,
ContractConfiguration,
EpochDistributionData,
EvaluationParameters,
Gateway,
Observations,
SmartWeaveContract,
WeightedObserver,
isContractConfiguration,
isContractTxIdConfiguration,
} from '../types.js';
import { RemoteContract } from './contracts/remote-contract.js';

// TODO: append this with other configuration options (e.g. local vs. remote evaluation)
export type ContractConfiguration =
| {
contract?: SmartWeaveContract<unknown>;
}
| {
contractTxId: string;
};

function isContractConfiguration<T>(
config: ContractConfiguration,
): config is { contract: SmartWeaveContract<T> } {
return 'contract' in config;
}

function isContractTxIdConfiguration(
config: ContractConfiguration,
): config is { contractTxId: string } {
return 'contractTxId' in config;
}

export class ArIO implements ArIOContract {
private contract: SmartWeaveContract<ArIOState>;

Expand Down
16 changes: 16 additions & 0 deletions src/contract-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,19 @@ export interface ArIOState {
vaults: RegistryVaults;
prescribedObservers: PrescribedObservers;
}

// ANT

export type ANTRecord = {
transactionId: string;
ttlSeconds: number;
};

export type ANTState = {
owner: WalletAddress;
controllers: WalletAddress[];
name: string;
ticker: string;
records: Record<string, ANTRecord>;
balances: Balances;
};
97 changes: 97 additions & 0 deletions tests/ant.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { ANT } from '../src/common/ant';
import { RemoteContract } from '../src/common/contracts/remote-contract';
import { ANTState } from '../src/contract-state';

describe('ANT contract apis', () => {
const ant = new ANT({
contract: new RemoteContract<ANTState>({
url: process.env.REMOTE_CACHE_URL || 'http://localhost:3000',
contractTxId: 'UC2zwawQoTnh0TNd9mYLQS4wObBBeaOU5LPQTNETqA4',
}),
});

const sortKey =
'000001383961,0000000000000,13987aba2d71b6229989690c15d2838a4deef0a90c3fc9e4d7227ed17e35d0bd';
const blockHeight = 1383961;

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get contract state with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const state = await ant.getState({ evaluationOptions: { evalTo } });
expect(state).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get record: ${JSON.stringify('%s')}`,
async (evalTo) => {
const record = await ant.getRecord({
domain: '@',
evaluationOptions: { evalTo },
});
expect(record).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get records with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const records = await ant.getRecords({ evaluationOptions: { evalTo } });
console.dir({ records: records['@'] }, { depth: 4 });
expect(records).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get owner with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const owner = await ant.getOwner({ evaluationOptions: { evalTo } });
expect(owner).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get controllers with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const controllers = await ant.getControllers({
evaluationOptions: { evalTo },
});
expect(controllers).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get name with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const state = await ant.getName({ evaluationOptions: { evalTo } });
expect(state).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get ticker with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const state = await ant.getTicker({ evaluationOptions: { evalTo } });
expect(state).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get balances with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const state = await ant.getBalances({ evaluationOptions: { evalTo } });
expect(state).toBeDefined();
},
);

it.each([[{ sortKey }], [{ blockHeight }]])(
`should get balance with evaluation options: ${JSON.stringify('%s')}`,
async (evalTo) => {
const state = await ant.getBalance({
address: 'TRVCopHzzO1VSwRUUS8umkiO2MpAL53XtVGlLaJuI94',
evaluationOptions: { evalTo },
});
expect(state).toBeDefined();
},
);
});

0 comments on commit c941c96

Please sign in to comment.