From 69acf809a453f4788c803af939391b4c65b219f3 Mon Sep 17 00:00:00 2001 From: Maks Nabokov Date: Thu, 21 Sep 2023 09:57:29 +0300 Subject: [PATCH 1/2] budget from preflight --- .../artifacts/budget_utilization.snap | 12 ++ integration-tests/tests/pool.sut.ts | 114 ++++++++++++------ .../tests/pool/6.tx.budget.spec.ts | 16 ++- integration-tests/tests/soroban.client.ts | 27 +++-- 4 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 integration-tests/artifacts/budget_utilization.snap diff --git a/integration-tests/artifacts/budget_utilization.snap b/integration-tests/artifacts/budget_utilization.snap new file mode 100644 index 00000000..cf3ec8f5 --- /dev/null +++ b/integration-tests/artifacts/budget_utilization.snap @@ -0,0 +1,12 @@ +{ + "borrow": { + "cpuInsns": "37577012", + "memBytes": "4027722" + } +} +{ + "withdraw": { + "cpuInsns": "37631489", + "memBytes": "4031271" + } +} diff --git a/integration-tests/tests/pool.sut.ts b/integration-tests/tests/pool.sut.ts index 46bcc832..5ca74f14 100644 --- a/integration-tests/tests/pool.sut.ts +++ b/integration-tests/tests/pool.sut.ts @@ -1,5 +1,5 @@ import { Address, Keypair, SorobanRpc } from "soroban-client"; -import { SorobanClient } from "./soroban.client"; +import { SendTransactionResult, SorobanClient } from "./soroban.client"; import { adminKeys, contractsFilename, setEnv, treasuryKeys } from "./soroban.config"; import { convertToScvAddress, @@ -14,6 +14,9 @@ import { parseScvToJs } from "./soroban.converter"; import { exec } from "child_process"; +import * as fs from 'fs'; + +export const BUDGET_SNAPSHOT_FILE = 'artifacts/budget_utilization.snap'; export type SlenderAsset = "XLM" | "XRP" | "USDC"; @@ -78,9 +81,10 @@ export async function mintUnderlyingTo( process.env[`SLENDER_TOKEN_${asset}`], "mint", adminKeys, + false, convertToScvAddress(to), convertToScvI128(amount) - ) + ) as Promise ); } @@ -93,9 +97,10 @@ export async function mintBurn( mintsBurns[i].asset_balance.get("asset"), mintsBurns[i].mint ? "mint" : "clawback", adminKeys, + false, convertToScvAddress(mintsBurns[i].who.toString()), convertToScvI128(mintsBurns[i].asset_balance.get("balance")) - ); + ) as SorobanRpc.GetTransactionResponse; if (response.status != "SUCCESS") { throw Error("Failed to transfer tokens!"); @@ -161,12 +166,14 @@ export async function accountPosition( export async function setPrice( client: SorobanClient, asset: SlenderAsset, - amount: bigint -): Promise { - await client.sendTransaction( + amount: bigint, + withBudget = false +): Promise { + return client.sendTransaction( process.env.SLENDER_POOL, "set_price", adminKeys, + withBudget, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); @@ -213,78 +220,92 @@ export async function borrow( client: SorobanClient, signer: Keypair, asset: SlenderAsset, - amount: bigint -): Promise { + amount: bigint, + withBudget = false, +): Promise { const response = await client.sendTransaction( process.env.SLENDER_POOL, "borrow", signer, + withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); - + const result = parseMetaXdrToJs>( - response.resultMetaXdr + withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr ); await mintBurn(client, result); + + return response; } export async function deposit( client: SorobanClient, signer: Keypair, asset: SlenderAsset, - amount: bigint -): Promise { + amount: bigint, + withBudget = false, +): Promise { const response = await client.sendTransaction( process.env.SLENDER_POOL, "deposit", signer, + withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); const result = parseMetaXdrToJs>( - response.resultMetaXdr + withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr ); await mintBurn(client, result); + + return response; } export async function repay( client: SorobanClient, signer: Keypair, asset: SlenderAsset, - amount: bigint -): Promise { + amount: bigint, + withBudget = false, +): Promise { const response = await client.sendTransaction( process.env.SLENDER_POOL, "repay", signer, + withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); const result = parseMetaXdrToJs>( - response.resultMetaXdr + withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr ); await mintBurn(client, result); + + return response; } export async function withdraw( client: SorobanClient, signer: Keypair, asset: SlenderAsset, - amount: bigint -): Promise { + amount: bigint, + withBudget = false, +): Promise { const response = await client.sendTransaction( process.env.SLENDER_POOL, "withdraw", signer, + withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount), @@ -292,32 +313,38 @@ export async function withdraw( ); const result = parseMetaXdrToJs>( - response.resultMetaXdr + withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr ); await mintBurn(client, result); + + return response; } export async function liquidate( client: SorobanClient, signer: Keypair, who: string, - receiveStoken: boolean -): Promise { + receiveStoken: boolean, + withBudget = false, +): Promise { const response = await client.sendTransaction( process.env.SLENDER_POOL, "liquidate", signer, + withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(who), convertToScvBool(receiveStoken) ); const result = parseMetaXdrToJs>( - response.resultMetaXdr + withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr ); await mintBurn(client, result); + + return response; } export async function collatCoeff( @@ -338,12 +365,14 @@ export async function transferStoken( asset: SlenderAsset, signer: Keypair, to: string, - amount: bigint -): Promise { - await client.sendTransaction( + amount: bigint, + withBudget = false, +): Promise { + return client.sendTransaction( process.env[`SLENDER_S_TOKEN_${asset}`], "transfer", signer, + withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(to), convertToScvI128(amount) @@ -388,6 +417,7 @@ export async function finalizeTransfer( process.env["SLENDER_POOL"], "finalize_transfer", signer, + false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvAddress(signer.publicKey()), convertToScvAddress(to), @@ -398,6 +428,13 @@ export async function finalizeTransfer( ); } +export function writeBudgetSnapshot(name: string, transactionResult: SendTransactionResult) { + if (Array.isArray(transactionResult)) { + const budget = transactionResult[1]; + fs.writeFileSync(BUDGET_SNAPSHOT_FILE, `${JSON.stringify({ [name]: budget }, null, 2)}\n`, { flag: 'a' }); + } +} + async function initContract( name: string, callback: () => Promise, @@ -424,11 +461,12 @@ async function initToken(client: SorobanClient, asset: SlenderAsset, name: strin process.env[`SLENDER_TOKEN_${asset}`], "initialize", adminKeys, + false, convertToScvAddress(adminKeys.publicKey()), convertToScvU32(9), convertToScvString(name), convertToScvString(asset) - ) + ) as Promise ); } @@ -439,13 +477,14 @@ async function initSToken(client: SorobanClient, asset: SlenderAsset, salt: stri process.env.SLENDER_DEPLOYER, "deploy_s_token", adminKeys, + false, convertToScvBytes(salt, "hex"), convertToScvBytes(process.env.SLENDER_S_TOKEN_HASH, "hex"), convertToScvString(`SToken ${asset}`), convertToScvString(`S${asset}`), convertToScvAddress(process.env.SLENDER_POOL), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), - ), + ) as Promise, result => result[0] ); } @@ -457,13 +496,14 @@ async function initDToken(client: SorobanClient, asset: SlenderAsset, salt: stri process.env.SLENDER_DEPLOYER, "deploy_debt_token", adminKeys, + false, convertToScvBytes(salt, "hex"), convertToScvBytes(process.env.SLENDER_DEBT_TOKEN_HASH, "hex"), convertToScvString(`DToken ${asset}`), convertToScvString(`D${asset}`), convertToScvAddress(process.env.SLENDER_POOL), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), - ), + ) as Promise, result => result[0]); } @@ -474,6 +514,7 @@ async function initPool(client: SorobanClient, salt: string): Promise { process.env.SLENDER_DEPLOYER, "deploy_pool", adminKeys, + false, convertToScvBytes(salt, "hex"), convertToScvBytes(process.env.SLENDER_POOL_HASH, "hex"), convertToScvAddress(adminKeys.publicKey()), @@ -485,7 +526,7 @@ async function initPool(client: SorobanClient, salt: string): Promise { "max_rate": convertToScvU32(50_000), "scaling_coeff": convertToScvU32(9_000) }) - ), + ) as Promise, result => result[0] ); } @@ -497,13 +538,14 @@ async function initPoolReserve(client: SorobanClient, asset: SlenderAsset, decim process.env.SLENDER_POOL, "init_reserve", adminKeys, + false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvMap({ "debt_token_address": convertToScvAddress(process.env[`SLENDER_DEBT_TOKEN_${asset}`]), // "decimals": convertToScvU32(9), "s_token_address": convertToScvAddress(process.env[`SLENDER_S_TOKEN_${asset}`]), }) - ) + ) as Promise ); } @@ -514,6 +556,7 @@ async function initPoolCollateral(client: SorobanClient, asset: SlenderAsset): P process.env.SLENDER_POOL, "configure_as_collateral", adminKeys, + false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvMap({ "discount": convertToScvU32(6000), @@ -521,7 +564,7 @@ async function initPoolCollateral(client: SorobanClient, asset: SlenderAsset): P "liq_cap": convertToScvI128(1000000000000000n), "util_cap": convertToScvU32(9000) }) - ) + ) as Promise ); } @@ -532,9 +575,10 @@ async function initPoolPriceFeed(client: SorobanClient, feed: string, assets: st process.env.SLENDER_POOL, "set_price_feed", adminKeys, + false, convertToScvAddress(feed), convertToScvVec(assets.map(asset => convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]))) - ) + ) as Promise ); } @@ -545,9 +589,10 @@ async function initPoolBorrowing(client: SorobanClient, asset: SlenderAsset): Pr process.env.SLENDER_POOL, "enable_borrowing_on_reserve", adminKeys, + false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvBool(true) - ) + ) as Promise ); } @@ -558,8 +603,9 @@ async function initBaseAsset(client: SorobanClient, asset: SlenderAsset): Promis process.env.SLENDER_POOL, "set_base_asset", adminKeys, + false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvBool(true) - ) + ) as Promise ); } diff --git a/integration-tests/tests/pool/6.tx.budget.spec.ts b/integration-tests/tests/pool/6.tx.budget.spec.ts index 898fd049..e8151a5f 100644 --- a/integration-tests/tests/pool/6.tx.budget.spec.ts +++ b/integration-tests/tests/pool/6.tx.budget.spec.ts @@ -1,5 +1,6 @@ import { SorobanClient } from "../soroban.client"; import { + BUDGET_SNAPSHOT_FILE, borrow, cleanSlenderEnvKeys, deploy, @@ -7,6 +8,7 @@ import { init, mintUnderlyingTo, withdraw, + writeBudgetSnapshot, } from "../pool.sut"; import { borrower1Keys, @@ -15,6 +17,8 @@ import { } from "../soroban.config"; import { expect, use } from "chai"; import chaiAsPromised from 'chai-as-promised'; +import * as fs from 'fs'; + use(chaiAsPromised); describe("LendingPool: methods must not exceed CPU/MEM limits", function () { @@ -59,15 +63,23 @@ describe("LendingPool: methods must not exceed CPU/MEM limits", function () { await deposit(client, borrower2Keys, "USDC", 20_000_000_000n); await borrow(client, borrower2Keys, "XLM", 6_000_000_000n); await borrow(client, borrower2Keys, "XRP", 5_900_000_000n); + + fs.unlinkSync(BUDGET_SNAPSHOT_FILE); }); it("Case 1: borrow()", async function () { // Borrower1 borrows 20_000_000 USDC - await expect(borrow(client, borrower1Keys, "USDC", 20_000_000n)).to.not.eventually.rejected; + await expect( + borrow(client, borrower1Keys, "USDC", 20_000_000n, true) + .then((result) => writeBudgetSnapshot("borrow", result)) // TODO: method name + ).to.not.eventually.rejected; }); it("Case 2: withdraw full", async function () { // Borrower1 witdraws all XLM - await expect(withdraw(client, borrower1Keys, "XLM", 170_141_183_460_469_231_731_687_303_715_884_105_727n)).to.not.eventually.rejected; // i128::MAX + await expect( + withdraw(client, borrower1Keys, "XLM", 170_141_183_460_469_231_731_687_303_715_884_105_727n, true) // i128::MAX + .then((result) => writeBudgetSnapshot("withdraw", result)) + ).to.not.eventually.rejected; }); }); diff --git a/integration-tests/tests/soroban.client.ts b/integration-tests/tests/soroban.client.ts index 377472f3..07591df8 100644 --- a/integration-tests/tests/soroban.client.ts +++ b/integration-tests/tests/soroban.client.ts @@ -1,8 +1,12 @@ -import { Server, Contract, TimeoutInfinite, TransactionBuilder, Keypair, xdr, SorobanRpc } from "soroban-client"; +import { Server, Contract, TimeoutInfinite, TransactionBuilder, Keypair, xdr, SorobanRpc, BASE_FEE, assembleTransaction } from "soroban-client"; import { promisify } from "util"; import "./soroban.config"; import { adminKeys } from "./soroban.config"; +export type SendTransactionResult = + SorobanRpc.GetSuccessfulTransactionResponse | + [SorobanRpc.GetSuccessfulTransactionResponse, SorobanRpc.Cost]; + export class SorobanClient { client: Server; @@ -23,22 +27,29 @@ export class SorobanClient { contractId: string, method: string, signer: Keypair, + withBudget: boolean, ...args: xdr.ScVal[] - ): Promise { + ): Promise { const source = await this.client.getAccount(signer.publicKey()); const contract = new Contract(contractId); const operation = new TransactionBuilder(source, { - fee: "100", + fee: BASE_FEE, networkPassphrase: process.env.PASSPHRASE, }).addOperation(contract.call(method, ...args || [])) .setTimeout(TimeoutInfinite) .build(); + + const simulated = await this.client.simulateTransaction(operation); - const transaction = await this.client.prepareTransaction( - operation, - process.env.PASSPHRASE); + if (SorobanRpc.isSimulationError(simulated)) { + throw new Error(simulated.error); + } else if (!simulated.result) { + throw new Error(`invalid simulation: no result in ${simulated}`); + } + const transaction = assembleTransaction(operation, process.env.PASSPHRASE, simulated).build() + transaction.sign(signer); const response = await this.client.sendTransaction(transaction); @@ -64,10 +75,10 @@ export class SorobanClient { const getResult = result as SorobanRpc.GetTransactionResponse; if (getResult.status !== SorobanRpc.GetTransactionStatus.SUCCESS) { console.error('Transaction submission failed! Returning full RPC response.'); - return result; + return withBudget ? [result, simulated.cost] : result; } - return result; + return withBudget ? [result, simulated.cost] : result; } throw Error(`Transaction failed (method: ${method})`); From b0630ab28739c86281a818f9acfad49233965bf5 Mon Sep 17 00:00:00 2001 From: Maks Nabokov Date: Thu, 21 Sep 2023 11:17:26 +0300 Subject: [PATCH 2/2] review fixes --- .../budget_utilization.snap | 4 +- integration-tests/tests/pool.sut.ts | 94 +++++++------------ .../tests/pool/6.tx.budget.spec.ts | 4 +- integration-tests/tests/soroban.client.ts | 17 ++-- 4 files changed, 49 insertions(+), 70 deletions(-) rename integration-tests/{artifacts => snapshots}/budget_utilization.snap (63%) diff --git a/integration-tests/artifacts/budget_utilization.snap b/integration-tests/snapshots/budget_utilization.snap similarity index 63% rename from integration-tests/artifacts/budget_utilization.snap rename to integration-tests/snapshots/budget_utilization.snap index cf3ec8f5..88f29625 100644 --- a/integration-tests/artifacts/budget_utilization.snap +++ b/integration-tests/snapshots/budget_utilization.snap @@ -1,12 +1,12 @@ { "borrow": { - "cpuInsns": "37577012", + "cpuInsns": "37580030", "memBytes": "4027722" } } { "withdraw": { - "cpuInsns": "37631489", + "cpuInsns": "37578751", "memBytes": "4031271" } } diff --git a/integration-tests/tests/pool.sut.ts b/integration-tests/tests/pool.sut.ts index 5ca74f14..0c7f85ee 100644 --- a/integration-tests/tests/pool.sut.ts +++ b/integration-tests/tests/pool.sut.ts @@ -16,7 +16,7 @@ import { import { exec } from "child_process"; import * as fs from 'fs'; -export const BUDGET_SNAPSHOT_FILE = 'artifacts/budget_utilization.snap'; +export const BUDGET_SNAPSHOT_FILE = 'snapshots/budget_utilization.snap'; export type SlenderAsset = "XLM" | "XRP" | "USDC"; @@ -81,10 +81,9 @@ export async function mintUnderlyingTo( process.env[`SLENDER_TOKEN_${asset}`], "mint", adminKeys, - false, convertToScvAddress(to), convertToScvI128(amount) - ) as Promise + ) ); } @@ -93,16 +92,15 @@ export async function mintBurn( mintsBurns: Array ): Promise { for (let i = 0; i < mintsBurns.length; i++) { - const response = await client.sendTransaction( + const txResult = await client.sendTransaction( mintsBurns[i].asset_balance.get("asset"), mintsBurns[i].mint ? "mint" : "clawback", adminKeys, - false, convertToScvAddress(mintsBurns[i].who.toString()), convertToScvI128(mintsBurns[i].asset_balance.get("balance")) - ) as SorobanRpc.GetTransactionResponse; + ) - if (response.status != "SUCCESS") { + if (txResult.response.status != "SUCCESS") { throw Error("Failed to transfer tokens!"); } } @@ -167,13 +165,11 @@ export async function setPrice( client: SorobanClient, asset: SlenderAsset, amount: bigint, - withBudget = false ): Promise { return client.sendTransaction( process.env.SLENDER_POOL, "set_price", adminKeys, - withBudget, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); @@ -221,25 +217,21 @@ export async function borrow( signer: Keypair, asset: SlenderAsset, amount: bigint, - withBudget = false, ): Promise { - const response = await client.sendTransaction( + const txResult = await client.sendTransaction( process.env.SLENDER_POOL, "borrow", signer, - withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); - const result = parseMetaXdrToJs>( - withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr - ); + const result = parseMetaXdrToJs>(txResult.response.resultMetaXdr); await mintBurn(client, result); - return response; + return txResult; } export async function deposit( @@ -249,23 +241,20 @@ export async function deposit( amount: bigint, withBudget = false, ): Promise { - const response = await client.sendTransaction( + const txResult = await client.sendTransaction( process.env.SLENDER_POOL, "deposit", signer, - withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); - const result = parseMetaXdrToJs>( - withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr - ); + const result = parseMetaXdrToJs>(txResult.response.resultMetaXdr); await mintBurn(client, result); - return response; + return txResult; } export async function repay( @@ -275,23 +264,22 @@ export async function repay( amount: bigint, withBudget = false, ): Promise { - const response = await client.sendTransaction( + const txResult = await client.sendTransaction( process.env.SLENDER_POOL, "repay", signer, - withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount) ); const result = parseMetaXdrToJs>( - withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr + txResult.response.resultMetaXdr ); await mintBurn(client, result); - return response; + return txResult; } export async function withdraw( @@ -301,11 +289,10 @@ export async function withdraw( amount: bigint, withBudget = false, ): Promise { - const response = await client.sendTransaction( + const txResult = await client.sendTransaction( process.env.SLENDER_POOL, "withdraw", signer, - withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvI128(amount), @@ -313,12 +300,12 @@ export async function withdraw( ); const result = parseMetaXdrToJs>( - withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr + txResult.response.resultMetaXdr ); await mintBurn(client, result); - return response; + return txResult; } export async function liquidate( @@ -328,23 +315,22 @@ export async function liquidate( receiveStoken: boolean, withBudget = false, ): Promise { - const response = await client.sendTransaction( + const txResult = await client.sendTransaction( process.env.SLENDER_POOL, "liquidate", signer, - withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(who), convertToScvBool(receiveStoken) ); const result = parseMetaXdrToJs>( - withBudget ? response[0].resultMetaXdr : (response as SorobanRpc.GetSuccessfulTransactionResponse).resultMetaXdr + txResult.response.resultMetaXdr ); await mintBurn(client, result); - return response; + return txResult; } export async function collatCoeff( @@ -372,7 +358,6 @@ export async function transferStoken( process.env[`SLENDER_S_TOKEN_${asset}`], "transfer", signer, - withBudget, convertToScvAddress(signer.publicKey()), convertToScvAddress(to), convertToScvI128(amount) @@ -417,7 +402,6 @@ export async function finalizeTransfer( process.env["SLENDER_POOL"], "finalize_transfer", signer, - false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvAddress(signer.publicKey()), convertToScvAddress(to), @@ -429,15 +413,14 @@ export async function finalizeTransfer( } export function writeBudgetSnapshot(name: string, transactionResult: SendTransactionResult) { - if (Array.isArray(transactionResult)) { - const budget = transactionResult[1]; - fs.writeFileSync(BUDGET_SNAPSHOT_FILE, `${JSON.stringify({ [name]: budget }, null, 2)}\n`, { flag: 'a' }); + if (transactionResult.cost !== null && transactionResult.cost !== undefined) { + fs.writeFileSync(BUDGET_SNAPSHOT_FILE, `${JSON.stringify({ [name]: transactionResult.cost }, null, 2)}\n`, { flag: 'a' }); } } async function initContract( name: string, - callback: () => Promise, + callback: () => Promise, success: (result: T) => string = undefined ): Promise { name = `SLENDER_${name}`; @@ -447,8 +430,8 @@ async function initContract( const result = await callback(); - if (result.status == "SUCCESS") { - setEnv(name, success && success(parseMetaXdrToJs(result.resultMetaXdr)) || "TRUE"); + if (result.response.status == "SUCCESS") { + setEnv(name, success && success(parseMetaXdrToJs(result.response.resultMetaXdr)) || "TRUE"); } else { throw Error(`Transaction failed: ${name} ${JSON.stringify(result)}`); } @@ -461,12 +444,11 @@ async function initToken(client: SorobanClient, asset: SlenderAsset, name: strin process.env[`SLENDER_TOKEN_${asset}`], "initialize", adminKeys, - false, convertToScvAddress(adminKeys.publicKey()), convertToScvU32(9), convertToScvString(name), convertToScvString(asset) - ) as Promise + ) ); } @@ -477,14 +459,13 @@ async function initSToken(client: SorobanClient, asset: SlenderAsset, salt: stri process.env.SLENDER_DEPLOYER, "deploy_s_token", adminKeys, - false, convertToScvBytes(salt, "hex"), convertToScvBytes(process.env.SLENDER_S_TOKEN_HASH, "hex"), convertToScvString(`SToken ${asset}`), convertToScvString(`S${asset}`), convertToScvAddress(process.env.SLENDER_POOL), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), - ) as Promise, + ), result => result[0] ); } @@ -496,14 +477,13 @@ async function initDToken(client: SorobanClient, asset: SlenderAsset, salt: stri process.env.SLENDER_DEPLOYER, "deploy_debt_token", adminKeys, - false, convertToScvBytes(salt, "hex"), convertToScvBytes(process.env.SLENDER_DEBT_TOKEN_HASH, "hex"), convertToScvString(`DToken ${asset}`), convertToScvString(`D${asset}`), convertToScvAddress(process.env.SLENDER_POOL), convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), - ) as Promise, + ), result => result[0]); } @@ -514,7 +494,6 @@ async function initPool(client: SorobanClient, salt: string): Promise { process.env.SLENDER_DEPLOYER, "deploy_pool", adminKeys, - false, convertToScvBytes(salt, "hex"), convertToScvBytes(process.env.SLENDER_POOL_HASH, "hex"), convertToScvAddress(adminKeys.publicKey()), @@ -526,7 +505,7 @@ async function initPool(client: SorobanClient, salt: string): Promise { "max_rate": convertToScvU32(50_000), "scaling_coeff": convertToScvU32(9_000) }) - ) as Promise, + ), result => result[0] ); } @@ -538,14 +517,13 @@ async function initPoolReserve(client: SorobanClient, asset: SlenderAsset, decim process.env.SLENDER_POOL, "init_reserve", adminKeys, - false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvMap({ "debt_token_address": convertToScvAddress(process.env[`SLENDER_DEBT_TOKEN_${asset}`]), // "decimals": convertToScvU32(9), "s_token_address": convertToScvAddress(process.env[`SLENDER_S_TOKEN_${asset}`]), }) - ) as Promise + ) ); } @@ -556,7 +534,6 @@ async function initPoolCollateral(client: SorobanClient, asset: SlenderAsset): P process.env.SLENDER_POOL, "configure_as_collateral", adminKeys, - false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvMap({ "discount": convertToScvU32(6000), @@ -564,7 +541,7 @@ async function initPoolCollateral(client: SorobanClient, asset: SlenderAsset): P "liq_cap": convertToScvI128(1000000000000000n), "util_cap": convertToScvU32(9000) }) - ) as Promise + ) ); } @@ -575,10 +552,9 @@ async function initPoolPriceFeed(client: SorobanClient, feed: string, assets: st process.env.SLENDER_POOL, "set_price_feed", adminKeys, - false, convertToScvAddress(feed), convertToScvVec(assets.map(asset => convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]))) - ) as Promise + ) ); } @@ -589,10 +565,9 @@ async function initPoolBorrowing(client: SorobanClient, asset: SlenderAsset): Pr process.env.SLENDER_POOL, "enable_borrowing_on_reserve", adminKeys, - false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvBool(true) - ) as Promise + ) ); } @@ -603,9 +578,8 @@ async function initBaseAsset(client: SorobanClient, asset: SlenderAsset): Promis process.env.SLENDER_POOL, "set_base_asset", adminKeys, - false, convertToScvAddress(process.env[`SLENDER_TOKEN_${asset}`]), convertToScvBool(true) - ) as Promise + ) ); } diff --git a/integration-tests/tests/pool/6.tx.budget.spec.ts b/integration-tests/tests/pool/6.tx.budget.spec.ts index e8151a5f..e1e5b64f 100644 --- a/integration-tests/tests/pool/6.tx.budget.spec.ts +++ b/integration-tests/tests/pool/6.tx.budget.spec.ts @@ -70,7 +70,7 @@ describe("LendingPool: methods must not exceed CPU/MEM limits", function () { it("Case 1: borrow()", async function () { // Borrower1 borrows 20_000_000 USDC await expect( - borrow(client, borrower1Keys, "USDC", 20_000_000n, true) + borrow(client, borrower1Keys, "USDC", 20_000_000n) .then((result) => writeBudgetSnapshot("borrow", result)) // TODO: method name ).to.not.eventually.rejected; }); @@ -78,7 +78,7 @@ describe("LendingPool: methods must not exceed CPU/MEM limits", function () { it("Case 2: withdraw full", async function () { // Borrower1 witdraws all XLM await expect( - withdraw(client, borrower1Keys, "XLM", 170_141_183_460_469_231_731_687_303_715_884_105_727n, true) // i128::MAX + withdraw(client, borrower1Keys, "XLM", 170_141_183_460_469_231_731_687_303_715_884_105_727n) // i128::MAX .then((result) => writeBudgetSnapshot("withdraw", result)) ).to.not.eventually.rejected; }); diff --git a/integration-tests/tests/soroban.client.ts b/integration-tests/tests/soroban.client.ts index 07591df8..871dc6e5 100644 --- a/integration-tests/tests/soroban.client.ts +++ b/integration-tests/tests/soroban.client.ts @@ -3,9 +3,15 @@ import { promisify } from "util"; import "./soroban.config"; import { adminKeys } from "./soroban.config"; -export type SendTransactionResult = - SorobanRpc.GetSuccessfulTransactionResponse | - [SorobanRpc.GetSuccessfulTransactionResponse, SorobanRpc.Cost]; +export class SendTransactionResult { + response: SorobanRpc.GetSuccessfulTransactionResponse; + cost?: SorobanRpc.Cost + + constructor(response: SorobanRpc.GetSuccessfulTransactionResponse, cost?: SorobanRpc.Cost) { + this.response = response; + this.cost = cost; + } +} export class SorobanClient { client: Server; @@ -27,7 +33,6 @@ export class SorobanClient { contractId: string, method: string, signer: Keypair, - withBudget: boolean, ...args: xdr.ScVal[] ): Promise { const source = await this.client.getAccount(signer.publicKey()); @@ -75,10 +80,10 @@ export class SorobanClient { const getResult = result as SorobanRpc.GetTransactionResponse; if (getResult.status !== SorobanRpc.GetTransactionStatus.SUCCESS) { console.error('Transaction submission failed! Returning full RPC response.'); - return withBudget ? [result, simulated.cost] : result; + return new SendTransactionResult(result, simulated.cost); } - return withBudget ? [result, simulated.cost] : result; + return new SendTransactionResult(result, simulated.cost); } throw Error(`Transaction failed (method: ${method})`);