Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(billing): resolve with valid only grants and allowances from htt… #572

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/mvm.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"dependencies": {
"@akashnetwork/database": "1.0.0",
"@akashnetwork/env-loader": "1.0.1",
"@akashnetwork/http-sdk": "1.0.8",
"@akashnetwork/http-sdk": "1.1.0",
"@akashnetwork/logging": "2.0.2"
}
}
38 changes: 14 additions & 24 deletions apps/api/src/billing/services/balances/balances.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllowanceHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService } from "@akashnetwork/http-sdk";
import { singleton } from "tsyringe";

import { BillingConfig, InjectBillingConfig } from "@src/billing/providers";
Expand All @@ -12,7 +12,7 @@ export class BalancesService {
@InjectBillingConfig() private readonly config: BillingConfig,
private readonly userWalletRepository: UserWalletRepository,
@InjectWallet("MANAGED") private readonly masterWallet: Wallet,
private readonly allowanceHttpService: AllowanceHttpService
private readonly authzHttpService: AuthzHttpService
) {}

async refreshUserWalletLimits(userWallet: UserWalletOutput, options?: { endTrial: boolean }): Promise<void> {
Expand Down Expand Up @@ -45,39 +45,29 @@ export class BalancesService {
}

async getFreshLimits(userWallet: UserWalletOutput): Promise<{ fee: number; deployment: number }> {
const [fee, deployment] = await Promise.all([this.retrieveAndCalcFeeLimit(userWallet), this.retrieveAndCalcDeploymentLimit(userWallet)]);
const [fee, deployment] = await Promise.all([this.retrieveAndCalcFeeLimit(userWallet), this.retrieveDeploymentLimit(userWallet)]);
return { fee, deployment };
}

private async retrieveAndCalcFeeLimit(userWallet: UserWalletOutput): Promise<number> {
const feeAllowance = await this.allowanceHttpService.getFeeAllowancesForGrantee(userWallet.address);
const masterWalletAddress = await this.masterWallet.getFirstAddress();
const feeAllowance = await this.authzHttpService.getFeeAllowanceForGranterAndGrantee(masterWalletAddress, userWallet.address);

return feeAllowance.reduce((acc, allowance) => {
if (allowance.granter !== masterWalletAddress) {
return acc;
}

return allowance.allowance.spend_limit.reduce((acc, { denom, amount }) => {
if (denom !== "uakt") {
return acc;
}
if (!feeAllowance) {
return 0;
}

return acc + parseInt(amount);
}, 0);
}, 0);
return feeAllowance.allowance.spend_limit.reduce((acc, { denom, amount }) => (denom === "uakt" ? acc + parseInt(amount) : acc), 0);
}

async retrieveAndCalcDeploymentLimit(userWallet: Pick<UserWalletOutput, "address">): Promise<number> {
const deploymentAllowance = await this.allowanceHttpService.getDeploymentAllowancesForGrantee(userWallet.address);
async retrieveDeploymentLimit(userWallet: Pick<UserWalletOutput, "address">): Promise<number> {
const masterWalletAddress = await this.masterWallet.getFirstAddress();
const depositDeploymentGrant = await this.authzHttpService.getDepositDeploymentGrantsForGranterAndGrantee(masterWalletAddress, userWallet.address);

return deploymentAllowance.reduce((acc, allowance) => {
if (allowance.granter !== masterWalletAddress || allowance.authorization.spend_limit.denom !== this.config.DEPLOYMENT_GRANT_DENOM) {
return acc;
}
if (!depositDeploymentGrant || depositDeploymentGrant.authorization.spend_limit.denom !== this.config.DEPLOYMENT_GRANT_DENOM) {
return 0;
}

return acc + parseInt(allowance.authorization.spend_limit.amount);
}, 0);
return parseInt(depositDeploymentGrant.authorization.spend_limit.amount);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllowanceHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService } from "@akashnetwork/http-sdk";
import { LoggerService } from "@akashnetwork/logging";
import { stringToPath } from "@cosmjs/crypto";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
Expand Down Expand Up @@ -38,7 +38,7 @@ export class ManagedUserWalletService {
@InjectWallet("MANAGED") private readonly masterWallet: Wallet,
private readonly managedSignerService: ManagedSignerService,
private readonly rpcMessageService: RpcMessageService,
private readonly allowanceHttpService: AllowanceHttpService
private readonly authzHttpService: AuthzHttpService
) {}

async createAndAuthorizeTrialSpending({ addressIndex }: { addressIndex: number }) {
Expand Down Expand Up @@ -94,8 +94,9 @@ export class ManagedUserWalletService {

private async authorizeFeeSpending(options: Omit<SpendingAuthorizationMsgOptions, "denom">) {
const messages: EncodeObject[] = [];
const hasValidFeeAllowance = await this.authzHttpService.hasValidFeeAllowance(options.granter, options.grantee);

if (await this.allowanceHttpService.hasFeeAllowance(options.granter, options.grantee)) {
if (hasValidFeeAllowance) {
messages.push(this.rpcMessageService.getRevokeAllowanceMsg(options));
}

Expand All @@ -118,12 +119,12 @@ export class ManagedUserWalletService {
deploymentGrant: false
};

if (await this.allowanceHttpService.hasFeeAllowance(params.granter, params.grantee)) {
if (await this.authzHttpService.hasValidFeeAllowance(params.granter, params.grantee)) {
revokeSummary.feeAllowance = true;
messages.push(this.rpcMessageService.getRevokeAllowanceMsg(params));
}

if (await this.allowanceHttpService.hasDeploymentGrant(params.granter, params.grantee)) {
if (await this.authzHttpService.hasValidDepositDeploymentGrant(params.granter, params.grantee)) {
revokeSummary.deploymentGrant = true;
messages.push(this.rpcMessageService.getRevokeDepositDeploymentGrantMsg(params));
}
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/billing/services/refill/refill.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class RefillService {
let currentLimit: number = 0;

if (userWallet) {
currentLimit = await this.balancesService.retrieveAndCalcDeploymentLimit(userWallet);
currentLimit = await this.balancesService.retrieveDeploymentLimit(userWallet);
} else {
userWallet = await this.walletInitializerService.initialize(userId);
}
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/core/providers/http-sdk.provider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AllowanceHttpService, BalanceHttpService, BlockHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService, BalanceHttpService, BlockHttpService } from "@akashnetwork/http-sdk";
import { container } from "tsyringe";

import { apiNodeUrl } from "@src/utils/constants";

const SERVICES = [BalanceHttpService, AllowanceHttpService, BlockHttpService];
const SERVICES = [BalanceHttpService, AuthzHttpService, BlockHttpService];

SERVICES.forEach(Service => container.register(Service, { useValue: new Service({ baseURL: apiNodeUrl }) }));
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "@test/mocks/logger-service.mock";

import { AllowanceHttpService, BalanceHttpService, Denom } from "@akashnetwork/http-sdk";
import { AuthzHttpService, BalanceHttpService, Denom } from "@akashnetwork/http-sdk";
import { MsgExec } from "cosmjs-types/cosmos/authz/v1beta1/tx";
import { secondsInWeek } from "date-fns/constants";
import { describe } from "node:test";
Expand Down Expand Up @@ -42,7 +42,7 @@ describe(TopUpCustodialDeploymentsService.name, () => {
});
};

const allowanceHttpService = new AllowanceHttpService();
const authzHttpService = new AuthzHttpService();
const balanceHttpService = new BalanceHttpService();
const blockHttpService = stub<BlockHttpService>({ getCurrentHeight: jest.fn() });
const uaktMasterWallet = mockManagedWallet(UAKT_TOP_UP_MASTER_WALLET_ADDRESS);
Expand All @@ -58,7 +58,7 @@ describe(TopUpCustodialDeploymentsService.name, () => {

const topUpDeploymentsService = new TopUpCustodialDeploymentsService(
topUpToolsService,
allowanceHttpService,
authzHttpService,
balanceHttpService,
drainingDeploymentService,
new RpcMessageService(),
Expand Down Expand Up @@ -177,10 +177,10 @@ describe(TopUpCustodialDeploymentsService.name, () => {
})
];

jest.spyOn(allowanceHttpService, "paginateDeploymentGrants").mockImplementation(async (params, cb) => {
jest.spyOn(authzHttpService, "paginateDepositDeploymentGrants").mockImplementation(async (params, cb) => {
return await cb(data.filter(({ grant }) => "grantee" in params && grant.grantee === params.grantee).map(({ grant }) => grant));
});
jest.spyOn(allowanceHttpService, "getFeeAllowanceForGranterAndGrantee").mockImplementation(async (granter: string, grantee: string) => {
jest.spyOn(authzHttpService, "getFeeAllowanceForGranterAndGrantee").mockImplementation(async (granter: string, grantee: string) => {
return data.find(({ grant }) => grant.granter === granter && grant.grantee === grantee)?.feeAllowance;
});
jest.spyOn(balanceHttpService, "getBalance").mockImplementation(async (address: string, denom: Denom) => {
Expand Down Expand Up @@ -271,7 +271,6 @@ describe(TopUpCustodialDeploymentsService.name, () => {
],
{ fee: { granter: owner } }
);

console.log("DEBUG res", res);
} catch (e) {
console.log("DEBUG e", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllowanceHttpService, BalanceHttpService, DeploymentAllowance } from "@akashnetwork/http-sdk";
import { AuthzHttpService, BalanceHttpService, DepositDeploymentGrant } from "@akashnetwork/http-sdk";
import { LoggerService } from "@akashnetwork/logging";
import { singleton } from "tsyringe";

Expand Down Expand Up @@ -36,7 +36,7 @@ export class TopUpCustodialDeploymentsService implements DeploymentsRefiller {

constructor(
private readonly topUpToolsService: TopUpToolsService,
private readonly allowanceHttpService: AllowanceHttpService,
private readonly authzHttpService: AuthzHttpService,
private readonly balanceHttpService: BalanceHttpService,
private readonly drainingDeploymentService: DrainingDeploymentService,
private readonly rpcClientService: RpcMessageService,
Expand All @@ -50,7 +50,7 @@ export class TopUpCustodialDeploymentsService implements DeploymentsRefiller {

const topUpAllCustodialDeployments = this.topUpToolsService.pairs.map(async ({ wallet, client }) => {
const address = await wallet.getFirstAddress();
await this.allowanceHttpService.paginateDeploymentGrants({ grantee: address, limit: this.CONCURRENCY }, async grants => {
await this.authzHttpService.paginateDepositDeploymentGrants({ grantee: address, limit: this.CONCURRENCY }, async grants => {
await Promise.all(
grants.map(async grant => {
await this.errorService.execWithErrorHandler(
Expand All @@ -69,7 +69,7 @@ export class TopUpCustodialDeploymentsService implements DeploymentsRefiller {
}

private async topUpForGrant(
grant: DeploymentAllowance,
grant: DepositDeploymentGrant,
client: BatchSigningClientService,
options: TopUpDeploymentsOptions,
summary: TopUpSummarizer
Expand Down Expand Up @@ -120,7 +120,7 @@ export class TopUpCustodialDeploymentsService implements DeploymentsRefiller {
}
}

private async collectWalletBalances(grant: DeploymentAllowance): Promise<Balances> {
private async collectWalletBalances(grant: DepositDeploymentGrant): Promise<Balances> {
const denom = grant.authorization.spend_limit.denom;
const deploymentLimit = parseFloat(grant.authorization.spend_limit.amount);

Expand All @@ -140,7 +140,7 @@ export class TopUpCustodialDeploymentsService implements DeploymentsRefiller {
}

private async retrieveFeesLimit(granter: string, grantee: string) {
const feesAllowance = await this.allowanceHttpService.getFeeAllowanceForGranterAndGrantee(granter, grantee);
const feesAllowance = await this.authzHttpService.getFeeAllowanceForGranterAndGrantee(granter, grantee);
const feesSpendLimit = feesAllowance.allowance.spend_limit.find(limit => limit.denom === "uakt");

return feesSpendLimit ? parseFloat(feesSpendLimit.amount) : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { stub } from "@test/services/stub";
describe(TopUpManagedDeploymentsService.name, () => {
const CURRENT_BLOCK_HEIGHT = 7481457;
const MANAGED_MASTER_WALLET_ADDRESS = AkashAddressSeeder.create();
const balancesService = stub<BalancesService>({ retrieveAndCalcDeploymentLimit: jest.fn() });
const balancesService = stub<BalancesService>({ retrieveDeploymentLimit: jest.fn() });
const userWalletRepository = stub<UserWalletRepository>({ paginate: jest.fn() });
const blockHttpService = stub<BlockHttpService>({ getCurrentHeight: () => CURRENT_BLOCK_HEIGHT });
const managedSignerService = stub<ManagedSignerService>({ executeManagedTx: jest.fn() });
Expand Down Expand Up @@ -93,7 +93,7 @@ describe(TopUpManagedDeploymentsService.name, () => {
return data.find(({ wallet }) => wallet.address == owner)?.drainingDeployments?.map(({ deployment }) => deployment) || [];
});
jest.spyOn(drainingDeploymentService, "calculateTopUpAmount").mockImplementation(async () => faker.number.int({ min: 3500000, max: 4000000 }));
balancesService.retrieveAndCalcDeploymentLimit.mockImplementation(async wallet => {
balancesService.retrieveDeploymentLimit.mockImplementation(async wallet => {
return parseInt(data.find(({ wallet: w }) => w.address == wallet.address)?.balance);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class TopUpManagedDeploymentsService implements DeploymentsRefiller {
return;
}

let balance = await this.balancesService.retrieveAndCalcDeploymentLimit(wallet);
let balance = await this.balancesService.retrieveDeploymentLimit(wallet);
let hasTopUp = false;

for (const deployment of drainingDeployments) {
Expand Down
16 changes: 8 additions & 8 deletions apps/api/test/functional/stale-anonymous-users-cleanup.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllowanceHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService } from "@akashnetwork/http-sdk";
import subDays from "date-fns/subDays";
import { container } from "tsyringe";

Expand All @@ -19,7 +19,7 @@ describe("Users", () => {
const userWalletRepository = container.resolve(UserWalletRepository);
const walletService = new WalletTestingService(app);
const controller = container.resolve(UserController);
const allowanceHttpService = container.resolve(AllowanceHttpService);
const authzHttpService = container.resolve(AuthzHttpService);
const masterWalletService = resolveWallet("MANAGED");
let masterAddress: string;

Expand Down Expand Up @@ -71,14 +71,14 @@ describe("Users", () => {
);

await Promise.all([
expect(allowanceHttpService.hasFeeAllowance(recent.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(allowanceHttpService.hasDeploymentGrant(recent.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(authzHttpService.hasValidFeeAllowance(recent.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(authzHttpService.hasValidDepositDeploymentGrant(recent.wallet.address, masterAddress)).resolves.toBeFalsy(),

expect(allowanceHttpService.hasFeeAllowance(reactivated.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(allowanceHttpService.hasDeploymentGrant(reactivated.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(authzHttpService.hasValidFeeAllowance(reactivated.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(authzHttpService.hasValidDepositDeploymentGrant(reactivated.wallet.address, masterAddress)).resolves.toBeFalsy(),

expect(allowanceHttpService.hasFeeAllowance(stale.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(allowanceHttpService.hasDeploymentGrant(stale.wallet.address, masterAddress)).resolves.toBeFalsy()
expect(authzHttpService.hasValidFeeAllowance(stale.wallet.address, masterAddress)).resolves.toBeFalsy(),
expect(authzHttpService.hasValidDepositDeploymentGrant(stale.wallet.address, masterAddress)).resolves.toBeFalsy()
]);
});
});
Expand Down
8 changes: 4 additions & 4 deletions apps/api/test/functional/start-trial.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllowanceHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService } from "@akashnetwork/http-sdk";
import { faker } from "@faker-js/faker";
import { eq } from "drizzle-orm";
import { container } from "tsyringe";
Expand All @@ -16,7 +16,7 @@ describe("start trial", () => {
const config = container.resolve<BillingConfig>(BILLING_CONFIG);
const db = container.resolve<ApiPgDatabase>(POSTGRES_DB);
const userWalletsQuery = db.query.UserWallets;
const allowanceHttpService = container.resolve(AllowanceHttpService);
const authzHttpService = container.resolve(AuthzHttpService);
const dbService = container.resolve(DbTestingService);

afterEach(async () => {
Expand All @@ -42,8 +42,8 @@ describe("start trial", () => {
const getWalletsResponse = await app.request(`/v1/wallets?userId=${userId}`, { headers });
const userWallet = await userWalletsQuery.findFirst({ where: eq(userWalletsTable.userId, userId) });
const allowances = await Promise.all([
allowanceHttpService.getDeploymentAllowancesForGrantee(userWallet.address),
allowanceHttpService.getFeeAllowancesForGrantee(userWallet.address)
authzHttpService.getDepositDeploymentGrantsForGrantee(userWallet.address),
authzHttpService.getFeeAllowancesForGrantee(userWallet.address)
]);

expect(createWalletResponse.status).toBe(200);
Expand Down
4 changes: 2 additions & 2 deletions apps/api/test/seeders/deployment-grant.seeder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DeploymentAllowance } from "@akashnetwork/http-sdk";
import type { DepositDeploymentGrant } from "@akashnetwork/http-sdk";
import { faker } from "@faker-js/faker";
import { merge } from "lodash";
import type { PartialDeep } from "type-fest";
Expand All @@ -8,7 +8,7 @@ import { AkashAddressSeeder } from "./akash-address.seeder";
import { DenomSeeder } from "@test/seeders/denom.seeder";

export class DeploymentGrantSeeder {
static create(input: PartialDeep<DeploymentAllowance> = {}): DeploymentAllowance {
static create(input: PartialDeep<DepositDeploymentGrant> = {}): DepositDeploymentGrant {
return merge(
{
granter: AkashAddressSeeder.create(),
Expand Down
2 changes: 1 addition & 1 deletion apps/deploy-web/mvm.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dependencies": {
"@akashnetwork/env-loader": "1.0.1",
"@akashnetwork/http-sdk": "1.0.8",
"@akashnetwork/http-sdk": "1.1.0",
"@akashnetwork/logging": "2.0.2",
"@akashnetwork/network-store": "1.0.1",
"@akashnetwork/ui": "1.0.0"
Expand Down
4 changes: 2 additions & 2 deletions apps/deploy-web/src/hooks/useAllowanceService.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useMemo } from "react";
import { AllowanceHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService } from "@akashnetwork/http-sdk";

import { useSettings } from "@src/context/SettingsProvider";

export const useAllowanceService = () => {
const { settings } = useSettings();
return useMemo(() => new AllowanceHttpService({ baseURL: settings.apiEndpoint }), [settings.apiEndpoint]);
return useMemo(() => new AuthzHttpService({ baseURL: settings.apiEndpoint }), [settings.apiEndpoint]);
};
4 changes: 2 additions & 2 deletions apps/deploy-web/src/hooks/useAutoTopUpLimits.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useMemo } from "react";
import { ExactDeploymentAllowance, FeeAllowance } from "@akashnetwork/http-sdk";
import { ExactDepositDeploymentGrant, FeeAllowance } from "@akashnetwork/http-sdk";
import { isFuture } from "date-fns";
import invokeMap from "lodash/invokeMap";

Expand Down Expand Up @@ -62,7 +62,7 @@ export const useAutoTopUpLimits = () => {
};
};

function extractDeploymentLimit(deploymentGrant?: ExactDeploymentAllowance) {
function extractDeploymentLimit(deploymentGrant?: ExactDepositDeploymentGrant) {
if (!deploymentGrant) {
return undefined;
}
Expand Down
10 changes: 7 additions & 3 deletions apps/deploy-web/src/queries/useExactDeploymentGrantsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { QueryKeys } from "@src/queries/queryKeys";

export function useExactDeploymentGrantsQuery(granter: string, grantee: string, { enabled = true } = {}) {
const allowanceHttpService = useAllowanceService();
return useQuery(QueryKeys.getDeploymentGrantsKey(granter, grantee), () => allowanceHttpService.getDeploymentGrantsForGranterAndGrantee(granter, grantee), {
enabled
});
return useQuery(
QueryKeys.getDeploymentGrantsKey(granter, grantee),
() => allowanceHttpService.getDepositDeploymentGrantsForGranterAndGrantee(granter, grantee),
{
enabled
}
);
}
Loading
Loading