Skip to content

Commit

Permalink
Add stateChanges support to rpc.Server.simulateTransaction respon…
Browse files Browse the repository at this point in the history
…se (#963)

Each item in the array is a diff of the state as such:

```typescript
  interface LedgerEntryDiff {
    type: number;
    key: xdr.LedgerKey;
    before: xdr.LedgerEntry | null;
    after: xdr.LedgerEntry | null;
  }
```
  • Loading branch information
psheth9 committed May 30, 2024
1 parent 1b6d7aa commit e32dcd9
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 16 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ A breaking change will get clearly marked in this log.

## Unreleased

Added:

- `rpc.server.simulateTransaction` now supports optional stateChanges as mentioned below ([#963](https://github.com/stellar/js-stellar-sdk/pull/963))
- If `Before` is omitted, it constitutes a creation, if `After` is omitted, it constitutes a deletions, note that `Before` and `After` cannot be be omitted at the same time.


```
/** State Difference information */
stateChanges?: LedgerEntryChange[];
interface LedgerEntryChange{
type: number;
key: xdr.LedgerKey;
before: xdr.LedgerEntry | null;
after: xdr.LedgerEntry | null;
}
```

## [v12.0.0-rc.3](https://github.com/stellar/js-stellar-sdk/compare/v11.3.0...v12.0.0-rc.3)

Expand Down
43 changes: 28 additions & 15 deletions src/rpc/api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { AssetType, Contract, SorobanDataBuilder, xdr } from '@stellar/stellar-base';
import { Contract, SorobanDataBuilder, xdr } from '@stellar/stellar-base';

/* tslint:disable-next-line:no-namespace */
/** @namespace Api */
export namespace Api {
export interface Balance {
asset_type: AssetType.credit4 | AssetType.credit12;
asset_code: string;
asset_issuer: string;
classic: string;
smart: string;
}

export interface Cost {
cpuInsns: string;
Expand Down Expand Up @@ -175,8 +168,21 @@ export namespace Api {
value: string;
}

export interface RequestAirdropResponse {
transaction_id: string;
interface RawLedgerEntryChange {
type: number;
/** This is LedgerKey in base64 */
key: string;
/** This is xdr.LedgerEntry in base64 */
before: string | null;
/** This is xdr.LedgerEntry in base64 */
after: string | null;
}

export interface LedgerEntryChange {
type: number;
key: xdr.LedgerKey;
before: xdr.LedgerEntry | null;
after: xdr.LedgerEntry | null;
}

export type SendTransactionStatus =
Expand Down Expand Up @@ -264,6 +270,9 @@ export namespace Api {

/** present only for invocation simulation */
result?: SimulateHostFunctionResult;

/** State Difference information */
stateChanges?: LedgerEntryChange[];
}

/** Includes details about why the simulation failed */
Expand Down Expand Up @@ -333,19 +342,23 @@ export namespace Api {
id: string;
latestLedger: number;
error?: string;
// this is an xdr.SorobanTransactionData in base64
/** This is an xdr.SorobanTransactionData in base64 */
transactionData?: string;
// these are xdr.DiagnosticEvents in base64
/** These are xdr.DiagnosticEvents in base64 */
events?: string[];
minResourceFee?: string;
// This will only contain a single element if present, because only a single
// invokeHostFunctionOperation is supported per transaction.
/** This will only contain a single element if present, because only a single
* invokeHostFunctionOperation is supported per transaction.
* */
results?: RawSimulateHostFunctionResult[];
cost?: Cost;
// present if succeeded but has expired ledger entries
/** Present if succeeded but has expired ledger entries */
restorePreamble?: {
minResourceFee: string;
transactionData: string;
};

/** State Difference information */
stateChanges?: RawLedgerEntryChange[];
}
}
14 changes: 13 additions & 1 deletion src/rpc/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,19 @@ function parseSuccessful(
: xdr.ScVal.scvVoid()
};
})[0]
})
}),

...(sim.stateChanges?.length ?? 0 > 0) && {
stateChanges: sim.stateChanges?.map((entryChange) => {
return {
type: entryChange.type,
key: xdr.LedgerKey.fromXDR(entryChange.key, 'base64'),
before: entryChange.before ? xdr.LedgerEntry.fromXDR(entryChange.before, 'base64') : null,
after: entryChange.after ? xdr.LedgerEntry.fromXDR(entryChange.after, 'base64') : null,
};
})
}

};

