Skip to content
Draft
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
7 changes: 6 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"@oclif/core": "^4",
"@oclif/plugin-help": "^6",
"@oclif/plugin-plugins": "^5",
"tty-table": "^5.0.0"
"tty-table": "^5.0.0",
"viem": "^2.37.5"
},
"devDependencies": {
"@oclif/test": "^4",
Expand All @@ -57,7 +58,11 @@
},
"spokes": {
"description": "List available spokes"
},
"sandbox": {
"description": "Sandbox related commands"
}

}
},
"license": "MIT",
Expand Down
179 changes: 179 additions & 0 deletions packages/cli/src/commands/sandbox/reserve/update-caps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
type BigDecimal,
err,
InvariantError,
ok,
type Reserve,
type ReserveRequest,
ResultAsync,
RoundingMode,
type TxHash,
txHash,
} from '@aave/client';
import { reserve } from '@aave/client/actions';
import { createNewWallet } from '@aave/client/testing';
import type { Account, Chain, Transport, WalletClient } from 'viem';
import { writeContract } from 'viem/actions';
import * as common from '../../../common.js';

export default class UpdateCaps extends common.V4Command {
static override description = 'Update the caps for a specific reserve';

static override flags = {
reserve: common.reserve({
required: true,
relationships: [
{
type: 'some',
flags: ['supply_cap', 'borrow_cap'],
},
],
}),
supply_cap: common.decimal({
required: false,
name: 'supply_cap',
description: 'The new supply cap for the reserve (e.g. 1000.00)',
}),
borrow_cap: common.decimal({
name: 'borrow_cap',
description: 'The new borrow cap for the reserve (e.g. 1000.00)',
}),
};

override headers = [{ value: 'Reserve' }, { value: 'TxHash' }];

private updateSpokeConfigABI = [
{
type: 'function',
name: 'updateSpokeConfig',
stateMutability: 'nonpayable',
constant: false,
inputs: [
{ type: 'uint256', name: 'assetId', simpleType: 'uint' },
{ type: 'address', name: 'spoke', simpleType: 'address' },
{
type: 'tuple',
name: 'config',
simpleType: 'tuple',
components: [
{ type: 'uint40', name: 'addCap', simpleType: 'uint' },
{ type: 'uint40', name: 'drawCap', simpleType: 'uint' },
{
type: 'uint24',
name: 'riskPremiumThreshold',
simpleType: 'uint',
},
{ type: 'bool', name: 'active', simpleType: 'bool' },
{ type: 'bool', name: 'paused', simpleType: 'bool' },
],
},
],
outputs: undefined,
},
] as const;

private getReserveRequest(): ResultAsync<ReserveRequest, InvariantError> {
return ResultAsync.fromPromise(
this.parse(UpdateCaps),
(error) => new InvariantError(String(error)),
).andThen(({ flags }) => {
if (flags.reserve) {
return ok({
query: {
reserveId: flags.reserve,
},
});
}

return err(
new InvariantError(
'You must provide a <reserve> and either <supply_cap> or <borrow_cap>',
),
);
});
}

private sendUpdateCapsTransaction(
wallet: WalletClient<Transport, Chain, Account>,
reserveInfo: Reserve,
flags: { supply_cap?: BigDecimal; borrow_cap?: BigDecimal },
): ResultAsync<TxHash, InvariantError> {
const hubAddress = reserveInfo.asset.hub.address;
const spokeAddress = reserveInfo.spoke.address;
const assetId = BigInt(reserveInfo.asset.onchainAssetId);

// Convert BigDecimal caps to BigInt (uint40)
// Note: Caps appear to be stored in a format that doesn't require full decimal conversion
// Converting with full decimals would exceed this limit, so we use the integer part
const convertCapToUint40 = (cap: BigDecimal): bigint => {
// Convert to integer (rounding down)
const capBigInt = BigInt(cap.toFixed(0, RoundingMode.Down));
const maxUint40 = BigInt('0xFFFFFFFFFF'); // 2^40 - 1 = 1,099,511,627,775
if (capBigInt > maxUint40) {
throw new InvariantError(
`Cap value ${capBigInt} exceeds uint40 maximum of ${maxUint40}`,
);
}
return capBigInt;
};

// Use current values from reserve or update with new caps
const newAddCap = flags.supply_cap
? convertCapToUint40(flags.supply_cap)
: convertCapToUint40(reserveInfo.supplyCap);
const newDrawCap = flags.borrow_cap
? convertCapToUint40(flags.borrow_cap)
: convertCapToUint40(reserveInfo.borrowCap);

// Call contract function directly
return ResultAsync.fromPromise(
writeContract(wallet, {
address: hubAddress,
abi: this.updateSpokeConfigABI,
functionName: 'updateSpokeConfig',
args: [
assetId,
spokeAddress,
{
addCap: newAddCap,
drawCap: newDrawCap,
riskPremiumThreshold: 0n, // Default value, not available in reserve info
active: reserveInfo.status.active,
paused: reserveInfo.status.paused,
},
],
}),
(error) => new InvariantError(String(error)),
).map((txHashString) => txHash(txHashString));
}

async run(): Promise<TxHash | InvariantError> {
const user = await createNewWallet(
'0x3d9ca529fb78a4ca983231de205242cdfa8b02aea48689c137cd91a4ed3426d0',
);
const parsed = await this.parse(UpdateCaps);

const result = await this.getReserveRequest()
.andThen((request) => reserve(this.client, request))
.andThen((reserveInfo) => {
if (!reserveInfo) {
return err(new InvariantError('Reserve not found'));
}

return this.sendUpdateCapsTransaction(user, reserveInfo, {
supply_cap: parsed.flags.supply_cap,
borrow_cap: parsed.flags.borrow_cap,
}).andThen((txHash) => {
// Display the result
this.display([[reserveInfo.id, txHash]]);
return ok(txHash);
});
});

if (result.isErr()) {
this.error(result.error);
}

return result.value;
}
}
22 changes: 21 additions & 1 deletion packages/cli/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
AaveClient,
type BigDecimal,
bigDecimal,
type ChainId,
chainId,
type EvmAddress,
evmAddress,
type HubId,
hubId,
type ReserveId,
reserveId,
staging,
} from '@aave/client';
import { Command, Flags } from '@oclif/core';
import TtyTable from 'tty-table';
Expand All @@ -26,17 +31,32 @@ export const hub = Flags.custom<HubId>({
parse: async (input) => hubId(input),
});

