Skip to content

Commit

Permalink
stellar#76: added test coverage on invokehostfn simulation
Browse files Browse the repository at this point in the history
  • Loading branch information
sreuland committed May 11, 2023
1 parent 79eff07 commit d15d753
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 124 deletions.
7 changes: 1 addition & 6 deletions src/soroban_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ export namespace SorobanRpc {
smart: string;
}

export interface Cost {
cpuInsns: string;
memBytes: string;
}

export interface GetHealthResponse {
status: "healthy";
}
Expand Down Expand Up @@ -120,13 +115,13 @@ export namespace SorobanRpc {
}

export interface SimulateTransactionResponse {
id: string;
error?: jsonrpc.Error;
// this is SorobanTransactionData XDR in base64
transactionData: string;
events: string[];
minResourceFee: string;
results: SimulateHostFunctionResult[];
cost: Cost;
latestLedger: number;
}
}
42 changes: 20 additions & 22 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,29 @@ export function assembleTransaction(
raw.operations[0].type !== "invokeHostFunction"
) {
throw new Error(
"unsupported operation type for soroban, must be only one InvokeHostFunctionOp in the tx.",
"unsupported operation type, must be only one InvokeHostFunctionOp in the transaction.",
);
}

const rawInvokeHostFunctionOp: any = raw.operations[0];

if (
!rawInvokeHostFunctionOp.functions ||
!simulation.results ||
rawInvokeHostFunctionOp.functions.length !== simulation.results.length
) {
throw new Error(
"preflight simulation results do not contain same count of HostFunctions that InvokeHostFunctionOp in the transaction has.",
);
}

// TODO: Figure out a cleaner way to clone this transaction.
const source = new Account(raw.source, `${parseInt(raw.sequence, 10) - 1}`);
const authDecoratedHostFunctions: xdr.HostFunction[] = [];

const txnBuilder = new TransactionBuilder(source, {
fee: (
parseInt(raw.fee, 10) ||
0 + parseInt(simulation.minResourceFee, 10) ||
0
fee: Math.max(
parseInt(raw.fee, 10) || 0,
parseInt(simulation.minResourceFee, 10) || 0,
).toString(),
memo: raw.memo,
networkPassphrase,
Expand All @@ -50,20 +62,6 @@ export function assembleTransaction(
extraSigners: raw.extraSigners,
});

// can only be single Op in a Tx that has InvokeHostFunctionOp.
const rawInvokeHostFunctionOp: any = raw.operations[0];

const authDecoratedHostFunctions: xdr.HostFunction[] = [];
if (
!rawInvokeHostFunctionOp.functions ||
!simulation.results ||
rawInvokeHostFunctionOp.functions.length !== simulation.results.length
) {
throw new Error(
"preflight simulation for soroban does not have same HostFunction total as InvokeHostFunctionOp in the tx.",
);
}

// apply the pre-built Auth from simulation onto each Tx/Op/HostFunction invocation
for (
let hostFnIndex = 0;
Expand All @@ -79,7 +77,7 @@ export function assembleTransaction(
}

txnBuilder.addOperation(
Operation.invokeHostFunction({
Operation.invokeHostFunctions({
functions: authDecoratedHostFunctions,
}),
);
Expand All @@ -95,7 +93,7 @@ function buildExt(sorobanTxDataStr: string) {
sorobanTxDataStr,
"base64",
);
const txExt: xdr.TransactionExt = new xdr.TransactionExt();
const txExt: xdr.TransactionExt = new xdr.TransactionExt(1);
txExt.sorobanData(sorobanTxData);
return txExt;
}
Expand Down
103 changes: 66 additions & 37 deletions test/unit/server/simulate_transaction_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,71 @@ describe("Server#simulateTransaction", function() {
"56199647068161",
);

const simulationResponse = {
transactionData: new SorobanClient.xdr.SorobanTransactionData({
resources: new SorobanClient.xdr.SorobanResources({
footprint: new SorobanClient.xdr.LedgerFootprint({
readOnly: [],
readWrite: [],
}),
instructions: 0,
readBytes: 0,
writeBytes: 0,
extendedMetaDataSizeBytes: 0,
}),
refundableFee: SorobanClient.xdr.Int64.fromString("0"),
ext: new SorobanClient.xdr.ExtensionPoint(0),
}).toXDR("base64"),
events: [],
minResourceFee: "15",
results: [
{
auth: [
new SorobanClient.xdr.ContractAuth({
addressWithNonce: null,
rootInvocation: new SorobanClient.xdr.AuthorizedInvocation({
contractId: Buffer.alloc(32),
functionName: "fn",
args: [],
subInvocations: [],
}),
signatureArgs: [],
}).toXDR("base64"),
],
xdr: SorobanClient.xdr.ScVal.scvU32(0)
.toXDR()
.toString("base64"),
},
],
latestLedger: 3,
};

beforeEach(function() {
this.server = new SorobanClient.Server(serverUrl);
this.axiosMock = sinon.mock(AxiosClient);
let transaction = new SorobanClient.TransactionBuilder(account, {
fee: 100,
networkPassphrase: SorobanClient.Networks.TESTNET,
v1: true,
})
.addOperation(
SorobanClient.Operation.payment({
destination:
"GASOCNHNNLYFNMDJYQ3XFMI7BYHIOCFW3GJEOWRPEGK2TDPGTG2E5EDW",
asset: SorobanClient.Asset.native(),
amount: "100.50",
}),
)
.setTimeout(SorobanClient.TimeoutInfinite)
.build();
const source = new SorobanClient.Account(
"GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI",
"1",
);
function emptyContractTransaction() {
return new SorobanClient.TransactionBuilder(source, {
fee: 100,
networkPassphrase: "Test",
v1: true,
})
.addOperation(
SorobanClient.Operation.invokeHostFunction({
args: new SorobanClient.xdr.HostFunctionArgs.hostFunctionTypeInvokeContract(
[],
),
auth: [],
}),
)
.setTimeout(SorobanClient.TimeoutInfinite)
.build();
}

const transaction = emptyContractTransaction();
transaction.sign(keypair);

this.transaction = transaction;
Expand All @@ -38,26 +85,6 @@ describe("Server#simulateTransaction", function() {
this.axiosMock.restore();
});

const result = {
cost: {
cpuInsns: "10000",
memBytes: "10000",
},
results: [
{
xdr: SorobanClient.xdr.ScVal.scvU32(0)
.toXDR()
.toString("base64"),
footprint: new SorobanClient.xdr.LedgerFootprint({
readOnly: [],
readWrite: [],
}).toXDR("base64"),
events: [],
},
],
latestLedger: 1,
};

it("simulates a transaction", function(done) {
this.axiosMock
.expects("post")
Expand All @@ -67,12 +94,14 @@ describe("Server#simulateTransaction", function() {
method: "simulateTransaction",
params: [this.blob],
})
.returns(Promise.resolve({ data: { id: 1, result } }));
.returns(
Promise.resolve({ data: { id: 1, result: simulationResponse } }),
);

this.server
.simulateTransaction(this.transaction)
.then(function(response) {
expect(response).to.be.deep.equal(result);
expect(response).to.be.deep.equal(simulationResponse);
done();
})
.catch(function(err) {
Expand Down
Loading

0 comments on commit d15d753

Please sign in to comment.