Skip to content

Commit

Permalink
Add bump and restore operation support to assembly
Browse files Browse the repository at this point in the history
  • Loading branch information
Shaptic committed Jul 10, 2023
1 parent c597f01 commit e638d1b
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 46 deletions.
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
82 changes: 59 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,63 @@ 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":
const bumpOp: Operation.BumpFootprintExpiration = raw.operations[0];
txnBuilder.addOperation(
Operation.bumpFootprintExpiration({
source: bumpOp.source,
ledgersToExpire: bumpOp.ledgersToExpire,
})
);
break;

case "restoreFootprint":
const restoreOp: Operation.RestoreFootprint = raw.operations[0];
txnBuilder.addOperation(
Operation.restoreFootprint({ source: restoreOp.source })
);
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;
}
}

0 comments on commit e638d1b

Please sign in to comment.