if (!sim.restorePreamble || sim.restorePreamble.transactionData === '') {
Expand Down
153 changes: 153 additions & 0 deletions test/unit/server/soroban/simulate_transaction_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ describe("Server#simulateTransaction", async function (done) {
let contract = new StellarSdk.Contract(contractId);
let address = contract.address().toScAddress();

const accountId =
"GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";
const accountKey = xdr.LedgerKey.account(
new xdr.LedgerKeyAccount({
accountId: Keypair.fromPublicKey(accountId).xdrPublicKey(),
}),
);

const simulationResponse = await invokeSimulationResponse(address);
const parsedSimulationResponse = {
id: simulationResponse.id,
Expand All @@ -32,6 +40,22 @@ describe("Server#simulateTransaction", async function (done) {
retval: xdr.ScVal.fromXDR(simulationResponse.results[0].xdr, "base64"),
},
cost: simulationResponse.cost,
stateChanges: [
{
type: 2,
key: accountKey,
before: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}),
after: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}),
},
],
_parsed: true,
};

Expand Down Expand Up @@ -166,6 +190,53 @@ describe("Server#simulateTransaction", async function (done) {
);
});

it("works with state changes", async function () {
return invokeSimulationResponseWithStateChanges(address).then(
(simResponse) => {
const expected = cloneSimulation(parsedSimulationResponse);
expected.stateChanges = [
{
type: 2,
key: accountKey,
before: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}),
after: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}),
},
{
type: 1,
key: accountKey,
before: null,
after: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}),
},
{
type: 3,
key: accountKey,
before: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}),
after: null,
},
]

const parsed = parseRawSimulation(simResponse);
expect(parsed).to.be.deep.equal(expected);
},
);
});

it("works with errors", function () {
let simResponse = simulationResponseError();

Expand All @@ -175,6 +246,7 @@ describe("Server#simulateTransaction", async function (done) {
delete expected.cost;
delete expected.transactionData;
delete expected.minResourceFee;
delete expected.stateChanges;
expected.error = "This is an error";
expected.events = [];

Expand All @@ -200,6 +272,7 @@ function cloneSimulation(sim) {
retval: xdr.ScVal.fromXDR(sim.result.retval.toXDR()),
},
cost: sim.cost,
stateChanges: sim.stateChanges,
_parsed: sim._parsed,
};
}
Expand Down Expand Up @@ -251,6 +324,8 @@ function simulationResponseError(events) {
}

function baseSimulationResponse(results) {
const accountId = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";

return {
id: 1,
events: [],
Expand All @@ -262,6 +337,26 @@ function baseSimulationResponse(results) {
cpuInsns: "1",
memBytes: "2",
},
stateChanges: [
{
type: 2,
key: xdr.LedgerKey.account(
new xdr.LedgerKeyAccount({
accountId: Keypair.fromPublicKey(accountId).xdrPublicKey(),
}),
).toXDR("base64"),
before: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}).toXDR("base64"),
after: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}).toXDR("base64"),
}
],
};
}

Expand All @@ -275,6 +370,64 @@ async function invokeSimulationResponseWithRestoration(address) {
};
}

async function invokeSimulationResponseWithStateChanges(address) {
const accountId = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";

return {

...(await invokeSimulationResponse(address)),
stateChanges: [
{
type: 2,
key: xdr.LedgerKey.account(
new xdr.LedgerKeyAccount({
accountId: Keypair.fromPublicKey(accountId).xdrPublicKey(),
}),
).toXDR("base64"),
before: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}).toXDR("base64"),
after: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}).toXDR("base64"),
},
{
type: 1,
key: xdr.LedgerKey.account(
new xdr.LedgerKeyAccount({
accountId: Keypair.fromPublicKey(accountId).xdrPublicKey(),
}),
).toXDR("base64"),
before: null,
after: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}).toXDR("base64"),
},
{
type: 3,
key: xdr.LedgerKey.account(
new xdr.LedgerKeyAccount({
accountId: Keypair.fromPublicKey(accountId).xdrPublicKey(),
}),
).toXDR("base64"),
before: new xdr.LedgerEntry({
lastModifiedLedgerSeq: 0,
data: new xdr.LedgerEntryData(),
ext: new xdr.LedgerEntryExt(),
}).toXDR("base64"),
after: null,
},
],
};
}


describe("works with real responses", function () {
const schema = {
transactionData:
Expand Down

0 comments on commit e32dcd9

Please sign in to comment.