export const reserve = Flags.custom<ReserveId>({
char: 'r',
name: 'reserve',
description: 'The reserve ID (e.g. SGVsbG8h…)',
helpValue: '<reserve-id>',
parse: async (input) => reserveId(input),
});

export const address = Flags.custom<EvmAddress>({
parse: async (input) => evmAddress(input),
helpValue: '<evm-address>',
});

export const decimal = Flags.custom<BigDecimal>({
parse: async (input) => bigDecimal(input),
helpValue: '<human-readable-decimal>',
});

export abstract class V4Command extends Command {
protected headers: TtyTable.Header[] = [];

public static enableJsonFlag = true;

protected client = AaveClient.create();
protected client = AaveClient.create({
environment: staging,
});

protected display(rows: unknown[]) {
const out = TtyTable(this.headers, rows).render();
Expand Down
15 changes: 8 additions & 7 deletions packages/client/src/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ import { local, production, staging } from './environments';
import { toViemChain } from './viem';

export const environment =
import.meta.env.ENVIRONMENT === 'local'
import.meta.env?.ENVIRONMENT === 'local'
? local
: import.meta.env.ENVIRONMENT === 'production'
: import.meta.env?.ENVIRONMENT === 'production'
? production
: staging;

export const ETHEREUM_FORK_ID = chainId(
Number.parseInt(import.meta.env.ETHEREUM_TENDERLY_FORK_ID, 10),
Number.parseInt(import.meta.env?.ETHEREUM_TENDERLY_FORK_ID ?? '0', 10),
);

// Token addresses
Expand Down Expand Up @@ -105,11 +105,11 @@ export const ETHEREUM_HUB_CORE_ID = encodeHubId({
address: ETHEREUM_HUB_CORE_ADDRESS,
});

export const ETHEREUM_FORK_RPC_URL = import.meta.env
.ETHEREUM_TENDERLY_PUBLIC_RPC;
export const ETHEREUM_FORK_RPC_URL =
import.meta.env?.ETHEREUM_TENDERLY_PUBLIC_RPC ?? '';

export const ETHEREUM_FORK_RPC_URL_ADMIN = import.meta.env
.ETHEREUM_TENDERLY_ADMIN_RPC;
export const ETHEREUM_FORK_RPC_URL_ADMIN =
import.meta.env?.ETHEREUM_TENDERLY_ADMIN_RPC ?? '';

export const client = AaveClient.create({
environment,
Expand All @@ -128,6 +128,7 @@ export async function createNewWallet(
): Promise<WalletClient<Transport, Chain, Account>> {
if (!privateKey) {
const privateKey = generatePrivateKey();
console.log('privateKey', privateKey);
const wallet = createWalletClient({
account: privateKeyToAccount(privateKey),
chain: devnetChain,
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading