Skip to content

Commit

Permalink
Add support for Preview 10 ops when assembling Soroban txs. (#108)
Browse files Browse the repository at this point in the history
* Upgrade stellar-base to latest version
* Add bump and restore operation support to assembly
* Add changelog entry
  • Loading branch information
Shaptic authored Jul 11, 2023
1 parent 8554b7a commit a615931
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 150 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ A breaking change should be clearly marked in this log.

## Unreleased

## v0.9.0

### Updated
* `Server.getContractData` has an additional, optional parameter: `expirationType?: string` which should be set to either `'temporary'` or `'persistent'` depending on the type of ledger key. By default, it will attempt to fetch both, returning whichever one it finds ([#103](https://github.com/stellar/js-soroban-client/pull/103)).
* `assembleTransaction` now accepts simulation results for the new `BumpFootprintExpirationOp`s and `RestoreFootprintOp`s ([#108](https://github.com/stellar/js-soroban-client/pull/108)).
* The XDR library (`stellar-base`) has been upgraded to Preview 10's protocol format. This includes the following changes:

#### Breaking Changes
Expand All @@ -25,7 +25,6 @@ A breaking change should be clearly marked in this log.
- We have added two new operations related to state expiration in Soroban: `BumpFootprintExpiration` and `RestoreFootprint`. Please refer to their docstrings for details ([#633](https://github.com/stellar/js-stellar-base/pull/633)).



## v0.8.1

### Fix
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"eventsource": "^2.0.2",
"lodash": "^4.17.21",
"randombytes": "^2.1.0",
"stellar-base": "10.0.0-soroban.1",
"stellar-base": "10.0.0-soroban.2",
"toml": "^3.0.0",
"urijs": "^1.19.1"
}
Expand Down
54 changes: 31 additions & 23 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ export class Server {
* // Uncomment the following line to build transactions for the live network. Be
* // sure to also change the horizon hostname.
* // networkPassphrase: SorobanClient.Networks.PUBLIC,
* networkPassphrase: SorobanClient.Networks.STANDALONE
* networkPassphrase: SorobanClient.Networks.FUTURENET
* })
* // Add a contract.increment soroban contract invocation operation
* .addOperation(contract.call("increment"))
Expand All @@ -434,11 +434,15 @@ export class Server {
* });
*
* @param {Transaction | FeeBumpTransaction} transaction - The transaction to
* simulate. It should include exactly one operation, which must be a
* {@link InvokeHostFunctionOp}. Any provided footprint will be ignored.
* simulate. It should include exactly one operation, which must be one of
* {@link xdr.InvokeHostFunctionOp}, {@link xdr.BumpFootprintExpirationOp},
* or {@link xdr.RestoreFootprintOp}. Any provided footprint will be
* ignored.
*
* @returns {Promise<SorobanRpc.SimulateTransactionResponse>} Returns a
* promise to the {@link SorobanRpc.SimulateTransactionResponse} object
* with the cost, result, footprint, auth, and error of the transaction.
* with the cost, footprint, result/auth requirements (if applicable), and
* error of the transaction.
*/
public async simulateTransaction(
transaction: Transaction | FeeBumpTransaction,
Expand All @@ -452,18 +456,20 @@ export class Server {

/**
* Submit a trial contract invocation, first run a simulation of the contract
* invocation as defined on the incoming transaction, and apply the results
* to a new copy of the transaction which is then returned. Setting the ledger
* footprint and authorization, so the resulting transaction is ready for signing & sending.
* invocation as defined on the incoming transaction, and apply the results to
* a new copy of the transaction which is then returned. Setting the ledger
* footprint and authorization, so the resulting transaction is ready for
* signing & sending.
*
* The returned transaction will also have an updated fee that is the sum of fee set
* on incoming transaction with the contract resource fees estimated from simulation. It is
* adviseable to check the fee on returned transaction and validate or take appropriate
* measures for interaction with user to confirm it is acceptable.
* The returned transaction will also have an updated fee that is the sum of
* fee set on incoming transaction with the contract resource fees estimated
* from simulation. It is adviseable to check the fee on returned transaction
* and validate or take appropriate measures for interaction with user to
* confirm it is acceptable.
*
* You can call the {simulateTransaction(transaction)} method directly first if you
* want to inspect estimated fees for a given transaction in detail first if that is
* of importance.
* You can call the {@link Server.simulateTransaction} method directly first
* if you want to inspect estimated fees for a given transaction in detail
* first, if that is of importance.
*
* @example
* const contractId = '0000000000000000000000000000000000000000000000000000000000000001';
Expand Down Expand Up @@ -503,17 +509,19 @@ export class Server {
* });
*
* @param {Transaction | FeeBumpTransaction} transaction - The transaction to
* prepare. It should include exactly one operation, which must be a
* {@link InvokeHostFunctionOp}. Any provided footprint will be overwritten.
* prepare. It should include exactly one operation, which must be one of
* {@link xdr.InvokeHostFunctionOp}, {@link xdr.BumpFootprintExpirationOp},
* or {@link xdr.RestoreFootprintOp}. Any provided footprint will be
* overwritten.
* @param {string} [networkPassphrase] - Explicitly provide a network
* passphrase. If not passed, the current network passphrase will be requested
* from the server via `getNetwork`.
* @returns {Promise<Transaction | FeeBumpTransaction>} Returns a copy of the
* transaction, with the expected ledger footprint and authorizations added
* and the transaction fee will automatically be adjusted to the sum of
* the incoming transaction fee and the contract minimum resource fees
* discovered from the simulation,
* passphrase. If not passed, the current network passphrase will be
* requested from the server via {@link Server.getNetwork}.
*
* @returns {Promise<Transaction | FeeBumpTransaction>} Returns a copy of the
* transaction, with the expected authorizations (in the case of
* invocation) and ledger footprint added. The transaction fee will also
* automatically be padded with the contract's minimum resource fees
* discovered from the simulation.
*/
public async prepareTransaction(
transaction: Transaction | FeeBumpTransaction,
Expand Down
70 changes: 47 additions & 23 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,29 @@ import { SorobanRpc } from "./soroban_rpc";
export function assembleTransaction(
raw: Transaction | FeeBumpTransaction,
networkPassphrase: string,
simulation: SorobanRpc.SimulateTransactionResponse,
simulation: SorobanRpc.SimulateTransactionResponse
): Transaction {
if ("innerTransaction" in raw) {
// TODO: Handle feebump transactions
return assembleTransaction(
raw.innerTransaction,
networkPassphrase,
simulation,
simulation
);
}

if (
raw.operations.length !== 1 ||
raw.operations[0].type !== "invokeHostFunction"
) {
throw new Error(
"unsupported operation type, must be only one InvokeHostFunctionOp in the transaction.",
if (!isSorobanTransaction(raw)) {
throw new TypeError(
"unsupported transaction: must contain exactly one " +
"invokeHostFunction, bumpFootprintExpiration, or restoreFootprint " +
"operation"
);
}

if (simulation.results.length !== 1) {
throw new Error(`simulation results invalid: ${simulation.results}`);
}


const source = new Account(raw.source, `${parseInt(raw.sequence, 10) - 1}`);
const classicFeeNum = parseInt(raw.fee, 10) || 0;
const minResourceFeeNum = parseInt(simulation.minResourceFee, 10) || 0;
Expand All @@ -61,25 +59,51 @@ export function assembleTransaction(
extraSigners: raw.extraSigners,
});

// apply the auth from the simulation to the invokeHostFunction op's props
const invokeOp: Operation.InvokeHostFunction = raw.operations[0];
txnBuilder.addOperation(
Operation.invokeHostFunction({
func: invokeOp.func,
auth: (invokeOp.auth ?? []).concat(
simulation.results[0].auth?.map((a: string) =>
xdr.SorobanAuthorizationEntry.fromXDR(a, "base64")
) ?? []
),
})
);
switch (raw.operations[0].type) {
case "invokeHostFunction":
const invokeOp: Operation.InvokeHostFunction = raw.operations[0];
txnBuilder.addOperation(
Operation.invokeHostFunction({
source: invokeOp.source,
func: invokeOp.func,
// apply the auth from the simulation
auth: (invokeOp.auth ?? []).concat(
simulation.results[0].auth?.map((a: string) =>
xdr.SorobanAuthorizationEntry.fromXDR(a, "base64")
) ?? []
),
})
);
break;

case "bumpFootprintExpiration":
case "restoreFootprint":
txnBuilder.addOperation(Operation.restoreFootprint(raw.operations[0]));
break;
}

// apply the pre-built Soroban Tx Data from simulation onto the Tx
const sorobanTxData = xdr.SorobanTransactionData.fromXDR(
simulation.transactionData,
"base64",
"base64"
);
txnBuilder.setSorobanData(sorobanTxData);

return txnBuilder.build();
}
}

function isSorobanTransaction(tx: Transaction): boolean {
if (tx.operations.length !== 1) {
return false;
}

switch (tx.operations[0].type) {
case "invokeHostFunction":
case "bumpFootprintExpiration":
case "restoreFootprint":
return true;

default:
return false;
}
}
4 changes: 1 addition & 3 deletions test/unit/transaction_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,7 @@ describe("assembleTransaction", () => {
});
expect.fail();
} catch (err) {
expect(err.toString()).to.equal(
"Error: unsupported operation type, must be only one InvokeHostFunctionOp in the transaction."
);
expect(err.toString()).to.match(/TypeError: unsupported transaction/i);
}
});
});
Expand Down
Loading

0 comments on commit a615931

Please sign in to comment.