cashscript 0.10.0-next.6 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # CashScript
2
2
 
3
3
  ![Build Status](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml/badge.svg)
4
- [![Coverage Status](https://img.shields.io/codecov/c/github/Bitcoin-com/cashscript.svg)](https://codecov.io/gh/Bitcoin-com/cashscript/)
4
+ [![Coverage Status](https://img.shields.io/codecov/c/github/CashScript/cashscript.svg)](https://codecov.io/gh/CashScript/cashscript/)
5
5
  [![NPM Version](https://img.shields.io/npm/v/cashscript.svg)](https://www.npmjs.com/package/cashscript)
6
6
  [![NPM Monthly Downloads](https://img.shields.io/npm/dm/cashscript.svg)](https://www.npmjs.com/package/cashscript)
7
7
  [![NPM License](https://img.shields.io/npm/l/cashscript.svg)](https://www.npmjs.com/package/cashscript)
@@ -1,9 +1,10 @@
1
1
  import { AbiFunction, Artifact } from '@cashscript/utils';
2
2
  import SignatureTemplate from './SignatureTemplate.js';
3
- export declare type Argument = bigint | boolean | string | Uint8Array | SignatureTemplate;
4
- export declare type EncodedArgument = Uint8Array | SignatureTemplate;
5
- export declare type EncodeFunction = (arg: Argument, typeStr: string) => EncodedArgument;
6
- export declare function encodeArgument(argument: Argument, typeStr: string): EncodedArgument;
7
- export declare const encodeConstructorArguments: (artifact: Artifact, constructorArgs: Argument[], encodeFunction?: EncodeFunction) => Uint8Array[];
8
- export declare const encodeFunctionArguments: (abiFunction: AbiFunction, functionArgs: Argument[], encodeFunction?: EncodeFunction) => EncodedArgument[];
9
- export declare function encodeArgumentForLibauthTemplate(argument: Argument, typeStr: string): Uint8Array | SignatureTemplate;
3
+ export type ConstructorArgument = bigint | boolean | string | Uint8Array;
4
+ export type FunctionArgument = ConstructorArgument | SignatureTemplate;
5
+ export type EncodedConstructorArgument = Uint8Array;
6
+ export type EncodedFunctionArgument = Uint8Array | SignatureTemplate;
7
+ export type EncodeFunction = (arg: FunctionArgument, typeStr: string) => EncodedFunctionArgument;
8
+ export declare function encodeFunctionArgument(argument: FunctionArgument, typeStr: string): EncodedFunctionArgument;
9
+ export declare const encodeConstructorArguments: (artifact: Artifact, constructorArgs: ConstructorArgument[], encodeFunction?: EncodeFunction) => Uint8Array[];
10
+ export declare const encodeFunctionArguments: (abiFunction: AbiFunction, functionArgs: FunctionArgument[], encodeFunction?: EncodeFunction) => EncodedFunctionArgument[];
package/dist/Argument.js CHANGED
@@ -2,7 +2,7 @@ import { hexToBin } from '@bitauth/libauth';
2
2
  import { BytesType, encodeBool, encodeInt, encodeString, parseType, PrimitiveType, } from '@cashscript/utils';
3
3
  import { TypeError } from './Errors.js';
4
4
  import SignatureTemplate from './SignatureTemplate.js';
5
- export function encodeArgument(argument, typeStr) {
5
+ export function encodeFunctionArgument(argument, typeStr) {
6
6
  let type = parseType(typeStr);
7
7
  if (type === PrimitiveType.BOOL) {
8
8
  if (typeof argument !== 'boolean') {
@@ -50,7 +50,7 @@ export function encodeArgument(argument, typeStr) {
50
50
  }
51
51
  return argument;
52
52
  }
53
- export const encodeConstructorArguments = (artifact, constructorArgs, encodeFunction = encodeArgument) => {
53
+ export const encodeConstructorArguments = (artifact, constructorArgs, encodeFunction = encodeFunctionArgument) => {
54
54
  // Check there's no signature templates in the constructor
55
55
  if (constructorArgs.some((arg) => arg instanceof SignatureTemplate)) {
56
56
  throw new Error('Cannot use signatures in constructor');
@@ -59,15 +59,8 @@ export const encodeConstructorArguments = (artifact, constructorArgs, encodeFunc
59
59
  .map((arg, i) => encodeFunction(arg, artifact.constructorInputs[i].type));
60
60
  return encodedArgs;
61
61
  };
62
- export const encodeFunctionArguments = (abiFunction, functionArgs, encodeFunction = encodeArgument) => {
62
+ export const encodeFunctionArguments = (abiFunction, functionArgs, encodeFunction = encodeFunctionArgument) => {
63
63
  const encodedArgs = functionArgs.map((arg, i) => encodeFunction(arg, abiFunction.inputs[i].type));
64
64
  return encodedArgs;
65
65
  };
66
- // Note: BitAuth IDE requires 0 to be encoded as a single byte (rather than the default empty byte array)
67
- // TODO: Double check this with Pat
68
- export function encodeArgumentForLibauthTemplate(argument, typeStr) {
69
- if (typeStr === PrimitiveType.INT && argument === 0n)
70
- return Uint8Array.from([0]);
71
- return encodeArgument(argument, typeStr);
72
- }
73
66
  //# sourceMappingURL=Argument.js.map
@@ -1,6 +1,6 @@
1
1
  import { Artifact, Script } from '@cashscript/utils';
2
2
  import { Transaction } from './Transaction.js';
3
- import { Argument } from './Argument.js';
3
+ import { ConstructorArgument, FunctionArgument } from './Argument.js';
4
4
  import { Unlocker, ContractOptions, Utxo, AddressType } from './interfaces.js';
5
5
  import NetworkProvider from './network/NetworkProvider.js';
6
6
  export declare class Contract {
@@ -18,11 +18,11 @@ export declare class Contract {
18
18
  provider: NetworkProvider;
19
19
  addressType: AddressType;
20
20
  encodedConstructorArgs: Uint8Array[];
21
- constructor(artifact: Artifact, constructorArgs: Argument[], options?: ContractOptions | undefined);
21
+ constructor(artifact: Artifact, constructorArgs: ConstructorArgument[], options?: ContractOptions | undefined);
22
22
  getBalance(): Promise<bigint>;
23
23
  getUtxos(): Promise<Utxo[]>;
24
24
  private createFunction;
25
25
  private createUnlocker;
26
26
  }
27
- export declare type ContractFunction = (...args: Argument[]) => Transaction;
28
- export declare type ContractUnlocker = (...args: Argument[]) => Unlocker;
27
+ export type ContractFunction = (...args: FunctionArgument[]) => Transaction;
28
+ export type ContractUnlocker = (...args: FunctionArgument[]) => Unlocker;
package/dist/Contract.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { binToHex } from '@bitauth/libauth';
2
2
  import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
3
3
  import { Transaction } from './Transaction.js';
4
- import { encodeArgument, encodeConstructorArguments, encodeFunctionArguments } from './Argument.js';
4
+ import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments } from './Argument.js';
5
5
  import { addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress, } from './utils.js';
6
6
  import SignatureTemplate from './SignatureTemplate.js';
7
7
  import { ElectrumNetworkProvider } from './network/index.js';
@@ -77,7 +77,7 @@ export class Contract {
77
77
  }
78
78
  const bytecode = scriptToBytecode(this.redeemScript);
79
79
  const encodedArgs = args
80
- .map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
80
+ .map((arg, i) => encodeFunctionArgument(arg, abiFunction.inputs[i].type));
81
81
  const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
82
82
  // TODO: Remove old-style covenant code for v1.0 release
83
83
  let covenantHashType = -1;
package/dist/Errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RequireStatement, Type } from '@cashscript/utils';
1
+ import { Artifact, RequireStatement, Type } from '@cashscript/utils';
2
2
  export declare class TypeError extends Error {
3
3
  constructor(actual: string, expected: Type);
4
4
  }
@@ -8,64 +8,28 @@ export declare class OutputSatoshisTooSmallError extends Error {
8
8
  export declare class TokensToNonTokenAddressError extends Error {
9
9
  constructor(address: string);
10
10
  }
11
+ export declare class NoDebugInformationInArtifactError extends Error {
12
+ constructor();
13
+ }
11
14
  export declare class FailedTransactionError extends Error {
12
15
  reason: string;
13
16
  bitauthUri?: string | undefined;
14
17
  constructor(reason: string, bitauthUri?: string | undefined);
15
18
  }
16
- export declare class FailedRequireError extends Error {
17
- contractName: string;
19
+ export declare class FailedTransactionEvaluationError extends FailedTransactionError {
20
+ artifact: Artifact;
21
+ failingInstructionPointer: number;
22
+ inputIndex: number;
23
+ bitauthUri: string;
24
+ libauthErrorMessage: string;
25
+ constructor(artifact: Artifact, failingInstructionPointer: number, inputIndex: number, bitauthUri: string, libauthErrorMessage: string);
26
+ }
27
+ export declare class FailedRequireError extends FailedTransactionError {
28
+ artifact: Artifact;
29
+ failingInstructionPointer: number;
18
30
  requireStatement: RequireStatement;
19
31
  inputIndex: number;
32
+ bitauthUri: string;
20
33
  libauthErrorMessage?: string | undefined;
21
- bitauthUri?: string;
22
- constructor(contractName: string, requireStatement: RequireStatement, inputIndex: number, libauthErrorMessage?: string | undefined);
23
- }
24
- export declare enum NodeErrorReason {
25
- EVAL_FALSE = "Script evaluated without error but finished with a false/empty top stack element",
26
- VERIFY = "Script failed an OP_VERIFY operation",
27
- EQUALVERIFY = "Script failed an OP_EQUALVERIFY operation",
28
- CHECKMULTISIGVERIFY = "Script failed an OP_CHECKMULTISIGVERIFY operation",
29
- CHECKSIGVERIFY = "Script failed an OP_CHECKSIGVERIFY operation",
30
- CHECKDATASIGVERIFY = "Script failed an OP_CHECKDATASIGVERIFY operation",
31
- NUMEQUALVERIFY = "Script failed an OP_NUMEQUALVERIFY operation",
32
- SCRIPT_SIZE = "Script is too big",
33
- PUSH_SIZE = "Push value size limit exceeded",
34
- OP_COUNT = "Operation limit exceeded",
35
- STACK_SIZE = "Stack size limit exceeded",
36
- SIG_COUNT = "Signature count negative or greater than pubkey count",
37
- PUBKEY_COUNT = "Pubkey count negative or limit exceeded",
38
- INVALID_OPERAND_SIZE = "Invalid operand size",
39
- INVALID_NUMBER_RANGE = "Given operand is not a number within the valid range",
40
- IMPOSSIBLE_ENCODING = "The requested encoding is impossible to satisfy",
41
- INVALID_SPLIT_RANGE = "Invalid OP_SPLIT range",
42
- INVALID_BIT_COUNT = "Invalid number of bit set in OP_CHECKMULTISIG",
43
- BAD_OPCODE = "Opcode missing or not understood",
44
- DISABLED_OPCODE = "Attempted to use a disabled opcode",
45
- INVALID_STACK_OPERATION = "Operation not valid with the current stack size",
46
- INVALID_ALTSTACK_OPERATION = "Operation not valid with the current altstack size",
47
- OP_RETURN = "OP_RETURN was encountered",
48
- UNBALANCED_CONDITIONAL = "Invalid OP_IF construction",
49
- DIV_BY_ZERO = "Division by zero error",
50
- MOD_BY_ZERO = "Modulo by zero error",
51
- INVALID_BITFIELD_SIZE = "Bitfield of unexpected size error",
52
- INVALID_BIT_RANGE = "Bitfield's bit out of the expected range",
53
- NEGATIVE_LOCKTIME = "Negative locktime",
54
- UNSATISFIED_LOCKTIME = "Locktime requirement not satisfied",
55
- SIG_HASHTYPE = "Signature hash type missing or not understood",
56
- SIG_DER = "Non-canonical DER signature",
57
- MINIMALDATA = "Data push larger than necessary",
58
- SIG_PUSHONLY = "Only push operators allowed in signature scripts",
59
- SIG_HIGH_S = "Non-canonical signature: S value is unnecessarily high",
60
- MINIMALIF = "OP_IF/NOTIF argument must be minimal",
61
- SIG_NULLFAIL = "Signature must be zero for failed CHECK(MULTI)SIG operation",
62
- SIG_BADLENGTH = "Signature cannot be 65 bytes in CHECKMULTISIG",
63
- SIG_NONSCHNORR = "Only Schnorr signatures allowed in this operation",
64
- DISCOURAGE_UPGRADABLE_NOPS = "NOPx reserved for soft-fork upgrades",
65
- PUBKEYTYPE = "Public key is neither compressed or uncompressed",
66
- CLEANSTACK = "Script did not clean its stack",
67
- NONCOMPRESSED_PUBKEY = "Using non-compressed public key",
68
- ILLEGAL_FORKID = "Illegal use of SIGHASH_FORKID",
69
- MUST_USE_FORKID = "Signature must use SIGHASH_FORKID",
70
- UNKNOWN = "unknown error"
34
+ constructor(artifact: Artifact, failingInstructionPointer: number, requireStatement: RequireStatement, inputIndex: number, bitauthUri: string, libauthErrorMessage?: string | undefined);
71
35
  }
package/dist/Errors.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { sourceMapToLocationData } from '@cashscript/utils';
1
2
  export class TypeError extends Error {
2
3
  constructor(actual, expected) {
3
4
  super(`Found type '${actual}' where type '${expected.toString()}' was expected`);
@@ -13,73 +14,68 @@ export class TokensToNonTokenAddressError extends Error {
13
14
  super(`Tried to send tokens to an address without token support, ${address}.`);
14
15
  }
15
16
  }
17
+ export class NoDebugInformationInArtifactError extends Error {
18
+ constructor() {
19
+ super('No debug information found in artifact, please recompile with cashc version 0.10.0 or newer.');
20
+ }
21
+ }
16
22
  export class FailedTransactionError extends Error {
17
23
  constructor(reason, bitauthUri) {
18
- super(`${reason}\n\nBitauth URI: ${bitauthUri}`);
24
+ super(`${reason}${bitauthUri ? `\n\nBitauth URI: ${bitauthUri}` : ''}`);
19
25
  this.reason = reason;
20
26
  this.bitauthUri = bitauthUri;
21
27
  }
22
28
  }
23
- // TODO: Add tests for some non-require evaluation errors (e.g. invalid op_split range)
24
- export class FailedRequireError extends Error {
25
- constructor(contractName, requireStatement, inputIndex, libauthErrorMessage) {
26
- const baseMessage = `${contractName}.cash:${requireStatement.line} Require statement failed at line ${requireStatement.line}`;
27
- const fullMessage = `${baseMessage} with the following message: ${requireStatement.message}`;
28
- super(requireStatement.message ? fullMessage : baseMessage);
29
- this.contractName = contractName;
29
+ export class FailedTransactionEvaluationError extends FailedTransactionError {
30
+ constructor(artifact, failingInstructionPointer, inputIndex, bitauthUri, libauthErrorMessage) {
31
+ let message = `${artifact.contractName}.cash Error in transaction at input ${inputIndex} in contract ${artifact.contractName}.cash.\nReason: ${libauthErrorMessage}`;
32
+ if (artifact.debug) {
33
+ const { statement, lineNumber } = getLocationDataForInstructionPointer(artifact, failingInstructionPointer);
34
+ message = `${artifact.contractName}.cash:${lineNumber} Error in transaction at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}.\nReason: ${libauthErrorMessage}\nFailing statement: ${statement}`;
35
+ }
36
+ super(message, bitauthUri);
37
+ this.artifact = artifact;
38
+ this.failingInstructionPointer = failingInstructionPointer;
39
+ this.inputIndex = inputIndex;
40
+ this.bitauthUri = bitauthUri;
41
+ this.libauthErrorMessage = libauthErrorMessage;
42
+ }
43
+ }
44
+ export class FailedRequireError extends FailedTransactionError {
45
+ constructor(artifact, failingInstructionPointer, requireStatement, inputIndex, bitauthUri, libauthErrorMessage) {
46
+ let { statement, lineNumber } = getLocationDataForInstructionPointer(artifact, failingInstructionPointer);
47
+ if (!statement.includes('require')) {
48
+ statement = requireStatement.message
49
+ ? `require(${statement}, "${requireStatement.message}")`
50
+ : `require(${statement})`;
51
+ // Sometimes in reconstructed multiline require statements, we get double commas
52
+ statement = statement.replace(/,,/g, ',');
53
+ }
54
+ const baseMessage = `${artifact.contractName}.cash:${lineNumber} Require statement failed at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}`;
55
+ const baseMessageWithRequireMessage = `${baseMessage} with the following message: ${requireStatement.message}`;
56
+ const fullMessage = `${requireStatement.message ? baseMessageWithRequireMessage : baseMessage}.\nFailing statement: ${statement}`;
57
+ super(fullMessage, bitauthUri);
58
+ this.artifact = artifact;
59
+ this.failingInstructionPointer = failingInstructionPointer;
30
60
  this.requireStatement = requireStatement;
31
61
  this.inputIndex = inputIndex;
62
+ this.bitauthUri = bitauthUri;
32
63
  this.libauthErrorMessage = libauthErrorMessage;
33
64
  }
34
65
  }
35
- // TODO: Expand these reasons with non-script failures (like tx-mempool-conflict)
36
- export var NodeErrorReason;
37
- (function (NodeErrorReason) {
38
- NodeErrorReason["EVAL_FALSE"] = "Script evaluated without error but finished with a false/empty top stack element";
39
- NodeErrorReason["VERIFY"] = "Script failed an OP_VERIFY operation";
40
- NodeErrorReason["EQUALVERIFY"] = "Script failed an OP_EQUALVERIFY operation";
41
- NodeErrorReason["CHECKMULTISIGVERIFY"] = "Script failed an OP_CHECKMULTISIGVERIFY operation";
42
- NodeErrorReason["CHECKSIGVERIFY"] = "Script failed an OP_CHECKSIGVERIFY operation";
43
- NodeErrorReason["CHECKDATASIGVERIFY"] = "Script failed an OP_CHECKDATASIGVERIFY operation";
44
- NodeErrorReason["NUMEQUALVERIFY"] = "Script failed an OP_NUMEQUALVERIFY operation";
45
- NodeErrorReason["SCRIPT_SIZE"] = "Script is too big";
46
- NodeErrorReason["PUSH_SIZE"] = "Push value size limit exceeded";
47
- NodeErrorReason["OP_COUNT"] = "Operation limit exceeded";
48
- NodeErrorReason["STACK_SIZE"] = "Stack size limit exceeded";
49
- NodeErrorReason["SIG_COUNT"] = "Signature count negative or greater than pubkey count";
50
- NodeErrorReason["PUBKEY_COUNT"] = "Pubkey count negative or limit exceeded";
51
- NodeErrorReason["INVALID_OPERAND_SIZE"] = "Invalid operand size";
52
- NodeErrorReason["INVALID_NUMBER_RANGE"] = "Given operand is not a number within the valid range";
53
- NodeErrorReason["IMPOSSIBLE_ENCODING"] = "The requested encoding is impossible to satisfy";
54
- NodeErrorReason["INVALID_SPLIT_RANGE"] = "Invalid OP_SPLIT range";
55
- NodeErrorReason["INVALID_BIT_COUNT"] = "Invalid number of bit set in OP_CHECKMULTISIG";
56
- NodeErrorReason["BAD_OPCODE"] = "Opcode missing or not understood";
57
- NodeErrorReason["DISABLED_OPCODE"] = "Attempted to use a disabled opcode";
58
- NodeErrorReason["INVALID_STACK_OPERATION"] = "Operation not valid with the current stack size";
59
- NodeErrorReason["INVALID_ALTSTACK_OPERATION"] = "Operation not valid with the current altstack size";
60
- NodeErrorReason["OP_RETURN"] = "OP_RETURN was encountered";
61
- NodeErrorReason["UNBALANCED_CONDITIONAL"] = "Invalid OP_IF construction";
62
- NodeErrorReason["DIV_BY_ZERO"] = "Division by zero error";
63
- NodeErrorReason["MOD_BY_ZERO"] = "Modulo by zero error";
64
- NodeErrorReason["INVALID_BITFIELD_SIZE"] = "Bitfield of unexpected size error";
65
- NodeErrorReason["INVALID_BIT_RANGE"] = "Bitfield's bit out of the expected range";
66
- NodeErrorReason["NEGATIVE_LOCKTIME"] = "Negative locktime";
67
- NodeErrorReason["UNSATISFIED_LOCKTIME"] = "Locktime requirement not satisfied";
68
- NodeErrorReason["SIG_HASHTYPE"] = "Signature hash type missing or not understood";
69
- NodeErrorReason["SIG_DER"] = "Non-canonical DER signature";
70
- NodeErrorReason["MINIMALDATA"] = "Data push larger than necessary";
71
- NodeErrorReason["SIG_PUSHONLY"] = "Only push operators allowed in signature scripts";
72
- NodeErrorReason["SIG_HIGH_S"] = "Non-canonical signature: S value is unnecessarily high";
73
- NodeErrorReason["MINIMALIF"] = "OP_IF/NOTIF argument must be minimal";
74
- NodeErrorReason["SIG_NULLFAIL"] = "Signature must be zero for failed CHECK(MULTI)SIG operation";
75
- NodeErrorReason["SIG_BADLENGTH"] = "Signature cannot be 65 bytes in CHECKMULTISIG";
76
- NodeErrorReason["SIG_NONSCHNORR"] = "Only Schnorr signatures allowed in this operation";
77
- NodeErrorReason["DISCOURAGE_UPGRADABLE_NOPS"] = "NOPx reserved for soft-fork upgrades";
78
- NodeErrorReason["PUBKEYTYPE"] = "Public key is neither compressed or uncompressed";
79
- NodeErrorReason["CLEANSTACK"] = "Script did not clean its stack";
80
- NodeErrorReason["NONCOMPRESSED_PUBKEY"] = "Using non-compressed public key";
81
- NodeErrorReason["ILLEGAL_FORKID"] = "Illegal use of SIGHASH_FORKID";
82
- NodeErrorReason["MUST_USE_FORKID"] = "Signature must use SIGHASH_FORKID";
83
- NodeErrorReason["UNKNOWN"] = "unknown error";
84
- })(NodeErrorReason || (NodeErrorReason = {}));
66
+ const getLocationDataForInstructionPointer = (artifact, instructionPointer) => {
67
+ const locationData = sourceMapToLocationData(artifact.debug.sourceMap);
68
+ // We subtract the constructor inputs because these are present in the evaluation (and thus the instruction pointer)
69
+ // but they are not present in the source code (and thus the location data)
70
+ const modifiedInstructionPointer = instructionPointer - artifact.constructorInputs.length;
71
+ const { location } = locationData[modifiedInstructionPointer];
72
+ const failingLines = artifact.source.split('\n').slice(location.start.line - 1, location.end.line);
73
+ // Slice off the start and end of the statement's start and end lines to only return the failing part
74
+ // Note that we first slice off the end, to avoid shifting the end column index
75
+ failingLines[failingLines.length - 1] = failingLines[failingLines.length - 1].slice(0, location.end.column);
76
+ failingLines[0] = failingLines[0].slice(location.start.column);
77
+ const statement = failingLines.join('\n');
78
+ const lineNumber = location.start.line;
79
+ return { statement, lineNumber };
80
+ };
85
81
  //# sourceMappingURL=Errors.js.map
@@ -1,49 +1,55 @@
1
- import { PrimitiveType, bytecodeToScript, formatBitAuthScript, } from '@cashscript/utils';
1
+ import { bytecodeToScript, formatBitAuthScript, } from '@cashscript/utils';
2
2
  import { hexToBin, decodeTransaction, binToHex, binToBase64, utf8ToBin, isHex, } from '@bitauth/libauth';
3
3
  import { deflate } from 'pako';
4
- import { isUtxoP2PKH, } from './interfaces.js';
4
+ import { isUtxoP2PKH, SignatureAlgorithm, HashType, } from './interfaces.js';
5
5
  import SignatureTemplate from './SignatureTemplate.js';
6
6
  import { addressToLockScript, extendedStringify, snakeCase, zip } from './utils.js';
7
- // TODO: Can we change this so we don't need to pass in both the transaction and the transactionHex?
8
7
  export const buildTemplate = async ({ transaction, transactionHex = undefined, // set this argument to prevent unnecessary call `transaction.build()`
9
8
  }) => {
10
9
  const contract = transaction.contract;
11
10
  const txHex = transactionHex ?? await transaction.build();
12
- const hasSignatureTemplates = transaction.inputs.filter((input) => isUtxoP2PKH(input)).length > 0;
13
11
  const template = {
14
12
  $schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
15
13
  description: 'Imported from cashscript',
16
14
  name: contract.artifact.contractName,
17
15
  supported: ['BCH_2023_05'],
18
16
  version: 0,
19
- entities: generateTemplateEntities(contract.artifact, transaction.abiFunction),
17
+ entities: generateTemplateEntities(contract.artifact, transaction.abiFunction, transaction.encodedFunctionArgs),
20
18
  scripts: generateTemplateScripts(contract.artifact, contract.addressType, transaction.abiFunction, transaction.encodedFunctionArgs, contract.encodedConstructorArgs),
21
- scenarios: generateTemplateScenarios(contract, transaction, txHex, contract.artifact, transaction.abiFunction, transaction.encodedFunctionArgs, contract.encodedConstructorArgs, hasSignatureTemplates),
19
+ scenarios: generateTemplateScenarios(contract, transaction, txHex, contract.artifact, transaction.abiFunction, transaction.encodedFunctionArgs, contract.encodedConstructorArgs),
22
20
  };
23
- // add extra variables for the p2pkh utxos spent together with our contract
24
- if (hasSignatureTemplates) {
25
- template.entities.parameters.scripts.push('p2pkh_placeholder_lock', 'p2pkh_placeholder_unlock');
21
+ transaction.inputs
22
+ .forEach((input, index) => {
23
+ if (!isUtxoP2PKH(input))
24
+ return;
25
+ const lockScriptName = `p2pkh_placeholder_lock_${index}`;
26
+ const unlockScriptName = `p2pkh_placeholder_unlock_${index}`;
27
+ const placeholderKeyName = `placeholder_key_${index}`;
28
+ const signatureAlgorithmName = getSignatureAlgorithmName(input.template.getSignatureAlgorithm());
29
+ const hashtypeName = getHashTypeName(input.template.getHashType(false));
30
+ const signatureString = `${placeholderKeyName}.${signatureAlgorithmName}.${hashtypeName}`;
31
+ template.entities.parameters.scripts.push(lockScriptName, unlockScriptName);
26
32
  template.entities.parameters.variables = {
27
33
  ...template.entities.parameters.variables,
28
- placeholder_key: {
29
- description: 'placeholder_key',
30
- name: 'placeholder_key',
34
+ [placeholderKeyName]: {
35
+ description: placeholderKeyName,
36
+ name: placeholderKeyName,
31
37
  type: 'Key',
32
38
  },
33
39
  };
34
40
  // add extra unlocking and locking script for P2PKH inputs spent alongside our contract
35
- // this is needed for correct cross-referrences in the template
36
- template.scripts.p2pkh_placeholder_unlock = {
37
- name: 'p2pkh_placeholder_unlock',
38
- script: '<placeholder_key.schnorr_signature.all_outputs>\n<placeholder_key.public_key>',
39
- unlocks: 'p2pkh_placeholder_lock',
41
+ // this is needed for correct cross-references in the template
42
+ template.scripts[unlockScriptName] = {
43
+ name: unlockScriptName,
44
+ script: `<${signatureString}>\n<${placeholderKeyName}.public_key>`,
45
+ unlocks: lockScriptName,
40
46
  };
41
- template.scripts.p2pkh_placeholder_lock = {
47
+ template.scripts[lockScriptName] = {
42
48
  lockingType: 'standard',
43
- name: 'p2pkh_placeholder_lock',
44
- script: 'OP_DUP\nOP_HASH160 <$(<placeholder_key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
49
+ name: lockScriptName,
50
+ script: `OP_DUP\nOP_HASH160 <$(<${placeholderKeyName}.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG`,
45
51
  };
46
- }
52
+ });
47
53
  return template;
48
54
  };
49
55
  export const getBitauthUri = (template) => {
@@ -51,13 +57,13 @@ export const getBitauthUri = (template) => {
51
57
  const payload = base64toBase64Url(binToBase64(deflate(utf8ToBin(extendedStringify(template)))));
52
58
  return `https://ide.bitauth.com/import-template/${payload}`;
53
59
  };
54
- const generateTemplateEntities = (artifact, abiFunction) => {
55
- const functionParameters = Object.fromEntries(abiFunction.inputs.map((input) => ([
60
+ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) => {
61
+ const functionParameters = Object.fromEntries(abiFunction.inputs.map((input, index) => ([
56
62
  snakeCase(input.name),
57
63
  {
58
64
  description: `"${input.name}" parameter of function "${abiFunction.name}"`,
59
65
  name: input.name,
60
- type: input.type === PrimitiveType.SIG ? 'Key' : 'WalletData',
66
+ type: encodedFunctionArgs[index] instanceof SignatureTemplate ? 'Key' : 'WalletData',
61
67
  },
62
68
  ])));
63
69
  const constructorParameters = Object.fromEntries(artifact.constructorInputs.map((input) => ([
@@ -130,7 +136,7 @@ const generateTemplateUnlockScript = (artifact, abiFunction, encodedFunctionArgs
130
136
  unlocks: 'lock',
131
137
  };
132
138
  };
133
- const generateTemplateScenarios = (contract, transaction, transactionHex, artifact, abiFunction, encodedFunctionArgs, encodedConstructorArgs, hasSignatureTemplates) => {
139
+ const generateTemplateScenarios = (contract, transaction, transactionHex, artifact, abiFunction, encodedFunctionArgs, encodedConstructorArgs) => {
134
140
  const libauthTransaction = decodeTransaction(hexToBin(transactionHex));
135
141
  if (typeof libauthTransaction === 'string')
136
142
  throw Error(libauthTransaction);
@@ -145,11 +151,10 @@ const generateTemplateScenarios = (contract, transaction, transactionHex, artifa
145
151
  ...generateTemplateScenarioParametersValues(abiFunction.inputs, encodedFunctionArgs),
146
152
  ...generateTemplateScenarioParametersValues(artifact.constructorInputs, encodedConstructorArgs),
147
153
  },
148
- // TODO: Don't hardcode these values
149
154
  currentBlockHeight: 2,
150
155
  currentBlockTime: Math.round(+new Date() / 1000),
151
156
  keys: {
152
- privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs, hasSignatureTemplates),
157
+ privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs),
153
158
  },
154
159
  },
155
160
  transaction: generateTemplateScenarioTransaction(contract, libauthTransaction, transaction),
@@ -165,21 +170,17 @@ const generateTemplateScenarios = (contract, transaction, transactionHex, artifa
165
170
  const generateTemplateScenarioTransaction = (contract, libauthTransaction, csTransaction) => {
166
171
  const slotIndex = csTransaction.inputs.findIndex((input) => !isUtxoP2PKH(input));
167
172
  const inputs = libauthTransaction.inputs.map((input, index) => {
168
- const csInput = csTransaction.inputs[index]; // TODO: Change
173
+ const csInput = csTransaction.inputs[index];
169
174
  return {
170
175
  outpointIndex: input.outpointIndex,
171
- outpointTransactionHash:
172
- // TODO: This should always be Uint8Array according to the libauth transaction types
173
- input.outpointTransactionHash instanceof Uint8Array
174
- ? binToHex(input.outpointTransactionHash)
175
- : input.outpointTransactionHash,
176
+ outpointTransactionHash: binToHex(input.outpointTransactionHash),
176
177
  sequenceNumber: input.sequenceNumber,
177
- unlockingBytecode: generateTemplateScenarioBytecode(csInput, 'p2pkh_placeholder_unlock', index === slotIndex),
178
+ unlockingBytecode: generateTemplateScenarioBytecode(csInput, `p2pkh_placeholder_unlock_${index}`, `placeholder_key_${index}`, index === slotIndex),
178
179
  };
179
180
  });
180
181
  const locktime = libauthTransaction.locktime;
181
182
  const outputs = libauthTransaction.outputs.map((output, index) => {
182
- const csOutput = csTransaction.outputs[index]; // TODO: Change
183
+ const csOutput = csTransaction.outputs[index];
183
184
  return {
184
185
  lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(csOutput, contract),
185
186
  token: serialiseTokenDetails(output.token),
@@ -200,21 +201,21 @@ const generateTemplateScenarioSourceOutputs = (csTransaction) => {
200
201
  const slotIndex = csTransaction.inputs.findIndex((input) => !isUtxoP2PKH(input));
201
202
  return csTransaction.inputs.map((input, index) => {
202
203
  return {
203
- lockingBytecode: generateTemplateScenarioBytecode(input, 'p2pkh_placeholder_lock', index === slotIndex),
204
+ lockingBytecode: generateTemplateScenarioBytecode(input, `p2pkh_placeholder_lock_${index}`, `placeholder_key_${index}`, index === slotIndex),
204
205
  valueSatoshis: Number(input.satoshis),
205
206
  token: serialiseTokenDetails(input.token),
206
207
  };
207
208
  });
208
209
  };
209
210
  // Used for generating the locking / unlocking bytecode for source outputs and inputs
210
- const generateTemplateScenarioBytecode = (input, p2pkhScriptName, insertSlot) => {
211
+ const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKeyName, insertSlot) => {
211
212
  if (isUtxoP2PKH(input)) {
212
213
  return {
213
214
  script: p2pkhScriptName,
214
215
  overrides: {
215
216
  keys: {
216
217
  privateKeys: {
217
- placeholder_key: binToHex(input.template.privateKey),
218
+ [placeholderKeyName]: binToHex(input.template.privateKey),
218
219
  },
219
220
  },
220
221
  },
@@ -229,21 +230,17 @@ const generateTemplateScenarioParametersValues = (types, encodedArgs) => {
229
230
  .filter(([, arg]) => !(arg instanceof SignatureTemplate))
230
231
  .map(([input, arg]) => {
231
232
  const encodedArgumentHex = binToHex(arg);
232
- // TODO: Is this really necessary?
233
- const prefixedEncodedArgument = encodedArgumentHex.length ? `0x${encodedArgumentHex}` : encodedArgumentHex;
233
+ const prefixedEncodedArgument = encodedArgumentHex.length > 0 ? `0x${encodedArgumentHex}` : '';
234
234
  return [snakeCase(input.name), prefixedEncodedArgument];
235
235
  });
236
236
  return Object.fromEntries(entries);
237
237
  };
238
- const generateTemplateScenarioKeys = (types, encodedArgs, hasSignatureTemplates) => {
238
+ const generateTemplateScenarioKeys = (types, encodedArgs) => {
239
239
  const typesAndArguments = zip(types, encodedArgs);
240
240
  const entries = typesAndArguments
241
241
  .filter(([, arg]) => arg instanceof SignatureTemplate)
242
242
  .map(([input, arg]) => [snakeCase(input.name), binToHex(arg.privateKey)]);
243
- const placeholderKey = hasSignatureTemplates
244
- ? [['placeholder_key', '0x0000000000000000000000000000000000000000000000000000000000000000']]
245
- : [];
246
- return Object.fromEntries([...entries, ...placeholderKey]);
243
+ return Object.fromEntries(entries);
247
244
  };
248
245
  const formatParametersForDebugging = (types, args) => {
249
246
  if (types.length === 0)
@@ -252,8 +249,9 @@ const formatParametersForDebugging = (types, args) => {
252
249
  const typesAndArguments = zip(types, args).reverse();
253
250
  return typesAndArguments.map(([input, arg]) => {
254
251
  if (arg instanceof SignatureTemplate) {
255
- // TODO: Different signing algorithms / hashtypes
256
- return `<${snakeCase(input.name)}.schnorr_signature.all_outputs> // ${input.type}`;
252
+ const signatureAlgorithmName = getSignatureAlgorithmName(arg.getSignatureAlgorithm());
253
+ const hashtypeName = getHashTypeName(arg.getHashType(false));
254
+ return `<${snakeCase(input.name)}.${signatureAlgorithmName}.${hashtypeName}> // ${input.type}`;
257
255
  }
258
256
  const typeStr = input.type === 'bytes' ? `bytes${arg.length}` : input.type;
259
257
  // we output these values as pushdata, comment will contain the type and the value of the variable
@@ -261,9 +259,32 @@ const formatParametersForDebugging = (types, args) => {
261
259
  return `<${snakeCase(input.name)}> // ${typeStr} = <${`0x${binToHex(arg)}`}>`;
262
260
  }).join('\n');
263
261
  };
262
+ const getSignatureAlgorithmName = (signatureAlgorithm) => {
263
+ const signatureAlgorithmNames = {
264
+ [SignatureAlgorithm.SCHNORR]: 'schnorr_signature',
265
+ [SignatureAlgorithm.ECDSA]: 'ecdsa_signature',
266
+ };
267
+ return signatureAlgorithmNames[signatureAlgorithm];
268
+ };
269
+ const getHashTypeName = (hashType) => {
270
+ const hashtypeNames = {
271
+ [HashType.SIGHASH_ALL]: 'all_outputs',
272
+ [HashType.SIGHASH_ALL | HashType.SIGHASH_ANYONECANPAY]: 'all_outputs_single_input',
273
+ [HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS]: 'all_outputs_all_utxos',
274
+ [HashType.SIGHASH_ALL | HashType.SIGHASH_ANYONECANPAY | HashType.SIGHASH_UTXOS]: 'all_outputs_single_input_INVALID_all_utxos',
275
+ [HashType.SIGHASH_SINGLE]: 'corresponding_output',
276
+ [HashType.SIGHASH_SINGLE | HashType.SIGHASH_ANYONECANPAY]: 'corresponding_output_single_input',
277
+ [HashType.SIGHASH_SINGLE | HashType.SIGHASH_UTXOS]: 'corresponding_output_all_utxos',
278
+ [HashType.SIGHASH_SINGLE | HashType.SIGHASH_ANYONECANPAY | HashType.SIGHASH_UTXOS]: 'corresponding_output_single_input_INVALID_all_utxos',
279
+ [HashType.SIGHASH_NONE]: 'no_outputs',
280
+ [HashType.SIGHASH_NONE | HashType.SIGHASH_ANYONECANPAY]: 'no_outputs_single_input',
281
+ [HashType.SIGHASH_NONE | HashType.SIGHASH_UTXOS]: 'no_outputs_all_utxos',
282
+ [HashType.SIGHASH_NONE | HashType.SIGHASH_ANYONECANPAY | HashType.SIGHASH_UTXOS]: 'no_outputs_single_input_INVALID_all_utxos',
283
+ };
284
+ return hashtypeNames[hashType];
285
+ };
264
286
  const formatBytecodeForDebugging = (artifact) => {
265
287
  if (!artifact.debug) {
266
- // TODO: See if we can merge this with code from @cashscript/utils -> script.ts
267
288
  return artifact.bytecode
268
289
  .split(' ')
269
290
  .map((asmElement) => (isHex(asmElement) ? `<0x${asmElement}>` : asmElement))
@@ -271,7 +292,6 @@ const formatBytecodeForDebugging = (artifact) => {
271
292
  }
272
293
  return formatBitAuthScript(bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, artifact.source);
273
294
  };
274
- // TODO: Maybe move / refactor
275
295
  const serialiseTokenDetails = (token) => {
276
296
  if (!token)
277
297
  return undefined;
@@ -6,6 +6,7 @@ export default class SignatureTemplate {
6
6
  constructor(signer: Keypair | Uint8Array | string, hashtype?: HashType, signatureAlgorithm?: SignatureAlgorithm);
7
7
  generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
8
8
  getHashType(bchForkId?: boolean): number;
9
+ getSignatureAlgorithm(): SignatureAlgorithm;
9
10
  getPublicKey(): Uint8Array;
10
11
  unlockP2PKH(): Unlocker;
11
12
  }
@@ -27,6 +27,9 @@ export default class SignatureTemplate {
27
27
  getHashType(bchForkId = true) {
28
28
  return bchForkId ? (this.hashtype | SigningSerializationFlag.forkId) : this.hashtype;
29
29
  }
30
+ getSignatureAlgorithm() {
31
+ return this.signatureAlgorithm;
32
+ }
30
33
  getPublicKey() {
31
34
  return secp256k1.derivePublicKeyCompressed(this.privateKey);
32
35
  }
@@ -4,12 +4,12 @@ import { Utxo, Output, Recipient, TokenDetails, TransactionDetails, Unlocker } f
4
4
  import SignatureTemplate from './SignatureTemplate.js';
5
5
  import { Contract } from './Contract.js';
6
6
  import { DebugResult } from './debugging.js';
7
- import { EncodedArgument } from './Argument.js';
7
+ import { EncodedFunctionArgument } from './Argument.js';
8
8
  export declare class Transaction {
9
9
  contract: Contract;
10
10
  private unlocker;
11
11
  abiFunction: AbiFunction;
12
- encodedFunctionArgs: EncodedArgument[];
12
+ encodedFunctionArgs: EncodedFunctionArgument[];
13
13
  private selector?;
14
14
  inputs: Utxo[];
15
15
  outputs: Output[];
@@ -19,7 +19,7 @@ export declare class Transaction {
19
19
  private hardcodedFee;
20
20
  private minChange;
21
21
  private tokenChange;
22
- constructor(contract: Contract, unlocker: Unlocker, abiFunction: AbiFunction, encodedFunctionArgs: EncodedArgument[], selector?: number | undefined);
22
+ constructor(contract: Contract, unlocker: Unlocker, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], selector?: number | undefined);
23
23
  from(input: Utxo): this;
24
24
  from(inputs: Utxo[]): this;
25
25
  fromP2PKH(input: Utxo, template: SignatureTemplate): this;
@@ -8,10 +8,9 @@ import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithout
8
8
  import SignatureTemplate from './SignatureTemplate.js';
9
9
  import { P2PKH_INPUT_SIZE } from './constants.js';
10
10
  import { TransactionBuilder } from './TransactionBuilder.js';
11
- import MockNetworkProvider from './network/MockNetworkProvider.js';
12
11
  import { buildTemplate, getBitauthUri } from './LibauthTemplate.js';
13
- import { debugTemplate, evaluateTemplate } from './debugging.js';
14
- import { FailedRequireError, FailedTransactionError } from './Errors.js';
12
+ import { debugTemplate } from './debugging.js';
13
+ import { FailedTransactionError } from './Errors.js';
15
14
  export class Transaction {
16
15
  constructor(contract, unlocker, abiFunction, encodedFunctionArgs, selector) {
17
16
  this.contract = contract;
@@ -102,30 +101,12 @@ export class Transaction {
102
101
  }
103
102
  async send(raw) {
104
103
  const tx = await this.build();
105
- let template;
106
104
  // Debug the transaction locally before sending so any errors are caught early
107
- try {
108
- // Libauth debugging does not work with old-style covenents (or outdated artifacts)
109
- if (!this.abiFunction.covenant && this.contract.artifact.debug) {
110
- await this.debug();
111
- }
112
- }
113
- catch (error) {
114
- if (error instanceof FailedRequireError) {
115
- error.bitauthUri = await this.bitauthUri();
116
- error.message += `\n\nBitauth URI: ${error.bitauthUri}`;
117
- }
118
- throw error;
105
+ // Libauth debugging does not work with old-style covenants
106
+ if (!this.abiFunction.covenant) {
107
+ await this.debug();
119
108
  }
120
109
  try {
121
- // TODO: Can we move this to MockNetworkProvider?
122
- if (this.contract.provider instanceof MockNetworkProvider) {
123
- template = await buildTemplate({
124
- transaction: this,
125
- transactionHex: tx,
126
- });
127
- evaluateTemplate(template);
128
- }
129
110
  const txid = await this.contract.provider.sendRawTransaction(tx);
130
111
  return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
131
112
  }
@@ -136,6 +117,9 @@ export class Transaction {
136
117
  }
137
118
  // method to debug the transaction with libauth VM, throws upon evaluation error
138
119
  async debug() {
120
+ if (!this.contract.artifact.debug) {
121
+ console.warn('No debug information found in artifact. Recompile with cashc version 0.10.0 or newer to get better debugging information.');
122
+ }
139
123
  const template = await this.getLibauthTemplate();
140
124
  return debugTemplate(template, this.contract.artifact);
141
125
  }
@@ -165,7 +149,7 @@ export class Transaction {
165
149
  }
166
150
  async setInputsAndOutputs() {
167
151
  if (this.outputs.length === 0) {
168
- throw Error('Attempted to build a transaction without outputs');
152
+ throw new Error('Attempted to build a transaction without outputs');
169
153
  }
170
154
  // Fetched utxos are only used when no inputs are available, so only fetch in that case.
171
155
  const allUtxos = this.inputs.length === 0
@@ -207,7 +191,7 @@ export class Transaction {
207
191
  // Compare nfts in- and outputs, check if inputs have nfts corresponding to outputs
208
192
  // Keep list of nfts in inputs without matching output
209
193
  // First check immutable nfts, then mutable & minting nfts together
210
- // This is so an immutible input gets matched first and is removed from the list of unused nfts
194
+ // This is so an immutable input gets matched first and is removed from the list of unused nfts
211
195
  let unusedNfts = listNftsInputs;
212
196
  for (const nftInput of listNftsInputs) {
213
197
  if (nftInput.capability === 'none') {
@@ -4,9 +4,9 @@ export interface TransactionBuilderOptions {
4
4
  provider: NetworkProvider;
5
5
  }
6
6
  export declare class TransactionBuilder {
7
- private provider;
8
- private inputs;
9
- private outputs;
7
+ provider: NetworkProvider;
8
+ inputs: UnlockableUtxo[];
9
+ outputs: Output[];
10
10
  private locktime;
11
11
  private maxFee?;
12
12
  constructor(options: TransactionBuilderOptions);
@@ -17,7 +17,7 @@ export class TransactionBuilder {
17
17
  addInputs(utxos, unlocker, options) {
18
18
  if ((!unlocker && utxos.some((utxo) => !isUnlockableUtxo(utxo)))
19
19
  || (unlocker && utxos.some((utxo) => isUnlockableUtxo(utxo)))) {
20
- throw new Error('Either all UTXOs must have an individual unlocker speciifed, or no UTXOs must have an individual unlocker specified and a shared unlocker must be provided');
20
+ throw new Error('Either all UTXOs must have an individual unlocker specified, or no UTXOs must have an individual unlocker specified and a shared unlocker must be provided');
21
21
  }
22
22
  if (!unlocker) {
23
23
  this.inputs = this.inputs.concat(utxos);
@@ -1,5 +1,5 @@
1
1
  import { AuthenticationProgramStateCommon, WalletTemplate } from '@bitauth/libauth';
2
2
  import { Artifact } from '@cashscript/utils';
3
3
  export declare const evaluateTemplate: (template: WalletTemplate) => boolean;
4
- export declare type DebugResult = AuthenticationProgramStateCommon[];
4
+ export type DebugResult = AuthenticationProgramStateCommon[];
5
5
  export declare const debugTemplate: (template: WalletTemplate, artifact: Artifact) => DebugResult;
package/dist/debugging.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBCH2023, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
2
2
  import { Op, PrimitiveType, bytecodeToAsm, decodeBool, decodeInt, decodeString } from '@cashscript/utils';
3
3
  import { findLastIndex, toRegExp } from './utils.js';
4
- import { FailedRequireError, FailedTransactionError } from './Errors.js';
4
+ import { FailedRequireError, FailedTransactionError, FailedTransactionEvaluationError } from './Errors.js';
5
+ import { getBitauthUri } from './LibauthTemplate.js';
5
6
  // evaluates the fully defined template, throws upon error
6
7
  export const evaluateTemplate = (template) => {
7
8
  const { vm, program } = createProgram(template);
8
9
  const verifyResult = vm.verify(program);
9
10
  if (typeof verifyResult === 'string') {
10
- throw new FailedTransactionError(verifyResult);
11
+ throw new FailedTransactionError(verifyResult, getBitauthUri(template));
11
12
  }
12
13
  return verifyResult;
13
14
  };
@@ -36,31 +37,37 @@ export const debugTemplate = (template, artifact) => {
36
37
  const failingIp = lastExecutedDebugStep.ip - 1;
37
38
  // Generally speaking, an error is thrown by the OP_VERIFY opcode, but for NULLFAIL, the error is thrown in the
38
39
  // preceding OP_CHECKSIG opcode. The error message is registered in the next instruction, so we need to increment
39
- // the instruction pointer to get the correct error message.
40
+ // the instruction pointer to get the correct error message from the require messages in the artifact.
41
+ // Note that we do NOT use this adjusted IP when passing the failing IP into the FailedRequireError
40
42
  const isNullFail = lastExecutedDebugStep.error === AuthenticationErrorCommon.nonNullSignatureFailure;
41
43
  const requireStatementIp = failingIp + (isNullFail ? 1 : 0);
42
44
  const requireStatement = (artifact.debug?.requires ?? [])
43
45
  .find((statement) => statement.ip === requireStatementIp);
44
- // TODO: Also log the require statement that failed (e.g. "require(1 == 2, '1 is not equal to 2')")
46
+ const { program: { inputIndex }, error } = lastExecutedDebugStep;
45
47
  if (requireStatement) {
46
- const { program: { inputIndex }, error } = lastExecutedDebugStep;
47
- throw new FailedRequireError(artifact.contractName, requireStatement, inputIndex, error);
48
+ // Note that we use failingIp here rather than requireStatementIp, see comment above
49
+ throw new FailedRequireError(artifact, failingIp, requireStatement, inputIndex, getBitauthUri(template), error);
48
50
  }
49
- throw new FailedTransactionError(`Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex}.\n${lastExecutedDebugStep.error}`);
51
+ // Note that we use failingIp here rather than requireStatementIp, see comment above
52
+ throw new FailedTransactionEvaluationError(artifact, failingIp, inputIndex, getBitauthUri(template), error);
50
53
  }
51
54
  const evaluationResult = vm.verify(program);
52
55
  if (failedFinalVerify(evaluationResult)) {
53
56
  const finalExecutedVerifyIp = getFinalExecutedVerifyIp(executedDebugSteps);
57
+ // The final executed verify instruction points to the "implicit" VERIFY that is added at the end of the script.
58
+ // This instruction does not exist in the sourcemap, so we need to decrement the instruction pointer to get the
59
+ // actual final executed statement (which is *not* the require statement, but the evaluation within)
60
+ const sourcemapInstructionPointer = finalExecutedVerifyIp - 1;
54
61
  // logDebugSteps(executedDebugSteps, lastExecutedDebugStep.instructions);
55
62
  // console.warn('message', finalExecutedVerifyIp);
56
63
  // console.warn(artifact.debug?.requires);
57
64
  const requireStatement = (artifact.debug?.requires ?? [])
58
65
  .find((message) => message.ip === finalExecutedVerifyIp);
66
+ const { program: { inputIndex } } = lastExecutedDebugStep;
59
67
  if (requireStatement) {
60
- const { program: { inputIndex }, error } = lastExecutedDebugStep;
61
- throw new FailedRequireError(artifact.contractName, requireStatement, inputIndex, error);
68
+ throw new FailedRequireError(artifact, sourcemapInstructionPointer, requireStatement, inputIndex, getBitauthUri(template));
62
69
  }
63
- throw new FailedTransactionError(`Error in evaluating input index ${lastExecutedDebugStep.program.inputIndex}.\n${evaluationResult}`);
70
+ throw new FailedTransactionEvaluationError(artifact, sourcemapInstructionPointer, inputIndex, getBitauthUri(template), evaluationResult);
64
71
  }
65
72
  return fullDebugSteps;
66
73
  };
@@ -76,10 +83,10 @@ const createProgram = (template) => {
76
83
  scenarioId: 'evaluate_function',
77
84
  });
78
85
  if (typeof scenarioGeneration === 'string') {
79
- throw new FailedTransactionError(scenarioGeneration);
86
+ throw new FailedTransactionError(scenarioGeneration, getBitauthUri(template));
80
87
  }
81
88
  if (typeof scenarioGeneration.scenario === 'string') {
82
- throw new FailedTransactionError(scenarioGeneration.scenario);
89
+ throw new FailedTransactionError(scenarioGeneration.scenario, getBitauthUri(template));
83
90
  }
84
91
  return { vm, program: scenarioGeneration.scenario.program };
85
92
  };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
- import SignatureTemplate from './SignatureTemplate.js';
2
- export { SignatureTemplate };
3
- export { Contract, ContractFunction } from './Contract.js';
1
+ export { default as SignatureTemplate } from './SignatureTemplate.js';
2
+ export { Contract, type ContractFunction } from './Contract.js';
4
3
  export { Transaction } from './Transaction.js';
5
4
  export { TransactionBuilder } from './TransactionBuilder.js';
6
- export { Argument, encodeArgument } from './Argument.js';
7
- export { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
5
+ export { type ConstructorArgument, type FunctionArgument, type EncodedConstructorArgument, type EncodedFunctionArgument, encodeFunctionArgument, } from './Argument.js';
6
+ export type { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
8
7
  export * as utils from '@cashscript/utils';
9
8
  export * from './interfaces.js';
10
9
  export * from './Errors.js';
11
- export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
10
+ export { type NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
12
11
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
package/dist/index.js CHANGED
@@ -1,9 +1,8 @@
1
- import SignatureTemplate from './SignatureTemplate.js';
2
- export { SignatureTemplate };
1
+ export { default as SignatureTemplate } from './SignatureTemplate.js';
3
2
  export { Contract } from './Contract.js';
4
3
  export { Transaction } from './Transaction.js';
5
4
  export { TransactionBuilder } from './TransactionBuilder.js';
6
- export { encodeArgument } from './Argument.js';
5
+ export { encodeFunctionArgument, } from './Argument.js';
7
6
  export * as utils from '@cashscript/utils';
8
7
  export * from './interfaces.js';
9
8
  export * from './Errors.js';
@@ -80,9 +80,10 @@ export declare const Network: {
80
80
  TESTNET3: "testnet3";
81
81
  TESTNET4: "testnet4";
82
82
  CHIPNET: "chipnet";
83
+ MOCKNET: "mocknet";
83
84
  REGTEST: "regtest";
84
85
  };
85
- export declare type Network = (typeof Network)[keyof typeof Network];
86
+ export type Network = (typeof Network)[keyof typeof Network];
86
87
  export interface TransactionDetails extends Transaction {
87
88
  txid: string;
88
89
  hex: string;
@@ -91,4 +92,4 @@ export interface ContractOptions {
91
92
  provider?: NetworkProvider;
92
93
  addressType?: AddressType;
93
94
  }
94
- export declare type AddressType = 'p2sh20' | 'p2sh32';
95
+ export type AddressType = 'p2sh20' | 'p2sh32';
@@ -25,6 +25,7 @@ export const Network = {
25
25
  TESTNET3: literal('testnet3'),
26
26
  TESTNET4: literal('testnet4'),
27
27
  CHIPNET: literal('chipnet'),
28
+ MOCKNET: literal('mocknet'),
28
29
  REGTEST: literal('regtest'),
29
30
  };
30
31
  //# sourceMappingURL=interfaces.js.map
@@ -10,7 +10,7 @@ export default class MockNetworkProvider {
10
10
  constructor() {
11
11
  this.utxoMap = {};
12
12
  this.transactionMap = {};
13
- this.network = Network.CHIPNET;
13
+ this.network = Network.MOCKNET;
14
14
  for (let i = 0; i < 3; i += 1) {
15
15
  this.addUtxo(aliceAddress, randomUtxo());
16
16
  this.addUtxo(bobAddress, randomUtxo());
@@ -1,4 +1,4 @@
1
- export { default as NetworkProvider } from './NetworkProvider.js';
1
+ export type { default as NetworkProvider } from './NetworkProvider.js';
2
2
  export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
3
3
  export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
4
4
  export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
@@ -4,6 +4,7 @@ declare global {
4
4
  interface Matchers<R> {
5
5
  toLog(value?: RegExp | string): Promise<void>;
6
6
  toFailRequireWith(value: RegExp | string): Promise<void>;
7
+ toFailRequire(): Promise<void>;
7
8
  }
8
9
  }
9
10
  }
@@ -29,7 +29,7 @@ expect.extend({
29
29
  try {
30
30
  await transaction.debug();
31
31
  const matcherHint = this.utils.matcherHint('.toFailRequireWith', undefined, match.toString(), { isNot: this.isNot });
32
- const message = () => `${matcherHint}\n\nContract function did not fail a require statement`;
32
+ const message = () => `${matcherHint}\n\nContract function did not fail a require statement.`;
33
33
  return { message, pass: false };
34
34
  }
35
35
  catch (transactionError) {
@@ -46,6 +46,18 @@ expect.extend({
46
46
  }
47
47
  }
48
48
  },
49
+ async toFailRequire(transaction) {
50
+ try {
51
+ await transaction.debug();
52
+ const message = () => 'Contract function did not fail a require statement.';
53
+ return { message, pass: false };
54
+ }
55
+ catch (transactionError) {
56
+ const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
57
+ const message = () => `Contract function failed a require statement.\n${receivedText}`;
58
+ return { message, pass: true };
59
+ }
60
+ },
49
61
  });
50
62
  export {};
51
63
  //# sourceMappingURL=JestExtensions.js.map
package/dist/utils.d.ts CHANGED
@@ -28,10 +28,10 @@ export declare function utxoTokenComparator(a: Utxo, b: Utxo): number;
28
28
  */
29
29
  export declare function addressToLockScript(address: string): Uint8Array;
30
30
  export declare function getNetworkPrefix(network: string): 'bitcoincash' | 'bchtest' | 'bchreg';
31
- export declare const randomUtxo: (defaults?: Partial<Utxo> | undefined) => Utxo;
32
- export declare const randomToken: (defaults?: Partial<TokenDetails> | undefined) => TokenDetails;
33
- export declare const randomNFT: (defaults?: Partial<TokenDetails> | undefined) => TokenDetails;
31
+ export declare const randomUtxo: (defaults?: Partial<Utxo>) => Utxo;
32
+ export declare const randomToken: (defaults?: Partial<TokenDetails>) => TokenDetails;
33
+ export declare const randomNFT: (defaults?: Partial<TokenDetails>) => TokenDetails;
34
34
  export declare function findLastIndex<T>(array: Array<T>, predicate: (value: T, index: number, obj: T[]) => boolean): number;
35
35
  export declare const snakeCase: (str: string) => string;
36
- export declare const extendedStringify: (obj: any, spaces?: number | undefined) => string;
36
+ export declare const extendedStringify: (obj: any, spaces?: number) => string;
37
37
  export declare const zip: <T, U>(a: T[], b: U[]) => [T, U][];
package/dist/utils.js CHANGED
@@ -139,10 +139,12 @@ export function toRegExp(reasons) {
139
139
  return new RegExp(reasons.join('|').replace(/\(/g, '\\(').replace(/\)/g, '\\)'));
140
140
  }
141
141
  export function scriptToAddress(script, network, addressType, tokenSupport) {
142
- const lockingBytecode = scriptToLockingBytecode(script, addressType);
142
+ const bytecode = scriptToLockingBytecode(script, addressType);
143
143
  const prefix = getNetworkPrefix(network);
144
- const address = lockingBytecodeToCashAddress(lockingBytecode, prefix, { tokenSupport });
145
- return address;
144
+ const result = lockingBytecodeToCashAddress({ bytecode, prefix, tokenSupport });
145
+ if (typeof result === 'string')
146
+ throw new Error(result);
147
+ return result.address;
146
148
  }
147
149
  export function scriptToLockingBytecode(script, addressType) {
148
150
  const scriptBytecode = scriptToBytecode(script);
@@ -195,6 +197,7 @@ export function getNetworkPrefix(network) {
195
197
  case Network.TESTNET4:
196
198
  case Network.TESTNET3:
197
199
  case Network.CHIPNET:
200
+ case Network.MOCKNET:
198
201
  return 'bchtest';
199
202
  case Network.REGTEST:
200
203
  return 'bchreg';
@@ -221,7 +224,7 @@ function getPushDataOpcode(data) {
221
224
  return Uint8Array.from([byteLength]);
222
225
  if (byteLength < 256)
223
226
  return Uint8Array.from([0x4c, byteLength]);
224
- throw Error('Pushdata too large');
227
+ throw new Error('Pushdata too large');
225
228
  }
226
229
  const randomInt = () => BigInt(Math.floor(Math.random() * 10000));
227
230
  export const randomUtxo = (defaults) => ({
@@ -0,0 +1,5 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../dist/test/JestExtensions.d.ts",
4
+ "main": "../dist/test/JestExtensions.js"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.10.0-next.6",
3
+ "version": "0.10.1",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -10,11 +10,11 @@
10
10
  ],
11
11
  "homepage": "https://cashscript.org",
12
12
  "bugs": {
13
- "url": "https://github.com/Bitcoin-com/cashscript/issues"
13
+ "url": "https://github.com/CashScript/cashscript/issues"
14
14
  },
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/Bitcoin-com/cashscript.git"
17
+ "url": "git+https://github.com/CashScript/cashscript.git"
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": "Rosco Kalis <roscokalis@gmail.com>",
@@ -43,8 +43,8 @@
43
43
  "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
44
44
  },
45
45
  "dependencies": {
46
- "@bitauth/libauth": "^2.0.0",
47
- "@cashscript/utils": "^0.10.0-next.6",
46
+ "@bitauth/libauth": "^3.0.0",
47
+ "@cashscript/utils": "^0.10.1",
48
48
  "bip68": "^1.0.4",
49
49
  "bitcoin-rpc-promise-retry": "^1.3.0",
50
50
  "delay": "^5.0.0",
@@ -59,7 +59,7 @@
59
59
  "bip39": "^3.0.4",
60
60
  "eslint": "^8.54.0",
61
61
  "jest": "^29.4.1",
62
- "typescript": "^4.1.5"
62
+ "typescript": "^5.5.4"
63
63
  },
64
- "gitHead": "b26f1f20b89df6d857a5860eebd3bcc7668faa4e"
64
+ "gitHead": "0dd95d5b05a438ac1884bdb2252aec6a4c77e5b2"
65
65
  }