Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modified the AssembledTransaction and it's tests #1075

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 36 additions & 30 deletions src/contract/assembled_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,10 @@ export class AssembledTransaction<T> {
* Serialize the AssembledTransaction to a base64-encoded XDR string.
*/
toXDR(): string {
if(!this.built) throw new Error(
"Transaction has not yet been simulated; " +
"call `AssembledTransaction.simulate` first.",
);
if (!this.built) throw new Error(
"Transaction has not yet been simulated; " +
"call `AssembledTransaction.simulate` first.",
);
Comment on lines -381 to +384
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these changes should be included

return this.built?.toEnvelope().toXDR('base64');
}

Expand All @@ -405,17 +405,21 @@ export class AssembledTransaction<T> {
}
const method = invokeContractArgs.functionName().toString('utf-8');
const txn = new AssembledTransaction(
{ ...options,
{
...options,
Comment on lines -408 to +409
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nor these

method,
parseResultXdr: (result: xdr.ScVal) =>
spec.funcResToNative(method, result),
}
);
);
Comment on lines -413 to +414
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nor this

txn.built = built;
return txn;
}

private constructor(public options: AssembledTransactionOptions<T>) {
if (!options.publicKey) {
throw new Error("Public key not provided");
}
Comment on lines +420 to +422
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comments about this. The logic is wrong. We do not want this.

this.options.simulate = this.options.simulate ?? true;
this.server = new Server(this.options.rpcUrl, {
allowHttp: this.options.allowHttp ?? false,
Expand All @@ -441,16 +445,19 @@ export class AssembledTransaction<T> {
* simulate: false,
* })
*/
// AssembledTransaction.ts
PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved

PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines +448 to +449
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is noise

static async build<T>(
options: AssembledTransactionOptions<T>,
): Promise<AssembledTransaction<T>> {
if (!options.publicKey) {
throw new Error("Public key not provided. Have you forgotten to set the `publicKey` in AssembledTransactionOptions?");
}

Comment on lines +453 to +456
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong

const tx = new AssembledTransaction(options);
const contract = new Contract(options.contractId);

const account = await getAccount(
options,
tx.server
);
const account = await getAccount(options, tx.server);
Comment on lines -450 to +460
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong


tx.raw = new TransactionBuilder(account, {
fee: options.fee ?? BASE_FEE,
Expand All @@ -464,6 +471,7 @@ export class AssembledTransaction<T> {
return tx;
}


PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is noise

private static async buildFootprintRestoreTransaction<T>(
options: AssembledTransactionOptions<T>,
sorobanData: SorobanDataBuilder | xdr.SorobanTransactionData,
Expand All @@ -482,9 +490,9 @@ export class AssembledTransaction<T> {
return tx;
}

simulate = async ({ restore }: {restore?: boolean} = {}): Promise<this> => {
if (!this.built){
if(!this.raw) {
simulate = async ({ restore }: { restore?: boolean } = {}): Promise<this> => {
if (!this.built) {
if (!this.raw) {
throw new Error(
"Transaction has not yet been assembled; " +
"call `AssembledTransaction.build` first."
Expand Down Expand Up @@ -609,32 +617,30 @@ export class AssembledTransaction<T> {
force = false,
signTransaction = this.options.signTransaction,
}: {
/**
* If `true`, sign and send the transaction even if it is a read call
*/
force?: boolean;
/**
* You must provide this here if you did not provide one before
*/
PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
signTransaction?: ClientOptions["signTransaction"];
} = {}): Promise<void> => {
if (!this.built) {
throw new Error("Transaction has not yet been simulated");
throw new Error("Transaction has not yet been simulated.");
}

if (!force && this.isReadCall) {
throw new AssembledTransaction.Errors.NoSignatureNeeded(
"This is a read call. It requires no signature or sending. " +
"Use `force: true` to sign and send anyway."
"This is a read call. It requires no signature or sending. Use `force: true` to sign and send anyway."
);
}

if (!signTransaction) {
throw new AssembledTransaction.Errors.NoSigner(
"You must provide a signTransaction function, either when calling " +
"`signAndSend` or when initializing your Client"
"You must provide a `signTransaction` function, either when calling `signAndSend` or when initializing your Client."
);
}
// Ensure publicKey is provided for non-read calls
if (!this.options.publicKey) {
throw new Error(
"Public key not provided. Have you forgotten to set the `publicKey` in AssembledTransactionOptions?"
Comment on lines +638 to +641
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is the only needed change here. Everything except this block seems either like anti-linting or outright error. Maybe there's a test of this specific logic that should stay; I again didn't get that far.

Can you please force-push a single commit to this branch with only these specific changes? We do not want to merge anything else.

I see you pushed right to your master branch so it might be a little more complicated and error-prone, but here's a way you can do it:

  • log --pretty=oneline -n 10 --graph – this should show you the commit, eight commits ago, where you branched off what was then master on the upstream remote (not sure how your local remotes are named). Copy that commit hash.
  • git reset --hard [pasted commit hash] – this will reset all local changes to your branch
  • git pull upstream master – may as well update to the tip of the upstream branch to avoid as many conflicts as possible. Again, not sure what your local remotes are called. You can use git remote -v to see all remotes and make sure you have the name of the stellar/js-stellar-sdk one, and slot it in where I put the upstream.
  • Now you can copy-paste specific changes from this PR back into your local changes and commit them with a sensible commit message.
  • git push --force origin master – one more time, not sure what your remotes are called. Make sure to use whatever name you gave your remote, rather than origin.

);
}

// filter out contracts, as these are dealt with via cross contract calls
const sigsNeeded = this.needsNonInvokerSigningBy().filter(id => !id.startsWith('C'));
Expand All @@ -645,8 +651,7 @@ export class AssembledTransaction<T> {
);
}

const timeoutInSeconds =
this.options.timeoutInSeconds ?? DEFAULT_TIMEOUT;
const timeoutInSeconds = this.options.timeoutInSeconds ?? DEFAULT_TIMEOUT;
this.built = TransactionBuilder.cloneFrom(this.built!, {
fee: this.built!.fee,
timebounds: undefined,
Expand All @@ -668,12 +673,13 @@ export class AssembledTransaction<T> {
) as Tx;
};


PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Sends the transaction to the network to return a `SentTransaction` that
* keeps track of all the attempts to fetch the transaction.
*/
async send(){
if(!this.signed){
async send() {
if (!this.signed) {
throw new Error("The transaction has not yet been signed. Run `sign` first, or use `signAndSend` instead.");
}
const sent = await SentTransaction.init(undefined, this);
Expand All @@ -699,7 +705,7 @@ export class AssembledTransaction<T> {
*/
signTransaction?: ClientOptions["signTransaction"];
} = {}): Promise<SentTransaction<T>> => {
if(!this.signed){
if (!this.signed) {
await this.sign({ force, signTransaction });
}
return this.send();
Expand Down Expand Up @@ -813,7 +819,7 @@ export class AssembledTransaction<T> {
* If you have a pro use-case and need to override the default `authorizeEntry` function, rather than using the one in @stellar/stellar-base, you can do that! Your function needs to take at least the first argument, `entry: xdr.SorobanAuthorizationEntry`, and return a `Promise<xdr.SorobanAuthorizationEntry>`.
*
* Note that you if you pass this, then `signAuthEntry` will be ignored.
*/
*/
authorizeEntry?: typeof stellarBaseAuthorizeEntry;
} = {}): Promise<void> => {
if (!this.built)
Expand Down
30 changes: 19 additions & 11 deletions src/contract/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,42 @@ export class Client {
public readonly spec: Spec,
public readonly options: ClientOptions,
) {
this.spec.funcs().forEach((xdrFn) => {
spec.funcs().forEach((xdrFn) => {
const method = xdrFn.name().toString();
const isReadOnly = this.spec.isReadOnly(method);
PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
const assembleTransaction = (
args?: Record<string, any>,
methodOptions?: MethodOptions,
) =>
AssembledTransaction.build({
) => {
if (!isReadOnly && !this.options.publicKey) {
throw new Error(
`Public key not provided for write method "${method}". Have you forgotten to set the \`publicKey\` in ClientOptions?`
);
}
return AssembledTransaction.build({
method,
args: args && spec.funcArgsToScVals(method, args),
args: args && this.spec.funcArgsToScVals(method, args),
...options,
...methodOptions,
errorTypes: spec.errorCases().reduce(
errorTypes: this.spec.errorCases().reduce(
(acc, curr) => ({
...acc,
[curr.value()]: { message: curr.doc().toString() },
}),
{} as Pick<ClientOptions, "errorTypes">,
),
parseResultXdr: (result: xdr.ScVal) =>
spec.funcResToNative(method, result),
this.spec.funcResToNative(method, result),
});
};

// @ts-ignore error TS7053: Element implicitly has an 'any' type
this[method] =
spec.getFunc(method).inputs().length === 0
this.spec.getFunc(method).inputs().length === 0
? (opts?: MethodOptions) => assembleTransaction(undefined, opts)
: assembleTransaction;
});
}

/**
* Generates a Client instance from the provided ClientOptions and the contract's wasm hash.
* The wasmHash can be provided in either hex or base64 format.
Expand All @@ -64,7 +70,8 @@ export class Client {
* @returns {Promise<module:contract.Client>} A Promise that resolves to a Client instance.
* @throws {TypeError} If the provided options object does not contain an rpcUrl.
*/
static async fromWasmHash(wasmHash: Buffer | string,
static async fromWasmHash(
wasmHash: Buffer | string,
options: ClientOptions,
format: "hex" | "base64" = "hex"
): Promise<Client> {
Expand All @@ -77,6 +84,7 @@ export class Client {
const wasm = await server.getContractWasmByHash(wasmHash, format);
return Client.fromWasm(wasm, options);
}


/**
* Generates a Client instance from the provided ClientOptions and the contract's wasm binary.
Expand All @@ -97,7 +105,7 @@ export class Client {
const spec = new Spec(specEntryArray);
return new Client(spec, options);
}

/**
* Generates a Client instance from the provided ClientOptions, which must include the contractId and rpcUrl.
*
Expand All @@ -114,7 +122,7 @@ export class Client {
const server = new Server(rpcUrl, serverOpts);
const wasm = await server.getContractWasmByContractId(contractId);
return Client.fromWasm(wasm, options);
}
}
PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved

txFromJSON = <T>(json: string): AssembledTransaction<T> => {
const { method, ...tx } = JSON.parse(json);
Expand Down
18 changes: 18 additions & 0 deletions src/contract/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ function findCase(name: string) {
};
}



PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
function stringToScVal(str: string, ty: xdr.ScSpecType): xdr.ScVal {
switch (ty.value) {
case xdr.ScSpecType.scSpecTypeString().value:
Expand Down Expand Up @@ -485,6 +487,22 @@ export class Spec {
}
}


/**
* Determines if a given method is read-only.
PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
* Implement this based on your contract's specifications.
*
* @param {string} methodName - The name of the method.
* @returns {boolean} - Returns true if the method is read-only, false otherwise.
*/
isReadOnly(methodName: string): boolean {
// Implement logic to determine if the method is read-only.
// This could be based on naming conventions, annotations, or metadata.
// For example:
const readOnlyMethods = ["myReadMethod1", "myReadMethod2"]; // Example list
return readOnlyMethods.includes(methodName);
}

PoulavBhowmick03 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Gets the XDR functions from the spec.
* @returns {xdr.ScSpecFunctionV0[]} all contract functions
Expand Down
1 change: 1 addition & 0 deletions src/contract/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export type AssembledTransactionOptions<T = string> = MethodOptions &
method: string;
args?: any[];
parseResultXdr: (xdr: xdr.ScVal) => T;
publicKey?: ClientOptions["publicKey"];
};

/**
Expand Down
20 changes: 15 additions & 5 deletions src/contract/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { xdr, cereal, Account } from "@stellar/stellar-base";
/* eslint-disable no-else-return */
import { xdr, cereal, Account, StrKey } from "@stellar/stellar-base";
import { Server } from "../rpc";
import { type AssembledTransaction } from "./assembled_transaction";
import { NULL_ACCOUNT , AssembledTransactionOptions } from "./types";
import {AssembledTransactionOptions } from "./types";

/**
* Keep calling a `fn` for `timeoutInSeconds` seconds, if `keepWaitingIf` is
Expand Down Expand Up @@ -117,7 +118,16 @@ export async function getAccount<T>(
options: AssembledTransactionOptions<T>,
server: Server
): Promise<Account> {
return options.publicKey
? server.getAccount(options.publicKey)
: new Account(NULL_ACCOUNT, "0");
if (options.publicKey) {
// Validate the publicKey format
if (!StrKey.isValidEd25519PublicKey(options.publicKey)) {
throw new Error("Invalid `publicKey` format. Please provide a valid Stellar ed25519 public key.");
}
return server.getAccount(options.publicKey);
} else {
// For read-only operations, fetching the account might not be necessary.
// Decide based on your application's logic.
// If it's necessary, throw an error.
throw new Error("Public key is required to fetch the account. Please provide a valid `publicKey`.");
}
}
Loading